Rating: 5.0

## meta

There have been many nice and high quality challenges of this ctf, but Imposter was my personal favorite, because it is kind of self-contained: One doesn't need any prior special knowledge and everything needed for the solution can be learned in a reasonable time frame. Even if the solution is not clear from the beginning, there are clear objectives one can solve one after the other. Every little step gives a rewarding feeling. I was so HAPPY finally seeing this flag!

This write-up got quite long, but I tried to include the train of thought, because that is something I sometimes miss at some write-ups. At the end everything is obvious and SOO EASY, but for hours it was not.

Since the challenge author probably spend some time for creating this well done challenge, I want to pay my respect by sharing this write-up. Also, there is no tl;dr.

## poking around

The website greets you with an error notice Invalid session please sign in
and a login form, where the user is supposed to enter username, password and a 2fa otp.

Since the challenge name is "imposter", it "MUST" have to do something with session hijacking (so I was sure).

Trying admin:admin and some other combination lead to nothing, so further discovery:

Besides the login, there is a "password reset" function that I just tried once.
The user is supposed to enter the username. In the next step, if the username is valid, the form displays fields with disabled inputs for username and e-mail, prefilled with the data from the username entered in the first step. Oddly enough, for user admin, the form shows admin@congon4tor.me which is very unusual and probably the first flaw.
Submission of the form show "Password changed successfully" - but there never was the possibility to actually enter a new password!
Strange, so I ignored this reset form and decided to dig somewhere else.

The registration form looks normal: username e-mail, password, password confirmation. Second step of the registration is to scan a qr code to enable two-factor authentication. I did as I was told to and scanned the code with the Google authenticator app and got a working 2fa code which allowed me to log in with the fresh created account.

After log in, the page allows storing notes. To view a note one must enter the 2fa code again.

It was pretty obvious, that the flag will be hidden in a note of the admin account. But how to log in as admin!?

Since the name of the challenge was "imposter", it was clear that it "HAS" to do something with the cookie, therefore I tried to make sense of the cookie. It was displayed as an array with 3 values, for example eyJpZCI6M30.YE6WjQ.pk1cB9AQUY6sA3Xuin62MJJ-CWI (entries separated by dots).
But they behaved strangely. Even after an account change, the first value did not change at all. But even after long starring and thinking and trying I was not able to make further sense out of those values, therefore I decided that the cookie is not too important and that I should dig somewhere else.
(Yes, I missed something very obvious here!)

## 2fa

