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 allowed
  • List, Tuple, Set: all elements in these are checked for safeness
  • Dict: all keys and values are checked for safeness
  • Name: the name has to equal math and has to be loaded meaning that math is the only object for that we can use the dot notation to call functions or get constants
  • UnaryOp: the single operand is checked for safeness
  • BinOp: both operands are checked for safeness
  • Call: the function, arguments and keywords are checked for safeness
  • Attribute: 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"])')}

flag.png

Other resources