Wednesday, October 6, 2010 At 3:22PM
On the weekend of September 24-26, NYU Polytechnic held a CTF qualifying round for its annual Capture The Flag competition to be held during Cyber Security Awareness Week, attracting high school students, undergrads, graduates and industry professionals to compete for a spot in the finals. Teams from all over brought a wide range of skillsets, and had just 48 hours to rack up as many points as possible.
Two of of our former interns, Julian Cohen and Luis Garcia, who were responsible for organizing the CTF asked that I help write some crypto challenges, as well as be one of the judges of the competition. Besides myself, Dino Dai Zovi designed the Exploitation challenges; Jon Chittenden, Alex Sotirov and Erik Cabetas the Reversing challenges; Efstratios Gavas on Forensics; and lastly, Julian and Luis wrote the web security challenges.
For this round of the CTF, I wrote four crypto challenges, each of varying difficulty. I wrote the challenges in Python, and served them using the CherryPy HTTP framework. This worked pretty well, and I’m happy to hear there were no complaints regarding server availability/performance. In addition, using CherryPy made deployment a breeze, since CherryPy also includes an embedded WSGI server which required almost no configuration.
Background
I designed all of the challenges to reflect actual examples of insecure crypto implementations we’ve encountered at Aon’s Cyber Solutions during security assessments at various clients. In this post, I describe the premise behind each of the challenges as well as any potential hurdles that players would have ran into. I leave the actual exploitation of each challenge open as an exercise for the reader. I look forward to reading any write-ups by teams who completed the challenges and plan to include links to them in future updates to this post.
Crypto #1 / Padding Oracles
Given all the recent discussion surrounding the ASP.NET Padding Oracle attack by Juliano Rizzo and Thai Duong, I found it fitting to include a challenge involving padding oracles. In this challenge, a user submits a form containing a “name” and “team” parameter. These values are encrypted using AES in CBC mode, then encoded using URL-base64, and returned within a “SID” cookie value. The format of the plaintext looks like so, where %s was substituted with the name and team parameter values respectively:
challenge = 'CSAW 2010 CRYPTO #01|%s|%s|role=5|CHALLENGE' % (name, team)
The goal of this challenge was to decrypt the cookie using a padding oracle attack and modify the role to equal 0 using CBC-R. I specifically introduced several obstacles to thwart use of any existing exploit tools. First, I used URL-base64 encoding to throw off tools like PadBuster, which did not support URL-base64 at the time. URL-base64 encoding simply replaces the “+” character with “-“, “/” with “_”, and strips any padding characters. When the application caught a BadPaddingError during decryption, it would return an error page with the same HTTP status code and content-length as other errors. This would confuse most tools that relied on HTTP status codes, exceptions/stack traces and response content-lengths to distinguish invalid and valid padding. I only introduced a very subtle difference in the error message itself:
BadPaddingError:
Sorry, an error had occurred.
Other errors:
Sorry, an error has occurred.
Sneaky, eh? One intersting thing of note is that the Python Crypto library, by default does not validate decrypted blocks are properly padded. I had to verify the padding manually, and throw a BadPaddingError if the padding was incorrect. I understand this challenge could have been done via CBC bit flipping, though that is the premise behind Crypto #2. For an in-depth discussion of Exploiting Padding Oracles, see our related post on Automated Padding Oracle Attacks with PadBuster.
On successful completion of this challenge, the server returns a page with the flag as well as a form to submit for challenge2. Without this form and the role parameter value that was set, it was not possible to move onto the next challenge.
Crypto #2 / CBC Bit Flipping
CBC mode bit flipping attacks can allow an attacker to manipulate portions of an encrypted message by flipping specific bits of ciphertext. If you are not familiar with the mechanics of this attack, theres a great post on the Matasano Chargen blog from 2009 (see resources below) that discusses the attack in detail. The following diagram provides a visual explanation that shows how bit flipping works:
The block containing the flipped byte will be mangled when decrypted, however the corresponding byte in the next decrypted block will be altered.
Upon completing Crypo #1, the success response contained a form with an already populated role parameter (again URL-base64 encoded). This value was an AES encrypted string containing the user’s name and team name submitted to Crypto #1. In addition, it reset the user’s role level back to 5, and the goal again was for the players to attain a role level of 0. A hexdump of the URL-base64 decoded “role” value is shown below:
On the server, this block would decrypt to:
To perform a CBC bit flipping attack, we’re going to need to manipulate a byte in the 3rd cipher block that when decrypted, lines up with the 5 in “role=5”, (0xa8 in the 3rd cipherblock, highlighted in blue in the first hexdump above). So XOR “5” with “168” (the ordinal value of 0xa8), and get 173 (0xad). Replace 0xa8 with 0xad in our original ciphertext (this time, highlighted in red):
Which the server would then decrypt to:
Notice how the 3rd block (highlighted in red) was corrupted during the decryption process in order to flip “role=5” to “role=0”. This is an inherit property of CBC (as described earlier), because each decrypted block is XOR’d with the previous encrypted block to produce the plaintext. The key here is that as long as the garbled block does not contain any critical data that would otherwise cause the application logic to fail or error out, this block can be safely sacrificed and the exploit will still work.
Crypto #3
For the third challenge gimme, I created a scenario that allowed users to specify a username to login with, provided they also submit a special key in absence of a password. Unforunately, this challenge contained a critical bug in some validation logic that failed spectacularly. As a result, I will not be publishing the actual technique for this challenge at this time. Sorry guys, my bad.
Crypto Bonus / ECB Block Swapping
The number of players to complete this challenge took me a little by surprise, as this probably is one of the more easier attacks to perform. For those not familiar with ECB mode, individual plaintext blocks are encrypted completely independent of each other. As a result, it is easy to identify patterns in ciphertext, as two identical plaintext blocks will produce the same ciphertext. More importantly, ECB is vulnerable to “block swapping,” which is the underlying technique that is used to complete this challenge.
In this challenge, a user is allowed to login to the system with just their username, however admin and root users are not permitted to login. Fortunately, due to the use of ECB and lack of an HMAC, an attacker could forge a valid token that allowed them to impersonate the “root” user. To make things more difficult, each token only had a lifetime of 5 minutes… oh no! Let’s walk through how to exploit this:
Signing in as the user “AAAAAAAAA”, we receive the the following token. I’ve also included the equivalent base64 decoded hexdump for reference below it:
hIm-o9iObyxcyAaP5mitU5Ura2TC2W0xNAT2VDAtlIsCBmqySW9pKW7HpPkcKdWY
Submitting “AAAAAAAAA” again, we get the following token (decoded/hexdump output):
Note, only the second 8-byte block has changed (highlighted in green)… hmmmmm, could that be [part of] the timestamp? Let’s increase the length of our username to “AAAAAAAAAAAAAAAAAA” and see what happens.
Notice how the 3rd cipher block repeated (the blocks highlighted in blue). Now let’s do “AAAAAAAAAAAAAAAAAAAAAAAAadmin”:
On the server, this would decrypt to " 1285874686664|AAAAAAAAAAAAAAAAAAAAAAAAadmin|CSAW_CHALLENGE#4x02x02".
For sake of time, I increased the number of A’s to make “admin” within its own block (highlighted in red above). If we then swap the 6th block (highlighted in red) with the 3rd (highlighted in blue), to produce the following ciphertext, and then submit it in URL-base64 encoded form we successfully authenticate as the administrator:
The above would decrypt to: " 1285874686664|admin|CSAW_CHALLENGE#4x02x02".
Another potential expoit vector in this challenge was the ability to create future-dated tokens. Combine an admin token with a future-dated timestamp, and you’ve got yourself an “uber-token,” if you will. Below is a code snippet from some of the timestamp validation logic:
timestamp, userid, m = decrypted_token.split('|') if (time.time() * 1000) - int(timestamp) > 300000: cookie['CSAW_ID'] = '' cookie['CSAW_ID']['expires'] = 0 return ERROR_PAGE % "TIMESTAMP_EXPIRED"
Following the CTF Finals held October 28-29, I plan to publish the source code to the challenges I’ve written to CSAW Crypto Challenges on GitHub, so be sure to check back for more updates!
Resources
Below are links to some excellent resources that provide further information on some of the topics covered in this post.
-
If You’re Typing The Letters A-E-S Into Your Code, You’re Doing It Wrong (Thomas Ptacek)
-
Security Flaws Induced by CBC Padding Applications to SSL, IPSEC, WTLS… (Serge Vaudenay) [PDF]
CSAW CTF team writeups
Below are links to individual teams challenge writeups (to be added):
Author: Marcin Wielgoszewski
©Aon plc 2023