Anatomy of a Crypto Vulnerability
source link: https://alexgaynor.net/2016/mar/14/anatomy-of-a-crypto-vulnerability/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Alex Gaynor
Hi, I'm Alex. I've been CISO at a startup named Alloy, an engineer working on Firefox security, and before that at the U.S. Digital Service. I'm an avid open source contributor and live in Washington, DC.
© 2020. All rights reserved.
Anatomy of a Crypto Vulnerability
Mon, Mar 14, 2016Before I describe the vulnerability, I want to give huge thanks to Ben Bangert and Alessandro Molina for quickly responding to my report, and to Paul Kehrer for reviewing and confirming my findings.
Sessions are a core part of many web applications. Put an opaque identifier (e.g. a UUID) in a cookie, then in your web app find the session in a database of some sort. The session might contain data like the currently logged in user, whatever.
For a variety of reasons, sometimes you don’t want to have the database on your
backend. Instead you want to put the session data directly into a cookie. In
those cases, generally you want to ensure your cookie has two properties:
integrity and confidentiality. Integrity means a user should not be able to
change the contents of their session (e.g. to change what user_id
they’re
logged in as), and confidentiality means they should not be able to see the
contents of their session 1.
Beaker is a popular Python library for managing sessions. I was looking at it for reasons that I’ve completely forgotten at this point. Specifically, I was looking at its crypto. Beaker offers encrypted sessions, and I wanted to see how they were implemented.
Digging around a bit, I found an aesEncrypt
function 2:
def aesEncrypt(data, key):
cipher = AES.new(key, AES.MODE_CTR,
counter=Counter.new(128, initial_value=0))
return cipher.encrypt(data)
Ok, AES
in CTR
mode. The first thing that jumped out to me is that the
nonce
(or counter
here) is always
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
.
CTR
mode for block ciphers has one requirement: you must never reuse a
(key, nonce)
pair. If you reuse a pair, then any data encrypted under that
pair becomes decryptable by the attacker. Since our nonce
is constant, that
means we must have a unique key for every encryption, or confidentiality
is totally broken.
Let’s look to see where our key comes from.
A quick grep for aesEncrypt(
3 turns up:
nonce = b64encode(os.urandom(6))[:8]
encrypt_key = crypto.generateCryptoKeys(self.encrypt_key,
self.validate_key + nonce, 1)
data = util.serialize(session_data, self.data_serializer)
return nonce + b64encode(crypto.aesEncrypt(data, encrypt_key))
What’s happening here? First we generate a 6-byte nonce
from
/dev/urandom
and base64 encode it (note that this is different from the
nonce
in aesEncrypt
).
Then we call generateCryptoKeys
4. What does it do?
def generateCryptoKeys(master_key, salt, iterations):
return pbkdf2(master_key, salt, iterations=iterations, dklen=keyLength)
Doing a little substitution, this means that encrypt_key
is derived by:
pbkdf2(self.encrypt_key, self.validate_key + nonce, iterations=1, dklen=32)
self.encrypt_key
and self.validate_key
are constant in this context.
This means that if our nonce
repeats, then the result of
generateCryptoKeys
repeats, which is passed to aesEncrypt
as key
.
If nonce
here is 6 bytes, which are base64 encoded, how often does it
repeat? We can ignore the base64 encoding since it doesn’t affect the entropy.
6 bytes of random data means 48 bits. So 2 ** 48
possible values. The
birthday paradox means that we can, with 50% probability, expect a repeat after
sqrt(2 ** 48)
(or 2 ** 24
) values. 2 ** 24
is a little under 17
million.
In short, if we observe 17 million cookies, there’s a 50% chance we’ll be able to decrypt a pair of them. That may not sound like much, but it’s completely unnecessary: we know how to build more secure cryptographic systems. It’s particularly bad for a general purpose library, where we have no idea how important the data being protected is.
The immediate-term solution is for Beaker to use a larger nonce
size, which
decreases the frequency of repeat values. A better long-term solution is to
move away from custom cryptographic code and use safer, preprepared recipes,
such as NaCL’s secret_box
or Fernet
(available for Python in PyNACL
and pyca/cryptography), which have received extensive review from
cryptographers and offer safer APIs.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK