During a recent penetration test, I was hacking away at some packet captures and noticed unencrypted Microsoft SQL Server (MSSQL) traffic. The syntax was unmistakable. At first, I thought this might be a way to capture some authentication credentials. However, MSSQL encrypts login traffic which meant I would have to crack the encryption to get credentials. If the installation uses a self-signed certificate, that is fairly easy to crack.
Unfortunately, for this particular client engagement, cracking SQL Server encryption was beyond the scope of the project. So, I had to set my curiosity aside for the time being and complete the penetration test for the client. However, I could not help thinking I was on to something. Was there a way to attack an SQL Server box without any credentials? I decided to take my hypothesis to the lab and try some experiments.
What I found was that with a little packet hacking, I could take control of a Microsoft SQL Server box without having any stolen credentials using a Man in the Middle style attack.
Man in the Middle
Back in my lab, I began to research this more. For my investigation, I was running MSSQL Server 2014 Express on Windows Server 2012 R2. The client machine was a Windows 10 system running MSSQL Management Studio 2014. My attack machine was a relatively new installation of Kali 2.0 Linux. All of these systems are on the same subnet, simulating an attacker on the internal network. This was nearly identical to the setup I had at the client site.
This type of attack is known as a man-in-the-middle (MITM) attack. Anitian does these a lot, as we have a lot of expertise on hacking infrastructure devices. The typical setup is to perform some kind of redirect, like an ARP cache poison (which is still possible in some environments), which forces traffic between two systems to be redirected through the attacker’s computer. This allows the attacker to not only see all of the data between the victims but potentially also to manipulate that traffic.
This was exactly what I wanted to do.
Understanding the Data
The first thing I needed to do was to look at the MSSQL query traffic. In order to make this test more interesting, I used the “SA” account to log in. The SA account is the system admin account in SQL Server and can do anything. If my experiments were successful, I could do a lot of fun things with the SA account’s privileges.
Once logged in, I launched Wireshark 2.0 on the SQL Server box. It started capturing traffic on the primary interface. I configured Wireshark to use a display filter “tds.query”. This hides all the other traffic and displays just the TDS query packets. (Incidentally, I noticed that the “tds.query” filter object is not available on older versions of Wireshark.)
With a traffic capture underway, I switched back to the workstation and executed a query against the sample database I built for this test. The database is called testdb and includes one table called Products. The Products table has two columns named ProductID and ProductName. There is no actual data in the table, but for this test that is unnecessary. This query is designed to pull all information from the database table.
The query was executed successfully and the empty table was returned to me. You can see the empty columns listed toward the bottom-right of the screenshot. Switching back to Wireshark, I stopped the capture and looked at the captured data. I spotted one TDS query packet. Clicking on that packet showed me all of the data contained within. MSSQL Server 2014 Express did not have encryption enabled by default, so this data was easy to access.
Looking at the decoded data at the bottom of the center panel, it’s easy to identify the query. It even includes the carriage return and newline characters. Something interesting to notice is that in between each character of the query is a null byte (hexadecimal 0x00) which is normal for Unicode. This is only noticeable when looking at the raw data in the bottom pane. Wireshark displays these bytes as period characters but really, they are null. This meant that I couldn’t just look for a simple string like “select”. I’d have to take those Unicode null bytes into consideration when searching for the data later, and for when I ultimately try to replace it with my own data.
Fun with Ettercap Filters
Now that I knew what the data looked like, I could try to find a way to manipulate it. I decided to use Ettercap. Ettercap is a tool specifically designed to perform MITM attacks. It also has a nifty built-in feature called Ettercap filters. A filter would allow me to search the packets for specific data and then manipulate that data. You just write the filter and load it into Ettercap. Then Ettercap automatically replaces data every time it finds a match. The functionality is somewhat limited, but it should work for proof of concept.
The filters are written in a simple scripting language. The important functions I intended to use were the search and replace functions. The search function will search for specific data within the packets. The replace function will actually search for data and then replace it with other data. That was the key to this project.
Since the TDS query data includes those null bytes, some of the characters are not printable. This meant that I could not merely search for a simple string and replace it with another string. I needed a way to search for a non-printable null byte. Since I cannot type null on a keyboard, I needed another way. Fortunately, Ettercap filters support hexadecimal using “\x” to escape. For example, to search for the letter ‘s’, I can tell Ettercap to search for “\x73”. The null bytes are easily searchable now by searching for “\x00”. Kali includes a program called hexdump that can be used to convert strings to hexadecimal. I used this to convert the string “select” to hex.
Once I had the data I needed, I wrote the first test filter and called it “mssql.filter”.
The first line ensures that the filter will only run on TCP traffic with a destination port of 1433. If this matches, the filter will output a debugging message to the console to let me know that it found SQL traffic. This is just for my own peace of mind so I know it’s at least partially working. The next “if” statement searches for a string of hex data. This data translates to “select” with null bytes in between each character. If the filter locates that string, it will output another debugging message to the console.
Finally, the magic happens. The replace command swaps that exact string with a different string of “ssssss” including the required null bytes. This was just a test to see if the script would run properly. It is important to note that when you replace data in a TCP packet, you must replace it with the exact same number of bytes. If the size of the packet changes, the TCP connection will break.
Once the filter is written, it must be compiled. This is easily accomplished with the etterfilter command.
There were no errors, so the filter was now ready for testing. I fired up the Ettercap graphical interface and launched an ARP spoofing attack against the MSSQL server and the client workstation with sniffing enabled. I fired up Wireshark and verified that I was seeing traffic being sent between the two victims. Then in Ettercap, I went to “Filters -> Load a filter” and chose my filter. I was rewarded with a “Content filters loaded” message down in the Ettercap console. Almost immediately I was receiving “SQL Traffic Discovered” messages as well. Everything was looking positive.
The next step was to switch back to the workstation and try executing the query. If it goes according to plan, the “select” string should be replaced with “ssssss” and break the query. I executed the query, but this time I did not receive the empty table result as I did originally. Instead, I received an error.
“Incorrect syntax near ‘ssssss’.” That’s perfect! The filter worked exactly as expected. It replaced the “select” string with “ssssss”. The MSSQL server did not know how to handle that and returned an error. That was one step in the right direction. The next step was to replace the entire query string with something that will help me as the attacker.
Create the Login
I decided to try to add a login to the server. This would be pretty much the best possible scenario for me as an attacker, especially since in this case the workstation victim is logging in as the SA user. In order to add a login, I would have to submit this query to the MSSQL server:
CREATE LOGIN anitian WITH PASSWORD=’YouGotHacked1#’;
This would add a user to the MSSQL server called “anitian” with the password “YouGotHacked1#”. After converting everything to hex, I updated the mssql.filter file to contain the new data.
This filter will search for the string “select ProductID, ProductName from Products where ProductID=1;” and then replace it with the string “CREATE LOGIN anitian with PASSWORD=’YouGotHacked1#’”. I mentioned earlier that you must replace TCP data with the exact same amount of data. So how did I handle that since my new query is shorter than the original? I just added some spaces to the end of my new query with the null bytes surrounding them. This would ensure that the TCP packet stayed the same size, but the spaces wouldn’t interfere with my query being executed successfully. I compiled the filter just like before and then loaded it up into Ettercap. Then I submitted the query from the workstation.
Notice the difference between this response and the response before I used the Ettercap filter? Originally, the query returned an empty table. This time, no table was returned. Instead, the server returned a message, “Command(s) completed successfully.” If a database administrator saw this, they would likely dismiss it as some strange error. Unfortunately, they would be too late. I just added my own account to the database system. Now, the real hack was about to take place.
From the Windows 10 workstation, I logged out of the SA account and then attempted to log in using my (hopefully) newly created anitian account.
SUCCESS! I was now logged in with my own account. Unfortunately, this account did not have a lot of rights, so I could not do much. However, that could be solved. The next step would be to prepare another Ettercap filter to adjust my account’s rights and then perform a second SQL query injection attack.
At this point, I could have easily done all this, but it is rather tedious to do all the hex conversions by hand, and then add all of those null bytes and such. Who wants to go through all of that effort? This was a good enough proof of concept right?
No way! I was not about to give up that quickly. Besides, why do all that tedious work, when I can automate the entire process using a script!
Automating the Hack
The SQLinject.sh shell script can be downloaded here: https://pastebin.com/Nge9rx7g
This script automates the entire process from converting the SQL queries to hex all the way to performing the ARP spoofing and loading the Ettercap filter. It makes the process extremely easy.
In order to use the script you need four pieces of information:
- The IP address of the MSSQL server
- The IP address of the MSSQL client
- The original query you want to replace
- The new query you want to inject
In this case, I already knew everything except for the SQL query I wanted to inject. I knew I wanted to give the anitian user sysadmin privileges. After a quick lesson in SQL commands, I was able to design with the correct query:
ALTER SERVER ROLE sysadmin ADD MEMBER anitian;
This would add my new anitian user to the sysadmin role on the server, giving me access to pretty much anything I want. Now that I had all four key pieces of information, I ran the script like this:
./SQLInject.sh –o “select ProductID, ProductName from Products where ProductID=1;” –i “ALTER SERVER ROLE sysadmin ADD MEMBER anitian;” –s 192.168.1.114 –c 192.168.1.100 –f mssql.filter
Using the script, I do not have to worry about those pesky hex conversions or null bytes. The script handles it all for me. It will perform the conversions and then output an Ettercap filter to mssql.filter (The filename is based on the –f flag). From there, it runs etterfilter and compiles the filter into mssql.filter.ef. Finally, the script even loads up the command line interface to Ettercap, performs the ARP spoofing attack against the server and workstation and loads the filter! It will even compare the length of the old query and the new query and warn you if the new query is too long. And if the queries are not an identical length, it will pad the new one with spaces to make them identical in length! One single command does everything for me.
I executed the script and then switched over to the workstation. I then ran the familiar select query and noticed that I once again received the “Command(s) completed successfully” message. This was a good sign for the attack. I logged out of the SA account and logged back in as anitian.
Tada! You can see in the screenshot that the anitian account is now a sysadmin user. With this level of access, I can do whatever I want with the system. It gives me a great pivot point to start attacking other systems on the network. Of course, that assumes this database does not already contain what I am looking for like payment card numbers or personally identifiable information.
The biggest downfall to this script is that it requires you to know the original SQL query before it actually happens. Luckily, SQL servers often have batch jobs or queries that are executed regularly or on a set schedule. Watching a Wireshark capture over a period of time should result in at least one query you can target. Of course, I could always turn this into a more full-fledged program that performs the MITM attack on its own and then actually proxies the traffic, searching for TDS query packets by type and then automatically replacing the data without the need to know the original query beforehand, but that’s a project for another day.
Defending Against SQL MITM Attacks
Man in the middle attacks such as this can be devastating. As you can see, I was able to gain complete access to a critical system. A focused hacker may not follow some of the scientific processes I did. Moreover, they could automate this and do it repetitively for hours or days, waiting for just the right conditions.
The simplest way to prevent this attack is to require encryption on all database connections. However, merely enabling encryption might not be sufficient. Clients can negotiate the connection with no encryption if it is not required. Also ensure that you use a valid, trusted certificate. An attacker could easily spoof a self-signed certificate.
Another defense is to ensure remote queries never use accounts with elevated privileges, such as the SA account. All database queries, especially the programmatic ones, should use an account with the absolute least amount of access they need to do the job. This ensures that if the attacker is able to take over a connection, they cannot use that connection to forge accounts like I did in this test.
Lastly, ensure your infrastructure is patched regularly. Also, segment and isolate database systems from the corporate network. These are best practices that also make MITM type attacks very difficult (if not impossible) to execute.