The following excerpt, courtesy of Wiley Publishing, is from Chapter 22 of the book "The Database Hacker's Handbook: Defending Database Servers" written by David Litchfield, Chris Anley, John Heasman and Bill Grindlay. Click for the complete book excerpt series or purchase the book.
By submitting your personal information, you agree that TechTarget and its partners may contact you regarding relevant content, products and special offers.
Once an attacker has broken into a SQL Server, his efforts will turn to both ensuring that his intrusion is not detected and to making future attacks easier. The first goal is achieved by deleting access log entries and minimizing obvious changes to data; the second is commonly accomplished by means of subtle changes to the database software and structure that remove security checks, known as backdoors. This section describes techniques used to compromise a SQL Server's security controls and also details detection and defense methods.
Perhaps the subtlest of SQL Server backdoors is the three-byte patch as described by Chris Anley in his whitepaper "Violating Database-Enforced Security Mechanisms".
This method utilizes an existing attack vector, such as a buffer overflow exploit, to patch the SQL Server process in memory — an approach known as runtime patching. When patching bytes in memory the Windows SDK function VirtualProtect() must first be called on the region in order to mark it as writable. To determine the bytes to patch, a debugger, such as the one included with Microsoft Visual C++ .NET, is attached to the sqlservr.exe process. After logging on to the SQL Server as a low-privileged user using Microsoft Query Analyzer, a query attempting to access a prohibited table is executed:
select * from sysxlogins
By default only members of the dbo database administrators group can view this table, which contains usernames and password hashes for all database users. Running this query causes the SQL Server process to throw a C++ exception in the debugger; after allowing execution to continue the expected message is produced in Query Analyzer:
SELECT permission denied on object 'sysxlogins', database 'master', owner 'dbo'.
Logging into the server as the sa user, which does have select permission on the table, and running the query displays the table and does not produce the C++ exception. Clearly the access control mechanism throws an exception when access is denied to a table. A great help when debugging SQL Server is the symbols file (sqlservr.pdb), which is provided by Microsoft in the MSSQLBinnexe directory. This provides the original function names to the debugger and allows inference of the general purpose of large chunks of assembler. A case in point here is the function FHasObjPermissions, which after setting breakpoints on all functions containing the word "permission" is executed after the original select query is run. A static disassembly of the main SQL Server binary using DataRescue's excellent IDA Pro can be used to divine the behavior of this function. In this case the function is called from within the CheckPermissions function:
0087F9C0 call FHasObjPermissions 0087F9C5 add esp, 14h 0087F9C8 test eax, eax 0087F9CA jnz short loc_87F9DC 0087F9CC push 17h 0087F9CE push 19h 0087F9D0 push 2 0087F9D2 push 24h 0087F9D4 call ex_raise
FHasObjPermissions is called, and after it returns, the stack-pointer (esp) is increased by 0x14 to remove the arguments that were passed to the function. The eax register is then compared with itself using the test operator; the effect of this operation is to set the CPU's zero flag only if eax is zero. So if eax is set to zero by FhasObjPermission, the following jnz (jump if not zero) operator will not cause a jump and execution will continue on to the call to ex_raise. To avoid the exception being raised, the jump to the code that carries out the query should always occur. A quick way to achieve this would be to patch the conditional jump (jnz) to a non-conditional jump (jmp), however this may not bypass further checks; if the code is investigated further a neater patch can be found.
Looking at the code for FHasObjPermissions, an interesting section is
004262BB call ExecutionContext::Uid(void) 004262C0 cmp ax, 1 004262C4 jnz loc_616F76
The call to the Uid method in the ExecutionContext object places the current user's uid into the ax register (the 16-bit version of the eax register, effectively the lower 16 bits of this 32-bit register). SQL Server uids (user IDs) are listed in the sysxlogins table, and the uid with a value of 1 is associated with the database administrators group dbo. Because the code is comparing the uid returned by the Uid() call to 1, the best approach would be to patch ExecutionContext::Uid() to always return 1. Examining the function, the assignment takes place at the end, just before it returns:
00413A97 mov ax, [eax+2] 00413A9B pop esi 00413A9C retn
Changing the mov ax, [eax+2] assignment to mov ax, 1 requires patching three bytes. The bytes 66 8B 40 02 should be changed to 66 B8 01 00. Any user now has permissions on all objects in the database and any user can view the password hashes in sysxlogins. Attempting to execute the stored procedure xp_cmdshell as a non-admin user, however, results in
Msg 50001, Level 1, State 50001 xpsql.cpp: Error 183 from GetProxyAccount on line 604
This is because of a security feature in SQL Server that prevents nonadministrators from executing commands unless a proxy account is specified. SQL Server is attempting to retrieve this proxy information and failing because it is not set by default. Loading up SQL Server's Enterprise Manager, and selecting the Job System tab under SQL Server Agent properties, invalid proxy account information was entered. The error now produced when xp_cmdshell is run with low privileges is
Msg 50001, Level 1, State 50001 xpsql.cpp: Error 1326 from LogonUserW on line 488
Using APISpy32 to watch for calls to the Windows API function LogonUserW(PWSTR, PWSTR, PWSTR, DWORD, DWORD, PDWORD) when xp_cmdshell is executed, the output shows the function being called from within xplog70.dll. This DLL can be debugged by launching the sqlservr.exe process from within a debugger such as Microsoft's WinDbg or from IDA's internal debugger. After setting multiple breakpoints in the code and stepping through the code-path taken when xp_cmdshell is successfully and unsuccessfully executed, the divergence point can be established. This point on SQL Server 2000 with no service packs turns out to be a conditional jump (jnz):
42EA56D3 add esp, 0Ch 42EA56D6 push eax 42EA56D7 call strcmp 42EA56DC add esp, 8 42EA56DF test eax, eax 42EA56E1 jnz loc_42EA5A98
Patching the 2-byte op-code for jnz (0F 85) to the 2-byte op-code for a nonconditional jump jmp (90 E9) results in execution of xp_cmdshell being allowed for all users. Both this patch and Chris Anley's original patch require existing attack vectors for deployment such as a buffer overflow vulnerability. The decision on whether to patch bytes in memory (run-time patching) or to patch the actual SQL Server system files on the hard-drive depends on two factors; if the target is running software that offers file baselining features such as TripWire, and the SQL Server binaries are patched, this will be detected. However, if the SQL Server code is patched in memory, any backdoors will be removed on reboot of the server. A call to the function VirtualProtect is needed first in order to make the code segment writable.
Another tactic, known as the xstatus backdoor, uses a modification to the xstatus column of the master.dbo.sysxlogins table to permit users to login with system administrator privileges and no password. The xstatus column contains a smallint (2 byte) value that describes the user's role memberships together with the method of authentication to use. If the third bit of the number is set to zero, this denotes that the account authenticates using SQL Server's native authentication; a 1 means that Windows authentication is used. The default SQL Server account used with Windows authentication (BUILTINAdministrators) has a null password, which becomes a problem if the xstatus bit is changed to zero, giving an effective denary value of 18. This results in allowing anyone to log on to the server using native authentication, a username of BUILTINAdministrators, and a blank password.
The Windows .NET Server adds the NT AUTHORITYNETWORK SERVICE group as a SQL login and this account is also prone to xstatus changes in the same way as BUILTINAdministrators.
If the SQL Server is set up to use replication, the stored procedure sp_MSRepl_startup will be executed every time the server is restarted, in the security context of the SQL Server process. This makes it a target for Trojanning — all procedures that are run automatically should be examined for malicious instructions. The presence of the stored procedures sp_addlogin, sp_addrolemember, sp_addsrvrolemember,or xp_cmdshell in startup procedures may indicate that the server has been attacked and should be investigated.
Click for the complete book excerpt series.