Web安全(8)

数据存储区

  • 几乎所有应用程序都依赖数据存储区来管理在应用程序中处理的数据。在许多情况下,这些数据负责处理核心应用程序逻辑、保存用户账户、权限、应用程序配置设置等。大多数数据存储区都保存有结构化、可以使用预先定义的查询格式或语言访问的数据,并包含内部逻辑来管理这些数据。
  • 通常,应用程序使用常用的权限级别来管理对数据存储区的各种访问操作,以及处理属于不同应用程序用户的数据。如果攻击者能够破坏应用程序与数据存储区的交互,使应用程序检索或修改各种数据,攻击者就可以避开在应用层次对数据访问实施的任何控制
  • 迄今为止最为实用的数据存储区是SQL数据库,基于XML的资料库、LDAP目录,以及常见的一些示例。

注入解释型语言

解释型语言(interpreted language):是一种在运行时由一个运行时组件(runtime component)解释语言代码并执行其中包含的指令的语言。
编译型语言(compiled language):它的代码在生成时转换成机器指令,然后在运行时直接使用该语言的计算机处理器执行这些指令

  • 开发Web应用程序使用的许多核心语言使用解释器执行,包括SQL/LDAP/Perl/PHP
  • 基于解释型语言的执行方式,产生了一系列叫做代码注入(code injection)的漏洞。任何有实际用途的应用程序都会收到用户提交的数据,对其进行处理并执行相应的操作。因此,有解释器处理的数据实际上是由程序员编写的代码和用户提交的数据共同组成的。有些时候,攻击者可以提交专门设计的输入,通常提交某个在应用程序中使用解释型语言语法的具有特殊意义的句法,向应用程序实施攻击。结果,这个输入的一部分被解释为程序指令执行,好像它们是由最初开发的程序员写的一样。因此,如果这种攻击取得成功,它将完全攻破目标应用程序的组件
  • 另一方面,在编译型语言中实施旨在执行任意命令的攻击往往非常困难。这时,注入代码的方法通常不利用开发目标程序所使用语言的任何语法特性,注入的有效载荷为机器代码,而不是用那种语言编写的指令。

避开登录

无论访问操作是由普通用户还是应用程序管理员触发,应用程序访问数据存储区的过程都大致相同。Web应用程序对数据存储区采用自主访问控制机制,构造查询基于用户的账户和类型来检索、添加或修改数据存储区的数据。修改查询(不只是查询中的数据)的成功注入攻击可以避开应用程序的自主访问控制并获取未授权访问。

如果需要安全保护的应用程序逻辑由查询结果控制,攻击者就可以通过修改查询来更改应用程序的逻辑。一个例子,在后端数据存储区的数据表中查询与用户提供的证书匹配的记录。许多实施基于表单的登录功能的应用程序使用数据库来存储用户证书,并执行简单的SQL查询来确认每次登录尝试。以下是一个典型的示例:

管理员用户名已知

SELECT * FROM users WHERE username = ‘alice’ and password = ‘secret’
这个查询要求数据库检查用户表中的每一行,提取出每条username列值为alice、password列值为’secret’的记录。如果应用程序收到一名用户的资料,登录尝试将取得成功,应用程序将为该用户创建一个会话。
在这种情况下,攻击者可以注入用户名或密码字段,以修改应用程序执行的查询,从而破坏它的逻辑。例如,如果攻击者知道应用层程序管理员的用户名为admin,那么他就可以通过提交以下用户名和任意密码,以管理员的身份登录:
admin’–
应用程序将执行下列查询:
SELECT * FROM users WHERE username = ‘admin’–’ AND password = ‘foo’
因为使用了注释符号(–),上面的查询等同于:
SELECT * FROM users WHERE username=’admin’
于是这个查询完全避开了密码检查

管理员用户名未知

在大多数应用程序中,数据库第一个账户为管理员,因为这个账户通常手工创建,然后通过它生成其他应用程序账户。而且,如果查询返回几名用户的资料,许多应用程序只会处理第一名用户。因此,攻击者可以利用这种行为,通过提交以下用户名,以数据库中的第一个用户的身份登录:
‘ OR 1=1–
应用程序将执行下列查询
SELECT * FROM users WHERE username = ‘’ OR 1=1–’ AND password = ‘foo’
因为其中用了注释符号,上面的查询等同于
SELECT * FROM users WHERE username = ‘’ OR 1=1
该查询将返回全部应用程序用户的资料

注入SQL

几乎每一个Web应用程序都使用数据库来保存操作所需的各种信息。数据库中的信息通过SQL(Structured Query Language,结构化查询语句)访问。SQL可用于读取、更新、增加或删除数据库中保存的信息。

  • SQL是一种解释型语言,Web应用程序经常建立合并用户提交的数据的SQL语句。因此,如果建立语句的方法不安全,那么应用程序可能易于受到SQL注入攻击。在最严重的情况下,匿名攻击者可利用SQL注入读取并修改数据库中保存的所有数据,甚至完全控制运行数据库的服务器
  • 许多应用程序以API来避免SQL注入,如果使用得当,这些API能够有效阻止SQL注入攻击,在这种情况下,通常只有在无法应用这些防御机制时,SQL注入才会发生。
  • 虽然对于绝大多数数据库而言,SQL注入的基本原理大致相似,但它们之间存在许多差异,包括语法上的细微变化以及可能影响攻击者所使用的攻击类型的巨大行为与功能差异

