缓存投毒进阶 -- justctf 2025 Busy Traffic

题目核心逻辑如下

go 复制代码
let browser; // 全局浏览器实例

// 访问指定 URL 的异步函数
const visit = async (url) => {
    try {
        // 如果已有浏览器实例,先关闭并等待 2 秒
        if (browser) {
            await browser.close();
            await sleep(2000);
            console.log("Terminated ongoing job.");
        }
        // 启动 puppeteer,使用 chrome,无头模式
        browser = await puppeteer.launch({
            browser: 'chrome',
            headless: true,
            args: [
                "--disable-features=HttpsFirstBalancedModeAutoEnable",
                "--no-sandbox"
            ]
        });

        // 创建新的浏览器上下文
        const ctx = await browser.createBrowserContext();

        // 新建页面,访问 flag 页面并设置 localStorage
        page = await ctx.newPage();
        await page.goto(`http://traefik/flag`, { timeout: 3000, waitUntil: 'domcontentloaded' });
        await page.evaluate((flag) => {
            localStorage.setItem('flag', flag); // 将 flag 存入 localStorage
        }, FLAG);
        await sleep(500); // 等待 0.5 秒
        await page.close(); // 关闭页面

        // 新建页面,访问用户提交的 URL
        page = await ctx.newPage();
        await page.goto(url, { timeout: 3000, waitUntil: 'domcontentloaded' });

        await sleep(1000 * 60 * 2); // 停留 2 分钟

        await browser.close(); // 关闭浏览器
        browser = null;
    } catch (err) {
        // 捕获并打印异常
        console.log(err);
    } finally {
        // 最终关闭浏览器并打印日志
        console.log('close');
        if (browser) await browser.close();
    }
};
yml 复制代码
http:
  routers:
    bot:
      rule: 'PathPrefix(`/bot`)'
      service: bot
    dashboard:
      rule: "PathPrefix(`/dashboard`) || PathPrefix(`/api`)"
      service: api@internal

  services:
    bot:
      loadBalancer:
        servers:
          - url: "http://bot:3000"

  middlewares:
    cache-on-steroids:
      plugin:
        plugin-simplecache:
          path: /tmp/
          CacheQueryParams: True

    maxage:
      headers:
        customResponseHeaders:
          Cache-Control: "max-age=20"
    coop:
      headers:
        customResponseHeaders:
          Cross-Origin-Opener-Policy: "same-origin"
yml 复制代码
log:
  level: INFO

accessLog: {}

entryPoints:
  web:
    address: ":80"
    http:
      middlewares:
        - maxage
        - coop
        - cache-on-steroids

providers:
  file:
    filename: /config/dynamic.yml

api:
  dashboard: true

experimental:
  plugins:
    plugin-simplecache:
      moduleName: "github.com/scrazy77/plugin-simplecache-nocache"
      version: "v0.0.5"

服务器使用了此插件,插件存在默认不安全行为,忽略查询参数作为缓存判断的关键

go 复制代码
// Config 配置中间件的结构体。
type Config struct {
	Path               string   `json:"path" yaml:"path" toml:"path"`                         // 缓存文件存储路径
	MaxExpiry          int      `json:"maxExpiry" yaml:"maxExpiry" toml:"maxExpiry"`          // 缓存最大过期时间(秒)
	Cleanup            int      `json:"cleanup" yaml:"cleanup" toml:"cleanup"`                // 清理周期(秒)
	AddStatusHeader    bool     `json:"addStatusHeader" yaml:"addStatusHeader" toml:"addStatusHeader"` // 是否添加缓存状态头
	CacheQueryParams   bool     `json:"cacheQueryParams" yaml:"cacheQueryParams" toml:"cacheQueryParams"` // 是否缓存查询参数
	ForceNoCacheHeader bool     `json:"forceNoCacheHeader" yaml:"forceNoCacheHeader" toml:"forceNoCacheHeader"` // 是否强制添加 no-cache 头
	BlacklistedHeaders []string `json:"blacklistedHeaders" yaml:"blacklistedHeaders" toml:"blacklistedHeaders"` // 黑名单头部,命中则不缓存
}

