注入SQL
数据库中的信息通过SQL(Structured Query Language,结构化查询语言)访问。SQL可用于读取、更新、增加或删除数据库中保存的信息。
SQL是一种解释型语言,Web应用程序经常建立合并用户提交的数据的SQL语句。因此,如果建立语句的方法不安全,那么应用程序可能易于受到SQL注入攻击。
利用一个基本的漏洞
下面以一个书籍零售商使用的Web应用程序为例,该应用程序允许用户根据作者、书名、出版商等信息搜索产品。完整的书籍目录保存在数据库中,应用程序使用SQL查询、根据用户提交的搜索项获取各种书籍的信息。
当一名用户搜索由Wiley出版的所有书籍时,应用程序执行以下查询:
SELECT author, title, year FROM books WHERE publisher = ‘Wiley’ and published = 1
该查询要求数据库检查书籍表的每一行,提取每条publisher列为wiley值的记录,并返回所有这些记录。然后应用程序处理这组记录,并通过一个HTML页面将结果显示给用户。
在这个查询中,等号左边的词由SQL关键字、表和数据库列名称构成。这个部分的全部内容由程序员在创建应用程序时建立。当然,表达式wiley由用户提交,它是一个数据项。SQL查询中的字符串数据必须包含在单引号内,与查询的其他内容隔开来。
现在思考一下,如果用户搜索所有由O’Reilly出版的书籍,会出现什么情况。应用程序将执行以下查询:
SELECT author, title, year FROM books WHERE publisher = ‘O’Reilly’ and published = 1
在这个示例中,查询解释器以和前一个示例相同的方式到达字符串数据位置。它解析这个包含在单引号中的数据,得到值O。然后遇到表达式Reilly’,这并不是有效的SQL语法,因此,应用程序生成一条错误信息:
Incorrect syntax near ‘Reilly’.
Server: Msg 105,Level 15, State 1, Line1
**Unclosed quotation mark before the character string ‘ **
如果应用程序以这种方式运行,那么它非常容易遭到SQL注入。攻击者可提交包含引号的输入终止它控制的字符串,然后编写任意的SQL修改开发者想要的应用程序执行的查询。例如,在这个示例中,攻击者可以对查询进行修改,通过以下输入项,返回零售商目录中的每一本书籍。
Willey’ OR 1=1–
应用程序将执行以下查询:
SELECT author, title, year, FROM books WHERE publisher = ‘Willey’ OR 1=1 –’ AND published = 1
这个查询对开发者查询中的WHERE子句进行修改,增加了另外一个条件。数据库将检查书籍表的每一行,提取publisher列值位Willey或其中1=1的每一条记录。因为1总是等于1,所以数据库将返回书籍表中的所有记录。
攻击者的输入中的双连字符在SQL中是一个有意义的表达式,它告诉查询解释器该行的其他部分属于注释,应被忽略。在一些SQL注入攻击中,这种技巧机器重要,因为它允许忽略由应用程序开发者建立的查询的剩余部分。在MySQL中,需要在双连字符后加入一个空格,或者使用“#”符号指定注释。
有些时候,可以不使用注释符号处理字符串末尾部分的引号,而用一个需要引号包含的字符串数据结束注入的输入,依次“平衡引号”,例如,输入以下搜索项:
willey’ or ‘a’ = ‘a
将生成以下查询:
SELECT author, title,year FROM books where publisher = ‘willey’ OR ‘a’ = ‘a’ and publisher = 1
这个查询完全有效,可得到和1=1攻击相同的效果。
注入不同的语句类型
当然,当与一个远程应用程序交互时,通常情况下不可能提前知道用户输入的一个特殊数据项将由哪种类型的语句处理。但是,可以根据使用的应用程序功能进行合理地猜测。
SELECT 语句
SELECT语句被用于从数据库中获取信息。它们常用于应用程序响应用户操作而返回信息的功能中,如浏览一个产品目录、查看一名用户的资料或者进行一项搜索。根据数据库中的数据核对用户提交的信息的登录功能也经常使用这种语句。
如在前面的示例中说明的,SQL注入攻击的进入点(entry point)通常是查询中的WHERE子句,它将用户提交的数据传送给数据库,以控制查询结果的范围。因为WHERE子句一般在SELECT语句的最后,攻击者就可以使用注释符号将查询阶段到其输入的结束位置,而不会使整个查询的语法失效。
SQL注入漏洞偶尔也会影响SELECT查询的其他部分,如ORDER BY子句或表和列名称。
INSERT语句
INSERT语句用于在表中建立一个新的数据行。应用程序通常使用这种语句添加一条新的审计日志、创建一个新用户账户或生成一个新订单。
例如,如果一个应用程序允许用户自我注册, 指定它们自己的用户名和密码,就可以使用下面的语句将用户资料插入user表中。
INSERT INTO users (username, password, ID, privs) VALUES (‘daf’, ‘secret’, 2248, 1)
如果username或password字段存在SQL注入漏洞,那么攻击者就可以在表中插入任何数据,包括他自己的ID和privs值。然而,要想这样做,攻击者就必须确保VALUES子句的其他部分正常运行。特别是其中数据项的个数和类型必须正确。例如,当注入username字段时,攻击者可以提交以下输入:
foo’, ‘bar’, 9999, 0)–
它将建立一个ID为9999,privs为0的账户。加入privs字段用来决定账户权限,那么攻击者可以利用它创建的一个管理用户。
有时,攻击者完全盲目地注入一个INSERT语句也能够从应用程序中提取出字符串数据。例如,攻击者可以拦截数据库的版本字符串,并将它插入自己用户资料的一个字段中;正常情况下,浏览器将显示数据库的版本信息。
当设法注入一个INSERT语句时,可能无法提前知道需要提交多少个参数或参数的类型。在前面的示例中,可以通过在VALUES子句中持续增加一个新的字段,知道应用程序创建了确实想要的用户账户,从而解决上述问题。例如,当注入username字段时,可以提交以下输入:
Foo’)–
Foo’, 1)–
Foo’, 1, 1)–
Foo’, 1, 1, 1)–
由于大多数数据库都会隐式地讲一个整数转换为字符串,可以在每个位置都是用一个整数。在这个示例中,不管其他字段如何,它将生成一个用户名为Foo、密码为1的账户。
如果发现使用值1人员遭到拒绝,可以尝试使用值2000,许多数据库也会隐式地将它转换成基于数据的数据类型。
确定注入点之后的正确字段数后,在MS-SQL中,测试员可以任意添加另外一个查询,并采用后面将介绍的基于推断的技巧。
在Oracle中,则可以在insert查询内发布subselect查询。使用后面基于推断的技巧,该subselect查询可能导致主查询成功或失败。
update语句
UPDATE语句用于修改表中的一行或几行数据。它们经常用在用户修改已有数据值的功能中,例如,更新联系信息、修改密码或更改订单数量。
典型UPDATE语句的运行机制和INSERT语句类似,只是UPDATE语句中通常包含一个WHERE子句,告诉数据库更新表中哪些行的数据。例如,当用户修改密码时,应用程序可能会执行以下查询:
UPDATE users SET password=’newsecret’ WHERE user = ‘marcus’ and password = ‘secret’
实际上,这个查询首先合适用户的现有密码是否争取,如果密码无误,就用新的值更新它。如果这项功能存在SQL注入漏洞,那么攻击者就能避开现有密码检查,通过输入以下用户名更新管理员的密码:
admin’–
由于无法提前知道应用程序将根据专门设计的输入执行什么操作,因此,在一个远程应用程序中探查SQL注入漏洞往往非常危险。特别注意,修改UPDATE语句中的WHERE子句可能会使一个重要的数据库表发生彻底的改变。例如,上面的攻击者之前已经提交了以下用户名:
admin’ or 1 = 1
那么应用程序可能会执行以下查询:
UPDATE users SET password = ‘newsecret’ where user = ‘admin’ or 1 = 1
它会重新设置每一名用户的密码。
DELETE语句
DELETE语句用于删除表中的一行或几行数据,例如,用户从他们的购物篮中删除意见商品或从个人资料中删除一个交货地址。
与UPDATE语句一样,DELETE语句通常使用WHERE子句告诉数据库更新表中哪些行的数据,并很可能在这个子句中并入用户提交的数据。破坏正常运行的WHERE子句可能会造成严重的后果。
查明SQL注入漏洞
在最明显的情形中,只需向应用程序提交一个意外输入,就可以发现并最终确定一个SQL注入漏洞。在其他情况下,这种缺陷可能非常微妙,很难与其他类型的漏洞或不会造成安全威胁的“良性”异常区分开来。但是,可以按顺序采取各种步骤查明绝大多数的SQL注入漏洞。
在探查SQL注入漏洞时,一定要确保完全遍历任何可以提交专门设计的输入的多阶段过程。应用程序通常会从几个请求中收集一组数据,一旦收集到全部的数据,就将其保存在数据库中。这时,如果仅在每个请求中提交专门设计的数据并监控应用程序对那个请求的响应,就会遗漏许多SQL注入漏洞。
注入字符串数据
如果SQL查询合并用户提交的数据,它会将这些数据包含在单引号中。为利用任何SQL注入漏洞,攻击者需要摆脱这些引号的束缚。
测试步骤:
- 提交一个单引号作为目标查询的数据。观察是否会造成错误,或结果是否与原始结果不同。如果收到详细错误信息,了解该信息的含义。
- 如果发现错误或其他异常行为,同时提交两个单引号,看会出现什么情况。数据库使用两个单引号作为转义序列,表示一个原义单引号,因此这个序列被解释称引用字符串中的数据,而不是结束字符串的中支付。如果这个输入导致错误或异常行为消失,则应用程序可能易于受SQL注入攻击。
- 为进一步核实漏洞是否存在,可以使用SQL连接符建立一个等同于“良性”输入的字符串。如果应用程序以与处理对应“良性”输入相同的方式处理专门设计的输入,那么它很可能易于受到攻击。每种数据库使用的字符串连接方法各不相同。在易受攻击的应用程序中,可以注入以下示例构建等同于FOO的输入:
- Oracle: ‘||’FOO
- MS-SQL: ‘+’FOO
- MySQL: ‘ ‘FOO [两个单引号之间有一个空格]
注入数字数据
如果SQL查询合并用户提交的数字数据,应用程序仍然会将它包含在单引号之中,作为字符串数据进行处理。因此,一定要执行前面描述的针对字符串数据的渗透测试步骤。但是,许多时候,营养程序会将数字数据以数字格式直接传送到数据库中,并不把它放入单引号中。如果前面描述的测试方法无法检测到漏洞,还可以采取以下针对数字数据的特殊测试步骤。
测试步骤:
尝试输入一个结果等于原始数字值的简单数学表达式。例如,原始值为2,尝试提交1+1或3-1。如果应用程序作出相同的响应,则表示它易于受到攻击。
如果证实被修改的数据会对应用程序的行为造成明显影响,则前面描述的测试方法最为可靠。例如,如果应用程序使用数字化PageID参数指定应返回什么内容,则用1+1代替2得到相同的结果明显表示存在SQL注入。但是如果能够在数字化参数中插入任意输入,但应用程序的行为却没有发生改变,那么前面的方法就无法发现漏洞。
如果第一个测试方法取得成功,就可以利用更复杂的、使用特殊SQL关键字和语法的表达式进一步获得与漏洞有关的证据。ASCII值位65,在SQL中,以下表达式等于2.
67-ASCII(‘A’)
如果单引号被过滤掉,那么前面的测试方法就没有作用。但是,这时可以利用这样一个事实:即在必要时,数据库会隐含地将数字数据转化为字符串数据。例如,因为字符1的ASCII值位49,在SQL中,以下表达式等于2.
51-ASCII(1)
注入查询结构
如果用户提交的数据被插入SQL查询结构。而不是查询的数据项中,这时,实施SQL注入攻击只需要直接应用有效的SQL语法,而不需要任何“转义”。
SQL查询结构中最常见的注入点是ORDER BY子句。ORDER BY关键字接受某个列名称或编号,并根据该列中的值对结果集进行排序。
例如,使用以下查询可以检索一个可排序的图书表:
SELECT author, title, year FROM bookes WHERE publisher = ‘Wiley’ ORDER BY title ASC
如果ORDER BY中的列名称title由用户指定,就没有必要使用单引号,因为用户提交的数据已经直接修改了SQL查询的结构。
在列名称中查找SQL注入漏洞可能会相当困难。如果提交一个并非有效列名称的值,查询将导致错误。这意味着,无论攻击者提交路径遍历字符串、单引号、双引号或其他任意字符串,应用程序都会返回相同的响应。因此,采用程勇的自动模糊测试和手动测试技巧往往会遗漏某些漏洞。如果提交用于探查各种漏洞的标准测试字符串全部导致相同的响应,这本身并不表示出现任何错误。
记下任何可能控制应用程序返回的结果的顺序或其中的字段类型的参数。
提供一系列在参数中提交数值值的请求,从数字1开始,然后逐个请求递增:
如果更改输入中的数字会影响结果的顺序,则说明输入可能被插入到ORDER BY子句中。在SQL中,ORDER BY 1将依据第一列进行排序。然后将这个数字增加到2将更改数据的显示顺序,以依据第二个列进行排序。如果提交的数字大于结果集中的列数,查询将会失败。在这种情况下,可以通过使用以下字符串,检查是否可以颠倒结果的顺序,从而确认是否可以注入其他SQL:
1 ASC –
1 DESC –
如果提交数字1生成一组结果,其中一个列的每一行都包含一个1,则说明输入可能被插入到查询返回的列的名称中。例如:
SELECT 1, title, year FROM book WHERE publisher = ‘Wiley’
“指纹”识别数据库
即使由于某种原因无法提取到版本信息,还是可以使用其他方法识别数据库。一种最可靠的方法是根据数据库连接字符串的不同方式进行识别。在控制某个字符串数据项的查询中,可以在一个请求中提交一个特殊的值,然后测试各种连接方法,以生成那个字符串。如果得到相同的结果,皆可以确定所使用的数据库类型。下面的实例说明常用的数据库如何构建services字符串。
- Oracle:’serv’ || ‘ices’
- MS-SQL: ‘serv’ + ‘ices’
- MySQL: ‘serv’ ‘ices’ 【中间有空格】
如果注入数字数据,则可以使用下面的攻击字符串来识别数据库。每个数据项在目标数据库中的求值结果为0,在其他数据库中则会导致错误。
- Oracle:BITAND(1, 1)-BITAND(1, 1)
- MS-SQL: @@PACK_RECEIVED-@@PACK_RECEIVED
- MySQL: CONNECTION_ID()-CONNECTION_ID()
在识别数据库时,MySQL如何处理某些行内注释也是一个值得关注的问题。吐过一个注释以感叹号开头,接着是数据库版本字符串,那么只要数据库的实际版本等于或高于那个字符串,应用程序就会将注释内容解释为SQL;否则,应用程序就会忽略注释内容,将它作为注释处理。与C中的预处理指令类似,程序员也可以对这一点加以利用,编写出根据所使用数据库版本进行处理的不同代码。攻击者还可以利用它来识别数据库的实际版本。例如,如果使用的MySQL版本高于或等于3.23.02,注入下面的字符串将使SELECT语句的WHERE子句为假:
**/!32302 and 1=0 /
UNION 操作符
SQL使用UNION操作符将两个或几个SELECT语句的记过组合到一个独立结果中。如果一个Web应用程序的SELECT语句存在SQL注入漏洞,通常可以使用UNION操作符执行另一次完全独立的查询,并将它的结果与第一次查询的结果组合在一起。如果应用程序向浏览器返回查询结果,那么就可以使用这种技巧从应用程序中提取任意的数据。
UNION操作符可在SQL注入中发挥非常巨大的作用。但是,在利用它发动攻击之前,攻击者有必要了解它的两个重要限制。
- 如果使用UNION操作符组合两个查询的结果,这两个结果必须结构相同。也就是说他们的列数必须相同,必须使用按相同顺序出现的相同或兼容的数据类型。
- 为注入另一个返回有用结果的查询,攻击者必须知道他所针对的数据库表的名称以及有关列的名称。
使用UNION提取数据
下面我们将分析一个攻击,虽然该攻击针对的是MS-SQL数据库,但它采用的攻击方法适用于所有数据库技术。以用户维护联系人列表及查询和更新联系人信息的通讯录应用程序为例。如果用户在通讯录中搜索名为Matthew的联系人,浏览器将提交以下数据:
Name= Matthew
人名 | 电子邮件地址 |
---|---|
Matthew Adamson | handtrick@gmail.com |
首先,我们需要确定请求的猎术。对单一列进行测试导致了以下错误消息:
Name=Matthew’%20union%20select%20null–
All queries combined using a UNION, INTERSECT or EXCEPT operator must hava an equal number of expressions in their target lists.
我们添加另一个NULL,并得到同样的错误。于是,我们继续添加NULL, 知道查询被执行,并在结果表中生成另一个数据项,如下所示:
Name=Matthew’%20union%20select%20null,null,null,null,null–
人名 | 电子邮件格式 |
---|---|
Matthew Adamson | handtrick@gmail.com |
[空] | [空] |
我们验证查询的第一列是否包含字符串数据:
Name=Matthew’%20union%20select%20’a’,null,null,null,null–
人名 | 电子邮件格式 |
---|---|
Matthew Adamson | handtrick@gmail.com |
[a] | [空] |
接下来,需要查明可能包含有用信息的数据库表和列的名称。为此,我们需要查询元数据表information_schema .columns,其中包含数据库中的所有表和列名称的详细资料。使用以下请求可以检索上述信息:
Name=Matthew’%20union%20select%20table_name,column_name,null,null,null%20from%20information_schema.columns–
人名 | 电子邮件地址 |
---|---|
Matthew Adamson | handytrick@gmail.com |
shop_items | Price |
shop_items | Prodid |
shop_items | Prodname |
addr_book | Contactemail |
addr_book | Contactname |
Users | Username |
Users | Password |
从以上结果可以确定,很明显,我们可以同用户表开始提取数据。这是使用以下查询:
Name=Matthew’%20UNION%20select%20username,password,null,null,null%20from%20users–
人名 | 电子邮件地址 |
---|---|
Matthew Adamson | handytrick@gmail.com |
administrator | fme69 |
dev | uber |
marcus | 8printo |
smith | twosisty |
jlo | 6kdown |
MS-SQL、MySQL和许多其他数据库(包括SQLite和Postgresql)均支持information_schema。它主要用于保存数据库元数据,这也使它成为探查数据库的攻击者的主要目标。需要注意的是,Oracle并不支持该方案。对Oracle数据库实施攻击时,攻击方法在其他各方面可能完全相同。但是,需要使用查询SELECT table_name,column_name FROM all_tab_columns来检索有关数据库表和列的信息。(使用user_tab_columns表以针对当前数据库)通常,在分析大型数据库以探查攻击目标时,最好是查找有用的列名称,而不是表。例如:
SELECT table_name,column_name FROM infotmation_schema.columns where column_name like ‘%PASS%’
如果目标表返回了多个列,则可以将这些列串联到一个单独列中,这样肩锁起来会更加方便,因为,这时只需要在原始查询中确定一个varchar字段:
- Oracle:SELECT table_name||’:’||cikumn_name FROM all_tab_columns
- MS-SQL:SELECT table_name+’:’+column_name from information_schema.columns
- MySQL: SELECT CONTACT(table_name,’:’,column_name) FROM information_schema.columns
避开过滤
有时,易受SQL注入攻击的应用程序可能会执行各种输入过滤以防止攻击者无限制地利用其中存在的缺陷。例如,应用程序可能会删除或净化某些字符,或阻止常用的SQL关键字。这种过滤通常非常容易避开,这时可尝试使用各种技巧。
避免使用被阻止的字符
如果应用程序删除或编码某些在SQL注入攻击中经常用到的字符,不使用这些字符仍然能够实施攻击。
如果要注入数字数据字段或列名称,不一定必须使用单引号。要在攻击有效载荷中插入字符串,不使用引号仍可以做到这一点。这时,可以通过各种字符串函数,使用每个字符的ASCII代码动态构建一个字符串。例如,下面两个查询分别用于Oracle和MS-SQL,它们等同于SELECT ename,sal from emp where ename = ‘marcus’:
SELECT ename, sal FROM emp where ename=CHR(109)||CHR(97)||CHR(114)||CHR(99)||CHR(117)||CHR(115)
如果注释符号被阻止,通常可以设计注入的数据,使其不会破会周围查询的语法。例如,不用注入
‘ or 1=1–
可以注入
‘ or ‘a’=’a
在MS-SQL数据库中注入批量查询时,不必使用分号分隔符。只要纠正所有批量查询的语法,无论你是否使用分号,查询解析器都会正确解释他们。
避免使用简单确认
一些输入确认机制使用一个简答的黑名单,阻止或删除任何出现在这个名单中的数据。在这种情况下,应该尝试使用标准的攻击方法,寻找确认和规范化机制中的常见缺陷。例如,如果SELECT关键字被阻止或删除,可以尝试使用以下输入:
SeleCt
%00SELECT
SELSELECTECT
%52%45%4c%45%43%54
%2553%2545%254c%2545%2543%2554%
使用SQL注释
与C++一样,我们也可以再SQL语句中插入行内注释,注释内容包含在**/与/之间**。如果应用程序阻止或删除输入中空格,可以使用注释“冒充注入数据中的空白符。例如:
*SELECT /*foo*/ username,password FR/foo*/OM users
在MySQL语法中,注释甚至可以插入关键字中,这种方法可避开某些输入确认过了,同时保留查询中的语法。例如:
*SEL/*foo*/ECT /*foo*/ username,password FR/foo*/OM users
利用有缺陷的过滤
输入确认机制通常包含逻辑缺陷,可对这些缺陷加以利用,使被阻止的输入避开过滤。多数情况下,这类攻击会利用应用程序在对多个确认步骤进行排序,或未能以递归方式应用净化逻辑方面的缺陷。
二阶SQL注入
一种特别有益的避开过滤的方法与二阶有关。当数据首次插入数据库中时,许多应用程序能够安全处理这些数据。但是,一旦数据存储在数据库中,随后应用程序本身或其他后端进程可能会以危险的方式处理这些数据。许多这类应用程序并不想面向因特网的主要应用程序一样安全,但却拥有较高权限的数据库账户。
在一些应用程序中,用户输入在到达时通过转义单引号来进行确认。在前面搜索书籍的示例中,这种方法明显有效。当用户输入搜索项O’Reilly时,应用程序执行以下查询:
SELECT author,title,year FROM books WHERE publisher = ‘O’’Reilly’
在这个查询中,用户提交的单引号被转换为两个单引号,因而传送给数据库的搜索项与用户最初的输入的表达式具有相同的字符含义。
与单引号配对方法有关的问题出现在更复杂的情形中,此时同一个数据项被提交给几个SQL查询,然后写入数据库被几次读取。
回到前面那个允许用户自我注册并且在一个INSERT语句中存在SQL注入漏洞的应用程序。假设开发者将修复出现在用户数据中的所有单引号配对导致的漏洞。注册用户名**foo’**来建立如下查询,他不会在数据库中造成问题:
INSERT INTO users (username, password, ID,privs) VALUES(‘foo’’’, ‘secret’, 2248, 1)
目前为止,一切正常。然而,假设应用程序还执行密码修改功能,那么只有通过验证的用户才能访问这项功能,而且为了加强保护,应用程序要求用户提供原始密码。然后应用程序从数据库中提取用户的当前密码,并对两个字符串进行比较,核对用户提供的密码是否正确。要完成核对任务,它首先要从数据库中提取用户的用户名,然后建立如下查询:
SELECT password FROM users WHERE username = ‘foo’’
因为保存在数据库中的用户名是字面量字符串foo’,当应用程序提出访问要求时,数据库即返回这个值;只有在字符串被传送给数据库时才使用配对的转义序列。因此,当应用程序重复使用这个字符串并将它嵌入到另一个查询中时,就会造成一个SQL注入漏洞,用户最初的恶意输入就被嵌入到查询中。当用户尝试修改密码时,应用程序返回以下消息,暴露了上述缺陷:
Unclosed quotation mark before the character string ‘foo
要利用这种漏洞,攻击者值需注册一个包含专门设计的输入用户名,然后尝试修改密码。例如,如果注册如下用户名:
‘ or 1 in (select password from users where username = ‘admin’)–
注册步骤将会被应用程序安全处理。如果攻击者尝试修改密码,他注入的查询就会执行,导致生成以下消息,泄露管理员的密码:
Microsoft OLE DB Provider for ODBC Drivers error ‘80040e07’
Microsoft ODBC SQL Server Driver SQL Server Systax error converting
**the varchar value ‘fme69’ to a column of data type int **
攻击者已经成功避开旨在阻止SQL注入攻击的输入确认,现在他能够在数据库中执行任意查询并获得查询结果。
高级利用
应用程序所有者应该意识到,并非所有攻击都旨在盗窃敏感数据。一些攻击可能更具破坏性,例如,仅仅提交12个字符的输入,攻击者就能够给使用关闭命令(shutdown`)关闭一个MS-SQL数据库。
‘ shutdown–
攻击者还可以注入恶意命令,如下面这些命令可删除一些数据库表:
‘ drop table users–
‘ drop table accounts–
‘ drop table customers–
获取数字数据
如果包含单引号的输入得到正确处理,那么应用程序中的字符串字段就不易受SQL注入攻击。但是,数字数据字段可能仍可能存在漏洞。在这种字段中,用户输入并不包含在单引号中。这时攻击者只有通过应用程序的数值响应,才能获得注入查询的结果。
在这种情况下,攻击者需要做的是获取数字形式的有用数据,对注入查询的结果进行处理。他们可以使用以下两个关键函数:
- ASCII,它返回输入字符的ASCII代码;
- SUBSTRING(或Oracle中的SUBSTR),它返回输入的子字符串。
这些函数可结合在一起使用,以数字形式从一个字符串中提取一个单独字符。例如:
SUBSTRING(‘Admin’, 1, 1)返回A
ASCII(‘A’)返回65
因此
ASCII(SUBSTR(‘Admin’, 1, 1))
使用者两个函数,可以系统地将一个有用数据的字符串分割成单个的字符,并以数字形式分别返回每一个字符。在自定义攻击中,可以利用这种技巧,以一次一个字节的速度,迅速获得并重建大量基于字符串的数据。
使用带外通道
在许多SQL注入攻击中,应用程序并不在用户的浏览器中显示注入查询的结果,也不返回数据库生成的任何错误消息。很明显,在这种情况下,即使一个SQL注入漏洞确实存在,攻击者也无法对其加以利用,提取任意数据或执行任何其他操作。但是,这种想法是错误的,及时出现这种情况,仍然可以使用各种技巧获取数据、确认其他恶意操作是否取得成功。
许多时候,可以注入一个任意查询,但却无法获得查询结果。回到那个易受攻击的登录表单,它的用户名和密码字段易于遭受SQL注入:
SELECT * FROM users WHERE username = ‘marcus’ and password = ‘secret’
除了修改查询逻辑以避开登录外,还可以注入一个完全独立的子查询,使用字符串连接符把这个子查询的结果与控制的数据项连接起来。例如:
foo’ || (SELECT 1 FROM dual WHERE (SELECT username FROM all_users WHERE username=’SBSNMP’) = ‘DBSNMP’)–
应用程序将执行以下查询:
SELECT * FROM users WHERE username = ‘foo’ || (SELECT 1 FROM dual WHERE (SELECT username FROM all_users WHERE username = ‘DBSNMP’) = ‘DBSNMP’)
数据库将执行注入的任何子查询,并将它的结果附加在foo之后,然后查找所生成用户名的资料。当然,这种登录不会成功,但会执行注入的查询。在应用程序响应中受到的只是标准的登录失败消息。现在需要想办法获得注入查询的结果。
如果能对MS-SQL数据库使用批量查询,这时就会出现另一种情形。批量查询特别有用,因为它们允许执行一个完全独立的语句,在这个过程中,渗透测试员拥有全部的控制权,可以使用另外的SQL语句并针对不同的表进行查询。但是,因为批量查询执行查询的方式比较特殊,我们无法直接获得注入查询的执行结果,同样需要想办法获得注入查询的结果。
在这种情况下,一种获取数据的有效方法是使用带外通道。能够在数据库中执行任意SQL语句后,渗透测试员往往可以利用数据库的一些内置功能在护具库与自己的计算机之间建立网络连接,通过它传送从数据库中收集到的任何数据。
建立适当网络连接的方法以不同的数据库而定,而且取决于应用程序访问数据库所使用的的用户权限。
扩大数据库攻击范围
成功利用一个SQL注入漏洞往往可完全控制应用程序的所有数据。大多数应用程序仅使用一个账户访问数据库,并且依赖应用程序层控制在不同的用户间实施访问隔离。如果能够无限制地使用应用程序的数据库账户,就可以自由访问其中的数据。
因此,可以假设,拥有应用程序的所有数据时SQL注入攻击的最终目的。然而,许多原因表名,利用数据库中的漏洞,或者控制它的一些内置功能以达到目的,从而进一步实施攻击,可能会取得更大的功效。通过扩大数据库攻击范围可实施的其他攻击如下:
- 如果数据库被其他引用程序共享,可以通过提升数据库的使用权限访问其他应用程序的数据。
- 可以攻破数据库服务器的操作系统。
- 可以访问其他系统。通常,数据库服务器是一个在基层网络边界防御保护下的网络中的主机。如果能够控制数据库服务器,攻击者就处在一个可信的位置上,可以访问其他主机的关键服务,进一步对其加以利用。
- 可以在主机基础架构与自己的计算机之间建立网络连接。这样,攻击者就可以完全避开应用程序的防御,轻易传送从数据库收集到的大量敏感数据,并且可穿透许多入侵检测系统。
- 可以通过创建用户定义的功能任意扩充数据库的现有功能。有些时候,可以通过这种方式重新执行已被删除或禁用的功能,避开数据库实施的强化保护措施,只要已经获得数据库管理员权限,就有办法在每种主流数据库中执行这种操作。
防止SQL注入
部分有效的防御措施
由于单引号在SQL注入漏洞中占有突出地位,繁育这种攻击的一种常用方法,就是将用户输入的任何单引号配对,对它们进行转义。但是,在下面两种情况下,这种方法无效。
- 如果用户提交的数字数据内置在SQL查询中,这种数据通常并不包含在单引号内。因此,攻击者能够破坏数据的使用环境并开始输入任意SQL查询,这时就不必输入单引号。
- 在二阶SQL注入攻击中,最初在插入数据库中时已经安全转移的数据随后被从数据库中读取出来,然后又再次写入。当重新使用数据时,最初配对的引号又恢复到单引号形式。
另一种常用的应对措施是使用存储过程完成全部数据库访问。无疑,定制的存储过程可增强安全性,提高性能;然而,由于两方面的原因,它们并不能保证防止SQL漏洞:
- 编写存在缺陷的存储过程可能在自身代码中包含SQL注入漏洞。
- 及时使用安全可靠的存储过程,但如果使用用户提交的输入以不安全的方式调用这个存储过程,也仍然可能出现SQL注入漏洞。
参数化查询
大多数数据库和应用程序开发平台都提供API,对不可信的输入进行安全处理,以防止SQL注入漏洞。参数化查询分两个步骤建立一个包含用户输入的SQL语句。
- 应用程序制定查询结构,为用户输入的每个数据预留字符;
- 应用程序制定每个占位符的内容;
至关重要的是,在第二个步骤中制定的专门设计的数据无法破坏在第一个步骤中制定的查询结构。因为查询结构已经确定,且相关API对所有类型的占位符数据进行安全处理,因此它总被解释为数据,而不是语句结构的一部分。
使用参数化查询可有效防止SQL注入,但还要注意一下几个重要的限制。
- 应在每一个数据库查询中使用参数化查询。
- 插入查询中的每一种数据都应适当进行参数化。
- 参数占位符不能用于指定查询中的表和列的名称。
- 参数占位符不能用于查询的任何其他部分,如Order by子句中的ASC或DESC关键字,或其他任何SQL关键字,因为它们属于查询结构的一部分。
深层防御
通常,一种稳定的安全机制应采用深层防御措施提供额外的保护,以防止前端防御由于任何原因失效。当防御针对后端数据库的攻击时,应采用另外三层防御。
- 当访问数据库时,应用程序应尽可能使用最低权限的账户。
- 许多企业数据库包含大量默认功能,可被能够执行任意SQL语句的攻击者利用。
- 应评估、测试并及时安装供应商发布的所有安全补丁,以修复数据库软件本身已知的漏洞。