ThinkPHP5.0完全开发手册
某盘代码审计
学习完开发手册,先来套源码试试手。
想要快速入手代码审计,就要先看手册了解一些底层架构等等
从index.php
开始
和原来的相比改动不大,应用目录是application
,直接进行查看
先看config.php
,看一些配置信息能够帮助我们快速了解此套源码
全局过滤方法
访问模式,最最最重要的是我们的控制器
可以看到,它开启了路由但是并没有打开强制路由
这里看一下路由,并没有什么东西
想要得到未授权RCE,还是得先看Index
模块下面的控制器,因为可能有未授权访问,像这种 MVC 架构admin
模块下,未授权太少了Orz
除了Api
、Login
控制器,其他控制器均继承了Base
控制器
那他们为什么要都继承Base
呢?进入看一下
Orz
在构造函数下面做了权限验证,扫了一眼除非知道数据库里的$header_uid
不然无法绕过
但是,我们上面还提到了一个api
控制器,没有继承。进入查看一下
0x01 SSRF
在curlfun()
函数下面,看到了一个很明显的SSRF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public function curlfun($url, $params = array(), $method = 'GET')
{
$header = array();
$opts = array(CURLOPT_TIMEOUT => 10, CURLOPT_RETURNTRANSFER => 1, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_HTTPHEADER => $header);
/* 根据请求类型设置特定参数 */
switch (strtoupper($method)) {
case 'GET' :
$opts[CURLOPT_URL] = $url . '?' . http_build_query($params);
$opts[CURLOPT_URL] = substr($opts[CURLOPT_URL],0,-1);
break;
case 'POST' :
//判断是否传输文件
$params = http_build_query($params);
$opts[CURLOPT_URL] = $url;
$opts[CURLOPT_POST] = 1;
$opts[CURLOPT_POSTFIELDS] = $params;
break;
default :
}
/* 初始化并执行curl请求 */
$ch = curl_init();
curl_setopt_array($ch, $opts);
$data = curl_exec($ch);
$error = curl_error($ch);
curl_close($ch);
if($error){
$data = null;
}
return $data;
}
|
先本地起一个服务
构造一个poc
试试,
/index.php/index/api/curlfun?url=http://localhost:9080/ssrf.txt
0x02 SSRF
api
控制器下,还存在一个post_url
方法,也存在SSRF
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public function post_curl($url,$data){
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS,$data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$result = curl_exec($ch);
if (curl_errno($ch)) {
print curl_error($ch);
}
curl_close($ch);
return $result;
}
|
0x03 后台 RCE
在admin
模块setup
控制器editconf
方法处,存在文件上传漏洞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
public function editconf()
{
// echo "test";
// if($this->otype != 3){
// echo '死你全家!';exit;
// }
if(input('post.')){
$data = input('post.');
foreach ($data as $k => $v) {
$arr = explode('_',$k);
$_data['id'] = $arr[1];
$_data['value'] = $v;
$file = request()->file('pic_'.$_data['id']);
if($file){
$info = $file->move(ROOT_PATH . 'public' . DS . 'uploads');
if($info){
$_data['value'] = '/public' . DS . 'uploads/'.$info->getSaveName();
}
}
if($_data['value'] == '' && isset($arr[2]) && $arr[2] == 3){
continue;
}
Db::name('config')->update($_data);
}
cache('conf',null);
$this->success('编辑成功');
}
}
|
可以看到直接input
接受参数,并且利用了request()->file
来上传文件
而在thinkphp
中的file
函数,是没有安全设置的
所以可以直接进行上传,
构造poc
在public
目录下面,成功进行了上传
但是文件名字看起来是一串随机的字符串组成的,这怎么办呢?我们根进去查看一下是怎么生成的,跟进move
函数
看到了buildSavename
函数正是文件保存命名规则,
跟进去查看一下,命名规则是date
日期
前面的date
函数是 uploads 下面的目录,而后面的md5(microtime(true))
正是生成的那一串看似随机的字符串,爆破一下即可出文件名
那针对microtime(true)
这样的该如何爆破呢?
在ctfshow 元旦水友赛
中出过这样一道题
https://docs.qq.com/doc/DRlBMcWdhZW9ZUnFB
里面有爆破脚本
在System
控制器下,同样存在类似的功能点