PHP Conference Japan 2024

檔案系統安全性

目錄

PHP 受限於大多數伺服器系統內建的安全性,也就是檔案和目錄的權限。這讓您可以控制檔案系統中哪些檔案可以被讀取。對於任何全域可讀取的檔案,應謹慎處理,確保所有能存取該檔案系統的使用者都可以安全讀取。

由於 PHP 的設計允許使用者層級存取檔案系統,因此完全有可能編寫一個 PHP 指令稿,讓您能夠讀取系統檔案(例如 /etc/passwd)、修改您的乙太網路連線、送出大量的印表機工作等等。這有一些顯而易見的含意,也就是說,您需要確保您讀取和寫入的檔案是適當的檔案。

考量以下指令稿,其中使用者指出他們想刪除其主目錄中的檔案。假設一個情況,其中 PHP Web 介面經常被用於檔案管理,因此允許 Apache 使用者刪除使用者主目錄中的檔案。

範例 #1 變數檢查不足導致....

<?php

// 從使用者主目錄中移除檔案
$username = $_POST['user_submitted_name'];
$userfile = $_POST['user_submitted_filename'];
$homedir = "/home/$username";

unlink("$homedir/$userfile");

echo
"檔案已刪除!";

?>
由於使用者名稱和檔案名稱可以從使用者表單張貼,他們可以提交屬於其他人的使用者名稱和檔案名稱,並刪除它,即使他們不應該被允許這麼做。在這種情況下,您會想要使用其他形式的驗證。考慮一下如果提交的變數是 "../etc/""passwd" 會發生什麼。該程式碼實際上會讀取

範例 #2 ... 檔案系統攻擊

<?php

// 從硬碟上的任何位置移除檔案,PHP 使用者可以存取該檔案。
// 如果 PHP 具有 root 存取權:
$username = $_POST['user_submitted_name']; // "../etc"
$userfile = $_POST['user_submitted_filename']; // "passwd"
$homedir = "/home/$username"; // "/home/../etc"

unlink("$homedir/$userfile"); // "/home/../etc/passwd"

echo "檔案已刪除!";

?>
您應該採取兩項重要的措施來預防這些問題。
  • 僅允許 PHP Web 使用者二進位檔具有有限的權限。
  • 檢查所有提交的變數。
以下是改進的指令稿

範例 #3 更安全的檔案名稱檢查

<?php

// 從硬碟上移除檔案,PHP 使用者可以存取該檔案。
$username = $_SERVER['REMOTE_USER']; // 使用驗證機制
$userfile = basename($_POST['user_submitted_filename']);
$homedir = "/home/$username";

$filepath = "$homedir/$userfile";

if (
file_exists($filepath) && unlink($filepath)) {
$logstring = "已刪除 $filepath\n";
} else {
$logstring = "無法刪除 $filepath\n";
}

$fp = fopen("/home/logging/filedelete.log", "a");
fwrite($fp, $logstring);
fclose($fp);

echo
htmlentities($logstring, ENT_QUOTES);

?>
但是,即使這樣也不是沒有缺陷。如果您的驗證系統允許使用者建立自己的使用者登入,而使用者選擇了登入 "../etc/",則系統將再次暴露。因此,您可能更喜歡編寫更客製化的檢查

範例 #4 更安全的檔案名稱檢查

<?php

$username
= $_SERVER['REMOTE_USER']; // 使用驗證機制
$userfile = $_POST['user_submitted_filename'];
$homedir = "/home/$username";

$filepath = "$homedir/$userfile";

if (!
ctype_alnum($username) || !preg_match('/^(?:[a-z0-9_-]|\.(?!\.))+$/iD', $userfile)) {
die(
"錯誤的使用者名稱/檔案名稱");
}

// 等等。

?>