注:从目标应用程序收到的响应一般并不完整或者含义模糊,需要猜测才能理解,这个时候,访问和目标应用程序所使用的数据库相同的、在本地安装的数据库会帮助理解,如果这个方法不可行,最好找一个可以进行测试的适当交互式在线环境,如SQLzoo.net中的交互式在线教程

利用一个基本的漏洞

  1. 下面是一条书籍零售商Web应用程序的查询语句
    SELECT author,title,year FROM books WHERE publisher = ‘Wiley’ and published=1
    该查询语句要求数据库检查书籍表的第一行,提取每条publisher列为Wiley值的记录,并返回所有记录,然后应用程序处理这组记录,并通过一个HTML页面将结果显示给用户。

注:SQL查询中的字符串必须包含在单引号内,与查询的其他内容分隔开

  1. 如果查询所有由O’Reilly出版的书籍,则使用下列查询语句:
    SELECT author,title,year FROM books WHERE publisher = ‘O’Reilly’ and publisher=1
    在上述示例中,查询解释器以和第一个示例相同的方式到达字符串数据位置。它解析包含在单引号中的数据,得到值O。然后遇到表达式Reilly’,这并不是有效的SQL语法,因此应用程序会生成一条错误信息。

  2. 如果应用程序以这种方式运行,那么它非常容易遭受SQL注入。攻击者可提交包含引号的输入终止他控制的字符串,然后编写任意的SQL修改开发者想要应用程序执行的查询。例如,在这个示例中,可输入以下搜索项:
    Wiley’ OR 1=1–
    应用程序将执行以下查询:
    SELECT author,title,year FROM books WHERE publisher = ‘Wiley’ OR 1=1–’ and publisher=1
    此种做法不会引起2中的错误,原因在下一条进行解释。数据库将检查书籍表中的每一行,提取publisher列值为Wiley或其中1等于1的每条记录,因为1总是等于1,所以数据库将返回书籍表中的所有记录。

  3. 攻击者的输入中的双连字符串在SQL中是一个有意义的表达式,它告诉查询解释器该行的其他部分属于注释,应被忽略。在上面的示例中,应用程序将用户提交的字符串包含在单引号内,因为攻击者已经终止他控制的字符串并注入其他一些SQL,他需要处理字符串末尾的单引号,避免出现2中相同的语法错误。攻击者在3中添加一个双连字符达到这一目的,将查询的剩余部分以注释处理。在MySQL中,需要在双连字符后加入一个空格,或者使用”#”符号指定注释。在1中的原始查询中还将访问权限仅限于已出版的书籍,因为它指定and published = 1,通过注入注释序列,攻击者获得未授权访问权限,可以返回所有书籍(包括已出版的其他书籍)的详细信息。

注:有时,可以不使用注释符号处理字符串末尾部分的引号,而用一个需要引号包含的字符串数据结束注入的输入,以此“平衡引号”。例如,输入以下搜索项: Wiley’ OR ‘a’=’a 将生成以下查询:SELECT author,title,year FROM books WHERE publisher=’Wiley’ OR ‘a’=’a’。这个查询完全有效,可以得到和1=1攻击相同的效果

注入不同的语句类型

任何类型的SQL语句都可能存在SQL缺陷。当与一个远程应用程序交互时,通常情况下不可能提前知道用户输入的一个特殊数据项将有哪种类型的语句处理。但是,可以根据应用程序进行合理猜测,下面是最常用的SQL语句及其用法。

SELECT语句

  • SELECT语句被用于从数据库中获取信息。它们常用于应用程序响应用户操作而返回信息的功能中,如浏览一个产品目录、查看一名用户的资料或进行一项搜索。根据数据库中的数据核对用户提交的信息的登录功能也常用这种语句。
  • 正如在前面所说的,SQL注入攻击的进入点(entry point)通常是查询中的WHERE子句,它将用户提交的数据传送给数据库,以限制查询结果的查询范围。因为WHERE子句一般在SELECT语句的最后,攻击者就可以使用注释符号将查询截短到其输入的结束位置,而不会使整个查询语句失效。
  • SQL注入漏洞偶尔也会影响SELECT查询的其他部分,如ORDER BY子句或列和表的名称

INSERT语句

  • INSERT语句用于在表中建立一个新的数据行。应用程序通常使用这种语句添加一条新的审计日志、创建一个新用户账户或生成一个新订单。例如,如果一个应用程序允许用户自我注册,指定他们自己的用户名和密码,就可以使用下面的语句将用户的资料插入users的表中。

    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,许多数据库也会隐式地将它转换成基于数据的数据类型。
在Qracle中,则可以在insert查询内发布subselect查询。

UPDATES语句

