记录一次登录绕过的代码研究

前提

首先知道框架基于ThinkPHP5.0.24

还有两个漏洞的存在

完整的SSRF

代码如下,属于未授权的方法

而且存在return,相当于存在输出位置,可以进行文件读取

1618544281836

Phar反序列化

这是一个后台方法

需要最高权限3,才能访问

1618544366966

漏洞代码如下,可以看到base_dir是直接传入的,代表协议可控

后面虽然拼接了一堆堆东西,之前没有测试过,但是可以做一个测试

1618544503530

测试代码

1618544780602

测试结果

可以看到,只要phar文件存在,后面无论加多少路径,都会触发反序列化

1618544846486

绕过分析

SQL注入

第一步先看继承

所有的后台控制器都继承自Base控制器

通过Token验证权限,而token得值是从cookie中获取得

这个地方如果,在没有status得情况下,这里是不存在服务器得限制的

如果account完全可控,在windows下就存在完整的注入漏洞

Linux也也可以结合联合查询伪造数据登录

具体为什么存在注入,自行参考ThinkPHPnot like表达式注入

不在多解释

那为什么不存在两外一个参数的时候可以呢

因为mysql的自带的information_schema支持大写,且其中的表支持大小写

就导致可以通过tables表机器逆行数据伪造,从而登陆成功

1618545055153

解释了这么多,还是先看看account是否完全可控吧

跟进checkToken方法

可以看到这里是JWT编码

1618548305898

jwt_key的值被存储在文件中,这里是hello,也可以利用SSRF通过file协议读取

1618548367556

结论

payload不做讨论

第二种存储XSS

限制

  • 未开启ip检测

第三种爆破

限制

  • 未开启ip检测

由于这是测试环境的代码,所以未开启ip检测,但正式环境应该会开启的

在登录模块,创建token的参数中很有意思

并没有将密码的加密或者其他不可控的参数加如

导致这是一个弱加密,只需要根据一天的时间戳生成加密参数进行爆破,

只要管理员一天内登陆过

就可以登陆成功

1618549174345

第四种修改管理密码

限制

  • error_reporting(0)
  • 未开启ip限制

login控制器中虽然也继承自base,但是由于自己的初始化方法中不存在对付类初始化方法的调用,所以导致,此控制器的所有方法均可以未授权访问

1618549500736

在这个控制器中我们可以通过userDelete方法禁用掉管理员的账号

1618549601495

但是这么做对我们并没有什么意义

所以下面还存在一个,重置密码的操作

1618549720185

同样我们不知道管理的密码,但是没关系

这里需要知道的是,字段名,没有大小写的限制

id参数完全可控

这个样子我们先注册一个普通账号,假设id10,管理员id1

通过JWT解码后的id如下

1
2
$id= ['like',['1','10'],')or('];
$psd = 'aaaaaaa';//w偶们添加的账号的密码

最后通过底层代码处理后,where语句为

1
WHERE (`id` LIKE 1) OR (`ID` LIKE 10) AND `psd` = 'aaaaaaa'

这个逻辑一定会查到一条数据,就是我们新增的那个账号

为什么需要关闭报错呢

这里的update语句格式换了

1
$setresult = Db::name("admin")->where('id', $id)->update(['psd' => $newpsd]);

如果是如下

1
$setresult = Db::name("admin")->where(['id'=>$id])->update(['psd' => $newpsd]);

则完全无需关闭报错

目标大妈中的写法会触发一个list函数的警告

就是不存在下标1

用如下代码做测试

1
2
$id = $_POST['id'];
db('user')->where('id',$id)->update(['test'=>'2']);

测试结果如下

1618550545421

但是谁会关闭报错呢,之前还真遇到过一个

在关闭报错的情况下,最终的update语句的where条件是

1
WHERE (`id` LIKE 1) OR (`ID` LIKE 10)

这个会把id等与110的数据都修改了,导致密码被重置

第五种新增管理员

同样在login控制器下面存在一个新增用户的方法,但是这个方法不让添加管理员

1618551303739

这里刚开始考虑到了两种情况

  • 数据库中的role字段的类型为字符型时
  • int型时

字符型

字符型时,需要绕过的时权限的判断,这里对于鉴权用到的是in_array

有趣

1618551437744

我们知道,当后面的数组中为int类型是,由于存在弱类型的比较,导致很简单的就绕过

那数组中为字符型时,这个特性还可以用吗

测试代码

1
2
3
4
5
6
7
<?php
$role = "3a";
if (!in_array($role, ["3"])) {
echo "aaaaa";
}else{
echo "bbbb";
}

测试结果

发现无法适用于适用与字符型

1618551672458

fuzz一下什么字符可以绕过这个in_array

测试代码

1
2
3
4
5
6
$id = $_POST['id'];
if (!in_array($id, ["3"])) {
echo "aaaaa";
}else{
echo "bbbbbbbbbbbbbbbbb";
}

发现.可以的

1618555628258

但是不能用,可以绕过in_array的同样可以绕过==

1618555682402

这个时候想起来全角字符

可以插入

1618556554270

但是还是全角的,所以取出来还是匹配不到没有意义

1618556601282

结论

字符型,比较难搞

整形

整形比较容易一些,利用点比较多一点

第一种

浮点型,在数据库中,向数据库的int字段插入浮点,会自动强转

第二种

科学计数法

但是存在缺陷,绕不过去php的弱类型

第三种

PDO的参数绑定

在绑定int类型的字段时,pdo会自动强转

这个比较简单,不举例子了

结论

如果数据库时int类型我们可以利用此方法成果添加管理员

就知道没这么简单

然后查看了一下数据库的字段类型。

!!!!

enum类型

1618556448393

这个类型之前没遇到过

测试全角

1618555770692

测试浮点

1618556691662

测试pdo

测试完发现,PDO对这个enum类型的字段,并不会像int类型那样处理,而是原模原样的传进了数据库

最后也是报一个类型错误

模糊测试

只能按照原模原样区fuzz一些字符了

控制器方法中的接受的参数,相当于直接使用助手函数input

测试代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$role = input('id');//$_POST['id'];
$account = "aaa";
$remark = "bbb";
$psd = "aaaa";
$ip = "cccc";
if ($role == "3") {
exit('error');
}
$insert_data = [
"create_time" => time(),
"update_time" => time(),
"account" => $account,
"role" => $role,
"remark" => $remark,
"psd" => md5($psd),
"ip" => $ip
];
$flag = Db::name("zb_admin")
->insert($insert_data);
if($flag){
echo "22222222222222222222222222222222222222222222222222222222";
}

心动的来了,空格成果绕过,并且,成果插入数据

1618558078594

数据库中已插入并且role3

1618558213532

这也算是mysql在处理enum类型的时候的一个小问题吧

end