Bootstrap

SQL注入原理&&手动检测SQL注入

sql注入原理

SQL注入(SQL Injection)是一种常见的网络攻击手段,攻击者通过向应用程序的输入字段(如网页表单、URL参数等)中插入恶意的SQL语句,从而操控数据库执行未预期的操作。其原理基于以下几点:

  1. 动态生成SQL查询

    • 很多应用程序会根据用户输入动态生成SQL查询语句。例如,用户在登录表单中输入用户名和密码,系统会生成类似这样的SQL语句:

      SELECT * FROM users WHERE username = 'user' AND password = 'pass';

  2. 不安全的输入处理

    • 如果应用程序未对用户输入进行充分的验证或过滤,那么攻击者可以在输入中插入SQL语句,使得原本的查询语句发生改变。例如,攻击者在用户名字段输入:
      ' OR '1'='1
      
      生成的SQL查询语句可能变成:
      SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'pass';
      
      由于'1'='1'始终为真,这条语句将会返回所有用户的信息,而不仅仅是正确的用户名和密码。

      场景设定

      假设一个网站的登录表单需要输入用户名和密码,然后在后台生成如下的SQL查询语句来验证用户身份:

      SELECT * FROM users WHERE username = 'user' AND password = 'pass';

      攻击者的输入

      攻击者在用户名字段输入了以下内容:

      ' OR '1'='1

      在密码字段输入了任意内容(例如pass)。此时,生成的SQL语句变成了:

      SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'pass';

      SQL语句分析原始逻辑

      • 原始查询条件是:
        WHERE username = 'user' AND password = 'pass';
        
        这个条件要求数据库中的记录必须同时满足username等于输入的用户名,并且password等于输入的密码,才会返回匹配的用户记录。
    • 注入后的逻辑

      • 经过注入后的条件变成了:
        WHERE username = '' OR '1'='1' AND password = 'pass';
        
      • 这段条件可以分解成两个部分:
        • username = '':这个条件试图匹配用户名为空的记录,但通常不会有这样的记录。
        • OR '1'='1':这是一个总为True的条件(因为1始终等于1)。

      由于OR操作符的存在,只要其中一个条件为真,整个WHERE子句就为真。因为'1'='1'始终为真,所以无论用户名和密码是否匹配,整个查询条件都会被视为真。

    • 结果

      • 由于OR '1'='1'条件始终为真,导致整个WHERE条件不再依赖于用户名和密码的匹配。因此,查询将会返回users表中的所有记录,因为在逻辑上,对于每一条记录,OR '1'='1'都成立。
  3. 利用SQL的语法和逻辑

    • 攻击者可以利用SQL的语法特性,例如利用注释符号来忽略后续SQL语句,或者通过联接多个SQL语句来执行恶意操作。例如:
      '; DROP TABLE users; --
      
      这会将SQL语句截断并执行删除表的操作,导致数据库中的users表被删除。
    • 常见的SQL注释符号

    • 单行注释 (--)

      • -- 后的内容直到行末都被视为注释,数据库引擎不会执行这些内容。
      • 例如:
        SELECT * FROM users WHERE username = 'admin' -- AND password = 'password';
        
        这条SQL语句中,--后面的AND password = 'password';会被忽略,实际执行的查询是:
        SELECT * FROM users WHERE username = 'admin';
        
    • C风格注释 (/* ... */)

      • /**/ 之间的内容被视为注释,可以用于注释单行或多行内容。
      • 例如:
        SELECT * FROM users WHERE username = 'admin' /* AND password = 'password' */;
        
        这里/* AND password = 'password' */被注释,实际执行的查询是:

        SELECT * FROM users WHERE username = 'admin';

    • 利用注释符号的SQL注入攻击示例

      攻击者可以通过在输入中插入注释符号,忽略掉后续的SQL语句。例如:

      假设一个登录表单生成的SQL查询语句如下:

      SELECT * FROM users WHERE username = 'user' AND password = 'pass';

      如果攻击者在用户名字段输入以下内容:

      ' OR '1'='1 --

      生成的SQL语句将变成:

      SELECT * FROM users WHERE username = '' OR '1'='1' -- AND password = 'pass'; 由于--注释符号,AND password = 'pass'部分被忽略。SQL语句实际执行的是:

    • SELECT * FROM users WHERE username = '' OR '1'='1';

    • 由于'1'='1'始终为真,这将导致查询返回所有用户的数据。
  4. 数据库敏感信息的泄露

    • 通过SQL注入,攻击者可以执行查询、插入、更新、删除等操作,从而获取、修改甚至删除数据库中的敏感信息。