UPDATE语句用于修改表中的一行或几行数据。它们经常用在用户修改已有数据值的功能中,例如,更新联系信息、修改密码或更改订单数量。
典型UPDATES语句的运行机制与INSERT语句类似,只是UPDATES语句中通常包含一个WHERE子句,告诉数据库更新表中哪些行的数据。例如,用户在修改密码时,应用程序会执行以下查询:

UPDATE users SET password='newsecret' WHERE user='alice' AND password = 'secret'

实际上,这个查询首先核实用户的密码是否正确,如果密码无误,就用新的值更新它。如果这项功能存在SQL注入漏洞,那么攻击者就能避开密码检查,通过以下用户名更新管理员密码

admin'--

注:由于无法提前知道应用程序将根据专门设计的输入执行什么操作,因此,在一个远程应用程序中探查SQL漏洞往往非常危险。特别注意,修改UPDATES语句中的WHERE子句可能会使一个重要的数据库表发生彻底的改变。例如,如果上面的攻击者之前已经提交了以下用户名:admin’ OR 1=1– 那么应用程序可能会执行以下查询: UPDATES users SET password=’newsecret’ WHERE user=’admin’ OR 1=1 它会重置每个用户的密码!
即使攻击的应用程序功能并不会更新任何现有数据,有时候,在用户登录成功后,应用程序会使用用户提交的用户名执行各种UPDATES查询,这意味着任何针对WHERE子句的攻击都可能会“复制”到其他子句中,给所有应用程序用户的资料造成严重破坏。强烈建议在测试之前对数据库进行完整备份。

DELETE语句

DELETE语句用于删除表中一行或几行数据,例如用户从他们的购物车中删除一件商品或从个人资料中删除一个交货地址。与UPDATES语句一样,DELETE语句通常使用WHERE子句告诉数据库更新表中哪些行的数据,并很可能在这个子句中并入用户提交的数据。破坏正常运行的WHERE子句可能会造成严重的后果,UPDATES语句部分提出的警告同样适用于这种攻击。

查明SQL漏洞

在最明显的情况下,只需向应用程序提交一个意外输入,就可以发现并最终确定一个SQL注入漏洞。但在其他情况下,它很难与其他的漏洞或不会造成安全威胁的“良性”异常区分开来,但是,可以通过各种步骤准确查明绝大多数的SQL注入漏洞。

注入字符串数据

如果SQL查询合并用户提交的数据,它会将这些数据包含在单引号里,为利用任何SQL注入漏洞,攻击者需要摆脱这些引号的束缚。

注入数字数据

如果SQL查询合并用户提交的数字数据,应用程序仍然会将它包含在单引号之中,作为字符串数据进行处理。因此,一定要执行前面针对字符串数据的渗透测试步骤。但是,许多时候,应用程序会将数字数据以数字格式直接传送到数据库中,并不把它放入单引号中。

注:在探查应用程序是否存在SQL注入之类的缺陷时,我们常常会忘记某些字符在HTTP请求中具有特殊含义。如果希望在攻击有效载荷中插入这些字符,必须谨慎对它们进行URL编码,特别是以下字符:

  1. &和=用于连接名/值对,建立查询字符串和POST数据块。应当使用%26与%3d对它们进行编码
  2. 查询字符串中不允许使用空格,如果在其中提交空格,整个字符串会终止,必须使用+或%20对其进行编码
  3. 由于+用于编码空格,如果想在字符串中使用+,必须使用%2b对其进行编码。因此,1+1应以1%2b1的形式提交
  4. 分号用于分隔cookie字段,必须使用%3b对其编码。
    无论是通过拦截代理服务器直接从浏览器中编辑参数值,或是使用其他方法进行编辑,都必须使用这些编码方式。如果没有进行编码,那么整个请求可能会无效,或提交预期外的数据。

注入查询结构

  • 如果用户提交的数据被插入SQL查询结构,而不是查询中的数据项中,这时实施SQL注入攻击只需要直接应用有效的SQL语法,而不要进行任何转义。
  • SQL查询结构最常见的注入点是ORDER BY子句。ORDER BY关键字接受某个列名称或编号,并根据该列中的值对结果集进行排序,用户经常使用这种功能对浏览器中的表进行排序。例如,使用以下查询可以检索一个可排序的图书表:
    SELECT author,title,year FROM books WHERE publisher = 'Wiley' ORDER BY title ASC
    如果ORDER BY中的列名称title由用户指定,就没有必要使用单引号,因为用户提交的数据已经直接修改了SQL查询的结构

注:在ORDER BY子句中实施SQL注入时,数据库不会接受查询中的UNION/WHERE/OR/AND关键字。通常,实施注入攻击需要攻击者指定一个嵌套查询来替代参数,如用(select 1 where <> or 1/0=0)替代列名称。对于支持批量查询的数据库,这可能是最有效的注入攻击方法

“指纹”识别数据库

要了解所针对的数据库的类型,一种最可靠的方法是根据数据库连接字符串的不同方式进行识别。在控制某个字符串数据项查询时,可以在一个请求中提交一个特殊的值,然后测试各种连接方法,以生成那个字符串。如果得到相同的结果,就可以确定所使用的数据库类型。下面的示例说明常用的数据库如何构建Service字符串