根據您的作業系統,您需要注意的檔案類型繁多,包括裝置項目(/dev/COM1)、設定檔(/etc/ 檔案和 .ini 檔案)、常用的檔案儲存區域(/home/我的文件)等等。因此,通常比較簡單的做法是建立一個原則,禁止所有操作,只允許明確許可的操作。

新增註解

使用者貢獻的註解 5 則註解

anonymous
19 年前
(A) 最好不要使用使用者提供的名稱建立檔案或資料夾。如果您沒有充分驗證,可能會遇到麻煩。相反地,請使用隨機產生的名稱(例如 fg3754jk3h)建立檔案和資料夾,並將使用者名稱和此檔案或資料夾名稱儲存在一個名為 user_objects 的表格中。這樣可以確保無論使用者輸入什麼,傳送到 shell 的指令都只會包含特定集合的值,而不會造成任何損害。

(B) 這同樣適用於根據使用者選擇的操作而執行的指令。最好不要讓使用者輸入的任何部分傳送到您將執行的指令中。相反地,請保留一組固定的指令,並根據使用者輸入的內容,只執行這些指令。

例如,
(A) 保留一個名為 user_objects 的表格,其中包含以下值:
username|chosen_name |actual_name|file_or_dir
--------|--------------|-----------|-----------
jdoe |trekphotos |m5fg767h67 |D
jdoe |notes.txt |nm4b6jh756 |F
tim1997 |_imp_ folder |45jkh64j56 |D

並且在檔案系統操作中始終使用 actual_name,而不是使用者提供的名稱。

(B)
<?php
$op
= $_POST['op'];//在經過大量的驗證後
$dir = $_POST['dirname'];//在經過大量的驗證後,或者您可以使用 (A) 的技巧
switch($op){
case
"cd":
chdir($dir);
break;
case
"rd":
rmdir($dir);
break;
.....
default:
mail("webmaster@example.com", "Mischief", $_SERVER['REMOTE_ADDR']." 很有可能正在嘗試攻擊。");
}
fmrose at ncsu dot edu
19 年前
此處的所有修正都假設必須先允許使用者輸入系統敏感資訊。處理此問題的正確方法是提供一個編號的檔案清單,以執行 unlink 操作,然後使用者選擇符合的號碼。這樣使用者就無法指定任何聰明的攻擊,繞過您可能擁有的任何模式比對檔案名稱排除語法。

任何時候遇到安全問題,正確的行為是拒絕所有操作,然後允許特定的實例,而不是允許所有操作並限制。原因很簡單,您可能無法考慮到所有可能的限制。
devik at cdi dot cz
23 年前
事實上,所有使用者都在相同的 UID 下執行是一個大問題。使用者空間的安全漏洞(類似 safe_mode)不應該取代適當的 kernel 層級安全檢查/會計。
好消息:Apache 2 允許您為不同的 vhost 分配 UID。
devik
Latchezar Tzvetkoff
15 年前
基本的檔案名稱/目錄/符號連結檢查可以(而且我個人會)透過 realpath() 完成...

<?php

if (isset($_GET['file'])) {
$base = '/home/polizei/public_html/'; // 似乎這個路徑最好也用 realpath,表示不是符號連結的路徑...
if (strpos($file = realpath($base.$_GET['file']), $base) === 0 && is_file($file)) {
unlink($file);
} else {
die(
'blah!');
}
}
?>
cronos586(AT)caramail(DOT)com
22 年前
當使用 Apache 時,您可能會考慮對路徑使用 apache_lookup_uri,以發現真實的路徑,無論目錄技巧如何。
然後,查看前綴,並與允許的前綴清單進行比較。
例如,我網站的 source.php 包含:
if(isset($doc)) {
$apacheres = apache_lookup_uri($doc);
$really = realpath($apacheres->filename);
if(substr($really, 0, strlen($DOCUMENT_ROOT)) == $DOCUMENT_ROOT) {
if(is_file($really)) {
show_source($really);
}
}
}
希望這有幫助
問候,
KAT44
To Top