9
AUTOMATING SQLMAP

image

In this chapter, we make tools to automatically exploit SQL injection vectors. We use sqlmap—a popular utility you’ll learn about in this chapter—to first find and then verify HTTP parameters vulnerable to SQL injection. After that, we combine that functionality with the SOAP fuzzer we created in Chapter 3 to automatically verify any potential SQL injections in the vulnerable SOAP service. sqlmap ships with a REST API, meaning that it uses HTTP GET, PUT, POST, and DELETE requests to work with data and special URIs to reference resources in databases. We used REST APIs in Chapter 5 when we automated Nessus.

The sqlmap API also uses JSON to read objects in HTTP requests sent to the API URLs (known as endpoints in REST parlance). JSON is like XML in that it allows two programs to pass data to each other in a standard way, but it’s also much less verbose and lighter weight than XML. Normally, sqlmap is used by hand at the command line, but driving the JSON API programmatically will allow you to automate far more tasks than normal pentesting tools do, from automatically detecting a vulnerable parameter to exploiting it.

Written in Python, sqlmap is an actively developed utility available on GitHub at https://github.com/sqlmapproject/sqlmap/. You can download sqlmap using git or by downloading a ZIP file of the current master branch. Running sqlmap requires you to have Python installed (on most Linux distributions, this is usually installed by default).

If you prefer git, the following command will check out the latest master branch:

$ git clone https://github.com/sqlmapproject/sqlmap.git

If you prefer wget, you can download a ZIP archive of the latest master branch, as shown here:

$ wget https://github.com/sqlmapproject/sqlmap/archive/master.zip
$ unzip master.zip

In order to follow the examples in this chapter, you should also install a JSON serialization framework such as the open source option Json.NET. Download it from https://github.com/JamesNK/Newtonsoft.Json or use the NuGet package manager, available in most C# IDEs. We used this library before in Chapter 2 and Chapter 5.

Running sqlmap

Most security engineers and pentesters use the Python script sqlmap.py (in the root of the sqlmap project or installed system-wide) to drive sqlmap from the command line. We will briefly go over how the sqlmap command line tool works before jumping into the API. Kali has sqlmap installed so that you can just call sqlmap from anywhere on the system. Although the sqlmap command line tool has the same overall functionality as the API, it isn’t as easily integrated into other programs without invoking the shell. Driving the API programmatically should be safer and more flexible than just using the command line tool when integrating with other code.

NOTE

If you are not running Kali, you may have downloaded sqlmap but not installed it on the system. You can still use sqlmap without installing it system-wide by changing to the directory that sqlmap is in and calling the sqlmap.py script directly with Python using the following code:

$ python ./sqlmap.py [.. args ..]

A typical sqlmap command might look like the code in Listing 9-1.

$ sqlmap --method=GET --level=3 --technique=b --dbms=mysql
-u "http://10.37.129.3/cgi-bin/badstore.cgi?searchquery=fdsa&action=search"

Listing 9-1: A sample sqlmap command to run against BadStore

We won’t cover the output of Listing 9-1 at the moment, but note the syntax of the command. In this listing, the arguments we pass to sqlmap tell it that we want it to test a certain URL (hopefully a familiar URL, like the one we tested in Chapter 2 with BadStore). We tell sqlmap to use GET as the HTTP method and to use MySQL payloads specifically (rather than include payloads for PostgreSQL or Microsoft SQL Server), followed by the URL we want to test. There is only a small subset of arguments you can use with the sqlmap script. If you want to try out other commands manually, you can find more detailed information at https://github.com/sqlmapproject/sqlmap/wiki/Usage/. We can use the sqlmap REST API to drive the same functionality as the sqlmap command in Listing 9-1.