Oracle: 'serv'||'ices'
MS-SQL: 'serv'+'ices'
MySQL: 'serv' 'ices'(中间有间隔)

如果注入数字数据,则可以使用下面的攻击字符串来识别数据库。每个数据项会在目标数据库中的求值结果为0,在其他数据库中则会导致错误。

Oracle: BITAND(1,1)-BITAND(1,1)
MS-SQL: @@PACK_RECEVIED-@@PACK_RECEVIED
MySQL: CONNECTION_ID()-CONNECTION_ID

在识别数据库时,MySQL如何处理某些行内注释(inline comment)也是一个值得关注的问题。如果一个注释以!开头,接着是数据库版本字符串,那么只要数据库的实际版本等于或高于那个字符串,应用程序就会将注释内容解释为SQL;否则,应用程序就会忽略注释内容,将它作为注释处理。例如,如果使用的MySQL版本高于或等于3.23.02,注入下面的字符串将会使SELECT语句的WHERE子句为假

/*!32302 and 1=0*/

注:MS-SQL和Sybase数据库起源相同,绝大多数针对MS-SQL的攻击技巧同样适用于Sybase

UNION操作符

SQL使用UNION连接符将两个及以上的SELECT语句组合到独立的一个结果中。如果一个Web应用程序的SELECT语句存在SQL注入漏洞,通常可以使用UNION操作符执行另一次完全独立的查询,并将它的结果与第一次查询的结果组合在一起。如果应用程序向浏览器返回查询结果,那么就可以使用这种技巧从应用程序中提取任意数据。所有的主流DBMS产品者支持UNION,对于直接返回查询结果的情况,UNION是检索信息最快的方式
如下查询语句:

SELECT author,title,year FROM books WHERE publisher='Wiley'

当对此语句注入以下语句时,能够从另一个完全不同的数据库表中提取数据:

Wiley' UNION SELECT username,password,uid FROM users--

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

SELECT author,title,year FROM books WHERE publisher='Wiley' UNION SELECT username,password,uid FROM users--'

两个重要的UNION工作机制

  • 如果使用UNION操作符组合两个查询的结果,这两个结果必须结构相同,也就是说它们的列数必须相同,必须使用按相同顺序出现的相同或兼容的数据类型
  • 为注入另一个返回有用结果的查询,攻击者必须知道他所针对的数据库表的名称以及有关列的名称。

如果攻击者试图注入另一个列内数据类型不兼容的查询,会产生错误。

推测查询表结构

在许多现实例子中,数据库返回的错误消息将被应用程序截获,并不显示在用户的浏览器上,因此,如果想要查明第一个查询的结构,也许只能纯粹靠猜测,但是可以利用以下三点帮助简化这项任务

  • 为时注入的查询能够与第一个查询结合,它不一定要使用完全相同的数据类型。但是,它们必须互相兼容,也就是说,第二个查询中的每种数据类型要么必须与第一个查询中的对应类型完全相同,要么必须隐含第转换到那个类型。数据库会将一个数字值隐含地转换为一个字符串值。实际上,NULL可以被转换为任何数据类型,如果不知道某个特殊字段的数据类型,只需在那个字段输入“SELECT NULL”
  • 如果数据库返回的错误信息被应用程序截获,还是可以轻易确定注入的查询是否得以执行,因此,如果查询已经执行,那么应用程序第一个查询返回的结果后面会增加其他结果。因此据此进行系统的推测,直到查明需要注入的查询结构
  • 许多时候,只需在第一个查询中确定一个使用字符串数据类型的字段就能达到自己的目的。这足以允许注入任意返回字符串数据的查询并获得其结果,帮助系统性地从数据库中提取出任何想要的数据

注:在Oracle数据库中,每个SELECT语句必须包含一个FROM属性,因此,无论列数是否正确,注入UNION SELECT NULL将产生错误,可以选择使用全局可访问表(globally accessible table)DUAL来满足这一要求。例如:

' UNION SELECT NULL FROM DUAL--

提取数据库版本信息

如果已经确定注入的查询所需列数,并且已经找到一个使用字符串数据类型的列,就能够提取出任意数据。一个简单的概念验证测试是提取数据库的版本字符串数据,可以在MS-SQL和MySQL中注入以下查询提取数据库版本

' UNION SELECT @@version,NULL,NULL--

对Oracle注入以下查询将得到相同结果:

' UNION SELECT banner,NULL,NULL FROM v$version--

提取有用数据

为了从数据库提取有用的数据,通常需要了解表以及包含欲访问的数据所属列的名称。大型企业的DBMS中包含大量的数据数据库元数据,可以查询这些数据查明数据库中每一个表和列的名称。

避开过滤

有时,易受SQL注入攻击的应用程序可能会执行各种输入过滤以防止攻击者无限制第利用其中缺陷。例如,应用程序会删除或净化某些字符,或阻止常用的SQL关键字。避开这种过滤有如下技巧:

避免使用被阻止的字符

