题目核心逻辑如下
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.js
和api-DHmvWmr7.js
,而#/udp/routers
加载更多模块。
完整利用脚本