SECCON Quals 2016 – biscuiti (Web + Crypto 300) Write-up

Can you login as admin?

Note: You should estimate that exploits cost an hour.

The site is simply a login screen. We are also given the source code, so let’s take a peek :

How it works

The login function works as follows:

  • The username and corresponding encrypted password are retrieved from database by the query $dbh->query(“SELECT username, enc_password from user WHERE username='{$username}'”);
  • The encrypted password is a base64 encoded string of IV || password_cipher.
  • The password_cipher is then decrypted using AES-128-CBC with the IV and a secret key and compared with the user-specified password.
  • If the user-specified password is correct, session variables are set:
    • $SESSION[“name”] = query_result[‘username’];
    • $SESSION[“isadmin”] = query_result[‘isadmin’];
  • Cookie JSESSION is then set to:
    • $j = serialize($SESSION)
    • JSESSION =base64_encode( $j || MAC($j) )
    • The MAC function encrypts an input with a 16-null-bytes IV and the secret key, then returns the last cipher block.
  • Whenever we access to the site, the MAC is validated. We can get flag if $SESSION[“isadmin”] is not null.

The vulnerabilities

SQL Injection

It’s obvious that the code is vulnerable to SQL Injection since the username is not escaped :

Unfortunately, only username and encrypted password are returned from the query, so we are not able to set isadmin to a not-null value using this vuln.

Constant IV

The IV used to encrypt data is a 16-null-bytes constant. The encryption will produce the same ciphertext for the same plaintext and could allow us to infer relationships between segments of encrypted data.

PHP Type Juggling

After decrypting the encrypted_password, the decrypted password is compared with the user-specified password string by using a loose comparision:

This allow us to bypass the password checking mechanism since openssl_decrypt may return false if the cipher is not correctly decrypted.

How to exploit ?

Initial thoughts:

  • We need isadmin to be a not-null value, but the SQL result set does not contain this key.
  • We can change isadmin value in the serialization string, but we also have to calculate the correct MAC. What we have here are the fixed IV, the plaintext (the serialization string), the last cipher block (the MAC), but not the secret key.

The first way seems to be impossible. For the second thought, we can easily come up to these two options (in fact, only one :] ):

  • Brute-forcing the secret key. If we have the secret key, we can encrypt whatever we want.
  • Manipulating the plaintext and calculate the correct cipher blocks without any knowledge about the secret key.

Combining all of the vulnerabilities we can enforce a Padding Oracle Attack to recover the whole ciphertext from the MAC and do some simple maths on the retrieved cipher blocks to calculate the new MAC that is valid.

Recovering the ciphertext with Padding Oracle attack.

I am not going to explain how Padding Oracle attack works again, you can find a very-easy-to-understand blog post here.

You can bypass the password checking with this payload:


The (username, encrypted_password) retrieved from the query is (‘xxxxxxxxxxxxxxxx‘, ‘bb‘).

In the auth($enc_password, $input) function, openssl_decrypt will fail to decrypt ‘bb‘, and return false. Because bool(false == ”) is true in loose comparisions, we are now logged in !

We can take advantage of this vulnerability to use auth function as an oracle. With an empty specified-password, if the encrypted_password is successfully decrypted, the auth function will return false and we will get an error. Otherwise, we will be logged in.

Usually we use Padding Oracle attack to recover the plaintext. In this case the IV is fixed and we know a cipher block as well as the whole plaintext, we can recover the previous cipher blocks byte-by-byte:

Decryption in CBC mode (Illustration from
Decryption in CBC mode (Illustration from

It takes time to recover the ciphertext. What we will have should be similar to this:

The C5 is our MAC. We can modify the serialization string to as below to set isadmin to true:

Take a look again on how CBC encrypts, we will see that:

CBC encryption illustration from Wiki
CBC encryption illustration from Wiki

C3 = Encrypt(P3 ^ C2)

C5 = Encrypt(P5 ^ C4)

C5′ = Encrypt(P5′ ^ C4)

Because all cipher blocks previous to C5 will not be changed if we modify P5 and we can fully control P3, we can force the encryption to encrypts P3 = P3′ which will result a cipher block that is the same to C5′. Assume that we have a serialization string and cipher blocks as follow :

In which :


Then we know the C5′, which is the correct MAC for the last modified plaintext block. The last step is constructing a new JSESSION cookie value to get the flag.

Leave a Reply

Your email address will not be published. Required fields are marked *