如果应用程序删除或编码某些SQL注入攻击常用的字符,不使用这些字符仍然能够实施攻击。

  • 如果要注入数字数据字段或列名称,不一定使用单引号。可以通过各种字符串函数,使用每个字符的ASCII代码动态构建一个字符串。如下面两个查询语句:
    Oracle: SELECT ename,sal FROM emp WHERE ename=CHR(109)||CHR(97)||CHR(114)||CHR(99)||CHR(117)||CHR(115)
    MS-SQL: SELECT ename,sal FROM emp WHERE ename=CHAR(109)+CHAR(97)+CHAR(114)+CHAR(99)+CHAR(117)+CHAR(115)
    以上查询均等同于
    SELECT ename,sal FROM emp WHERE ename='marcus' 
  • 如果注释符号被阻止,通常可以设计注入的数据,失去不会破坏周围查询的语法
    可以不用注入 ' OR 1=1--
    可以注入     ' OR 'a'='a
  • 在MS-SQL数据库中注入批量查询时,不必使用分号分隔符,只要纠正所有批量查询的语法,无论你是否使用分号,查询解释器都会正确解释它们。

避免使用简单确认

一些输入确认机制使用一个简单的黑名单,阻止或删除任何出现在这个名单中的数据。例如,如果SELECT关键字被阻止或删除,可以尝试如下输入:

SeLeCt
%00SELECT
SELSELECTECT
%53%45%4C%45%43%54
%2553%2545%254c%2545%2543%2554

使用SQL注释

在SQL语句中插入行内注释,注释内容在“/* * /”之间。如果应用程序阻止或删除输入中的空格,可以使用注释“冒充”注入数据之间的空白符,如:

SELECT/*foo*/username,password/*foo*/FROM/*foo*/users

在MySQL中,注释甚至可以插入关键字,这种方法可以避开某些输入确认过滤,同时保留查询中的语法。例如:

SEL/*foo*/ECT username,password FR/*foo*/OM users

利用有缺陷的过滤

利用输入确认机制通常包含逻辑缺陷,可对这些缺陷加以利用,使被阻止的输入避开过滤。多数情况下,这类攻击会利用应用程序在对多个确认步骤进行排序,或未能以递归方式应用净化逻辑方面的缺陷。

二阶SQL注入

一种特别有效的避开过滤的方法与二阶SQL注入有关。当数据首次插入数据库时,许多应用程序能够安全处理这些数据。但是,数据一旦存储在数据库中,随后应用程序本身或其他后端进程可能以危险的方式处理这些数据。
在一些应用程序中,用户输入在到达时通过转义单引号来进行确认。如,当用户输入O’Reilly时,应用程序执行以下查询:

SELECT author,title,year FROM books WHERE publisher='O''Reilly'

在这个查询中,用户提交的单引号被转换为两个单引号,因而传送给数据库的搜索项与用户最初输入的表达式具有相同的字符含义。只有在字符串被传送给数据库时才会使用配对的转义序列。因此,当应用程序重复使用这个字符串并将它嵌入另一个查询时,就会造成一个SQL注入漏洞

高级利用

如今,即使遇到SQL注入漏洞,攻击者仍然无法直接获取注入的查询的结果。下面将对此种情形进行讨论

获取数字数据

如果包含单引号的输入得到正确处理,那么应用程序中的字符串字段就不易受到SQL注入攻击。但是,数字数据字段可能仍然存在漏洞。在这种字段中,用户输入并不包含在单引号内。这时攻击者只有通过应用程序的数值响应(numeric response)才能获得注入查询的结果
在这种情况下,攻击者需要做的就是获取数字形式的有用数据,对注入查询的结果进行处理。他们可以使用以下两个关键函数:

  • ASCII,它返回输入字符的ASCII码
  • SUBSTRING(或Oracle中的SUBSTR),它返回输入的子字符串
    这些函数可结合在一起使用,以数字形式从一个字符串中提取一个字符。如:
    SUBSTRING('Admin',1,1)返回A
    ASCII('A')返回65
    ASCII(SUBSTRING('Admin',1,1))返回65
    使用这两个函数,可以系统地将一个有用数据的字符串分割成单个字符,并以数字的形式分别返回每一个字符。在自定义攻击中,可以利用这个技巧,以一次一个字节的速度,迅速获得并重建大量基于字符串的数据

使用带外通道

在许多SQL注入攻击中,应用程序并不在用户的浏览器中显示注入查询的结果,也不返回数据库生成的任何错误信息。但是,即使出现这种情况,仍然可以使用使用各种技巧获取数据、确认其他恶意操作是否取得成功。
在这种情况下,一种获取数据的有效方法是使用带外通道。能够在数据库中执行任意SQL语句后,渗透测试员往往可以利用数据库的一些内置功能在数据库和自己的计算机之间建立网络连接,通过它传送从数据库中收集到的任何数据。
建立适当的网络连接的方法依不同的数据库而定,而且取决于应用程序访问数据库所用的用户权限。下面将描述一些使用每种数据库时最常用、最有效的技巧。

Oracle

