thinkphp本身不执行系统命令,所谓“命令注入”实为开发者调用exec、shell_exec等函数时未校验用户输入所致;input()过滤对shell元字符无效,必须用escapeshellarg()包裹每个完整参数或采用白名单控制动词,优先使用php原生替代方案。

ThinkPHP 本身不执行系统命令,所谓“命令注入”全是开发者自己调用 exec、shell_exec、system、passthru 或反引号 ` 时,把用户输入直接拼进命令字符串导致的——框架不会帮你拦,也拦不了。
为什么 input() 过滤对命令注入完全无效
input() 只取值,不做 shell 层过滤。传入 size=100x100; rm -rf /,input('size') 就原样返回这个字符串;htmlspecialchars 或 intval 对 ;、&&、|、`、$() 这些 shell 元字符毫无作用。
常见错误写法:
exec('convert -resize ' . input('size') . ' ' . $src . ' ' . $dst)shell_exec('ls ' . input('path'))
这些代码只要用户控制任意一个参数,就等于把终端交出去了。
必须用 escapeshellarg(),且只用于完整参数值
escapeshellarg() 是 PHP 唯一能安全包裹单个参数的函数,但它有硬性前提:只能用于「整个参数」,不能用于命令片段、选项名或中间位置。
✅ 正确用法(每个参数都包裹):
exec('convert -resize ' . escapeshellarg(input('size')) . ' ' . escapeshellarg($src) . ' ' . escapeshellarg($dst))
❌ 错误用法(部分未包裹,或用在错误位置):
-
exec('convert -resize ' . input('size') . ' ' . escapeshellarg($src))——size没包,仍可注入 -
exec('convert -' . input('flag') . ' ' . escapeshellarg($src))——flag是选项名,escapeshellarg()不适用,必须白名单
选项名、动词类参数必须走白名单硬校验
像 input('format') 决定输出 png 还是 jpg,或 input('action') 控制执行 zip 还是 unzip,这类字段绝不能靠过滤,必须显式比对。
示例:
$allow_format = ['png', 'jpg', 'webp'];
$format = input('format', 'png');
if (!in_array($format, $allow_format)) {
throw new \Exception('Invalid format');
}
同理处理动词:
$action = input('action');if (!in_array($action, ['compress', 'extract'])) { abort(400); }
禁止任何形式的动态拼命令字符串,例如 $cmd = 'tar -' . $action . 'f ...' —— 这种写法没有安全解,只有删掉重写。
优先用 PHP 原生替代方案,彻底绕过 shell
能不用 shell 就别用。比如图像处理优先用 imagick 扩展,压缩解压用 ZipArchive 或 PharData,文件操作用 file_get_contents/copy/rename 等内置函数。
优势很明显:
- 无 shell 元字符风险
- 错误更明确(不是静默失败或执行意外命令)
- 性能通常更好(少一次进程 fork)
- 调试更直接(堆栈不跨进程)
真正难处理的,往往是历史项目里那些“必须调外部二进制”的场景——这时候白名单 + escapeshellarg() + 输入路径/文件名校验(如 pathinfo($file, PATHINFO_EXTENSION))缺一不可。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