防御措施

  1. 使用预处理语句(Prepared Statements)和参数化查询

    • 通过将SQL语句和数据分开处理,可以避免SQL注入。例如:
      cursor.execute("SELECT * FROM users WHERE username = ? AND password = ?", (user, pass))
      
      在这种情况下,用户输入的数据会被自动转义,防止注入。
    • 通过将SQL语句和数据分开处理,可以避免SQL注入的原因在于使用了参数化查询(Parameterized Queries)或预处理语句(Prepared Statements)。这些方法确保用户输入的数据不会被直接嵌入到SQL语句中,而是作为独立的参数传递给数据库。这样,用户输入即使包含恶意的SQL代码,也不会影响SQL语句的结构。

      具体原理

    • SQL语句模板化

      • 在使用参数化查询时,SQL语句首先作为模板创建,其中包含占位符(如?或命名参数如%s)。这些占位符用于标记将要插入用户数据的位置,而不是将数据直接插入SQL字符串中。
      • 例如,在Python中使用sqlite3库时,可以编写如下代码:
        cursor.execute("SELECT * FROM users WHERE username = ? AND password = ?", (user, pass))
        
      • 在这个例子中,?是占位符,表示将要插入的参数。
    • 参数化处理

      • 用户输入的数据(如用户名和密码)作为参数传递给execute方法。数据库驱动程序会将这些参数与SQL模板进行绑定,而不会将它们直接拼接到SQL字符串中。
      • 例如,上面的userpass变量会分别绑定到?占位符上,而不是直接替换占位符。
    • 避免SQL语句结构变化

      • 在绑定参数时,数据库驱动程序会自动将用户输入的数据进行转义和处理,以确保它们不会改变SQL语句的逻辑结构。例如,如果user变量中包含了一个恶意的SQL代码,如' OR '1'='1,它会被当作纯文本处理,而不会改变SQL查询的逻辑。
      • 因此,无论用户输入什么内容,都不会改变原有的SQL查询逻辑,从而有效防止SQL注入。
    • 示例对比

      直接拼接SQL字符串(容易遭受SQL注入)

      user_input = "' OR '1'='1" query = f"SELECT * FROM users WHERE username = '{user_input}' AND password = 'pass'" cursor.execute(query)

      生成的SQL语句是:

      SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'pass';

      这个查询会返回所有用户,因为'1'='1'始终为真。

      参数化查询(安全)
      python
      

      user_input = "' OR '1'='1" cursor.execute("SELECT * FROM users WHERE username = ? AND password = ?", (user_input, 'pass'))

      生成的SQL语句是:

      sql
      

      SELECT * FROM users WHERE username = '\' OR \'1\'=\'1' AND password = 'pass';

      在这个例子中,用户输入的恶意SQL代码被当作纯文本,数据库引擎会将其视为普通的用户名,而不会改变查询逻辑。

  2. 输入验证和过滤

    • 对所有用户输入进行严格的验证和过滤,确保输入符合预期的格式,并且避免直接在SQL语句中使用未经处理的用户输入。
    • 实用性: 高。对输入进行验证和过滤是编写安全代码的基本要求,不仅可以防止SQL注入,还可以防止其他形式的攻击(如XSS)。
    • 优点:
      • 提高整体应用的安全性。
      • 可以防止多种类型的输入攻击。
    • 缺点:
      • 需要根据应用场景设计具体的验证规则,容易出错。
      • 不一定能完全防止SQL注入,特别是当验证或过滤不够严谨时。
  3. 使用ORM框架

    • 使用对象关系映射(ORM)框架可以减少直接编写SQL语句的需求,从而降低SQL注入的风险。
  4. 数据库权限控制

    • 最小权限原则,确保数据库用户只具有执行必要操作的权限,避免因SQL注入导致更严重的后果。

手动检测SQL注入

手动检测SQL注入涉及通过手动输入恶意字符串并观察应用程序的响应,以确定应用程序是否易受SQL注入攻击。