Oracle包含大量低权限用户可访问的默认功能,可以使用它们建立带外连接。

  • UTL_HTTP包可用于向其他主机提出HTTP请求。如果攻击者已经攻破一个企业内部网络中的数据库,他就能利用企业代理服务器与因特网建立外部连接
  • UTL_INADDR包旨在将主机名解析为IP地址。它可以利用于在攻击者控制的服务器中生成任意的DNS查询
  • UTL_SMTP包可用于发送电子邮件。在出站电子邮件中发送这个包,即可获得大量从数据库中截取的数据
  • UTL_TCP包可用于打开任意TCP套接字,以发送和接收网络数据。
MySQL

SELECT…INTO OUTFILE命令可将任意一个查询的输出指向一个文件。指定的文件名可包含UNC(Universal Naming Convention,通用命名规则)路径,允许将输出指向自己计算机上的一个文件。
要想接收到文件,必须在计算机上建立SMB共享,允许匿名写入访问

利用操作系统

通常可以在数据库服务器的操作系统上执行任意命令,以此实施权限提升攻击。

使用推论:条件式响应

造成带外通道不可用的原因很多,大多数情况下,是因为数据库处于一个被保护的网络中,它的边界防火墙禁止任何与因特网或其他网络的带外连接。这时,只能通过Web应用程序注入点(injection point)访问数据库。这些技巧全都基于如下概念:使用一个注入查询有条件地在数据库中触发某种可以探测的行为,然后根据这种行为是否发生推断出以下信息

引发条件性错误

核心理念:注入一个查询,依照某个特定的条件引发一个数据库错误。如果发生数据库错误,可以通过HTTP500响应码,或者通过某种错误信息或反常行为,从外部探测到这个错误
这种技巧利用了数据库在求条件语句的值时表现出的一个行为特点:数据库将根据其他部分的情况,仅对那些需要求值的语句部分求值。包含WHERE子句的SELECT语句就是表现出这种行为的一个典型示例:

SELECT x FROM y WHERE c

这条语句时数据库访问表y的每一行,评估条件c。如果c为真,返回x。如果条件c永为假,永远求不出表达式x的值,也不会发生错误。因此,可以通过是否发生错误测试任意一个条件c。下面的查询检查虚构用户AAA是否存在:

SELECT 1/0 FROM WHERE (SELECT username FROM all_users WHERE username='AAA')

如果WHERE的条件username=’AAA’永为假,就不会求表达式1/0的值,因而不会发生错误。如果为真,就会对表达式1/0求值,进而产生错误。
这种技巧应用非常广泛,因为它可以用在可以注入子查询的各种注入点中。

使用时间延迟

攻击者可以提交他设计的查询,然后监控服务器做出响应所花的时间。如果发生延迟,攻击者可以推测条件为真。即使在两种情况下服务器的响应完全相同,攻击者仍然可以根据是否存在时间延迟从数据库中提取一比特数据。通过大量执行这类查询,攻击者就能系统地从数据库中提取任何复杂的数据,每次一比特。

在MS-SQL中:

  1. 使用内置命令:引发适当时间延迟方法的精确性取决于所使用的目标数据库。MS-SQL中包含一个内置命令WAITFOR,可用于引起一个指定的时间延迟。如,如果下面的查询引发一次时间延迟,表示被截取的字符串的第一个字母为A
    if ASCII(SUBSTRING('Admin',1,1)) = 65 WAITFOR dalay '0:0:5'
  2. 划分数据:将每个字节的数据划分成比特,并在每次查询中获得一比特数据。POWER命令与按位“与”运算符&可在逐比特的基础上指定条件。例如,以下查询测试被截获数据的第一字节的第一比特,如果其值为1,则终止查询:
    if ASCII(SUBSTRING('Admin',1,1)) & (POWER(2,0)) > 0 WAITFOR delay '0:0:5' //POWER(2,0)=2^0,因为一比特是一个二进制位,所以使用2
    下面对第二比特进行相同的测试:
    if ASCII(SUBSTRING('Admin',1,1)) & (POWER(2,1)) > 0 WAITFOR delay '0:0:5'

MySQL:使用睡眠函数
Oracle:使用UTL_HTTP连接一个不存在的服务器,造成一次操作超时。这会使数据库尝试与指定的服务器建立连接,并最终造成超时。

SQL注入之外:扩大数据库攻击范围

通过扩大数据库攻击范围可以实施的其他攻击如下:

  • 如果数据库被其他应用程序共享,可以通过提升数据库的使用访问权限访问其他应用程序的数据
  • 可以攻破数据库服务器的操作系统
  • 可以访问其他系统。通常,数据库服务器是在一个几层网络边界防御保护下的网络中的主机。如果能够控制数据库服务器,攻击者就会在一个可信的位置上,可以访问其他主机上的关键服务,并对其加以利用。
  • 可以在主机基础架构与自己的计算机之间建立网络连接。
  • 可以通过创建用户定义的功能任意扩充数据库的现有功能。有些时候,可以通过这种方式重新执行已被删除或禁用的功能,避开数据库实施的强制保护措施。只要已经获得数据库管理员(DBA)的权限,就有办法在每种数据库中执行这种操作

MS-SQL

