MS SQL Server Injection Techniques in Sybase

A lot of papers have been published on SQL injection in Microsoft SQL Server applications, and because Sybase and MS SQL Server have a common heritage, it is worthwhile to take a quick survey of the known techniques and see how well they work in Sybase.

Comments

Sybase uses the -- and /* comment styles in exactly the same manner as MS SQL Server, so you can truncate queries in the same way using the -- sequence. It's unwise to get too hung up on -- because it's always possible to complete the query in a manner that makes the comment sequence unnecessary. For example, in the preceding UNION SELECT example,

 http://sybase.example.com/servlet/BookQuery?search=1234')+union+select+ name,null,null,null,null,null,null,null,null,0+from+master..syslogins-- 

we could just conclude the query with an unnecessary "or" term :

 http://sybase.example.com/servlet/BookQuery?search=1234')+union+select+ name,null,null,null,null,null,null,null,null,0+from+master..syslogins+ where+1=1+or+('a'='a 

This way we would make the entire query syntactically correct. In general, a superfluous "or" operator in a where clause will work, or (if you're injecting a batch of statements) an additional "select" at the end of the batch.

Union Select

As you have just seen, "union select" statements work in almost exactly the same way.

Error Messages

Sybase error messages are almost as helpful as MS SQL Server error messages. Specifically , the "integer conversion" trick works identically. This is where the attacker deliberately casts VARCHAR data to an integer, in order to provoke an error message containing the actual VARCHAR data. For example, to obtain a list of the databases on the server, you might use the following query:

 select name from master..sysdatabases order by name 

To achieve the same result in our example, using the integer conversion technique, you would request:

 BookQuery?search=')+and+1=convert(integer,(select+min(name)+from+sysdatabases+where+name>''))-- 

which returns the following message:

 com.sybase.jdbc2.jdbc.SybSQLException: Syntax error during explicit  conversion of VARCHAR value 'master' to a INT field. 

So the error message contains the string 'master', which is the first row of our resultset. To get the next , we modify the query to select the least value that is greater than 'master', thus:

 BookQuery?search=')+and+1=convert(integer,(select+min(name)+from+ sysdatabases+where+name>'master'))-- 

which returns "model" in the error message. In this way we iterate through all of the rows until our select statement returns no further data.

@@version

In MS SQL Server, the simple query

 select @@version 

returns the version of both the operating system and the DBMS, and the version number is sufficient to allow identification of missing patches. In Sybase, the @@version global variable is still present ”referencing the error message technique of the previous section, we can obtain it like this:

 BookQuery?search=')+and+1=convert(integer,(select+@@version))-- 

which returns something along the lines of:

 'Adaptive Server Enterprise/12.5.2/EBF 11948 ESD#1/P/NT (IX86)/OS  4.0/ase1252/1838/32-bit/OPT/Sat May 29 03:34:29 2004' 

The relevant terms here are 12.5.2, which is the version number of the DBMS; EBF 11948, which is the "emergency bug fix" number; and ESD#1, which is the "Electronic Software Delivery" number, which is a "roll up" patch set similar to a Service Pack in Windows.

Another global variable, @@version_as_integer, returns the "major version" of Sybase ”this is the same version that can be obtained via the version- grabbing script listed earlier, 12500 in this case, representing version 12.5.0.0.

To obtain an integer via the error message technique outlined earlier, we simply convert the integer into a string that cannot be implicitly converted to an integer, like this:

 convert(integer,(select 'z' + str(@@version_as_integer))) 

This gives us a query of

 BookQuery?search=')+and+1=convert(integer,(select+'z'%2bstr(@@version_as_integer)))-- 

which returns a result of

 com.sybase.jdbc2.jdbc.SybSQLException: Syntax error during explicit  conversion of VARCHAR value 'z 12500' to a INT field. 

In general, to obtain a variable of an arbitrary data type using the integer conversion error message, cast the variable to a string first, and then perform an integer conversion.

Having/ Group By

In MS SQL Server, it is possible to enumerate the tables and field names in a select statement by appending a having clause on the end, such as "having 1=1". The error message in MS SQL Server is of the form:

Column 'users.id' is invalid in the select list because it is not contained in an aggregate function and there is no GROUP BY clause.

The syntax for the "having" and "group by" clauses in Sybase and MS SQL Server are slightly different; specifically, Sybase has a much more liberal "having" and "group by" syntax, so this particular technique doesn't work.

SQL Batch Injection

In MS SQL Server, you can inject batches of statements that enable you to perform operations above and beyond those accessible in a single Transact-SQL statement, specifically batches of statements involving flow-of-control statements, variable declaration and manipulation, and mixing DDL and DML statements to alter the structure of the database.

Sybase flow-of-control and declare statements work in almost exactly the same way, so this type of injection works fine.

xp_cmdshell

Sybase does support the execution of extended stored procedures, in an extremely similar manner to MS SQL Server, but uses a slightly different mechanism. Only administrators can execute xp_cmdshell, by default. In addition, there is a specific xp_cmdshell configuration setting that determines the security context under which xp_cmdshell executes. If the option is set to 0 via the statement

 sp_configure 'xp_cmdshell context', 0 

Sybase will execute the command shell under the security context of Sybase itself. If the setting is 1 (the default), xp_cmdshell will execute under the context of the user that is executing the query, who must be an administrator at the operating system level.

xp_regread

Sybase has no equivalent to xp_regread, so any xp_regread tricks (such as reading the SAM database under Windows) will not work in Sybase.

Custom Extended Stored Procedures

The stored procedure API works in almost exactly the same way in Sybase as in MS SQL Server, so the concepts involved in malicious extended stored procedures are largely the same. The main significant difference is the fact that in Sybase, extended stored procedures are executed by the extended procedure service, which is a different process than the main Sybase database server process. Because of this separation, some attacks (for example, applying runtime code patches) will not work.

CHAR Function to Bypass Quote Filters

Occasionally you find that an organization has addressed SQL injection by simply "doubling" single-quote characters wherever they appear in queries. This is ( generally ) fine for string data, but it doesn't help with numeric data. One slight inconvenience when exploiting SQL injection in a numeric field, where single quotes are escaped, is that you will have difficulty representing string literals since you cannot use the single-quote character. The CHAR function allows you to create a string literal via a concatenation of characters based on character code, however. This is a commonly used technique in MS SQL Server, and works the same way in Sybase.

For example,

 select char(0x41)+char(0x42)+char(0x43) 

produces ('ABC').

In fact, because VARBINARY can be implicitly cast to VARCHAR, the following is a more economical way of encoding a string without using quotes:

 select char(0x41)+0x42434445 

This produces 'ABCDE'.

In some circumstances you might find that although single quotes are escaped, double quotes are not (and they are mostly interchangeable):

 select "ABCDE" 

Double-quote escaping works the same way in Sybase as single-quote escaping, that is, two consecutive double quotes within a string are parsed as one double quote.

SHUTDOWN

Often used in MS SQL Server injection walkthroughs, the SHUTDOWN command is a particularly good example of why even a small number of characters injected into a query can be dangerous. The SHUTDOWN command shuts down the database server; it's as simple as that. Although it requires admin privilege, the effects of this on a web application are easy to imagine. The SHUTDOWN command works in the same way in Sybase as it does in SQL Server, including the WITH NOWAIT option.

Audit Evasion via sp_password

In MS SQL Server, if the attacker appends the string

 sp_password 

to the Transact-SQL statement, this audit mechanism logs the following:

 -- 'sp_password' was found in the text of this event. -- The text has been replaced with this comment for security reasons. 

This behavior occurs in all T-SQL logging, even if sp_password occurs in a comment. This is, of course, intended to hide the plaintext passwords of users as they pass through sp_password, but it is quite a useful behavior for an attacker.

In Sybase, the auditing mechanism doesn't store the entire text of the query, so the default auditing mechanism is not vulnerable to this kind of evasion.

Linked Servers

Sybase has the ability to query external servers in a vaguely similar manner to MS SQL Server, but with a much more complex and adaptable configuration. You are as likely to find a pre-authenticated channel between Sybase servers as you are between MS SQL Servers, because the business factors that cause people to set those channels up are the same.

In Sybase, however, the passwords that are used to connect to external servers are (depending upon your configuration) stored in a weakly encrypted format, in a guest-readable table (sysattributes).

So if you configure an external login (sp_addexternlogin) you may get a weakly encrypted password in the sysattributes table:

 (from sp_addexternlogin)      update master.dbo.sysattributes      set    object_cinfo = @externname,      image_value = internal_encrypt(@externpasswd)          internal_encrypt() produces output like this:      select internal_encrypt('AAAAA')  ------------  0x4405440544 

We leave determining the algorithm to reverse this encryption as an exercise.

The weak encryption may pose a security risk because the sysattributes table is readable by guest. Any user can issue the query

 select image_value from sysattributes where len(convert(varbinary, image_value))>0 

to obtain the "encrypted" external passwords, and then trivially decrypt them to obtain credentials for all of the external logins that the server has been configured with. Of course, this is only a problem in some authentication models, but it is still worth bearing in mind when you're contemplating configuring external logins.

Another, incidental problem with the internal_encrypt function is that people sometimes use it in their own custom Sybase applications, as a substitute for a hashing algorithm. If you "google" for internal_encrypt, you'll see several postings along these lines to technical newsgroups. This is extremely unwise; as you have seen, the encryption provided by internal_encrypt is exceptionally weak. Also, using undocumented internal functions in production systems is not recommended. A much better solution would be to take advantage of Sybase's excellent Java support and use a salted version of MD5 or SHA1 as a password-hashing algorithm.

Using Time Delays as a Communications Channel

In a previous paper relating to SQL injection in MS SQL Server, we discussed a technique for extracting information from the database using time delays. Although the technique works for most DBMSs, the specific mechanism that was discussed was the waitfor statement in MS SQL Server. The technique is exceptionally powerful, and works unmodified for Sybase.

In Sybase, the command

 waitfor delay '0:0:5' 

will cause Sybase to wait for 5 seconds. If we try to get our sample vulnerable Servlet to pause in this way, the request looks like this:

 BookQuery?search=')+waitfor+delay+'0:0:5'-- 

In general, you can test a web application for SQL Injection using this technique. Try a number of forms of the waitfor command, in order to maximize the chances of correctly forming the statement:

 BookQuery?search=0+waitfor+delay+'0:0:5'--  BookQuery?search='+waitfor+delay+'0:0:5'--  BookQuery?search="+waitfor+delay+'0:0:5'--  BookQuery?search=')+waitfor+delay+'0:0:5'--  BookQuery?search=")+waitfor+delay+'0:0:5'-- 

In a database-driven web application, the request is transported from the user's web browser to some application environment ”in this case, a Java Servlet. The application composes a query and then issues it to the database. In almost every case, the application will wait until the query has completed, then return a result to the client. Because the process is synchronous, we can measure the delay from the client web browser. In the preceding examples, if the server takes longer than 5 seconds to respond to our HTTP request, the application is either very slow, or vulnerable to SQL injection. If we come up against a slow app, we can just increase our "injected" delay.

To extract arbitrary information from the database, we use a similar technique to the techniques we used when error messages were available. In general, we would form the data we wanted into a string, and then perform an explicit type cast to an integer. The resulting error message would include the text that we wanted to retrieve. The technique we use for extracting data using time delays is based on extracting individual bits from strings. Because we can represent any data in the database as a string, and we can extract any individual bit from a string, we can retrieve any data we wish, using time delays as the transmission channel.

The following statement will pause for 5 seconds if the low-order bit (bit 0) of the first byte of the string returned by db_name() is 1:

 if (ascii(substring(db_name(), 1, 1)) & ( power(2, 0))) > 0 waitfor delay '0:0:5' 

By changing the power of 2 (that is, the bit) we're extracting, we can determine all of the bits in the first byte:

 if (ascii(substring(db_name(), 1, 1)) & ( power(2, 1))) > 0 waitfor delay '0:0:5' if (ascii(substring(db_name(), 1, 1)) & ( power(2, 2))) > 0 waitfor delay '0:0:5' if (ascii(substring(db_name(), 1, 1)) & ( power(2, 3))) > 0 waitfor delay '0:0:5' 

and so on. In our example, it turns out that the bits are (in most to least significant order):

 01101101 

which is 0x6d, or 'm'. If we carry on and extract the remaining bytes, we find that db_name() was 'master'.

At first sight, this is not a terribly practical attack; although it provides us with a means of transporting a single bit from a string in the database to the browser, it has an apparent bandwidth of 1 bit per 5 seconds. An important point to realize here, though, is that the channel is random-access rather than sequential; we can request whatever bits we like, in whatever order we choose. We can therefore issue many simultaneous requests to the web application and retrieve multiple bits simultaneously ; we don't have to wait for the first bit before requesting the second. The bandwidth of the channel is therefore limited not by the time delay, but by the number of simultaneous requests that can be made through the web application to the database server; this is typically in the hundreds.

Obviously a harness script is required to submit the hundreds of requests that are needed in an automated fashion. This script would take as input the location of the vulnerable web server and script, the parameters to submit to the script, and the desired query to run. The hundreds of simultaneous web requests are made, and the script reassembles the bits into the string as they are received.

In our tests using real-world web applications, 4 seconds was demonstrated to be an effective time delay (resulting in a bit-error-rate of 1 per 2000), and a query rate of 32 simultaneous queries was sustainable. This results in a transfer rate of approximately 1 byte per second. This may not sound like a lot, but it is more than enough to transport an entire table of passwords or credit card numbers in a couple of hours.

VARBINARY Literal Encoding and Exec

In MS SQL Server, the exec function allows you to execute a dynamically composed query as a SQL statement. For example:

 exec('select @@version') 

Sometimes people implement filters for known SQL statements and constants, such as select, insert, update, delete, xp_cmdshell, and @@version. Exec makes these filters fairly easy to evade, by using queries like this:

 exec('sel'+'ect @'+'@ver'+'sion') 

Or even by encoding the entire string in a VARBINARY literal:

 declare @s varchar(2000) set @s=0x73656C65637420404076657273696F6E exec(@s) 

This is equivalent to select @@version. Obviously if exec itself is filtered, it makes things more difficult. In general, filtering user input on known SQL statements is an exceptionally bad way to address SQL injection. In some cases, people remove the "known bad" keywords, which can be easily evaded by using requests like

 selselectect @@ver@@versionsion 

In other words, embedding the "known bad" content within itself. This generally works, unless the filter is applied until no substitutions could be made.



Database Hacker's Handbook. Defending Database Servers
The Database Hackers Handbook: Defending Database Servers
ISBN: 0764578014
EAN: 2147483647
Year: 2003
Pages: 156

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net