Exploiting encrypted cookies for fun and profit
This is a sample project and text for a blog post found on spring.io.
Introduction
Developers often incorrectly use encryption in an attempt to provide authenticity. For example, a RESTful application may mistakenly use an encrypted cookie to embed the current user's identity.
The mistake is that encryption can only be used to keep a secret while signing is used to verify authenticity of a message. In this post, I will explain and provide an example of why encryption is not a guarantee of authenticity.
If you just want to see code, feel free to skip to the end which has a sample Java application that demonstrates the exploit.
Encrypted Cookies (whoops)
Assume we want to avoid looking up our users in session and instead want to embed the user information within a cookie. Since cookies can be modified by a malicious user we will need to be able to verify that the cookie that was provided was created by our application server.
To prevent users from tampering with the cookies we mistakenly decide to encrypt the cookie using AES encryption with CBC mode instead of signing the cookie. Our cookie is properly encrypted (but mistakenly not signed) as follows:
Cookie = Base64String( IV, aes_cbc(k, IV, plainText) )
Such that:
- Base64String - concatenates each byte[] and then returns the Base64 String of the concatenated byte[]
- k - is a secret key that is only known to our server
- IV - is a randomly generated Initialization Vector
- aes_cbc - encrypts the plainText using AES/CBC with the provided IV
- plainText - is in the format of "username=winch&firstName=Rob&lastName=Winch"
NOTE: It is safe and common for us to include the IV in plaintext along with the encrypted text. Since the IV is a fixed number of bytes, it can be easily extracted from a combined IV,encrypted_value byte[].
Review of XOR
Before we go any further it is important to understand XOR. To refresh your memory here is a truth table for XOR
A | B | Output |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
How does CBC decryption use the IV?
To understand how we are going to impersonate another user, we first need to understand a little bit how AES / CBC works. AES is a block cypher which means our message is broken into fixed size blocks and then operations are performed on each block.
When decrypting AES / CBC, the decrypted value of the first block is XORed with the IV. For example, the following would hold true.
decrypt(k, encrypted_first_block) XOR IV = plaintext_first_block
To get a better understanding, let's take a look at a concrete example. Assume that the following is true:
- decrypt(k, encrypted_first_block) is 11011101
- IV is 10101010
NOTE: Our example is simplified by using a block size of 8 bits instead of the actual block size of 128 bits. This makes it easier for humans to follow along.
This means our plaintext_first_block would be 01110111 ("w" in ASCII). Our work is shown below:
decrypt(k, encrypted_first_block)
XOR IV
------------
plaintext_first_block
11011101
XOR 10101010
------------
01110111 // "w" ASCII
Modifying the decrypted value
With the information above, we can modify the decrypted value. Specifically, given:
- a valid encrypted value
- the corresponding IV
- the corresponding plaintext
we can calculate a modified IV named IV' that will be combined with the original valid encrypted value to impersonate another user.
The first step is to calculate the unknown value of decrypt(k, encrypted_first_block) by canceling out all the bits in the IV by XOR with the first_block_plaintext. Our work is illustrated below:
IV
XOR plaintext_first_block
------------
decrypt(k, encrypted_first_block)
10101010
XOR 01110111
------------
11011101
The final step is to calculate IV' by executing decrypt(k, encrypted_first_block) XOR desired_plaintext_first_block. Once again, our work is illustrated below:
decrypt(k, encrypted_first_block)
XOR desired_plaintext_first_block
------------
IV'
11011101
XOR 01100001 // "a" ASCII
------------
10111100
We can now verify that providing IV' with the originally encrypted value would result in "a" instead of "w".
decrypt(k, encrypted_first_block)
XOR IV'
------------
desired_plaintext_first_block
11011101
XOR 10111100
------------
01100001 // This is "a" ASCII
This demonstrates that if we provide IV' (instead of IV) along with the originally encrypted value it will be decrypted as "a".
Impersonating another user
Now that we have seen how we can create a modified IV to make our encrypted value whatever we like, let's explore how this applies to us authenticating as a user and then modifying our encrypted cookie to impersonate another user.
In the Modifying the decrypted value section, we mentioned we needed some information before we could perform the exploit. Let's see how we can obtain the information necessary for the exploit with the encrypted cookie:
- a valid encrypted value - An encrypted value is transmitted in the cookie and can be viewed by anyone with a valid account
- the corresponding IV - An IV is transmitted in the cookie and can be viewed by anyone with a valid account
- the corresponding plaintext - For simplicity, assume a malicious user discovered the format of the cookie by observing the cookie name corresponded to an open source framework. The format was then calculated by knowledge of the user we authenticated as and studying the code of the open source framework.
Now that we have the necessary information and have an understanding of how we can modify the encrypted value, it is easy to see that we can impersonate any user we want. So long as we have a valid account, we can create an IV' that changes the username in the encrypted cookie to be the desired user of our choice.
Source Code
Not convinced of the exploit? See it demonstrated by running the sample project on github. To run the sample import it as a Maven project into your favorite IDE and run the demo.Main class.
You will observe that we authenticate as "winch" but are able to modify the encrypted cookie to impersonate a user named "admin".
Conclusion
At this point I hope you are convinced that encryption is not a valid way of providing authenticity. Instead, we would need ensure the cookie is signed. One solution would be to use authenticated encryption which provides secrecy, integrity, and authenticity.
Please keep in mind that simply signing our cookie does not make it secure. There are other attack vectors, like replay attacks, that must be addressed in order to make the solution secure.
We must realize that security is hard and should not be implemented on your own or even within a small number of individuals. Instead security is best implemented in a community that can check one another for mistakes.