最常被攻击者滥用的数据库功能是xp_cmdshell存储过程,它是MS-SQL默认内置的一项功能。这个存储过程允许数据库管理员用户以和cmd.exe命令提示符相同的方式执行操作系统命令。例如:

master..xp_cmdshell 'ipconfig > foo.txt'

攻击者可在众多情况下滥用这项功能。

  • 他们可以执行任意命令,将结果指向本地文件,然后读取文件内容。
  • 打开一个可以连通自己计算机的带外网络连接,并建立一条秘密的命令和通道,从服务器复制数据并上传攻击工具。
  • 由于MS-SQL默认以LocalSystem运行,攻击者一般能够完全攻破基本的操作系统,执行任意操作。
处理默认锁定

互联网上的大多数MS-SQL为更高版本,这些版本提供各种安全功能,可以在默认情况下锁定数据库,防止各种攻击。
但是,如果数据库中的Web应用程序用户账户拥有足够高的权限,则通过重新设置数据库,该用户就可以突破上述功能实施的限制。例如,可以使用sp_configure存储过程重新启用被禁用的xp_cmdshell。以下四行SQL代码用于实现这一目的:

EXECUTE sp_configure 'show advanced potions', 1
RECONFIGURE WITH OVERRIDE
EXECUTE sp_configure 'xp_cmdshell', '1'
RECONFIGURE WITH OVERRIDE

这样,xp_cmdshell就被重新启用,并可以通过以下命令运行:

exec xp_cmdshell 'dir'

Oracle

Oracle包含大量默认功能,这些功能可被低权限用户访问,并用于执行各种敏感操作,如建立网络连接或访问文件系统。UTL_FILE包可用于在数据库服务器文件系统上读取和写入文件。

MySQL

与前面的数据库相比,MySQL中包含的可被攻击者滥用的内置功能相对较少,其中一个示例是任何拥有FILE_PRIV许可的用户都可以读取并写入文件系统。
LOAD_FILE命令可以用于获取任何文件的内容。例如:

select LOAD_FILE('/etc/passwd')

SELECT…INTO OUTFILE命令可用于将任何一个查询的输出指向一个文件
出读取并写入关键的操作系统文件外,这些命令还可以用于指向其他攻击。

  • 因为MySQL将数据保存在明文文件中,数据库必须拥有读取这些文件的权限。拥有FILE_PRIV许可的攻击者可以打开相关文件并读取数据库中的任何数据,避开数据库实施的任何访问控制
  • MySQL允许用户通过调用一个包含函数执行过程的编译文件库文件创建一个用户定义的函数(UDF)。这个文件必须位于MySQL加载动态库的正常路径内,攻击者可以在这个路径中创建任意二进制文件,然后建立使用这个文件的UDF。

使用SQL注入工具

当前,多数工具通过以下方法来利用SQL注入漏洞

  • 对目标请求中的参数实施蛮力攻击,以查找SQL注入点
  • 通过附加各种字符,如闭括号、注释字符和SQL关键字,确定后端SQL查询中易受攻击字段的位置
  • 通过蛮力猜测请求的列数,然后确定包含varchar数据类型的列(可用于返回结果),尝试UNION攻击
  • 注入定制查询来检索任意数据——如果需要,将多个列的数据串连成一个字符串,以便于从单独一个varchar类型数据的结果中进行检索
  • 如果无法使用UNION检索结果,可以在查询中注入布尔型条件(AND 1=1 、AND 1=2等)。以确定是否可以使用条件响应来检索数据

防止SQL注入

部分有效的防御措施

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

  • 如果用户提交的数字数据内置在SQL查询中,这种数据通常并不包含在单引号内。因此,攻击者能够破坏数据的使用环境并开始输入任意的SQL查询,这时就不必输入单引号
  • 在二阶SQL注入中,最初在插入数据库中时已经安全转义的数据随后被从数据库中读取出来,然后又再次写入。当重新使用数据时,最初配对的引号又恢复到单引号形式。

参数化查询

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

  • 应用程序指定查询结构,为用户输入的每个数据预留占位符
  • 应用程序指定每个占位符的内容
    在第二个步骤中指定的专门设计的数据无法破坏在第一个步骤中指定的查询结构。因为查询结构已经确定,且相关API对所有类型的占位符数据进行安全处理,因此它总被解释为数据,而不是语句结构的一部分
    使用参数查询化可有效防止SQL注入,但还要注意以下几个重要限制:
  • 应在每个数据库查询中使用参数化查询
  • 插入查询中的每一种数据都应适当进行参数化
  • 参数占位符不能用于指定查询中表和列的名称
  • 参数占位符不能用于查询的任何部分

深层防御

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

  • 当访问数据库时,应用程序应尽量使用最低权限的账户
  • 许多企业数据库包含大量默认功能,可被能够执行任意SQL语句的攻击者有用。
  • 应评估、测试并及时安装供应商发布的所有安全补丁,以修复数据库软件本身已知漏洞。

注入NoSQL

术语NoSQL用于指各种不同于标准的关系数据库体系架构的数据存储区。NoSQL数据存储区呈现使用键/值映射的数据,并且不依赖于固定的方案,键和值可以任意定义,而且值的格式通常与数据存储区无关。以下是NoSQL数据存储区采用的一些常用查询方法:

  • 键/值查询
  • XPath
  • 编程语言(如JavaScript)

