A little while back we were conducting an application security test for a client, when we managed a fun little backflip with NTLM credential relaying that we felt was worth sharing.
The application was at first glance quite unremarkable. A fairly typical Windows thick client, an application server, and a back-end MSSQL database server. As it usually does, the fun started with one of those ‘huh, that’s interesting’ moments. Looking at some XML configuration files in the thick client’s directory we found a handful of base64 encoded values with names like ‘DBUsername’ and ‘DBPassword’, next to some plain-text host names and port numbers. Attempting to decode the base64 resulted in illegible binary garbage, so they were obviously encrypted. It seems likely that the application developers assumed that an attacker would give up at this point, but we are nothing if not stubborn, and our thinking was that if the client software was able to decrypt these credentials, and we had a copy of the client, we MUST be able to decrypt them as well.
Initially we braced ourselves for a tedious reverse-engineering process, but were quickly relieved to find that the relevant binaries were all managed .NET, meaning we were able to simply decompile them back to extremely legible source code. Grepping the source for the string ‘crypt’ very quickly identified the function responsible for the encryption and decryption of our stored database credentials, along with a hard coded key and IV, both of which were based very closely on the name of the software vendor.
After some short work in python reimplementing the decryption function from the application, we were the proud owners of some shiny new database credentials, but what could we do with them? Logging into the MS SQL Server instance we found that even though this WAS the same server used by the application for its main database, the credentials we had were only able to access a smaller, less interesting database on the same server. The client retrieved its most sensitive data using a more traditional approach, i.e. via an application layer server with its own back-end connection to the database.
‘xp_cmdshell’, that eternal hacker favourite, was not available to our user. If we wanted to find a way to pop the database server itself we were going to need to get at least a little bit creative.
Next we tried another classic trick using a different extended stored procedure, ‘xp_dirtree’. xp_dirtree essentially asks the SQL Service instance to provide a directory listing of a given file path. By providing a UNC path directing the server to an attacker-controlled host, one can usually use tools like Responder or ntlmrelayx to capture a NetNTLMv2 hash of the SQL Server service account password, or relay the service account’s credentials and use them to authenticate via SMB to a secondary host. While we were able to capture a hash, the password configured was strong enough to resist our efforts at cracking in the time we had available. Further, we were unable to relay the credential via SMB to any of the in-scope hosts, as all had been configured with SMB signing set to the dreaded ‘REQUIRED’, making our relayed credentials worthless.
But wait! NTLM authentication can be relayed to authenticate for many other protocols, including HTTP and MSSQL! Maybe the usual rules against reflecting NTLM authentication back to the source host wouldn’t apply in a cross-protocol scenario like this? Nope, they still apply.
At this point we became a little frustrated and went back to basics, digging a little deeper in some earlier reconnaissance data we had acquired, including some gathered using Scott Sutherland’s (@_nullbind on Twitter) amazingly useful PowerUpSQL PowerShell module. PowerUpSQL will enumerate (among many other things) the links between one database server and another. These links are especially common in scenarios where you have a primary ‘operational’ database server and a secondary server which acts as a ‘data warehouse’, with data regularly being groomed from one database and archived in the other, which is exactly the scenario that it identified for us in this case.
Sending queries via the first database to the data warehouse did not immediately yield any particularly useful results. We were similarly low-privileged in the data warehouse, and it was beginning to look like something of a dead end, when we suddenly realised that we already had everything we needed to own the whole box and dice.
First we set up ntlmrelayx to catch SMB authentication attempts against our attacker-controlled host and relay them on to the same MS SQL Server port as before, only this time instead of running xp_dirtree on the first server, we used the database link to send the same xp_dirtree query to the second server. It attempted to connect to our host, which negotiated NTLM authentication and then relayed the authentication attempt back around to the first server’s MS SQL Server port, essentially completing a full loop.
We were back where we started, but now we were authenticated as the SQL Server service account, which had local Administrator privileges on both SQL servers, and thus was granted full ‘sa’ privileges on them as well. With admin access in hand we were able to enable the ‘xp_cmdshell’ extended stored procedure and use it to execute shell commands in the underlying OS as a full admin user. We won’t go into full detail here but after this breakthrough it was a matter of mere minutes before we were able to gain full control of the entire AD/Windows environment.
So, what are the take-home lessons from all of this?
Don’t hard-code credentials and encryption keys into your application configs or binaries. If your security design relies on doing this, you’ve designed it wrong.
Don’t let users log directly into the database server – make them go via an application layer server.
Disable SQL Server extended procedures that aren’t needed, including xp_cmdshell, xp_dirtree, and xp_filexist.
Outbound firewall rules matter almost as much as inbound. If your backend servers don’t need to speak SMB to the client, don’t let them!
… and while SMB signing is an extremely underrated and effective security control, no single security control is ever completely effective in the face of a determined adversary.