XML外部实体注入(XXE)在PHP网站里不算罕见。很多开发者用simplexml_load_string或DOMDocument::loadXML解析用户提交的洞时的协XML时,没关掉外部实体加载,常用攻击者就能通过SYSTEM关键字让服务器去读取本地文件、站存探测内网,洞时的协甚至执行远程请求。常用协议选择直接决定了利用的站存深度和范围。
最常被拿来读文件的洞时的协是file://。构造一个外部实体指向file:///etc/passwd,常用如果PHP返回了实体内容,站存就能直接看到系统账户列表。洞时的协Windows下换成file:///C:/Windows/win.ini同样有效。常用这个协议几乎不依赖任何扩展,站存只要libxml没禁用实体加载,洞时的协成功率很高。常用我见过一个老旧的CMS,上传XML配置的功能就栽在这上面,攻击者用file://读到了数据库配置文件,整站沦陷。
php://filter是PHP环境里独有的利器。它不能直接读文件内容,但可以配合base64编码把文件内容带出来。比如php://filter/convert.base64-encode/resource=index.php,这样即使文件里有特殊字符或二进制数据,也能安全地通过XML实体回显。很多WAF只拦截file://,对php://filter的检测反而薄弱。实际测试中,用这个协议读源码比file://更稳,因为编码后的字符串不会破坏XML结构。
expect://协议需要expect扩展支持,默认不开启。一旦开启,攻击者可以直接执行系统命令,比如expect://id。这个协议在CTF里常见,真实环境里遇到得靠运气。不过有些PHP Docker镜像为了调试方便会装上expect,这就等于给XXE开了后门。
data://协议允许把外部实体指向一段内联数据。攻击者可以构造data://text/plain;base64,后面跟恶意XML,用来绕过某些基于文件路径的过滤规则。它本身不直接造成文件读取,但能作为跳板,配合其他漏洞形成利用链。比如先通过data://加载一个远程DTD,再让DTD去读文件。
http://和ftp://主要用于探测内网或发起SSRF。把外部实体指向http://192.168.1.1:8080/,根据响应时间或错误信息判断端口是否开放。FTP协议还能用来把文件内容外带到攻击者控制的服务器——如果服务器支持ftp://且允许匿名写入,攻击者可以构造一个外部实体,让PHP把读到的文件通过FTP上传出去。这种方式比直接回显更隐蔽,适合有回显限制的场景。
gopher://在XXE里用得少,因为PHP的libxml默认不支持。但某些老版本或特殊编译环境下,它能用来构造任意TCP数据包,攻击内网Redis、MySQL等未授权服务。我遇到过一例,攻击者用gopher://打内网Redis写SSH公钥,最终拿到服务器权限。这种利用链对协议支持要求高,但一旦打通,危害极大。
还有一个冷门协议compress.zlib://和compress.bzip2://,它们能读取压缩文件。如果目标服务器上有压缩过的日志或备份,攻击者可以用这些协议直接解压读取,绕过一些基于文件扩展名的检查。不过实际案例不多,因为需要zlib或bzip2扩展,且文件路径要猜得准。
协议的选择取决于目标PHP的编译选项、扩展加载情况和libxml版本。实战中,先试file://和php://filter,不行再换http://探测内网。expect://和gopher://属于高收益但低概率的选项。防御上,最直接的办法是在解析XML前调用libxml_disable_entity_loader(true),或者用LIBXML_NOENT以外的选项。PHP 8之后默认不加载外部实体,但老系统依然危险。