// CreateConfig 返回一个默认配置实例。
func CreateConfig() *Config {
	return &Config{
		MaxExpiry:          int((5 * time.Minute).Seconds()), // 默认最大过期时间5分钟
		Cleanup:            int((5 * time.Minute).Seconds()), // 默认清理周期5分钟
		AddStatusHeader:    true,                             // 默认添加缓存状态头
		CacheQueryParams:   false,                            // 默认不缓存查询参数
		ForceNoCacheHeader: false,                            // 默认不强制 no-cache
		BlacklistedHeaders: []string{},                       // 默认无黑名单头部
	}
}

可以通过使用 Host: traefik 标头发送请求来在 http://traefik 上执行缓存中毒。如果服务器后端没有正确处理host头,缓存中毒有可能使得攻击者凭空捏造出内部内部可访问的url(http://fake/)
可以通过使用 Host: traefik 标头发送请求来在 http://traefik 上执行缓存中毒。
缓存键仅由 HTTP 方法、主机名和路径组成,默认情况下甚至不包括查询参数缓存键完全忽略了 Range 请求头,这意味着具有不同/没有 Range 的请求会命中相同的缓存条目

go 复制代码
// cacheKey 生成缓存键。
func (m *cache) cacheKey(r *http.Request) string {
	if m.cfg.CacheQueryParams {
		return r.Method + r.Host + r.URL.Path + r.URL.RawQuery // 包含查询参数
	}

	return r.Method + r.Host + r.URL.Path // 不包含查询参数
}

我们只需想办法配合题目不缓存参数的错误从服务器上凑齐如下"ROP"的方法即可

html 复制代码
<iframe src="URL" name="XSS_payload">
Gadget 代码 作用
e=this 拿到全局对象
t=e.name 读取 window.name
i=t 复制变量
s=2 设置延时
setTimeout(i,t,s) 执行 XSS

我们还可以利用这一点使得不同的路径返回同一文件的不同部分

复制代码
http://a/b/c.txt?/../d.txt -> http://a/d.txt

配合题目不缓存参数的错误,想办法凑出即可
Default-qPSf0Yui.js/..%2f..%2f/#/udp/a 加载 index-BH-fqmTU.jsapi-DHmvWmr7.js,而 #/udp/routers 加载更多模块。
完整利用脚本

繁忙的交通 |justCTF 2025 --- Busy Traffic | justCTF 2025

相关推荐
xkxnq2 分钟前
第二阶段:Vue 组件化开发(第 18天)
前端·javascript·vue.js
晓得迷路了3 分钟前
栗子前端技术周刊第 112 期 - Rspack 1.7、2025 JS 新星榜单、HTML 状态调查...
前端·javascript·html
怕浪猫6 分钟前
React从入门到出门 第五章 React Router 配置与原理初探
前端·javascript·react.js
jinmo_C++6 分钟前
从零开始学前端 · HTML 基础篇(一):认识 HTML 与页面结构
前端·html·状态模式
鹏多多12 分钟前
前端2025年终总结:借着AI做大做强再创辉煌
前端·javascript
青云交14 分钟前
Java 大视界 -- 基于 Java+Redis Cluster 构建分布式缓存系统:实战与一致性保障(444)
java·redis·缓存·缓存穿透·分布式缓存·一致性保障·java+redis clus
哈__15 分钟前
React Native 鸿蒙跨平台开发:Vibration 实现鸿蒙端设备的震动反馈
javascript·react native·react.js
WebGISer_白茶乌龙桃18 分钟前
Cesium实现“悬浮岛”式,三维立体的行政区划
javascript·vue.js·3d·web3·html5·webgl
小Tomkk21 分钟前
⭐️ StarRocks Web 使用介绍与实战指南
前端·ffmpeg
不一样的少年_25 分钟前
产品催: 1 天优化 Vue 官网 SEO?我用这个插件半天搞定(不重构 Nuxt)
前端·javascript·vue.js