Instantly share code, notes, and snippets.

Embed
What would you like to do?
Stripe CTF Level 7 - Solution SHA1 Length-Extension Vulnerability
This level was a slight twist, you'll actually be doing an attack on their crypto. Looking at the code you'll see that they're using SHA1 hashes that are composed of the raw request that you made plus your secret. We also need to be making a request as a premium user. If you attempted to order a waffle, you'll receive a confirmation number--in this case if you order the premium waffle, the confirmation number will be your password to Level8.
Here is the block of code that verifies the signature... this is how we know how it is built and that it is sha1
def verify_signature(user_id, sig, raw_params):
# get secret token for user_id
try:
row = g.db.select_one('users', {'id': user_id})
except db.NotFound:
raise BadSignature('no such user_id')
secret = str(row['secret'])
h = hashlib.sha1()
h.update(secret + raw_params)
print 'computed signature', h.hexdigest(), 'for body', repr(raw_params)
if h.hexdigest() != sig:
raise BadSignature('signature does not match')
return True
Researching on SHA1 we can see that it has a length-extension attack vulnerability, a type of attack on certain hashes which allow inclusion of extra information. There's excellent documentation that describes this attack in the Flickr API Signature Forgery Vulnerability write-up. There's also a nice script and write-up about it at vnsecurity by RD, about how he solved a similar CodeGate 2010 challenge. For my solution I used the script that was supplied on vnsecurity to solve this problem. Since we know what the raw request will be, and we know the length of the secret (14), we can append stuff to the raw request and generate a valid hash. So looking at the /logs/ directory, we can also view other users requests… in this case we're interested in premium users, so id 1 or 2.
This is a request that was made by user_id 1:
count=10&lat=37.351&user_id=1&long=-119.827&waffle=eggo|sig:a75edb45bc6c0057e059b23bc48b84f7081a798f
As you can see, we have the raw request and the final hash... let's append to this and generate a new valid hash, but ordering a different waffle.
Bubba$ python sha-padding.py '14' 'count=10&lat=37.351&user_id=1&long=-119.827&waffle=eggo' 'a75edb45bc6c0057e059b23bc48b84f7081a798f' '&waffle=liege'
new msg: 'count=10&lat=37.351&user_id=1&long=-119.827&waffle=eggo\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02(&waffle=liege'
base64: Y291bnQ9MTAmbGF0PTM3LjM1MSZ1c2VyX2lkPTEmbG9uZz0tMTE5LjgyNyZ3YWZmbGU9ZWdnb4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIoJndhZmZsZT1saWVnZQ==
new sig: 4c230b26a20f192c4a258f529662d3dd0ad8b62d
And here we are... the script has supplied the correct amount of padding needed and gave us the raw required and a valid hash... let’s go ahead and make the request using a simple python script....
Bubba$ cat post.py
import urllib
import urllib2
url = 'https://level07-2.stripe-ctf.com/user-dsccixwxvo/orders'
data = 'count=10&lat=37.351&user_id=1&long=-119.827&waffle=eggo\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02(&waffle=liege|sig:4c230b26a20f192c4a258f529662d3dd0ad8b62d'
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
print response.read()
Bubba$ python post.py
{"confirm_code": "BdxavaMIKC", "message": "Great news: 10 liege waffles will soon be flying your way!", "success": true}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment