(OWASP汉化)攻击系列大全(六十二):SQL注入

最新版本(mm/dd/yy): 04/10/2016
翻译至SQL Injection

概览

SQL注入攻击(SQL injection)通过从客户端到应用程序的输入数据中插入或“注入”SQL查询语句。一个成功的SQL注入攻击可以从数据库中读取敏感数据,修改数据库数据(插入/更新/删除),对数据库执行管理操作(例如关闭DBMS(Database Management System:数据库管理系统)),恢复DBMS文件上存在的给定文件的内容系统,并在某些情况下向操作系统发出命令。 SQL注入攻击是一种注入式攻击,其中SQL命令被注入到数据平面输入中,以便执行预定义的SQL命令。

威胁建模

  • SQL注入攻击允许攻击者欺骗身份,篡改现有数据,引起拒绝问题(如排除交易或更改余额),允许完整披露系统上的所有数据,销毁数据或使其不可用,并成为数据库服务器管理员
  • 由于旧功能接口的普及,SQL注入在PHP和ASP应用程序中非常常见。由于编程接口的性质的可用性,J2EE和ASP.NET应用程序不太可能轻松地利用SQL注入。
  • SQL注入攻击的严重性受到攻击者的技能和想象力的限制,并且在较低程度上受到深层防御措施的限制,例如与数据库服务器的低特权连接等。一般来说,认为SQL注入带来的影响很严重。

相关安全活动

如何避免SQL注入漏洞

请参阅 OWASP SQL Injection Prevention Cheat Sheet.
请参阅OWASP Query Parameterization Cheat Sheet.
请参阅OWASP Guide文章看怎么去避免SQL注入漏洞

如何检查SQL注入漏洞的代码

请参阅 OWASP Code Review Guide 文章看怎么去检查SQL注入代码漏洞

如何测试SQL注入漏洞?

请参阅 OWASP Testing Guide 文章看怎么去测试SQL注入漏洞

如何使用SQLi绕过Web应用程序防火墙?

请参阅OWASP文章using SQL Injection to bypass a WAF

描述

SQL注入发生在:

  • 数据从一个不可信的源进入程序。
  • 用于动态构造SQL查询的数据。

主要后果是:

  • 保密性:由于SQL数据库通常存放敏感数据,因此SQL注入漏洞通常会导致机密性的丢失。
  • 认证:如果使用较差的SQL命令来检查用户名和密码,则有可能以不知道密码的其他用户身份连接到系统。
  • 授权:如果授权信息保存在SQL数据库中,则可以通过利用SQL注入漏洞来更改此信息。
  • 完整性:就像可能读取敏感信息一样,也可以使用SQL注入攻击来更改或删除此信息。

风险因素

受影响的平台可以是:

  • 语言:SQL
  • 平台:任何(需要与SQL数据库交互)的平台

SQL注入已成为数据库驱动的网站的常见问题。这个漏洞很容易被检测到,并且容易被利用,因此,即使是最小用户群的任何站点或软件包也可能遭受这种尝试攻击。

本质上来说,攻击是通过在数据输入中放置一个元字符来完成的,然后将SQL命令放置在控制平面中,这在之前并不存在。这个漏洞是由于SQL在控制平面和数据平面之间没有真正的区别开来。

实例

实例1

SQL中:

select id, firstname, lastname from authors

如果提供:

Firstname: evil'ex
Lastname: Newman

查询字符串变为:

select id, firstname, lastname from authors where forename = 'evil'ex' and surname ='newman'

数据库尝试运行的内容如下:

Incorrect syntax near il' as the database tried to execute evil. 
//数据库试图执行恶意代码,il'附近的语法错误。

上述SQL语句的安全版本可以用Java编码为:

String firstname = req.getParameter("firstname");
String lastname = req.getParameter("lastname");
// FIXME: do your own validation to detect attacks
String query = "SELECT id, firstname, lastname FROM authors WHERE forename = ? and surname = ?";
PreparedStatement pstmt = connection.prepareStatement( query );
pstmt.setString( 1, firstname );
pstmt.setString( 2, lastname );
try
{
    ResultSet results = pstmt.execute( );
}

实例2

下面的C#代码动态构造和执行一条SQL语句来搜索匹配指定名称的条目。查询将显示的条目限制为所有者与当前已通过身份验证的用户的用户名相匹配的项目。

    ...
    string userName = ctx.getAuthenticatedUserName();
    string query = "SELECT * FROM items WHERE owner = "'" 
                    + userName + "' AND itemname = '"  
                    + ItemName.Text + "'";
    sda = new SqlDataAdapter(query, conn);
    DataTable dt = new DataTable();
    sda.Fill(dt);
    ...

此代码打算执行的查询如下所示:

    SELECT * FROM items
    WHERE owner = 
    AND itemname = ;

但是,由于查询是通过连接常量基本查询字符串和用户输入字符串动态构造的,因此只有在itemName不包含单引号字符的情况下,查询才会正确运行。如果具有用户名wiley的攻击者为itemName输入字符串“name”或“a”=“a”,则查询变为以下内容:

    SELECT * FROM items
    WHERE owner = 'wiley'
    AND itemname = 'name' OR 'a'='a';

OR’a’=’a’条件的添加会导致where子句始终评估为true,因此查询在逻辑上等同于更简单的查询:

    SELECT * FROM items;

查询的简化允许攻击者绕过只返回经过身份验证的用户拥有的条目的要求;该查询现在返回存储在商品表中的所有条目,而不管其指定的所有者。

实例3

本示例将检查传递给示例1中构造和执行的查询的不同恶意值的影响。如果用户名为hacker的攻击者输入字符串“name”); DELETE FROM items; – “for itemName,则查询成为以下两个查询:

    SELECT * FROM items 
    WHERE owner = 'hacker'
    AND itemname = 'name';

    DELETE FROM items;

    --'

许多数据库服务器(包括Microsoft®SQL Server 2000)允许一次执行多个用分号分隔的SQL语句。虽然此攻击字符串在Oracle和其他数据库服务器中导致错误,这类数据库不允许批处理执行用分号分隔的语句。然而在允许批处理执行的数据库中,此类攻击允许攻击者针对数据库执行任意命令。

注意后面的连字符( – ),它指定大多数数据库服务器,该语句的其余部分将被视为注释并且不被执行。在这种情况下,注释字符用于从修改后的查询中删除遗留的单引号。在不允许以这种方式使用注释的数据库中,使用类似于示例1中所示的技巧的普通攻击仍然可以生效。如果攻击者输入字符串“name”); DELETE FROM items; SELECT * FROM items where’a’=’a“,将创建以下三个有效语句:

    SELECT * FROM items 
    WHERE owner = 'hacker'
    AND itemname = 'name';

    DELETE FROM items;

    SELECT * FROM items WHERE 'a'='a';

防止SQL注入攻击的一种传统方法是将它们作为输入验证问题来处理,并且只接受来自白名单中安全值的字符,或者识别并转义黑名单中的潜在恶意值。白名单可以是执行严格输入验证规则的非常有效的手段,但参数化SQL语句需要较少的维护,并且可以提供更多有关安全性的保证。几乎总是这样,黑名单上充满了漏洞,使其无法预防SQL注入攻击。例如,攻击者可以:

  • 目标字段没有被引用
  • 找到方法来绕过某些转义元字符的需求
  • 使用存储过程来隐藏注入的元字符

手动将输入到SQL查询中的字符转义有一定预防作用,但它不会使您的应用程序免受SQL注入攻击。

通常用于处理SQL注入攻击的另一个解决方案是使用存储过程。尽管存储过程可以防止某些类型的SQL注入攻击,但它们无法抵御其他许多攻击。例如,以下PL / SQL过程容易受到第一个示例中显示的相同SQL注入攻击的影响。

    procedure get_item (
        itm_cv IN OUT ItmCurTyp,
        usr in varchar2,
        itm in varchar2)
    is
        open itm_cv for ' SELECT * FROM items WHERE ' ||
                'owner = '''|| usr || 
                ' AND itemname = ''' || itm || '''';
    end get_item;

存储过程通常通过限制可传递给参数的语句类型来帮助防止SQL注入攻击。然而,围绕这些限制的许多方法和许多有趣的声明仍然可以传递给存储过程。同样,存储过程可以阻止一些漏洞利用,但它们不会使应用程序对SQL注入攻击免疫。

相关的威胁代理

相关攻击

相关漏洞

相关控件

参考文献