Hacking SQL servers is fun. Early this year, I blogged about hacking SQL servers without a password. I used Ettercap to perform a man-in-the-middle attack between a Microsoft SQL server and client. Using Ettercap filters I showed how you can replace a SQL query with your own malicious query in transit. This allows you to execute a diverse set of attacks, such as creating an admin database user to gain access to data or functions. I wrote a shell script to automate the entire process, which this blog published for all to use
While the script worked, it had limitations. First, you had to know an exact SQL query the database was handling. It is rare you would have such information. Second, the script relied on Ettercap to perform the MITM attack. Lastly, it only supported Microsoft SQL Server which, while a popular database engine, only represents a fraction of the database types in use.
During a recent pentest for an Anitian client, I decided to rewrite the whole tool to automate some testing I was performing. The goal was to make an All-in-One SQL MITM tool. You can download the Python script using the form below. (Here is the license agreement about this download.)
[email-download download_id=”1352″ contact_form_id=”1360″]
Since the previous blog covered the vulnerability and how to remediate it, this blog entry will focus on how to use this tool to test for SQL MITM vulnerabilities.
The tool has five required options.
<Interface> The network interface to listen on.
<SQL Type> The second option specifies the SQL server type. The tool currently has support for MSSQL, MySQL, Oracle, and PostgreSQL. These are four of the most popular database engines in use at this time.
<Client IP> IP address of the SQL client sending the queries
<Server IP> IP address of the SQL server. These two IP address options allow the script to perform ARP poisoning. This means that the script can initiate the MITM attack on its own, without the help of an external tool like Ettercap.
<New Query> The new SQL query. This is your malicious SQL string that will be injected into the connection.
There are also two optional arguments.
<Begin Keyword> the first keyword the script searches for.
<End Keyword> the final keyword (or character) the script searches for.
These two options are the beginning and ending keywords of the original SQL string. These keywords allow you to identify SQL queries without knowing the entire query ahead of time. For example, the default value for begin_keyword is “SELECT” and the default for end_keyword is a semicolon. This would match all of the following examples:
SELECT * FROM users WHERE userid = 1; SELECT * FROM products; SELECT username FROM users WHERE name = 'Joe';
This makes the script universally applicable. You do not need to know the exact SQL query to inject a malicious query.
I chose to use Python for this project because it’s easy to use and I knew I could leverage the incredibly useful packet manipulation library called Scapy. Scapy has built-in support for many protocols, in this case, the important ones being TCP and ARP. It can be used as a standalone packet manipulation program, or as a library for other projects.
The first step of the attack is to poison the client and server ARP caches. This tricks the server into thinking your host is the client and visa versa. All you need to do is enter the client and server IP addresses. The script then uses ARP to identify the corresponding MAC addresses. It then calls the arp_spoof() function, which builds the correct ARP packets to poison the client and server caches. The arp_spoof() function is called every 15 seconds using a timer. This ensures that the caches will stay poisoned throughout the attack.
The Scapy sniff() function is used to sniff packets. This function will sniff all incoming packets and loop indefinitely until the attacker presses ctrl+c to quit the program. Each time a packet is detected on the wire, the sniff() function uses a filter to identify if the packet is intended for the SQL server or client IP address. If not, the packet is ignored. If so, the packet data is sent to the processPacket() function.
This is where all the magic happens. The script will process the packet data based on which SQL engine was selected by the attacker. It searches the packet for the matching begin and end keywords. If it does not find them, the packet is sent on its way. If it does find a match, it compares the length of the original SQL query with the new query. If the new query is longer, the script spits out an error and forwards the original packet on unaltered. This is because of how TCP works. The altered packet must be the exact same length as the original packet, otherwise, this will break the TCP connection and cause it to reset. The result is the longer the original query, the more options you have for (malicious) replacement queries.
If the new query is shorter than the original one, everything is good. The script updates the packet with the new query and then adds some padding to the end to ensure that the packet stays the same length. Then the edited packet is forwarded on to the server.
At the end of the attack, press ctrl+c to quit the program. The script detects this and calls the undo_arp_spoof() function before quitting. This resets the ARP cache so the victims will receive traffic normally. Without this final step, the client and server would have networking problems and the attack may be more easily detected.
Below are some examples to consider.
First I will demonstrate the tool with Microsoft SQL. I created a simple test database as shown below.
You can see there are three entries in the Products table. Next I will run a basic query to pull a single object from the table.
You can see that I searched for products where the ProductName was ‘ProductZero’. The server returned only one matching entry. Next, I fire up the sqlmitm.py script to inject a malicious query.
With these arguments, the script should replace any SELECT query with the one I specified. This means that no matter what I SELECT from the database, the server should respond only with the ProductOne product entry.
You can see in the above image that I executed a query to pull the ProductZero data, but the server responded with ProductOne! That’s not right! The MITM script replaced the query in transit. The client has no indication this has happened. This allows for all manner of maliciousness.
Now let’s try this with MySQL. I set up a similar database on a test MySQL server.
Next, I run the sqlmitm.py script again, but I specify that I am attacking a mysql server.
You can see in the output that the script identified a matching packet and replaced the query. What did it look like for the client?
You can see that I attempted to pull the information for “Test Product 1” but instead was served information for “Test Product 2”. Again, the client did not detect any problem at all.
Next, I tested Oracle. Here is the test database.
Then I executed the sqlmitm.py script again.
Once it was up and running, I executed another similar query.
Once again, the server replied with the “wrong” product data.
The process is once again the same. The test database.
Run the script.
Execute a query.
The script once again replaced the query and the client received the wrong data.
Create a MySQL User Example
Those tests were all harmless. Let’s try something more malicious: create a user in a MySQL? This requires three operations.
First, we have to create the database user.
This query will create a user called “anitian” and allow them to log in from anywhere. The user’s password is “hacked”. After running the mitm script, I go back the the MySQL client and execute an innocent SELECT query.
Note that no rows were actually returned. The response we receive does not match the request. However, if this request were generated from an application, it is unlikely anybody would notice.
Next, we have to grant some elevated privileges to the user. This query should give our new user all privileges on all databases, as long as the victim has the permission to grant those privileges.
Once again, I run a SELECT query.
No errors were returned, so it is likely that the command succeeded. Finally, we have to tell the MySQL server to flush privileges.
And I run one final SELECT query.
At this point, the user should have been created. Let’s try to log in.
Jackpot! It worked! This user now has admin privileges on the database. Which of course means you could do almost anything, such as stealing protected data. This is merely one example of what the script can do.
In the first installment of this series, we discussed the ways you can avoid this type of attack. This script will only work on database connections that are unencrypted or where you can break the encryption with an invalid certificate. As such, the easiest way to avoid SQL MITM attacks is to require encryption for all SQL traffic and certification validation.
Second, always follow the principles of least privilege. Remote accounts should be limited to only the access necessary to function. Admin access should be very tightly controlled.
Lastly, as with almost all security issues, keep your systems and infrastructure patched.