When running the sqlmapapi.py API examples, you may need to run the API server differently than with the sqlmap utility since it might not be installed like the sqlmap.py script, which is callable from the system shell like on Kali. If you need to download sqlmap in order to use the sqlmap API, you can find it on GitHub (https://github.com/sqlmapproject/sqlmap/).

The sqlmap REST API

Official documentation on the sqlmap REST API is a bit bare, but we cover everything you need to know to use it efficiently and effectively in this book. First, run sqlmapapi.py -–server (located in the root of the sqlmap project directory you downloaded earlier) to start the sqlmap API server listening at 127.0.0.1 (on port 8775 by default), as shown in Listing 9-2.

$ ./sqlmapapi.py --server
[22:56:24] [INFO] Running REST-JSON API server at '127.0.0.1:8775'..
[22:56:24] [INFO] Admin ID: 75d9b5817a94ff9a07450c0305c03f4f
[22:56:24] [DEBUG] IPC database: /tmp/sqlmapipc-34A3Nn
[22:56:24] [DEBUG] REST-JSON API server connected to IPC database

Listing 9-2: Starting the sqlmap server

sqlmap has several REST API endpoints that we need to create our automated tool. In order to use sqlmap, we need to create tasks and then use API requests to act on those tasks. Most of the available endpoints use GET requests, which are meant to retrieve data. To see what GET API endpoints are available, run rgrep "@get". from the root of the sqlmap project directory, as shown in Listing 9-3. This command lists many of the available API endpoints, which are special URLs used in the API for certain actions.

$ rgrep "@get" .
lib/utils/api.py:@get("/task/new")
lib/utils/api.py:@get("/task/taskid/delete")
lib/utils/api.py:@get("/admin/taskid/list")
lib/utils/api.py:@get("/admin/taskid/flush")
lib/utils/api.py:@get("/option/taskid/list")
lib/utils/api.py:@get("/scan/taskid/stop")
--snip--

Listing 9-3: Available sqlmap REST API GET requests

Soon we’ll cover how to use the API endpoints to create , stop , and delete sqlmap tasks. You can replace @get in this command with @post to see the API’s available endpoints for POST requests. Only three API calls require an HTTP POST request, as shown in Listing 9-4.

$ rgrep "@post" .
lib/utils/api.py:@post("/option/taskid/get")
lib/utils/api.py:@post("/option/taskid/set")
lib/utils/api.py:@post("/scan/taskid/start")

Listing 9-4: REST API endpoints for POST requests

When using the sqlmap API, we need to create a task to test a given URL for SQL injections. Tasks are identified by their task ID, which we enter in place of taskid in the API options in Listings 9-3 and 9-4. We can use curl to test the sqlmap server to ensure it is running properly and to get a feel for how the API behaves and the data it sends back. This will give us a good idea of how our C# code is going to work when we begin writing our sqlmap classes.

Testing the sqlmap API with curl

Normally, sqlmap is run on the command line using the Python script we covered earlier in this chapter, but the Python commands will hide what sqlmap is doing on the backend and won’t give us insight into how each API call will work. To get a feel for using the sqlmap API directly, we’ll use curl, which is a command line tool generally used to make HTTP requests and see the responses to those requests. For example, Listing 9-5 shows how to make a new sqlmap task by calling to the port sqlmap is listening to.

$ curl 127.0.0.1:8775/task/new
{
"taskid": "dce7f46a991c5238",
  "success": true
}

Listing 9-5: Creating a new sqlmap task with curl

Here, the port is 127.0.0.1:8775 . This returns a new task ID after the taskid key and a colon . Make sure that your sqlmap server is running as in Listing 9-2 before making this HTTP request.

After making a simple GET request with curl to the /task/new endpoint, sqlmap returns a new task ID for us to use. We’ll use this task ID to make other API calls later, including starting and stopping the task and getting the task results. To view a list of all scan options for a given task ID available for use with sqlmap, call the /option/taskid/list endpoint and substitute the ID you created earlier, as shown in Listing 9-6. Note we are using the same task ID in the API endpoint request that was returned in Listing 9-5. Knowing the options for a task is important for starting the SQL injection scan later.

$ curl 127.0.0.1:8775/option/dce7f46a991c5238/list
{
  "options": {
    "crawlDepth": null,
    "osShell": false,
  "getUsers": false,
  "getPasswordHashes": false,
    "excludeSysDbs": false,
    "uChar": null,
    --snip--
  "tech": "BEUSTQ",
    "textOnly": false,
    "commonColumns": false,
    "keepAlive": false
  }
}

Listing 9-6: Listing the options for a given task ID

Each of these task options corresponds with a command line argument from the command line sqlmap tool. These options tell sqlmap how it should perform a SQL injection scan and how it should exploit any injections it finds. Among the interesting options shown in Listing 9-6 is one for setting the injection techniques (tech) to test for; here it is set to the default BEUSTQ to test for all SQL injection types . You also see options for dumping the user database, which is off in this example , and dumping password hashes, which is also off . If you are interested in what all the options do, run sqlmap --help at the command line to see the option descriptions and usage.

After creating our task and viewing its currently set options, we can set one of the options and then start a scan. To set specific options, we make a POST request and need to include some data that tells sqlmap what to set the options to. Listing 9-7 details starting a sqlmap scan with curl to test a new URL.

$ curl -X POST -H "Content-Type:application/json"
 --data '{"url":"http://10.37.129.3/cgi-bin/badstore.cgi?searchquery=fdsa&action=search"}'
 http://127.0.0.1:8775/scan/dce7f46a991c5238/start
{
  "engineid": 7181,
  "success": true
}

Listing 9-7: Starting a scan with new options using the sqlmap API

This POST request command looks different from the GET request in Listing 9-5, but it is actually very similar. First, we designate the command as a POST request . Then we list the data to send to the API by placing the name of the option to set in quotes (such as "url"), followed by a colon, then the data to set the option to . We designate the content of the data to be JSON using the -H argument to define a new HTTP header , which ensures the Content-Type header will be correctly set to the application/json MIME-type for the sqlmap server. Then we start the command with a POST request using the same API call format as the GET request in Listing 9-6, with the endpoint /scan/taskid/start .

Once the scan has been started and sqlmap reports success , we need to get the scan status. We can do that with a simple curl call using the status endpoint, as shown in Listing 9-8.

$ curl 127.0.0.1:8775/scan/dce7f46a991c5238/status
{
"status": "terminated",
  "returncode": 0,
  "success": true
}

Listing 9-8: Getting the status of a scan

After the scan has finished running, sqlmap will change the status of the scan to terminated . Once the scan has terminated, we can use the log endpoint to retrieve the scan log and see whether sqlmap found anything during the scan, as Listing 9-9 shows.

$ curl 127.0.0.1:8775/scan/dce7f46a991c5238/log
{
  "log": [
    {
    "message": "flushing session file",
    "level": "INFO",
    "time": "09:24:18"
    },
    {
      "message": "testing connection to the target URL",
      "level": "INFO",
      "time": "09:24:18"
    },
    --snip--
  ],
  "success": true
}

Listing 9-9: Making a request for the scan log

The sqlmap scan log is an array of statuses that includes the message , message level , and timestamp for each status. The scan log gives us great visibility into what happened during a sqlmap scan of a given URL, including any injectable parameters. Once we are done with the scan and have our results, we should go ahead and clean up to conserve resources. To delete the task we just created when we’re done with it, call /task/taskid/delete, as shown in Listing 9-10. Tasks can be freely created and deleted in the API, so feel free to create new tasks, play around with them, and then delete them.

$ curl 127.0.0.1:8775/task/dce7f46a991c5238/delete
{
  "success": true
}

Listing 9-10: Deleting a task in the sqlmap API

After calling the /task/taskid/delete endpoint , the API will return the task’s status and whether it was successfully deleted . Now that we have the general workflow of creating, running, and deleting a sqlmap scan, we can begin working on our C# classes to automate the whole process from start to finish.

Creating a Session for sqlmap

No authentication is required to use the REST API, so we can easily use the session/manager pattern, which is a simple pattern similar to the other API patterns in previous chapters. This pattern allows us to separate the protocol’s transport (how we talk to the API) from the protocol’s exposed functionality (what the API can do). We’ll implement SqlmapSession and SqlmapManager classes to drive the sqlmap API to automatically find and exploit injections.

We’ll begin by writing the SqlmapSession class. This class, shown in Listing 9-11, requires only a constructor and two methods called ExecuteGet() and ExecutePost(). These methods will do most of the heavy lifting of the two classes we’ll write. They will make the HTTP requests (one for GET requests and one for POST requests, respectively) that allow our classes to talk with the sqlmap REST API.

public class SqlmapSession : IDisposable
{
  private string _host = string.Empty;
  private int _port = 8775; //default port

  public SqlmapSession(string host, int port = 8775)
  {
    _host = host;
    _port = port;
  }

  public string ExecuteGet(string url)
  {
    return string.Empty;
  }

  public string ExecutePost(string url, string data)
  {
    return string.Empty;
  }
  public void Dispose()
  {
    _host = null;
  }
}

Listing 9-11: The SqlmapSession class

We start by creating a public class called SqlmapSession that will implement the IDisposable interface. This lets us use the SqlmapSession with a using statement, allowing us to write cleaner code with variables managed through garbage collection. We also declare two private fields, a host and a port, which we will use when making our HTTP requests. We assign the _host variable a value of string.Empty by default. This is a feature of C# that allows you to assign an empty string to a variable without actually instantiating a string object, resulting in a slight performance boost (but for now, it’s just to assign a default value). We assign the _port variable the port that sqlmap listens on, which is 8775, the default.

After declaring the private fields, we create a constructor that accepts two arguments : the host and the port. We assign the private fields the values that are passed as the parameters to the constructor so we can connect to the correct API host and port. We also declare two stub methods for executing GET and POST requests that return string.Empty for the time being. We’ll define these methods next. The ExecuteGet() method only requires a URL as input. The ExecutePost() method requires a URL and the data to be posted. Finally, we write the Dispose() method , which is required when implementing the IDisposable interface. Within this method, we clean up our private fields by assigning them a value of null.

Creating a Method to Execute a GET Request

Listing 9-12 shows how to use WebRequest to implement the first of the two stubbed methods to execute a GET request and return a string.

public string ExecuteGet(string url)
{
  HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://" + _host + ":" + _port + url);
  req.Method = "GET";

  string resp = string.Empty;
  using (StreamReader rdr = new StreamReader(req.GetResponse().GetResponseStream()))
    resp = rdr.ReadToEnd();

  return resp;
 }

Listing 9-12: The ExecuteGet() method

We create a WebRequest with the _host, _port, and url variables to build a full URL and then set the Method property to GET. Next, we perform the request and read the response into a string with ReadToEnd() , which is then returned to the caller method. When you implement SqlmapManager, you’ll use the Json.NET library to deserialize the JSON returned in the string so that you can easily pull values from it. Deserialization is the process of converting strings into JSON objects, and serialization is the opposite process.

Executing a POST Request

The ExecutePost() method is only slightly more complex than the ExecuteGet() method. Since ExecuteGet() can only make simple HTTP requests, ExecutePost() will allow us to send complex requests with more data (such as JSON). It will also return a string containing the JSON response that will be deserialized by the SqlmapManager. Listing 9-13 shows how to implement the ExecutePost() method.

public string ExecutePost(string url, string data)
{
  byte[] buffer = Encoding.ASCII.GetBytes(data);
  HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://"+_host+":"+_port+url);
  req.Method = "POST";
  req.ContentType = "application/json";
  req.ContentLength = buffer.Length;

  using (Stream stream = req.GetRequestStream())
    stream.Write(buffer, 0, buffer.Length);

  string resp = string.Empty;
  using (StreamReader r = new StreamReader(req.GetResponse().GetResponseStream()))
    resp = r.ReadToEnd();

  return resp;
}

Listing 9-13: The ExecutePost() method

This is very similar to the code we wrote when fuzzing POST requests in Chapters 2 and 3. This method expects two arguments: an absolute URI and the data to be posted into the method. The Encoding class (available in the System.Text namespace) is used to create a byte array that represents the data to be posted. We then create a WebRequest object and set it up as we did for the ExecuteGet() method, except we set the Method to POST . Notice that we also specify a ContentType of application/json and a ContentLength that matches the length of the byte array. Since we will be sending the server JSON data, we need to set the appropriate content type and length of our data in the HTTP request. We write the byte array to the request TCP stream (the connection between your computer and the HTTP server) once the WebRequest is set up, sending the JSON data to the server as the HTTP request body. Finally, we read the HTTP response into a string that is returned to the calling method.

Testing the Session Class

Now we are ready to write a small application to test the new SqlmapSession class in the Main() method. We’ll create a new task, call our methods, and then delete the task, as Listing 9-14 shows.

public static void Main(string[] args)
{
  string host = args[0];
  int port = int.Parse(args[1]);
  using (SqlmapSession session = new SqlmapSession(host, port))
  {
    string response = session.ExecuteGet("/task/new");
    JToken token = JObject.Parse(response);
    string taskID = token.SelectToken("taskid").ToString();

  Console.WriteLine("New task id: " + taskID);
    Console.WriteLine("Deleting task: " + taskID);

  response = session.ExecuteGet("/task/" + taskID + "/delete");
    token = JObject.Parse(response);
    bool success = (bool)token.SelectToken("success");

    Console.WriteLine("Delete successful: " + success);
  }
}

Listing 9-14: The Main() method of our sqlmap console application

The Json.NET library makes dealing with JSON in C# simple (as you saw in Chapter 5). We grab the host and port from the first and second arguments passed into the program , respectively. Then we use int.Parse() to parse the integer from the string argument for the port. Although we’ve been using port 8775 for this whole chapter, since the port is configurable (8775 is just the default), we shouldn’t assume it will be 8775 all the time. Once we have assigned values to the variables, we instantiate a new SqlmapSession using the parameters passed into the program. We then call the /task/new endpoint to retrieve a new task ID and use the JObject class to parse the JSON returned. Once we have the response parsed, we use the SelectToken() method to retrieve the value for the taskid key and assign this value to the taskID variable.

NOTE

A few standard types in C# have a Parse() method, like the int.Parse() method we just used. The int type is an Int32, so it will attempt to parse a 32-bit integer. Int16 is a short integer, so short.Parse() will attempt to parse a 16-bit integer. Int64 is a long integer, and long.Parse() will attempt to parse a 64-bit integer. Another useful Parse() method exists on the DateTime class. Each of these methods is static, so no object instantiation is necessary.

After printing the new taskID to the console , we can delete the task by calling the /task/taskid/delete endpoint . We again use the JObject class to parse the JSON response and then retrieve the value for the success key , cast it as a Boolean, and assign it to the success variable. This variable is printed to the console, showing the user whether the task was successfully deleted. When you run the tool, it produces output about creating and deleting a task, as shown in Listing 9-15.

$ mono ./ch9_automating_sqlmap.exe 127.0.0.1 8775
New task id: 96d9fb9d277aa082
Deleting task: 96d9fb9d277aa082
Delete successful: True

Listing 9-15: Running the program that creates a sqlmap task and then deletes it

Once we know we can successfully create and delete a task, we can create the SqlmapManager class to encapsulate the API functionality we want to use in the future, such as setting scan options and getting the scan results.

The SqlmapManager Class

The SqlmapManager class, shown in Listing 9-16, wraps the methods exposed through the API in an easy-to-use (and maintainable!) way. When we finish writing the methods needed for this chapter, we can start a scan on a given URL, watch it until it completes, and then retrieve the results and delete the task. We’ll also make heavy use of the Json.NET library. To reiterate, the goal of the session/manager pattern is to separate the transport of the API from the functionality exposed by the API. An added benefit to this pattern is that it allows the programmer using the library to focus on the results API calls. The programmer can, however, still interact directly with the session if needed.

public class SqlmapManager : IDisposable
{
  private SqlmapSession _session = null;

  public SqlmapManager(SqlmapSession session)
  {
    if (session == null)
      throw new ArgumentNullException("session");
     _session = session;
  }

  public void Dispose()
  {
    _session.Dispose();
    _session = null;
  }
}

Listing 9-16: The SqlmapManager class

We declare the SqlmapManager class and have it implement the IDisposable interface. We also declare a private field for the SqlmapSession that will be used throughout the class. Then, we create the SqlmapManager constructor , which accepts a SqlmapSession, and we assign the session to the private _session field.

Finally, we implement the Dispose() method , which cleans up the private SqlmapSession. You may wonder why we have both the SqlmapSession and SqlmapManager implement IDisposable, when in the Dispose() method of the SqlmapManager, we call Dispose() on the SqlmapSession as well. A programmer may want to instantiate only a SqlmapSession and interact with it directly in case a new API endpoint is introduced that the manager hasn’t been updated to support. Having both classes implement IDisposable offers the greatest flexibility.

Since we just implemented the methods needed to create a new task and delete an existing one when we tested the SqlmapSession class in Listing 9-14, we’ll add these actions as their own methods to the SqlmapManager class above the Dispose() method, as shown in Listing 9-17.

public string NewTask()
{
  JToken tok = JObject.Parse(_session.ExecuteGet("/task/new"));
return tok.SelectToken("taskid").ToString();
}

public bool DeleteTask(string taskid)
{
  JToken tok = Jobject.Parse(session.ExecuteGet("/task/" + taskid + "/delete"));
return (bool)tok.SelectToken("success");
}

Listing 9-17: The NewTask() and DeleteTask() methods to manage a task in sqlmap

The NewTask() and DeleteTask() methods make it easy to create and delete tasks as we need in the SqlmapManager class and are nearly identical to the code in Listing 9-14, except that they print less output and return the task ID after creating a new task or the result (success or failure) of deleting a task .

Now we can use these new methods to rewrite the previous command line application testing the SqlmapSession class, as seen in Listing 9-18.

public static void Main(string[] args)
{
  string host = args[0];
  int port = int.Parse(args[1]);
  using (SqlmapManager mgr = new SqlmapManager(new SqlmapSession(host, port)))
  {
    string taskID = mgr.NewTask();

    Console.WriteLine("Created task: " + taskID);
    Console.WriteLine("Deleting task");
    bool success = mgr.DeleteTask(taskID);

    Console.WriteLine("Delete successful: " + success);
  } //clean up and dispose manager automatically
}

Listing 9-18: Rewriting the application to use the SqlmapManager class

This code is more intuitive to read and easier to understand at a quick glance than the original application in Listing 9-14. We’ve replaced the code to create and delete tasks with the NewTask() and DeleteTask() methods. By just reading the code, you have no idea that the API uses HTTP as its transport or that we are dealing with JSON responses.

Listing sqlmap Options

The next method we’ll implement (shown in Listing 9-19) retrieves the current options for tasks. One thing to note is that because sqlmap is written in Python, it’s weakly typed. This means that a few of the responses will have a mixture of types that are a bit difficult to deal with in C#, which is strongly typed. JSON requires all keys to be strings, but the values in the JSON will have different types, such as integers, floats, Booleans, and strings. What this means for us is that we must treat all the values as generically as possible on the C# side of things. To do that, we’ll treat them as simple objects until we need to know their types.

public Dictionary<string, object> GetOptions(string taskid)
{
  Dictionary<string, object> options = new Dictionary<string, object>();

  JObject tok = JObject.Parse(_session.ExecuteGet ("/option/" + taskid + "/list"));

  tok = tok["options"] as JObject;

foreach (var pair in tok)
    options.Add(pair.Key, pair.Value);

  return options;
}

Listing 9-19: The GetOptions() method

The GetOptions() method in Listing 9-19 accepts a single argument: the task ID to retrieve the options for. This method will use the same API endpoint we used in Listing 9-5 when testing the sqlmap API with curl. We begin the method by instantiating a new Dictionary that requires the key to be a string but allows you to store any kind of object as the other value of the pair. After making the API call to the options endpoint and parsing the response , we loop through the key/value pairs in the JSON response from the API and add them to the options dictionary . Finally, the currently set options for the task are returned so that we can update them and use them later when we start the scan.

We’ll use this dictionary of options in the StartTask() method, which we’ll implement soon, to pass options as an argument to start a task with. First, though, go ahead and add the following lines in Listing 9-20 to your console application after calling mgr.NewTask() but before deleting the task with mgr.DeleteTask().

  Dictionary<string, object> options = mgr.GetOptions(taskID);

foreach (var pair in options)
    Console.WriteLine("Key: " + pair.Key + " :: Value: " + pair.Value);

Listing 9-20: Lines appended to the main application to retrieve and print the current task options

In this code, a taskID is given to GetOptions() as an argument, and the returned options dictionary is assigned to a new Dictionary, which is also called options . The code then loops through options and prints each of its key/value pairs . After adding these lines, rerun your application in your IDE or in the console, and you should see the full list of options you can set with their current values printed to the console. This is shown in Listing 9-21.

$ mono ./ch9_automating_sqlmap.exe 127.0.0.1 8775
Key: crawlDepth  ::Value:
Key: osShell     ::Value: False
Key: getUsers    ::Value: False
Key: getPasswordHashes   ::Value: False
Key: excludeSysDbs       ::Value: False
Key: uChar       ::Value:
Key: regData     ::Value:
Key: prefix      ::Value:
Key: code        ::Value:
--snip--

Listing 9-21: Printing the task options to the screen after retrieving them with GetOptions()

Now that we’re able to see task options, it’s time to perform a scan.

Making a Method to Perform Scans

Now we’re ready to prepare our task to perform a scan. Within our options dictionary, we have a key that’s a url, which is the URL we’ll test for SQL injections. We pass the modified Dictionary to a new StartTask() method, which posts the dictionary as a JSON object to the endpoint and uses the new options when the task begins.

Using the Json.NET library makes the StartTask() method super short because it takes care of all the serialization and deserialization for us, as Listing 9-22 shows.

public bool StartTask(string taskID, Dictionary<string, object> opts)
{
  string json = JsonConvert.SerializeObject(opts);
  JToken tok = JObject.Parse(session.ExecutePost("/scan/"+taskID+"/start", json));
return(bool)tok.SelectToken("success");
}

Listing 9-22: The StartTask() method

We use the Json.NET JsonConvert class to convert a whole object into JSON. The SerializeObject() method is used to get a JSON string representing the options dictionary that we can post to the endpoint. Then we make the API request and parse the JSON response . Finally, we return the value of the success key from the JSON response, which is hopefully true. This JSON key should always be present in the response for this API call, and it will be true when the task was started successfully or false if the task was not started.

It would also be useful to know when a task is complete. This way, you know when you can get the full log of the task and when to delete the task. To get the task’s status, we implement a small class (shown in Listing 9-23) that represents a sqlmap status response from the /scan/taskid/status API endpoint. This can be added in a new class file if you like, even though it’s a super-short class.

public class SqlmapStatus
{
public string Status { get; set; }
public int ReturnCode { get; set; }
}

Listing 9-23: The SqlmapStatus class

For the SqlmapStatus class, we don’t need to define a constructor because, by default, every class has a public constructor. We do define two public properties on the class: a string status message and the integer return code . To get the task status and store it in SqlmapStatus, we implement GetScanStatus, which takes a taskid as input and returns a SqlmapStatus object.

The GetScanStatus() method is shown in Listing 9-24.

public SqlmapStatus GetScanStatus(string taskid)
{
  JObject tok = JObject.Parse(_session.ExecuteGet("/scan/" + taskid + "/status"));

  SqlmapStatus stat = new SqlmapStatus();
  stat.Status = (string)tok["status"];

  if (tok["returncode"].Type != JTokenType.Null)
    stat.ReturnCode = (int)tok["returncode"];

  return stat;
}

Listing 9-24: The GetScanStatus() method

We use the ExecuteGet() method we defined earlier to retrieve the /scan/taskid/status API endpoint , which returns a JSON object with information about the task’s scan status. After calling the API endpoint, we create a new SqlmapStatus object and assign the status value from the API call to the Status property. If the returncode JSON value isn’t null , we cast it to an integer and assign the result to the ReturnCode property. Finally, we return the SqlmapStatus object to the caller.

The New Main() Method

Now we’ll add the logic to the command line application so that we can scan the vulnerable Search page within BadStore that we exploited in Chapter 2 and monitor the scan. Begin by adding the code shown in Listing 9-25 to the Main() method before you call DeleteTask.

    options["url"] = "http://192.168.1.75/cgi-bin/badstore.cgi?" +
                       "searchquery=fdsa&action=search";

   mgr.StartTask(taskID, options);

   SqlmapStatus status = mgr.GetScanStatus(taskID);

   while (status.Status != "terminated")
    {
      System.Threading.Thread.Sleep(new TimeSpan(0,0,10));
      status = mgr.GetScanStatus(taskID);
    }

Console.WriteLine("Scan finished!");

Listing 9-25: Starting a scan and watching it finish in the main sqlmap application

Replace the IP address with that of the BadStore you wish to scan. After the application assigns the url key in the options dictionary, it will start the task with the new options and get the scan status , which should be running. Then, the application will loop until the status of the scan is terminated, which means the scan has finished. The application will print "Scan finished!" once it exits the loop.

Reporting on a Scan

To see if sqlmap was able to exploit any of the vulnerable parameters, we’ll create a SqlmapLogItem class to retrieve the scan log, as shown in Listing 9-26.

public class SqlmapLogItem
{
  public string Message { get; set; }
  public string Level { get; set; }
  public string Time { get; set; }
}

Listing 9-26: The SqlmapLogItem class

This class has only three properties: Message, Level, and Time. The Message property contains the message describing the log item. Level controls how much information sqlmap will print in the report, which will be Error, Warn, or Info. Each log item has only one of these levels, which makes it easy to search for specific types of log items later (say, when you just want to print the errors but not the warnings or informational items). Errors are generally fatal, while warnings mean something seems wrong but sqlmap can keep going. Informational items are just that: basic information about what the scan is doing or finding, such as the type of injection being tested for. Finally, Time is the time the item was logged.

Next, we implement the GetLog() method to return a list of these SqlmapLogItems and then retrieve the log by executing a GET request on the /scan/taskid/log endpoint, as shown in Listing 9-27.

public List<SqlmapLogItem> GetLog(string taskid)
{
  JObject tok = JObject.Parse(session.ExecuteGet("/scan/" + taskid + "/log"));
  JArray items = tok ["log"] as JArray;
  List<SqlmapLogItem> logItems = new List<SqlmapLogItem>();
 foreach (var item in items)
  {
  SqlmapLogItem i = new SqlmapLogItem();   i.Message = (string)item["message"];
   i.Level = (string)item["level"];
   i.Time = (string)item["time"];
   logItems.Add(i);
  }
 return logItems;
}

Listing 9-27: The GetLog() method

The first thing we do in the GetLog() method is make the request to the endpoint and parse the request into a JObject. The log key has an array of items as its value, so we pull its value as a JArray using the as operator and assign it to the items variable . This may be the first time you have seen the as operator. My main reason for using it is readability, but the primary difference between the as operator and explicit casting is that as will return null if the object to the left cannot be cast to the type on the right. You can’t use it on value types because value types can’t be null.

Once we have an array of log items, we create a list of SqlmapLogItems. We loop over each item in the array and instantiate a new SqlmapLogItem each time . Then we assign the new object the value in the log item returned by sqlmap. Finally, we add the log item to the list and return the list to the caller method .

Automating a Full sqlmap Scan

We’ll call GetLog() from the console application after the scan terminates and print the log messages to the screen. Your application’s logic should look like Listing 9-28 now.

public static void Main(string[] args)
{
  using (SqlmapSession session = new SqlmapSession("127.0.0.1", 8775))
  {
    using (SqlmapManager manager = new SqlmapManager(session))
    {
      string taskid = manager.NewTask();

      Dictionary<string, object> options = manager.GetOptions(taskid);
      options["url"] = args[0];
      options["flushSession"] = true;

      manager.StartTask(taskid, options);

      SqlmapStatus status = manager.GetScanStatus(taskid);
      while (status.Status != "terminated")
      {
        System.Threading.Thread.Sleep(new TimeSpan(0,0,10));
        status = manager.GetScanStatus(taskid);
      }

      List<SqlmapLogItem> logItems = manager.GetLog(taskid);
      foreach (SqlmapLogItem item in logItems)
      Console.WriteLine(item.Message);

      manager.DeleteTask(taskid);
    }
  }
}

Listing 9-28: The full Main() method to automate sqlmap to scan a URL

After adding the call to GetLog() to the end of the sqlmap main application, we can iterate over the log messages and print them to the screen for us to see when the scan is finished. Finally, we are ready to run the full sqlmap scan and retrieve the results. Passing the BadStore URL as an argument to the application will send the scan request to sqlmap. The results should look something like Listing 9-29.

$ ./ch9_automating_sqlmap.exe "http://10.37.129.3/cgi-bin/badstore.cgi?
searchquery=fdsa&action=search"

flushing session file
testing connection to the target URL
heuristics detected web page charset 'windows-1252'
checking if the target is protected by some kind of WAF/IPS/IDS
testing if the target URL is stable
target URL is stable
testing if GET parameter 'searchquery' is dynamic
confirming that GET parameter 'searchquery' is dynamic
GET parameter 'searchquery' is dynamic
heuristics detected web page charset 'ascii'
heuristic (basic) test shows that GET parameter 'searchquery' might be
injectable
(possible DBMS: 'MySQL')
–-snip--
GET parameter 'searchquery' seems to be 'MySQL <= 5.0.11 OR time-based blind
(heavy query)' injectable
testing 'Generic UNION query (NULL) - 1 to 20 columns'
automatically extending ranges for UNION query injection technique tests as
there is at least one other (potential) technique found
ORDER BY technique seems to be usable. This should reduce the time needed to
find the right number of query columns. Automatically extending the range for
current UNION query injection technique test
target URL appears to have 4 columns in query
GET parameter 'searchquery' is 'Generic UNION query (NULL) - 1 to 20
columns' injectable
the back-end DBMS is MySQL

Listing 9-29: Running the sqlmap application on a vulnerable BadStore URL

It works! The output from sqlmap can be very verbose and potentially confusing for someone not used to reading it. But even though it can be a lot to take in, there are key points to look for. As you can see in the output, sqlmap finds that the searchquery parameter is vulnerable to a time-based SQL injection , that there is a UNION-based SQL injection , and that the database is MySQL . The rest of the messages are information regarding what sqlmap is doing during the scan. With these results, we can definitely say this URL is vulnerable to at least two SQL injection techniques.

Integrating sqlmap with the SOAP Fuzzer

We have now seen how to use the sqlmap API to audit and exploit a simple URL. In Chapters 2 and 3, we wrote a few fuzzers for vulnerable GET and POST requests in SOAP endpoints and JSON requests. We can use the information we gather from our fuzzers to drive sqlmap and, with only a few more lines of code, go from finding potential vulnerabilities to fully validating and exploiting them.

Adding sqlmap GET Request Support to the SOAP Fuzzer

Only two types of HTTP requests are made in the SOAP fuzzer: GET and POST requests. First, we add support to our fuzzer so it will send URLs with GET parameters to sqlmap. We also want the ability to tell sqlmap which parameter we think is vulnerable. We add the methods TestGetRequestWithSqlmap() and TestPostRequestWithSqlmap() to the bottom of the SOAP fuzzer console application to test GET and POST requests, respectively. We’ll also update the FuzzHttpGetPort(), FuzzSoapPort(), and FuzzHttpPostPort() methods in a later section to use the two new methods.

Let’s start by writing the TestGetRequestWithSqlmap() method, shown in Listing 9-30.

static void TestGetRequestWithSqlmap(string url, string parameter)
{
  Console.WriteLine("Testing url with sqlmap: " + url);
using (SqlmapSession session = new SqlmapSession("127.0.0.1", 8775))
  {
    using (SqlmapManager manager = new SqlmapManager(session))
    {
    string taskID = manager.NewTask();
    var options = manager.GetOptions(taskID);
      options["url"] = url;
      options["level"] = 1;
      options["risk"] = 1;
      options["dbms"] = "postgresql";
      options["testParameter"] = parameter;
      options["flushSession"] = true;

      manager.StartTask(taskID, options);

Listing 9-30: First half of the TestGetRequestWithSqlmap() method

The first half of the method creates our SqlmapSession and SqlmapManager objects, which we call session and manager, respectively. Then it creates a new task and retrieves and sets up the sqlmap options for our scan . We explicitly set the DBMS to PostgreSQL since we know the SOAP service uses PostgreSQL. This saves us some time and bandwidth by testing only PostgreSQL payloads. We also set the testParameter option to the parameter we decided is vulnerable after previously testing it with a single apostrophe and receiving an error from the server. We then pass the task ID and the options to the StartTask() method of manager to begin the scan.

Listing 9-31 details the second half of the TestGetRequestWithSqlmap() method, similar to the code we wrote in Listing 9-25.

     SqlmapStatus status = manager.GetScanStatus(taskid);
     while (status.Status != "terminated")
     {
       System.Threading.Thread.Sleep(new TimeSpan(0,0,10));
       status = manager.GetScanStatus(taskID);
     }

     List<SqlmapLogItem> logItems = manager.GetLog(taskID);

     foreach (SqlmapLogItem item in logItems)
       Console.WriteLine(item.Message);

     manager.DeleteTask(taskID);
    }
  }
}

Listing 9-31: The second half of the TestGetRequestWithSqlmap() method

The second half of the method watches the scan until it is finished, just like in our original test application. Since we have written similar code before, I won’t go over every line. After waiting until the scan is finished running , we retrieve the scan results using GetLog() . We then write the scan results to the screen for the user to see. Finally, the task is deleted when the task ID is passed to the DeleteTask() method .

Adding sqlmap POST Request Support

The TestPostRequestWithSqlmap() method is a bit more complex than its companion. Listing 9-32 shows the beginning lines of the method.

static void TestPostRequestWithSqlmap(string url, string data,
             string soapAction, string vulnValue)
{
Console.WriteLine("Testing url with sqlmap: " + url);
using (SqlmapSession session = new SqlmapSession("127.0.0.1", 8775))
  {
    using (SqlmapManager manager = new SqlmapManager(session))
    {
    string taskID = manager.NewTask();
      var options = manager.GetOptions(taskID);
      options["url"] = url;
      options["level"] = 1;
      options["risk"] = 1;
      options["dbms"] = "postgresql";
      options["data"] = data.Replace(vulnValue, "*").Trim();
      options["flushSession"] = "true";

Listing 9-32: Beginning lines of the TestPostRequestWithSqlmap() method

The TestPostRequestWithSqlmap() method accepts four arguments . The first argument is the URL that will be sent to sqlmap. The second argument is the data that will be in the post body of the HTTP request—be it POST parameters or SOAP XML. The third argument is the value that will be passed in the SOAPAction header in the HTTP request. The last argument is the unique value that is vulnerable. It will be replaced with an asterisk in the data from the second argument before being sent to sqlmap to fuzz.

After we print a message to the screen to tell the user which URL is being tested , we create our SqlmapSession and SqlmapManager objects . Then, as before, we create a new task and set the current options . Pay special attention to the data option . This is where we replace the vulnerable value in the post data with an asterisk. The asterisk is a special notation in sqlmap that says, “Ignore any kind of smart parsing of the data and just search for a SQL injection in this specific spot.”

We still need to set one more option before we can start the task. We need to set the correct content type and SOAP action in the HTTP headers in the request. Otherwise, the server will just return 500 errors. This is what the next part of the method does, as detailed in Listing 9-33.

   string headers = string.Empty;
   if (!string.IsNullOrWhitespace(soapAction))
     headers = "Content-Type: text/xml SOAPAction: " + soapAction;
   else
     headers = "Content-Type: application/x-www-form-urlencoded";
   options["headers"] = headers;

   manager.StartTask(taskID, options);

Listing 9-33: Setting the right headers in the TestPostRequestWithSqlmap() method

If the soapAction variable (the value we want in the SOAPAction header telling the SOAP server the action we want to perform) is null or an empty string , we can assume this is not an XML request but rather a POST parameter request. The latter only requires the correct Content-Type to be set to x-www-form-urlencoded. If soapAction is not an empty string, however, we should assume we are dealing with an XML request and then set the Content-Type to text/xml and add a SOAPAction header with the soapAction variable as the value. After setting the correct headers in the scan options , we finally pass the task ID and the options to the StartTask() method.

The rest of the method, shown in Listing 9-34, should look familiar. It just watches the scan and returns the results, much as does the TestGetRequestWithSqlmap() method.

      SqlmapStatus status = manager.GetScanStatus(taskID);
      while (status.Status != "terminated")
      {
        System.Threading.Thread.Sleep(new TimeSpan(0,0,10));
        status = manager.GetScanStatus(taskID);
      }

      List<SqlmapLogItem> logItems = manager.GetLog(taskID);

      foreach (SqlmapLogItem item in logItems)
        Console.WriteLine(item.Message);

      manager.DeleteTask(taskID);
    }
  }
}

Listing 9-34: The final lines in the TestPostRequestWithSqlmap() method

This is exactly like the code in Listing 9-25. We use the GetScanStatus() method to retrieve the current status of the task, and while the status isn’t terminated, we sleep for 10 seconds . Then we get the status again. Once finished, we pull the log items and iterate over each item, printing the log message . Finally, we delete the task when all is done.

Calling the New Methods

In order to complete our utility, we need to call these new methods from their respective fuzzing methods in the SOAP fuzzer. First, we update the FuzzSoapPort() method that we made in Chapter 3 by adding the method call for TestPostRequestWithSqlmap() into the if statement that tests whether a syntax error has occurred due to our fuzzing, as shown in Listing 9-35.

if (resp.Contains("syntax error"))
{
  Console.WriteLine("Possible SQL injection vector in parameter: " +
                      type.Parameters[k].Name);
TestPostRequestWithSqlmap(_endpoint, soapDoc.ToString(),
                            op.SoapAction, parm.ToString());
}

Listing 9-35: Adding support to use sqlmap to the FuzzSoapPort() method in the SOAP fuzzer from Chapter 3

In our original SOAP fuzzer in the FuzzSoapPort() method at the very bottom, we tested whether the response came back with an error message reporting a syntax error . If so, we printed the injection vector for the user. To make the FuzzSoapPort() method use our new method for testing a POST request with sqlmap, we just need to add a single line after the original WriteLine() method call printing the vulnerable parameter. Add a line that calls the TestPostRequestWithSqlmap() method , and your fuzzer will automatically submit potentially vulnerable requests to sqlmap for processing.

Similarly, we update the FuzzHttpGetPort() method in the if statement testing for a syntax error in the HTTP response, as shown in Listing 9-36.

if (resp.Contains("syntax error"))
{
  Console.WriteLine("Possible SQL injection vector in parameter: " +
                    input.Parts[k].Name);
  TestGetRequestWithSqlmap(url, input.Parts[k].Name);
}

Listing 9-36: Adding sqlmap support to the FuzzHttpGetPort() method from the SOAP fuzzer

Finally, we update the if statement testing for the syntax error in FuzzHttpPostPort() just as simply, as Listing 9-37 shows.

if (resp.Contains("syntax error"))
{
  Console.WriteLine("Possible SQL injection vector in parameter: " +
                    input.Parts[k].Name);
  TestPostRequestWithSqlmap(url, testParams, null, guid.ToString());
}

Listing 9-37: Adding sqlmap support to the FuzzHttpPostPort() method from the SOAP fuzzer

With these lines added to the SOAP fuzzer, it should now not only output potentially vulnerable parameters but also any of the SQL injection techniques sqlmap was able to use to exploit the vulnerabilities.

Running the updated SOAP fuzzer tool in your IDE or in a terminal should yield new information printed to the screen regarding sqlmap, as Listing 9-38 shows.

  $ mono ./ch9_automating_sqlmap_soap.exe http://172.18.20.40/Vulnerable.asmx
  Fetching the WSDL for service: http://172.18.20.40/Vulnerable.asmx
  Fetched and loaded the web service description.
  Fuzzing service: VulnerableService
  Fuzzing soap port: VulnerableServiceSoap
  Fuzzing operation: AddUser
  Possible SQL injection vector in parameter: username
Testing url with sqlmap: http://172.18.20.40/Vulnerable.asmx
  --snip--

Listing 9-38: Running the updated SOAP fuzzer with sqlmap support against the vulnerable SOAP service from Chapter 3

In the SOAP fuzzer output, note the new lines regarding testing the URL with sqlmap . Once sqlmap has finished testing the SOAP request, the sqlmap log should be printed to the screen for the user to see the results.

Conclusion

In this chapter, you saw how to wrap the functionality of the sqlmap API into easy-to-use C# classes to create a small application that starts basic sqlmap scans against URLs passed as an argument. After we created the basic sqlmap application, we added sqlmap support to the SOAP fuzzer from Chapter 3 to make a tool that automatically exploits and reports on potentially vulnerable HTTP requests.

The sqlmap API can use any argument that the command line–based sqlmap tool can, making it just as powerful, if not more so. With sqlmap, you can use your C# skills to automatically retrieve password hashes and database users after verifying that a given URL or HTTP request is indeed vulnerable. We’ve only scratched the surface of sqlmap’s power for offensive pentesters or security-minded developers looking for more exposure to the tools hackers use. Hopefully, you can take the time to learn the more subtle nuances of the sqlmap features to really bring flexible security practices to your work.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset