As you approach SafetyCorp’s headquarters, you come across an enormous cogwork tree, and as you watch, a mechanical snake slithers out of a valve, inspecting you carefully. Can you build a disguise, and slip past it?
Category: misc
Solver: 3mb0, lmarschk
Flag: HTB{45ts_4r3_pr3tty_c00l!}
Writeup
For this challenge, we can download the python code (python 3.10 to be able to use the new match-case statement) for a server that offers python remote code execution via eval
. But the server has some security checks to prevent us from executing arbitrary python code:
if is_safe(ex):
try:
print(eval(ex, {'math': math}, {}))
except Exception as e:
print(f"Something bad happened! {e}")
First, the server checks whether our function is safe (more on that later) and if it is, it executes our expression using eval
and with the math
and builtins
modules as the global namespace and with an empty local namespace.
def is_safe(expr: str) -> bool:
for bad in ['_']:
if bad in expr:
# Just in case!
return False
return is_expression_safe(ast.parse(expr, mode='eval').body)
The first step to check whether the code is safe, is to disallow all underscores. We do not see a way to get past that check. Furthermore, the server builds a syntax tree and checks all nodes of our code for safeness:
Constant
: constants like strings or numbers are allowedList
,Tuple
,Set
: all elements in these are checked for safenessDict
: all keys and values are checked for safenessName
: the name has to equalmath
and has to be loaded meaning thatmath
is the only object for that we can use the dot notation to call functions or get constantsUnaryOp
: the single operand is checked for safenessBinOp
: both operands are checked for safenessCall
: the function, arguments and keywords are checked for safenessAttribute
: the value is checked for safeness- all other nodes are considered to be unsafe
The ban of underscores together with math
as the only usable module, common python remote code execution strategies like using import
or __class__
do not seem to be possible.
After reading the server code, I thought really long about the math
library and read the full documentation multiple times. While explaining the code to a team member, I realized that the method that checks the safeness of dictionaries is not properly implemented. The most important part of the method looks like this:
if not is_expression_safe(k) and is_expression_safe(v):
return False
We can create a truth table for this if expression:
key is safe and value is safe: not True and True => False
key is safe and value is unsafe: not True and False => False
key is unsafe and value is safe: not False and True => True
key is unsafe and value is unsafe: not False and False => False
So the only case in which a directory is unsafe is when the key is unsafe and the value is safe. Every other combination is a safe directory. Because of that, we can write unsafe code in the value of a dictionary and it will be safe as long as the input string does not contain underscores.
We tried the following as our first command:
{1: exec('import subprocess; subprocess.Popen(["ls"])')}
As we saw the flag just lying in the root directory, we did not go for a real shell but just cat’ed the flag:
{1: exec('import subprocess; subprocess.Popen(["cat", "flag.txt"])')}