步骤
  1. 识别输入点

    • 找到应用程序中接受用户输入并将其用于数据库查询的地方。例如,登录表单、搜索框、URL参数等。
  2. 初步测试

    • 在可疑的输入点输入一些典型的注入载荷,并观察应用程序的反应。例如:
      • 输入单引号':如果返回数据库错误信息,可能存在SQL注入。
      • 输入 OR '1'='1' --OR '1'='1' /*:试图通过条件始终为真的语句绕过身份验证。
  3. 更深入的测试

    • 联合查询测试
      • 使用UNION关键字尝试联合查询。例如:
        ' UNION SELECT null,null,null --
        
      • 逐步增加返回列的数量,以确认列数和数据类型
      • 举例:

        联合查询测试是通过使用UNION关键字,将攻击者的查询结果与原始查询结果合并,从而试图获取更多信息或注入数据的一种SQL注入攻击方式。

        联合查询测试的原理

        UNION关键字允许将两条或多条SELECT语句的结果合并到一个结果集中。要成功执行UNION操作,两个查询的列数和数据类型必须匹配。

        步骤详解

      • 初始测试

        • 攻击者通常会在输入点输入一个带有UNION SELECT的语句。例如:
           sql 

          复制代码

          ' UNION SELECT null,null,null --

        • 这个语句的作用是尝试将SELECT null,null,null的结果与前一个查询结果合并。如果查询成功,说明攻击者注入的查询列数与原始查询的列数匹配。
      • 确认列数

        • 如果UNION SELECT null,null,null --没有产生错误,那么查询的列数可能是3。
        • 如果出现错误,表示列数不匹配,攻击者会调整null的数量。例如,尝试:
           sql 

          复制代码

          ' UNION SELECT null,null --

           sql 

          复制代码

          ' UNION SELECT null,null,null,null --

        • 通过不断增加或减少null的数量,直到不再出现错误,从而确定列数。
      • 数据类型确认

        • 一旦确定列数,下一步是确认每一列的数据类型。攻击者会尝试插入特定的数据类型值,如字符串、数字等。例如:
           sql 

          复制代码

          ' UNION SELECT 1,'a',null --

        • 如果该查询成功执行且没有报错,表明第二列可能是字符串类型。
        • 攻击者会继续尝试其他类型的数据,直到识别出每一列的数据类型。
      • 获取有用信息

        • 确认列数和数据类型后,攻击者可以替换null值为实际的查询或敏感数据的列名。例如:
           sql 

          复制代码

          ' UNION SELECT username, password, null FROM users --

        • 这样,攻击者可以将数据库中的敏感信息(如用户名和密码)与原始查询结果一起返回。
      • 示例

        假设原始查询为:

         

        sql

        复制代码

        SELECT id, name, email FROM users WHERE id = '1';

        攻击者输入以下注入语句:

         

        sql

        复制代码

        1' UNION SELECT null,null,null --

        结果返回错误,说明列数不匹配。攻击者尝试减少null值:

         

        sql

        复制代码

        1' UNION SELECT null,null --

        仍然错误。最后攻击者尝试:

         

        sql

        复制代码

        1' UNION SELECT null,null,null,null --

        如果查询成功,则原查询表可能有4列。接下来,攻击者可以通过注入:

         

        sql

        复制代码

        1' UNION SELECT 1,'a',null,null --

        来测试第二列是否为字符串类型。最终,他们可以通过类似下面的语句提取数据库信息:

         

        sql

        复制代码

        1' UNION SELECT username, password, null,null FROM users --

        关键点

      • 列数和数据类型必须匹配UNION操作要求两边的查询有相同数量的列,并且对应位置的列数据类型相兼容。
      • 逐步测试:攻击者会逐步增加或减少null的数量,直到找到正确的列数。
      • 返回有用信息:在确定列数和类型后,攻击者会利用UNION注入获取敏感数据。
      • 在SQL注入攻击中,1'是一个恶意输入的片段,用于引发SQL查询语句的结构性变化,从而实现注入攻击。

        解释

      • 1'的作用

        • 1 是数字常量,可能是原始查询语句的一部分。
        • ' 是单引号,通常用于包围字符串常量。在这种情况下,单引号被用来结束原始SQL语句中的字符串。

        例如,假设原始查询语句是:

         sql 

        复制代码

        SELECT * FROM users WHERE id = '1';

        如果用户输入1',那么最终的SQL语句变成:

         sql 

        复制代码

        SELECT * FROM users WHERE id = '1'';

        这将导致SQL语法错误,因为1''中的双引号是不合法的字符串结束符。

      • 利用错误引入SQL注入

        • 攻击者利用这个错误通过加入SQL关键字(例如UNION),并操控原始查询语句:
           sql 

          复制代码

          1' UNION SELECT null,null,null --

        • 上述输入会导致SQL语句变成:
           sql 

          复制代码

          SELECT * FROM users WHERE id = '1' UNION SELECT null,null,null -- ';

          这里:
          • 1'关闭了原本的字符串字面量。
          • UNION SELECT null,null,null 是攻击者添加的新查询部分,用于联合原始查询。
          • -- 是SQL注释符号,注释掉了后面的部分,使其不再参与查询执行。
      • 最终效果

      • 成功注入的SQL语句: 如果SQL语句成功执行,将会执行UNION语句,将SELECT null,null,null的结果与原查询的结果合并。

      • 原查询被修改: 通过1' UNION ... --这样的构造,攻击者可以将自己的SQL语句注入到原查询中,从而访问或操纵数据库中的数据。

      • 在SQL注入攻击中,利用UNION关键字可以将攻击者构造的查询结果与原始查询结果合并,从而达到提取数据库敏感信息的目的。以下是这类注入语句工作的详细原理:

        语句分析

        假设攻击者使用以下语句进行注入:

         

        sql

        复制代码

        1' UNION SELECT username, password, null,null FROM users --

        这句话被拼接进原始SQL查询中,例如,原始查询可能是:

         

        sql

        复制代码

        SELECT id, name, email FROM users WHERE id = '1';

        拼接后的SQL语句

        在注入之后,SQL查询变成:

         

        sql

        复制代码

        SELECT id, name, email FROM users WHERE id = '1' UNION SELECT username, password, null, null FROM users --';

        分析步骤

      • 原始查询部分

        • 原始查询为:
           sql 

          复制代码

          SELECT id, name, email FROM users WHERE id = '1';

          这个查询可能是根据idusers表中获取用户的idnameemail字段。
      • 注入部分

        • 攻击者插入了:
           sql 

          复制代码

          ' UNION SELECT username, password, null, null FROM users --

        • UNION关键字允许将两条SELECT语句的结果合并。这里攻击者构造了一条新的SELECT语句,用于提取users表中的usernamepassword信息。
        • null, null 是用来填充与原查询相同的列数,因为UNION要求两条SELECT语句的列数和数据类型相同。
      • 注释符号

        • -- 是SQL中的注释符号,它将其后的内容注释掉,使得原始SQL语句的末尾部分不再执行。例如,'; 将不会被执行,这避免了语法错误。
      • 最终效果

        • 结果集中将包含两个查询的结果:
          • 第一部分来自于原始查询,但通常只返回一行或几行数据(例如id1的用户)。
          • 第二部分来自于攻击者的查询,提取了所有用户的usernamepassword
      • 利用UNION合并结果集

        • UNION将攻击者的查询结果与原查询结果合并,因此只要攻击者的查询语句合法,并且数据类型匹配,他们就可以将敏感信息包括在结果集中返回。
      • 突破应用层过滤

        • 如果应用程序的前端代码仅简单地显示查询结果,而没有做进一步处理,数据库返回的数据(包括攻击者注入的数据)可能直接显示在页面上。
      • 信息泄露

        • 通过这种方式,攻击者可以在不修改前端代码的情况下,利用后端数据库的查询能力提取敏感数据,例如用户的用户名和密码。
      • 结论

        通过1' UNION SELECT username, password, null,null FROM users --这样的注入语句,攻击者能够突破原有的SQL查询结构,利用UNION合并的功能,成功提取数据库中的敏感信息,如用户名和密码。这种攻击利用了SQL查询的灵活性以及应用程序对用户输入的缺乏有效过滤。

        为什么能提取数据库信息

      • 利用UNION合并结果集

        • UNION将攻击者的查询结果与原查询结果合并,因此只要攻击者的查询语句合法,并且数据类型匹配,他们就可以将敏感信息包括在结果集中返回。
      • 突破应用层过滤

        • 如果应用程序的前端代码仅简单地显示查询结果,而没有做进一步处理,数据库返回的数据(包括攻击者注入的数据)可能直接显示在页面上。
      • 信息泄露

        • 通过这种方式,攻击者可以在不修改前端代码的情况下,利用后端数据库的查询能力提取敏感数据,例如用户的用户名和密码。
        • 在SQL语句中使用UNION关键字时,要求被合并的两个SELECT语句的列数和数据类型必须匹配。如果这两个条件不满足,SQL引擎将会抛出错误。

          具体解释:

        • 列数匹配
          • 当你使用UNION时,前后两个SELECT语句所返回的结果集必须具有相同数量的列。例如,如果第一个SELECT语句返回了3列,第二个SELECT语句也必须返回3列。
        • 数据类型匹配
          • 不仅列数需要匹配,对应位置的列的数据类型也应该兼容。例如,如果第一个SELECT语句的第一列是整数类型,那么第二个SELECT语句的第一列应该是整数类型或者是可以转换为整数的数据类型。
        • null, null 的作用:

        • 在这个注入场景中,攻击者往往不知道原始查询的确切列数和数据类型。为了让注入的UNION查询语句能够成功执行,攻击者通常会使用null来填充列。

        • null的好处

          • null是一种通用的值,可以被SQL引擎接受为任何数据类型。无论原查询的列是整数、字符串、日期等,null都可以匹配。因此,使用null可以确保UNION查询在数据类型匹配上不会出错。
        • 例如

          • 如果原始查询为:
             sql 

            复制代码

            SELECT id, name, email FROM users WHERE id = '1';

            这个查询返回了3列:id(可能是整数类型)、name(字符串类型)、email(字符串类型)。
          • 攻击者可以构造如下的注入语句:
             sql 

            复制代码

            ' UNION SELECT username, password, null FROM users --

            在这个例子中,username替代了idpassword替代了name,但攻击者不知道第三列的具体数据类型,因此使用了null来占位。null可以适应第三列的任何数据类型,使得整个UNION查询语句能够成功执行。
        • 总结

        • null, null填充列数null用于填充额外的列,以保证注入的SELECT语句与原始查询的列数相同。
        • 匹配数据类型:由于null可以适应任何数据类型,它是SQL注入中一种安全的选择,确保UNION查询语句能够成功执行,而不会因为数据类型不匹配而导致SQL语法错误。
        • 这就是为什么在SQL注入攻击中,经常看到攻击者使用null来填充和匹配列的原因。

    • 布尔盲注
      • 通过传递布尔条件测试响应差异。例如:
        ' AND 1=1 -- ' AND 1=2 --
        
      • 比较两次响应是否不同,以确定应用程序是否执行了SQL注入。
      • 检测和利用

      • 获取更多信息
        • 攻击者可以利用布尔盲注逐步探测数据库结构。例如,通过测试不同的布尔条件,逐步猜测表名、列名、数据等。
      • 信息泄露
        • 通过布尔盲注,攻击者可以推断数据库的内容。例如,使用如下条件判断某个字段是否包含特定值:
           sql 

          复制代码

          ' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin')='a' --

        • 如果条件为真,则返回的响应与布尔条件为真时相同,否则不同。
    • 时间盲注
      • 使用延时函数(如SLEEP())测试注入。例如:
        ' AND SLEEP(5) --
        
      • 如果页面延迟响应,表明可能存在注入。
      • 确定注入位置

        • 攻击者可以逐步增加延时函数的时间,例如从1秒到10秒,以确认注入的影响和效果。
      • 数据推断

        • 在某些情况下,攻击者可以结合延时函数和其他SQL注入技术来推测数据库内容。例如,可以通过复杂的查询逻辑和延时函数来判断数据的存在与否。
      • 测试各种条件

        • 可以使用不同的布尔条件与延时函数结合,以确定查询是否容易受到注入攻击。例如:
           sql 

          复制代码

          ' AND IF(1=1, SLEEP(5), 0) --

        • 这种注入测试可以帮助攻击者进一步了解应用程序对SQL注入的处理情况。
      • 注意事项

      • 数据库和环境依赖

        • 延时函数的具体名称和实现可能因数据库系统而异。例如,MySQL 使用 SLEEP(),PostgreSQL 使用 pg_sleep()
      • 防御措施

        • 应用程序应避免直接将用户输入插入到SQL查询中,使用参数化查询或预处理语句来防止SQL注入。
        • 还可以实现网络层和应用层的安全措施,如Web应用防火墙(WAF)来检测和阻止恶意请求。
  4. 利用注释符号

    • 尝试在输入中使用注释符号(--, #, /* ... */)截断或注释掉原本的SQL代码,看看是否能改变查询逻辑。
  5. 分析响应

    • 错误消息:通过返回的错误消息,可以了解数据库类型和结构信息,帮助进一步的攻击。
    • 行为变化:观察到应用程序的行为(如页面加载时间、返回的数据变化等)可以推测SQL语句的执行情况。
  6. 确认漏洞

    • 使用组合注入测试,例如联合查询加上时间盲注,确认注入点的存在。

 

;