another hmac collision with delimiter injection
In the previous post, we discussed about how hmac collision can arise because of the lack of delimiter between two concatenated values.
The program can be modified with the additional delimiter between two different values username + ":" + str(amount)
from cryptography.hazmat.primitives import hashes, hmac
import os
key = os.urandom(16)
def generate_transaction_hmac(username, amount):
h = hmac.HMAC(key, hashes.SHA256())
data = username + ":" + str(amount)
h.update(data.encode())
signature = h.finalize()
return data, signature.hex()
def sign_transaction(username, amount):
return generate_transaction_hmac(username, amount)
def verify_transaction_signature(data, signature) -> bool:
print("Verifying: ", data, signature)
data_arr = data.split(":")
expected_username, expected_amount = data_arr[0], data_arr[1]
expected_data, expected_signature = generate_transaction_hmac(expected_username, expected_amount)
print("Expected: ", expected_data, expected_signature)
return expected_signature == signature
Yet, there is an edge case that the code fails to address.
Given these inputs:
username = "amy"
amount = 999
Normal scenario
We use the sign_transaction
function to generate the data and signature variable.
data, signature = sign_transaction(username, amount)
print("Original: ", data, signature)
print(verify_transaction_signature(data, signature))
When we execute the code, we can observe that the transaction signature is valid.
Original: amy:999 4df3eaddd776b1963a3013ab2af1d6ef20d2442a706ce0c84780d3dfbb5b1067
Verifying: amy:999 4df3eaddd776b1963a3013ab2af1d6ef20d2442a706ce0c84780d3dfbb5b1067
Expected: amy:999 4df3eaddd776b1963a3013ab2af1d6ef20d2442a706ce0c84780d3dfbb5b1067
True
Injecting extra delimiter to create hash collision
Instead of using the data
as an input for verify_transaction_signature()
, we can write a custom payload that can cause a hash collision.
data, signature = sign_transaction(username, amount)
payload = f"{username}:{amount}:0"
print("Original: ", data, signature)
print(verify_transaction_signature(payload, signature))
Notice that the data (payload amy:999:0
) for the verifying function is different.
Original: amy:999 4df3eaddd776b1963a3013ab2af1d6ef20d2442a706ce0c84780d3dfbb5b1067
Verifying: amy:999:0 4df3eaddd776b1963a3013ab2af1d6ef20d2442a706ce0c84780d3dfbb5b1067
Expected: amy:999 4df3eaddd776b1963a3013ab2af1d6ef20d2442a706ce0c84780d3dfbb5b1067
True
Because verify_transaction_signature()
splits the data and uses the first two array values, we can exploit this behavior to allow the verifying function to generate the hash that matches the original data.
Where is the hash collision?
Two different sets of inputs are producing the same signature 4df3eaddd776b1963a3013ab2af1d6ef20d2442a706ce0c84780d3dfbb5b1067
.
User 1:
- username:
amy
- amount:
999
Malicious user:
- username:
amy:999
- amount:
0
Even though their usernames and amounts are different, the signatures generated (by the transaction signing function) are the same.
Credits
Please sign up for https://pentesterlab.com/ if you want similar practices on code reviews.
Member discussion