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.

    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.