游客发表
一个没做过滤的命令 system() 调用,就能让攻击者在你服务器上执行任意命令。执行这不是漏洞危言耸听。去年某企业内网的复现一台运维跳板机被拿下,起因就是命令 Web 控制台里一个“ping 检测”功能,用户输入框直接拼进了 shell 命令。执行攻击者输入 127.0.0.1; cat /etc/passwd,漏洞服务器乖乖把密码文件吐了出来。复现
我本地搭了个 PHP 环境复现这类漏洞。命令代码简单到令人发指:
<?执行php $ip = $_GET['ip']; system("ping -c 2 " . $ip); ?> 浏览器访问 test.php?ip=127.0.0.1,页面正常返回 ping 结果。漏洞换成 test.php?复现ip=127.0.0.1;whoami,命令分隔符 ; 把两条指令串了起来。命令页面先显示 ping 的执行输出,紧接着蹦出当前 Web 服务的漏洞运行用户——通常是 www-data 或 apache。权限不高,但足以读取源码、配置文件,甚至写入 WebShell。
这里有个细节值得注意:system() 会直接回显命令执行结果,而 exec() 只返回最后一行且需要手动打印。很多教程用 exec() 做演示,实际攻击中 system() 和 shell_exec() 更常见,因为回显直观。防守方如果只盯着 exec 做黑名单,漏掉另外两个函数是迟早的事。
换一种注入方式试试。管道符 | 也能用:ip=127.0.0.1|id。和 ; 的区别在于,管道把前一条命令的输出作为后一条的输入,但这里 ping 的输出被丢弃了,id 的结果照样显示。还有反引号 `、&&、||,各有各的适用场景。&& 要求前一条命令成功才执行后一条,|| 则相反。攻击者会根据目标环境灵活切换,防守方想靠过滤几个特殊字符堵住所有路径,基本是徒劳。
我试过用 127.0.0.1%0aid 绕过简单过滤。%0a 是换行符的 URL 编码,某些后端在解码后把它当作命令分隔符处理。如果代码里只检查了分号和管道符,换行就能溜过去。还有更隐蔽的:127.0.0.1$(id),利用命令替换,把 id 的执行结果嵌入到 ping 的参数里。这种注入不会在页面上直接回显命令输出,但可以通过时间盲注或 DNS 外带数据来验证。
修复方案其实不复杂。PHP 的 escapeshellarg() 函数能把参数用单引号包裹并转义内部单引号,让注入的字符变成普通字符串。把代码改成 system("ping -c 2 " . escapeshellarg($ip)),再试之前的 payload,; 和 whoami 都被当成 IP 地址的一部分,ping 直接报错退出。但这不是万能药——如果开发者把用户输入拼进了命令名本身而非参数位置,escapeshellarg 也救不了。
更彻底的做法是避免调用 shell。PHP 的 filter_var($ip, FILTER_VALIDATE_IP) 能直接验证输入是否为合法 IP 地址,不合法就拒绝。这样根本不需要转义,因为输入已经被限制在安全范围内。可惜很多老旧系统不会这么写,它们更愿意相信“用户不会故意搞破坏”。
复现这个漏洞花了我不到十分钟。搭建环境、写测试代码、构造 payload、验证回显——整个过程顺畅得让人不安。一个没有安全意识的开发者,可能花五分钟写出有漏洞的功能,而攻击者利用它只需要几秒钟。差距不在技术难度,在意识。
友情链接