The Book of Gehn

CTR Bitflipping

August 22, 2019

No much to explain: encryption does not offer any protection against forgery.

– Spoiler Alert! –

We saw this in the CBC Bitflipping post and we will see it again here but this time it will be the CTR encryption mode our victim.

Setup

Recall from CBC Bitflipping post the scenario where we have a add_user_data function that adds arbitrary data to users’ profiles:

>>> cfg = generate_config(random_state=seed, block_size=block_size,
...         enc_mode='ctr',
...         prefix = "comment1=cooking%20MCs;userdata=",
...         posfix = ";comment2=%20like%20a%20pound%20of%20bacon")

>>> def add_user_data(m):
...     assert ';' not in m and '=' not in m
...     msg = B(cfg.prefix + m + cfg.posfix)
...     return enc_ctr(msg, cfg.key, cfg.nonce)

On the server side there is a function that checks for admin role:

>>> def is_admin(c):
...     msg = dec_ctr(c, cfg.key, cfg.nonce)
...     return b'admin=true' in msg.split(b';')

We control the user’s data (userdata field) but we cannot control the entire profile.

In particular, we cannot say that we have the administration role adding admin=true.

Well… technically the check in add_user_data is made with an assert.

In Python these asserts are removed if the code is executed with the optimization flag so if the add_user_data runs with the flag on we could inject anything.

So do not use assert for any real check.

So, no, we cannot do this:

>>> add_user_data("somedata;admin=true")
<...>AssertionError

Bit flipping

In CBC Bitflipping post we saw that CBC does not offer any protection against forgery and how to break it.

In this post we will do the same but attacking the CTR mode.

First we will create our target plaintext and a padding plaintext. The former is the plaintext that we want to inject and the latter is the one that we are allowed to inject.

>>> target = B(';admin=true;')
>>> padding = 'A' * len(target)

Then we create our profile:

>>> ctext = add_user_data(padding)

Now, because CTR turns a block cipher into a stream cipher using xor, we can patch it trivially:

>>> patch = target ^ B(padding)

The only catch is that we don’t know where our padding is located so we don’t know where to patch.

For this we can use is_admin as an oracle function, trying each position and knowing the correct one only when we get is_admin(..) == True

>>> l = len(patch)
>>> for i in range(len(ctext) - l + 1):
...     cpatched = ctext[:i] + (ctext[i:i+l] ^ patch) + ctext[i+l:]
...     assert len(cpatched) == len(ctext)
...
...     if is_admin(cpatched):
...         print("Priv escalated! Patch at %i" % i)
Priv escalated! Patch at 32

Broken! CTR bitflipping challenge unlock!

Related tags: cryptography, matasano, cryptonita, CTR, counter, forgery

CTR Bitflipping - August 22, 2019 - Martin Di Paola