The next step was to further investigate this 2fa: A quick online search showed that there are some security problems with those qr-codes: many vendors and 2fa apps include the username and the issuer together with the code, for users convenience. But the question is: would you write your houses address next to your house key? [[why you shouldn't scan two-factor authentication qr codes](https://medium.com/crypto-punks/why-you-shouldnt-scan-two-factor-authentication-qr-codes-e2a44876a524)]

Inspired by this I decided to scan the qr code with another app and check what details are visible. The codes are in the form of otpauth://totp/2Password:cfnjo?secret=MNTG42TPGEZDGNBVGY3TQOI%3D&issuer=2Password (standard usage for topt, as I learned now). One problem (it's not really a problem, it's by design) is, that if someone is able to find the secret, an attacker would be able to calculate the 2fa-code. Therefore, I searched for "the algorithm" to generate these secrets. As far as I have seen (again: maybe/probably missed something) there doesn't is "the" algorithm for it. Another dead end?

One sentence in one of the rfcs ([rfc6238](https://tools.ietf.org/html/rfc6238) and some more) made me think. It was something like "the secret SHOULD be random". By resetting and re-register the same account again, I tried if the secret would change. It did not. Same user-details, same secret! OK, this has to be something!

### sledgehammer against nuts (going nuts?)

After this finding, I started to systematically create accounts with different usernames: "a", "aa", "aaa", "b", "bb", "c", "d" ... and so on. The secret only changed at the first digits!
"From now on, this must be easy!" (haha)

Now I started to get rid of the browser ui and wrote a python script, that registered all accounts near the name "admin". For every letter, there are neighbors. For 'c', the 2 neighbors are 'b' and 'd'. The neighbors of "admin" are e.g. "bdmin", "aemin" and so on.

After some brain cracking, how to permute all the neighbor letters (this stuff always gives me headaches, maybe I should not do computers science? -- Or science at all?), I went for this probably not optimal python solution (if its stupid, and it works, it's not stupid!):

python
import requests

def neighbours(letter: str, offset: int = 2):
if offset == 0:
return [letter]

if offset > 0:
ords = ord(letter)
mins = max(97, ords - offset)
maxs = min(122, ords + offset)

ret = []
for x in range(mins, maxs + 1):
ret.append(chr(x))

return ret

if __name__ == '__main__':

ranges = []

ax = neighbours("a")
dx = neighbours("d")
mx = neighbours("m")
ix = neighbours("i")
nx = neighbours("n")

for a in ax:
for d in dx:
for m in mx:
for i in ix:
for n in nx:
username = a + d + m + i + n



It seems to work!
I registered all the neighbors within a range of 2 letters: 1874 accounts. (That is why I named this part the "sledgehammer").




It really looks like it is a direct mapping of "a" to MF, "d" to SG, "m" to 22, "i" to LM???
Noooo, sometimes "i" maps to LP or LQ.

This hit me hard. I was seriously frustrated and began to think about some life decisions (I *really* did!).

This was the second time, I went to bed after not solving this challenge. (I already did some other challenges, so I was still a little bit confident, that I might get this too at the end).

Next day I started to talk to my teammate about this. After presenting all my findings, he told me that I should check the cookie again. And we did: The entry eyJpZCI6M30 is base64 {"id":3}. But even with this, the other values didn't make sense. (What did we miss here??)

## being ugly

Since I was sure that the main part of the challenge is to get the 2fa, I even tried the first few-thousand passwords of the [rockyou.txt](https://www.kaggle.com/wjburns/common-password-list-rockyoutxt) -- of course with no success. Sorry orga team, but at this point I felt like YOU made me do it!

This was only possible because the error message differentiate between "wrong credentials" and "wrong otp", so theoretically it would have been possible to brute force the password, even without the 2fa.

The big thing follows: He asked if the script really sends a passwort-reset mail. For me, it was clear, that a ctf challenge usually does not really send real e-mails. But just to be sure, we checked. We created an [e-mail webhook](webhook.site/) and tried to reset the password of a new registered account: **There really is a reset mail!**

Within the reset-mail there is a reset-token and the url and with this information it was possible to reset account passwords.
But how to reset the admin? The token changes for every request, and it seems like it is implemented with a proper method.

As written at the beginning, it was quite odd that the request reset form showed the e-mail of the account. Obvious try was to remove the blocking of the input and enter our webhook email. But this would have been too simple and ended in an error message "The provided email does not contail the user".
After some search for the term "contail" there was another down.

So I decided to poke this form with a stick, and created a little python script to better interact with it. After some thinking it hit me in the face (yes, sometimes I am slow...) that the error wasn't "is not the same" but it was "does not contai[n]". So I tried admin@congon4tor.me AND I got a success!

"From now on everything is easy!" (How often did I write this now?)

So we (my teammate returned) thought about how to make this form to send the reset token to our address. We tried to append our address to the real address, separated by + (knowing that some (most?) e-mail provider accept pre+name@hier.da as the same as name@hier.da). But of course this did not work.

The next Idea was that the tool responsible for sending mails must be able to handle more than one entry in the TO: field, so we thought about how it would be possible to give it a list of addresses. But what separator? To make sure not to miss anything (and to further practice python), I changed the script to try all the ascii characters as separator between his@addr.es and our@addr.es.
This again was a little bit sledgehammering... but it got the correct result, and we got many reset token.

Now it was easy to change the script to just ask for one mail.
(There have been some difficulties with the requests, so we try until the request makes it.)

python
import requests

if __name__ == '__main__':

our = "c96c328b-4fa0-40e4-8c6e-a698007225ec@email.webhook.site"

joiner = ","

email = our + joiner + his
print(data)

while True:
try:
except:
continue
break

if "The provided email does not contail the user" in response.text:
print(joiner, "does not contail")
elif "Error sending email: HTTP Error 400: Bad Request" in response.text:
print(joiner, "Error sending email: HTTP Error 400: Bad Request")
else:
print(joiner, response.text)



With his reset-mail send to both accounts, his and ours, we were able to reset the admin password!

## 2fa (again)

My teammate killed me with "but you have tried to decode the secret with base32??". No I have not, and I feel utterly stupid for this.
For example MJSG22LOGEZDGNBVGY3TQOI, the secret I got for registering the account bdmin, is bdmin123456789.

Okay, from now on it REALLY was easy.
admin123456789 is the secret MFSG22LOGEZDGNBVGY3TQOI and therefore I now can create the correct qr code to import it to the Google 2fa software. (There are more ways to get the otp, once you have the secret, but this was easiest.) One super easy way to create a qr code is to use [DuckDuckGo](https://duckduckgo.com/?q=qrcode+%27otpauth%3A%2F%2Ftotp%2F2Password%3Aadmin%3Fsecret%3DMFSG22LOGEZDGNBVGY3TQOI%253D%26issuer%3D2Password%27&t=ffab&ia=answer).

## win