Rating: 0

**Description**

> Dear all
>
> Welcome to our new enterprise solution.
> Data is safe - cause we use strong encryption !
> It is soooo safe, that even I am using this system.
> You even can audit the code [here](files/webapp.py)
> Plz DO NOT hax this - cause it is impossible !1111
>
> Regards
>
> Martin, the Webmaster
>
> URL: http://solution.hackable.software:8080

**Files provided**

**Solution** (by [Aurel300](https://github.com/Aurel300))

On our first visit to the webpage, we can only login or register. We can find out that the server is running Flask, although this is immediately obvious from the source code given.

The registration is normal enough:

The login is slightly unusual though, it emulates a 2FA system:

(No screenshot for the second form or anything else after, because the challenge is not working anymore. The form asks for the password and a 2FA token that is not actually implemented.)

Once we login, we can add notes, list all of our notes, and show specific notes. Whenever we look at a note, the browser first shows it encrypted, then it obtains our encryption key via AJAX, then proceeds to animate XOR-decryption of the note.

Even without looking at the source code, we can spot the first vulnerability: consecutive, non-encrypted IDs for notes. If we change the URL to /note/show/0, we get to see a note added by admin:

07D8B68CDB92A687DFC74217C9D7F47E84540A3C97BA3D2B8B5B3E1C110A4C54F09392
6534FFB7BCF859FD3ED8863611400F9ECB56064C20EDF0B6F6B1BF1CBB522A91F0C9B2

The browser still animates XOR-decryption, but it uses our own key instead of admin's, so the decrypted data is just garbage.

The note is 105 bytes, and our own key is only 20 bytes - perhaps this cipher could be broken? Well, after some playing around it is clear that the key is not 20 bytes, and is at least 100 bytes long. The simplest possible explanation is that the admin note is actually encrypted using proper [OTP](https://en.wikipedia.org/wiki/One-time_pad), unlike our own notes.

Now let's finally have a look at the source code. Most of it is basic Flask stuff. The @loginzone decorator is applied consistently, and it explains why /note/show/0 was accessible to us - it simply checks IF whe are logged in, not WHO we are.

The one strange thing is in the bit that seemed unusual before - namely the two-step authentication process:

python
# first part of authentication
backend.cache_save(
)
if state > 0:
return do_render()

#second part of authentication
def do_auth_post():
record = sql_session.query(model.Users).filter_by(
).first()
if record is None:
return do_render()
# well .. not implemented yet
if 1 == 0 and not backend.check_token(username, token=1):
return do_render()
return do_302("/home/")


The backend.cache_save(...) call is odd. Even before we are properly logged in, the cache contains the encryption key for that username, if it exists. (Also note that the challenge is probably called 3NTERPRISE because caching becomes relevant with large-scale projects.) We cannot simply call the first step and get the encryption key, however, since the getkey API endpoint does not rely on the cache, not to mention that it requires a full login (@loginzone).

python
@app.route("/note/getkey")
def do_note_getkey():
))


But there is a place where the cached key is used:

python
text = get_required_params("POST", ["text"])["text"]
if key is None:
raise WebException("Cached key")
text = backend.xor_1337_encrypt(
data=text,
key=key,
)
note = model.Notes(
message=backend.hex_encode(text),
)
sql_session.commit()
return do_render()


So whichever key is in the cache (which may not be the one for our username!) will be used to encrypt the notes we submit. Since the encryption method is XOR, submitting a known plaintext will allow us to recover an unknown key.

But how to ensure a different key is cached?

The reason caching can be problematic is because there is a lot of things that can go wrong. The wrong user can be served personal details of another. Old information may be shown to the user, misinforming them. In the case of this challenge, the problem is time-based: there exists a race condition between the note adding (using the cached key) and the first step of the authentication (setting the cached key).

The first step of authentication is supposed to log out the user, which would prevent us from submitting notes and using the cached key. But before it logs us out, it puts the encryption key into the cached, and then does a state check of some sort, presumably a slow database operation.

So the plan is:

1. Login completely as our own user (/login/user, then /login/auth)
2. Do the first step of authentication as admin (/login/user again)
3. Create a known-plaintext note (/note/add)

The key is that 3 needs to happen a very short time after 2.

During the CTF, there were some issues with the server being very slow (10+ seconds for a page load), so I didn't even try to exploit this. After some maintenance downtime, the service was slightly faster, though a page could still take up to 5 seconds to load, so I was somewhat sceptical. Much to my surprise, the exploit worked on the first try - there was a note encrypted with the admin key in the list of notes for our user. Then simply XORing that note with aaa... (which was the known plaintext) revealed the admin key, and XORing the admin key with the admin note revealed:

Hi. I wish U luck. Only I can posses flag: DrgnS{L0l!_U_h4z_bR4ak_that_5upr_w33b4pp!Gratz!} ... he he he

DrgnS{L0l!_U_h4z_bR4ak_that_5upr_w33b4pp!Gratz!}