I gave the “Intro to Crypto Attacks” workshop during Bsides Seattle and will do a little recap in a series of blog posts. Overall, the workshop aimed to cover some of the basics and background of crypto and its uses in applications. I didn’t focus on things like how AES worked or the mathematics supporting encryption, but instead on what you’re likely to see when pen testing an application or auditing code. The lab challenges then focused on some introductory-level (but practical) crypto attacks where attendees attempted to do things like disclose the balance of a targeted account number, open an account and give themselves $1,000,000, capture management secrets, and encrypt / decrypt data without knowing the key. In this blog post series, I’ll briefly summarize the workshop for those who missed it. I also received requests for the slides, but those aren’t very useful; I believe this is much better.
The challenge exercises are written in C# and are designed as Windows Azure Cloud Services and make use of cloudy things like Azure’s blob storage. You could host everything on an IIS instance as a WCF service, but I hadn’t spent much time with Azure before and wanted to get a bit better at understanding how it works.
If you don’t have an Azure account, you can get the free trial but note you’ll need a credit card for an identity proof. You can (like me) set a spending limit of $0 to make sure you don’t get charged when your free usage is exceeded, which is nice, but your instances will be suspended until the beginning of the next month if you reach that limit. If you’re unwilling / unable to use Azure, feel free to deploy on your own IIS server and hard code (or read from a file) anything that normally grabbed data from the cloud.
Prior to digging into the material, I asked attendees to grab a resource file containing some of the service code for some spot-the-vuln style bug hunting as well as to understand what this stuff looks like in code. It also contains various other things that are useful or even necessary for some of the exercises. However, you won’t need any of that until Part 2. I’ve also uploaded the service code for you to build your own project with. I’ve sprinkled comments around the functions you’ll need to edit to basically hard code your own flags and values for things.
Part 1: A Little Background
Some developers know they should use AES over other options when it comes to encryption, but sometimes little else when it comes to the other specifics. There is also sometimes the misconception that because something is encrypted, it’s secure and all security considerations are complete. However, encryption provides confidentiality, not integrity; ciphertext is malleable. As security testers, we encounter crypto all the time and are expected to find vulnerabilities with implementations, which include network protocols, software products, and web applications / web services. We frequently encounter code that implements cryptographic functionality using standard APIs / libraries which are exposed in the forms of things like cookies, CSRF tokens, encrypted user data (on disk, in a URL..), encapsulated in (proprietary?) network protocol payloads you need to dissect, etc.
The Bsides workshop focused on C# applications, so let’s take a (very) quick look at how I used the APIs, some default values, and some basics of the general concepts before getting into the offense. When developing an application in C# that uses crypto (we’ll limit ourselves to ~AES), you have several options in terms of AES/Rijndael classes to use, which at first might seem a little redundant.
For brevity purposes, the gist is that Rijndael was the algorithm selected for the AES standard (so Rijndael is AES), but the AES standard does not allow for variable block sizes (so you can change the block size on a Rijndael object but not AES) nor feedback modes (your Rijndael object will offer more mode options). Class names ending with “Managed” will work across more platforms/versions, but are not FIPS certified. Class names ending in “CryptoServiceProvider” implement the algorithm natively and are FIPS certified implementations, but are less portable. There’s a registry setting / Windows security policy setting on systems that require FIPS compliance so if you try to use an AesManaged object in code running on systems requiring FIPS compliance, you’ll trigger an exception.
Using the APIs requires a little working knowledge of basic cryptographic concepts, like:
This is the secret that is used with the cipher to encrypt the plaintext. If Alice wants to send an encrypted message to Bob, she supplies the plaintext message and the key (K) to the encrypter function to get ciphertext, which is sent to Bob. E.g.: E(“I am a secret”, K); –> “\x72\xDA\x2B\x9C\xDF”. The same key is then used by Bob with the decrypter function: D(“\x72\xDA\x2B\x9C\xDF”, K); –> “I am a secret”. In AES, the key length is configurable (AES 128, AES 192, AES 256…). Consider the code below:
What’s the default key size?
Since we’re working with block ciphers, our data needs to be divided into blocks (by the block size) so that the plaintext (or ciphertext) can be encrypted/decrypted block by block. For example, if our plaintext were “Hello Blocks”, and our block size were 8 bytes, it would be divided as follows:
Note as placeholders after the plaintext I’ve put “?” marks. We’ll get to padding in a moment, just note for now that those bytes can’t be empty — something has to go there.
Consider the code below:
What’s the default block size?
Therefore, remember that 16 A’s (or any character) passed to an application using this block size will fill up a block. Seriously. Or write it down.
Let’s say we have 3 messages for Alice to send to Bob. Message 1 and 3 are of the same plaintext. Though the key will provide confidentiality and a passive attacker will not know the plaintext value, there is no randomness involved so the ciphertexts will be equal:
This gives away some information to attackers conducting passive analysis, maybe trying determine when a good time to replay some ciphertext may be. An initialization vector is a random value that is used as the first block to cause cipher texts of identical plain texts to be different. Consider the following code whereby our string “Secret” is encrypted 5 times using the same key and same IV:
Our ciphertexts are identical.
If we instead move the initialization of our AesManaged object inside the loop allowing the IV to be randomly generated on each iteration (and hard code the key so that it’s not randomly generated for us each time), we observe how the IV affects the ciphertext:
Therefore, when using a random IV..:
Recall that when we split up our “Hello blocks” message into a 64 bit block size, we had a few bytes left over and that I said they couldn’t be empty.
What actually goes in there is called padding, and what that padding consists of depends on the mode you use. Looking at the MSDN article, we have a few options:
The default is PKCS7, and it’s pretty easy to understand. If you have one byte left over, your padding will be 0x1. If you have 3 bytes, your padding will be 0x3 0x3 0x3. Here’s a chart.
The other padding modes are also fairly straight forward to understand. You might notice the option “None” and recall that I said bytes in a block can’t be empty. If you set your padding to “None” for the encryption, your plaintext must be perfectly divisible by the block size. If not, you’ll trigger a runtime exception when the encrypter tries to work with your data. The option for no padding is there because there are other modes you can use with Rijndael, some of which allow it to behave differently than a block cipher and don’t have a use for padding. Setting your padding to “None” on the decryption side of things will leave the padding bytes in your decrypted text if you’re interested in seeing what it actually looks like.
Padding is automatically added and removed based on the padding mode you specify.
Something to remember: If by chance your plaintext perfectly divides into the block size, you’d think you’d need no padding. Actually, the rule is you need at least one byte of padding. If your plaintext perfectly fills up one block, you will have a full block of padding. E.g., if our plaintext is 16 A’s (considering the default block size), we will have two blocks. First block would be the ciphertext representing those 16 A’s, and the second block would be the ciphertext representing 16 “0x10″s (16 in hex).
Exclusive Or (XOR) can be summarized with a truth table:
And it is commonly represented by this symbol:
When comparing two bits, if they are the same (0,0 or 1,1) you will get a 0 (false). If they are different (1,0 or 0,1) you will get a 1 (true). XOR is extremely useful for crypto as it allows you to use any 2/3 of a series of bits to recover the missing 1/3.
The next post will cover a bit about one of the block cipher modes and some vulnerabilities associated with it, along with our first exercise.