注入MongoDB

许多NoSQL数据库利用现有的编程语言来提供灵活、可编程的查询机制。如果使用字符串连接构建查询,攻击者就可以尝试破坏数据并更改查询的语法。以下面的查询为例,它基于MongoDB数据存储区中的用户记录进行登录

$js = "function(){
    return this.username='$username' & this.password='$password'
}

$js是一个JavaScript函数,其代码是动态创建的,并且包含用户提交的用户名和密码。攻击者可以通过提供以下用户名和任意密码来避开验证逻辑:

Alice'//

生成的javaScript函数如下:

function() {
    return this.username='Alice'//' & this.password='aaa';  //在javaScript中//是注释符号,因此语句//后所有代码都被注释! 
}

注入XPath

XPath(XML路径语言)是一种用于导航XML文档并从中获取数据的解释型语言。许多时候,一个XPath表达式代表由一个文档节点导航到另一个文档节点所需要的一系列步骤。如果Web应用程序将应用程序保存在XML文档中,那么它们可能使用XPath访问数据,以响应用户提交的输入。如果这个输入未经过任何过滤或净化就插入到XPath查询中,攻击者就可以通过控制查询来破坏应用程序的逻辑,或者获取未授权访问的数据。

注:与SQL注入一样,注入一个数字值时不需要单引号。与SQL查询不同的是,XPath查询中的关键字区分大小写,XML文档中的元素名也区分大小写

注入LDAP

LDAP(Lightweight Directory Access Protocol,轻量级目录访问协议)用于访问网络中的目录服务。目录是一个分级结构的数据存储区,其中可能包含任何类型的信息。LDAP还常用在企业内联网Web应用程序中,如允许用户查看并修改雇员信息的人力资源应用程序。
每个LDAP查询使用一个或多个搜索过滤器,它们决定了请求返回的目录项。搜索过滤器可以使用各种逻辑运算符来表示复杂的搜索条件。最常用的搜索过滤器如下:

  • 简单匹配条件:对单个属性的值进行匹配,通过用户名搜索:
    (username=daf)
  • 析取查询:指定多个条件。返回的目录项必须满足其中任何一个条件。
    (|(cn=searchterm)(sn=searchterm)(ou=searchterm)
  • 合取查询:指定多个条件,返回目录时必须同时满足这些条件
    (&(username=daf)(password=secret))

与SQL注入漏洞相比,LDAP注入漏洞更难以被攻击者利用,原因如下:

  • 搜索过滤器采用逻辑运算符来指定析取和合取查询的位置通常位于用户提交数据的插入位置之前,因而无法修改。因此,简单匹配条件和合取查询不会受到SQL注入类似”or 1=1”类型的攻击
  • 在常用的LDAP服务中,返回的目录属性将作为搜索过滤器中的独立参数传递给LDAP API,并且通常在应用程序中进行了硬编码。因此,攻击者无法通过修改用户提交的输入来检索和查询检索的属性不同的属性
  • 应用程序很少返回有用的错误信息。因此,通常攻击者只能“盲目”利用各种漏洞

利用LDAP进行注入

析取查询

应用程序执行的一项析取查询如下:

(|(username=London sales)(department=Reading sales))

这里,应用程序构建了一个析取查询,并在用户提交输入之前牵制了一些表达式来执行所需访问的访问控制。在这种情况下,攻击者可以通过提交以下搜索项对查询进行修改:

)(department=*
  • 是LDAP的通配符,可匹配任何数据项。如果将这个输入嵌入到LDAP搜索过滤器中,应用程序将执行以下查询,进而返回所有信息:
    (|(username=London)(department=*)(department=Reading)(department=*))

合取查询

应用程序执行的一项合取查询如下:

(&(givenName=daf)(department=London*))

攻击者可以提交

*))(&(givenName=daf

如果能将这个输入嵌入原始搜索过滤器中,将得到以下查询

(&(givenName=*))(&(givenName=daf)(department=London*))

第一个过滤器包含一个通配符匹配条件,因此,结果也会返回所有信息

NULL字节处理漏洞

针对合取查询的攻击多利用许多LDAP服务在处理NULL字节方面的漏洞。由于这些服务常常以本地代码编写,因此,搜索过滤器中的NULL字节将立即终止字符串,NULL之后的任何字符将被忽略。虽然LDAP本身不支持注释,但是采用NULL字节可以取得与SQL注释语句相同的效果
在上一个示例中,攻击者可以提交:

*))%00

应用程序将%00序列解码成原义NULL字节,因此,查询将变为:

(&(givenName=*))[NULL])(department=London*))

由于这个过滤器在NULL字节被截断,在LDAP看来,其中只包含一个通配符条件,因此会返回所有信息

防止LDAP注入

应根据一份可接受字符的“白名单”检查用户输入,其中最好只包括字母数字字符。应阻止任何可能破坏LDAP查询的字符,包括();,* | & =和空字节


   转载规则


《Web安全(8)》 fightingtree 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录