題目核心邏輯如下
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 頁面并設置 localStoragepage = 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(); // 關閉頁面// 新建頁面,訪問用戶提交的 URLpage = 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();}
};
http:routers:bot:rule: 'PathPrefix(`/bot`)'service: botdashboard:rule: "PathPrefix(`/dashboard`) || PathPrefix(`/api`)"service: api@internalservices:bot:loadBalancer:servers:- url: "http://bot:3000"middlewares:cache-on-steroids:plugin:plugin-simplecache:path: /tmp/CacheQueryParams: Truemaxage:headers:customResponseHeaders:Cache-Control: "max-age=20"coop:headers:customResponseHeaders:Cross-Origin-Opener-Policy: "same-origin"
log:level: INFOaccessLog: {}entryPoints:web:address: ":80"http:middlewares:- maxage- coop- cache-on-steroidsproviders:file:filename: /config/dynamic.ymlapi:dashboard: trueexperimental:plugins:plugin-simplecache:moduleName: "github.com/scrazy77/plugin-simplecache-nocache"version: "v0.0.5"
服務器使用了此插件,插件存在默認不安全行為,忽略查詢參數作為緩存判斷的關鍵
// 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-cacheBlacklistedHeaders: []string{}, // 默認無黑名單頭部}
}
可以通過使用?
Host: traefik
?標頭發送請求來在?http://traefik
?上執行緩存中毒。如果服務器后端沒有正確處理host頭,緩存中毒有可能使得攻擊者憑空捏造出內部內部可訪問的url(http://fake/)
可以通過使用?
Host: traefik
?標頭發送請求來在?http://traefik
?上執行緩存中毒。
緩存鍵僅由 HTTP?方法、主機名和路徑組成,默認情況下甚至不包括查詢參數緩存鍵完全忽略了?Range?請求頭,這意味著具有不同/沒有 Range 的請求會命中相同的緩存條目
// 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”的方法即可
<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.js
?和?api-DHmvWmr7.js
,而?#/udp/routers
?加載更多模塊。
完整利用腳本
繁忙的交通 |justCTF 2025 — Busy Traffic | justCTF 2025