Intro to Crypto Attacks – Part 2

This is the 2nd post in a recap of my “Intro to Crypto Attacks” workshop given at Bsides Seattle. In my previous post, I covered some background of the workshop session, linked to the files you’ll need for the exercises, and went over some core background you’ll use in the first challenge exercise.

Since the exercises are implemented as Azure Cloud Services (or you can deploy on your IIS server as a regular WCF service), you can interact with the services either by:

  • Using the WCF Test Client, which if you have Visual Studio should already be installed. On my system it’s in C:\Program Files (x86)\Microsoft Visual Studio X.X\Common7\IDE\WcfTestClient.exe.
  • Using SoapUI
  • Other useful tools include Burp Suite for its encoding/decoding functions as well as intruder (for automating an attack later if you’re not as experienced writing code) and a really simple, short python file I used during the workshop so that I wouldn’t have to type as much and could avoid typos.

    Part 2: ECB Mode

    Block ciphers divide the plaintext into blocks by block size N. By default, this block size is 128 bits (i.e., 16 characters). From here, what happens depends on the cipher mode. A look at the MSDN article shows several cipher mode options when using block ciphers in C#.

    What’s the default mode? With these AES classes, it’s actually Cipher Block Chaining (CBC). But, we’ll discuss and conduct attacks against CBC in a later exercise. For now, let’s take a look at the Electronic Codebook (ECB) mode. Though in C# you have to go out of your way to specify this mode, it’s the default mode in some other languages but not recommended since it exposes some vulnerabilities. That is, it can be dangerous to use in modern systems, considered insecure, and should be avoided. Take another look at the description above, it has an important note and everything. To understand the insecurity, let’s look at how it works.

    The plaintext is divided into the blocks, and together with the key, each block is individually encrypted. The application will typically return CiphertextBlock1 + CiphertextBlock2 + CiphertextBlockN as a concatenated result. Depending on the protocol transporting the ciphertext, if you’re looking at something going over the network, the ciphertext may be encoded in some manner (like Base64) to avoid having ciphertext characters accidentally modified or misinterpreted during transport, parsing, etc.

    We start by encrypting the letter A. This gives us the ciphertext of A with 15 padding bytes.

    Now let’s try 15 A’s, which will give back the ciphertext for 15 A’s and 1 padding byte, so the ciphertext lengths when encrypting 1 A or 15 A’s is the same (1 block).

    Next, we’ll encrypt 16 A’s. Our ciphertext increases by another block, because recall we need at least one byte of padding, and if our plaintext divides evenly by the block size, we’ll have a full block of padding.

    Let’s encrypt 64 “A”s and see what happens when we inspect the ciphertext. If we base64 decode the data and divide the resulting hex bytes into the block size… Notice a pattern?

    $ python
    Python 2.7.1 
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import crypto
    >>> crypto.decode("W/qVlI7fggiYQTdqTvoJUFv6lZSO34IImEE3ak76CVBb+pWUjt+CCJhBN2pO+glQW/qVlI7fggiYQTdqTvoJUJv3MwVDvIR/RmOyMw1Hu8c=")
    5bfa95948edf82089841376a4efa09505bfa95948edf82089841376a4efa09505bfa95948edf82089841376a4efa09505bfa95948edf82089841376a4efa09509bf7330543bc847f4663b2330d47bbc7
    >>> crypto.blocks("5bfa95948edf82089841376a4efa09505bfa95948edf82089841376a4efa09505bfa95948edf82089841376a4efa09505bfa95948edf82089841376a4efa09509bf7330543bc847f4663b2330d47bbc7")
    
    
    5bfa95948edf82089841376a4efa0950
    5bfa95948edf82089841376a4efa0950
    5bfa95948edf82089841376a4efa0950
    5bfa95948edf82089841376a4efa0950
    9bf7330543bc847f4663b2330d47bbc7
    
    
    >>> 
    

    We have 4 blocks of encrypted “A”s, and an encrypted padding block. If we try to encrypt 2 blocks of “A”s, a block of “B”s and another 2 blocks of “A”s, we learn that no matter where in the message they are, a full block of “A”s will always encrypt to “5bfa95948edf82089841376a4efa0950”.

    >>> print "A"*32 + "B"*16 + "A"*32 
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    
    <Got the base64 encoded ciphertext from the service>
    
    >>> crypto.decode("W/qVlI7fggiYQTdqTvoJUFv6lZSO34IImEE3ak76CVBUSdikF55w6Bx4nh8GNhNEW/qVlI7fggiYQTdqT" +
    ...     "voJUFv6lZSO34IImEE3ak76CVCb9zMFQ7yEf0ZjsjMNR7vH")
    5bfa95948edf82089841376a4efa09505bfa95948edf82089841376a4efa09505449d8a4179e70e81c789e1f063613445bfa95948edf82089841376a4efa09505bfa95948edf82089841376a4efa09509bf7330543bc847f4663b2330d47bbc7
    >>> crypto.blocks("5bfa95948edf82089841376a4efa09505bfa95948edf82089841376a4efa09505449d8a4179e70e81c789e1f063613445bfa95948edf82089841376a4efa09505bfa95948edf82089841376a4efa09509bf7330543bc847f4663b2330d47bbc7")
    
    
    5bfa95948edf82089841376a4efa0950
    5bfa95948edf82089841376a4efa0950
    5449d8a4179e70e81c789e1f06361344
    5bfa95948edf82089841376a4efa0950
    5bfa95948edf82089841376a4efa0950
    9bf7330543bc847f4663b2330d47bbc7
    
    
    >>> 
    

    Due to this behavior, we know we can systematically slice ciphertext blocks in and out, or rearrange them to attack the application. Where might we see implementations that actually use ECB mode? It looks like its the default mode in some things.

    In others, it’s the code example used to demonstrate how to encrypt something.

    To be fair, one of the user contributed notes mentions CBC mode (but I don’t see it there anymore).

    Then again, another suggests you can write your own encryption algorithm.

    Overall, many of the notes seem to suggest ECB mode for things like encrypting cookies; keep that in mind next time you’re conducting a security assessment against a web application.

    With that, let’s look at the first challenge exercise.

    Lab 1

    Objectives
    1) Disclose the balance of account number 14100305921007
    2) Open an account for yourself and give yourself $1,000,000

    The two objectives are not related. In fact, you can technically solve #2 without leveraging any crypto flaws. If you’ve stood up your target using the resources provided in the last post, you can start interacting with the service and learn how it works by opening an account in which you provide a clear text account name (or number, it’s equivalent) and get back the ciphertext, checking your balance in which you provide your encrypted account number, and closing an account in which you also provide your encrypted account number. The security of this banking application relies on the user not being able to encrypt values (existing account names/numbers) which have already been encrypted. The (important) code is also available in the Lab1 files you downloaded in Part 1, an interesting piece being:

    static byte[] EncryptStringToBytes(string plainText, byte[] Key)
            {
                byte[] encrypted;
                using (Rijndael rijAlg = Rijndael.Create())
                {
                    rijAlg.Key = Key;
                    rijAlg.Mode = CipherMode.ECB;
                    ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, Encoding.ASCII.GetBytes(""));
    
                    using (MemoryStream msEncrypt = new MemoryStream())
                    {
                        using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                        {
                            using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                            {
                                swEncrypt.Write(plainText);
                            }
                            encrypted = msEncrypt.ToArray();
                        }
                    }
                }
                return encrypted;
            }
    

    If you’re having trouble thinking through the attack, there are tons of articles on simple ECB mode attacks that might help you get started, like this one from 2007. One of the (free) “Introduction to Cryptography” course lectures on coursera.org also covers insecurities of ECB mode.

    Conclusion

    In the next post, I’ll step through the solution for this challenge and talk about attacks against CBC mode and kick off the next lab.

    Advertisements

    Intro to Crypto Attacks – Part 1

    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:

    Encryption Keys:

    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?

    Blocks:

    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.

    Initialization Vector

    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..:

    Padding

    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).

    XOR

    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.

    Conclusion

    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.