** ๐ 5-1๋จ๊ณ: CORS(Cross-Origin Resource Sharing) ์์ ์ ๋ณต**
Ajax ์์ฒญ์ ๋ณด๋ผ ๋ ๊ฐ์๊ธฐ โCORS ์ค๋ฅโ๊ฐ ๋จ๋์?
๐ ์ด๊ฑด ๋ณด์ ์ ์ฑ ๋๋ฌธ์ด์์! ์ ๊ทธ๋ฐ์ง, ์ด๋ป๊ฒ ํด๊ฒฐํ๋์ง ๊ธฐ์ด๋ถํฐ ์ฌํ๊น์ง ์ค๋ช ๋๋ฆด๊ฒ์.
โ 1. CORS๋?
๐ ์ ์
โCORS(Cross-Origin Resource Sharing)๋
๋ธ๋ผ์ฐ์ ์์ ๋ค๋ฅธ ์ถ์ฒ(origin)์ ์๋ฒ์ Ajax ์์ฒญ์ ๋ณด๋ผ ์ ์๊ฒ ํ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.
๐ง โ์ถ์ฒ(origin)โ๋?
ํญ๋ชฉ | ๊ฐ์ ์ถ์ฒ์ธ๊ฐ? |
---|---|
http://abc.com โ http://abc.com | โ (๊ฐ์) |
http://abc.com โ https://abc.com | โ (ํ๋กํ ์ฝ ๋ค๋ฆ) |
http://abc.com โ http://def.com | โ (๋๋ฉ์ธ ๋ค๋ฆ) |
http://abc.com โ http://abc.com:3000 | โ (ํฌํธ ๋ค๋ฆ) |
๐ฆ ์ถ์ฒ(origin) = ํ๋กํ ์ฝ + ๋๋ฉ์ธ + ํฌํธ๊ฐ ๋ชจ๋ ๊ฐ์์ผ ํจ
โ 2. ์ ์ด๋ฐ ์ ํ์ด ์์๊น?
๋ณด์์ ์ํด์์์! ๐จ
์์
- ๋น์ ์ด abc.com์ ๋ก๊ทธ์ธํ ์ํ์์
- ๋๊ตฐ๊ฐ evil.com์์ ๋ชฐ๋ Ajax๋ก abc.com์ ๊ฐ์ธ์ ๋ณด๋ฅผ ํ์น๋ฉด ์ ๋๊ฒ ์ฃ ?
๐ ๊ทธ๋์ ๋ธ๋ผ์ฐ์ ๋ ๋ค๋ฅธ ์ถ์ฒ์ Ajax ์์ฒญ์ ์์น์ ์ผ๋ก ๋ง์์
โ 3. ๊ทธ๋ผ ์ ์ฐ๋ฆฌ๋ API๋ฅผ ์ฌ์ฉํ ์ ์๋ ๊ฑธ๊น?
๋ฐ๋ก CORS ๋๋ถ์ด์์!
์๋ฒ๊ฐ โ์ด ์์ฒญ์ ๊ด์ฐฎ์~โ๋ผ๊ณ ํ์ฉํ๋ฉด, ๋ธ๋ผ์ฐ์ ๋ ํ๋ฝํฉ๋๋ค.
โ 4. ํต์ฌ ์๋ต ํค๋: Access-Control-Allow-Origin
Access-Control-Allow-Origin: *
๐ ์๋ฏธ
โ โ์ด๋์ ์๋ ๋ค ํ์ฉโ
โ ๋ชจ๋ ์ฌ์ดํธ์์ Ajax ๊ฐ๋ฅ
๐ก ๋ ์์ ํ ์ค์ ์์
Access-Control-Allow-Origin: https://yourfrontend.com
โ ์ค์ง ์ด ์ฌ์ดํธ์์๋ง Ajax ํ์ฉ
โ 5. Preflight ์์ฒญ์ด๋?
โ ์ค์ Ajax ์์ฒญ ์ ์ ๋ธ๋ผ์ฐ์ ๊ฐ ํ๋ฝ๋ฐ๊ธฐ ์ํด ๋ณด๋ด๋ ์ฌ์ ํ์ธ ์์ฒญ
๐ฆ ์ธ์ ๋ฐ์ํ๋์?
์กฐ๊ฑด | Preflight ๋ฐ์ |
---|---|
POST ์์ฒญ์ด๋ฉด์, Content-Type์ด application/json |
โ YES |
PUT , DELETE ์์ฒญ |
โ YES |
์์ฒญ ํค๋์ Authorization , X-Custom-Header ๋ฑ ์ปค์คํ
ํค๋๊ฐ ์์ |
โ YES |
๐ ์ด ๊ฒฝ์ฐ, ๋ธ๋ผ์ฐ์ ๊ฐ ๋จผ์ OPTIONS ์์ฒญ์ ๋ณด๋ ๋๋ค.
๐ฆ Preflight ์์ฒญ ์์
OPTIONS /api/user
Origin: https://frontend.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
โ ์๋ฒ๋ ์ด๋ ๊ฒ ์๋ตํด์ผ ํฉ๋๋ค:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://frontend.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type
โ ์ด๊ฑธ ๋ณด๊ณ ๋ธ๋ผ์ฐ์ ๊ฐ โ์ข์, ์ง์ง ์์ฒญ ๋ณด๋ผ๊ฒ!โ๋ผ๊ณ ํฉ๋๋ค.
โ 6. ์ ๋ฆฌ๋ ์์ฒญ ํ๋ฆ (๋์ํ)
[๋ธ๋ผ์ฐ์ ]
โ โ OPTIONS (Preflight)
[์๋ฒ] โ ์๋ต OK
โ โก ์ค์ Ajax ์์ฒญ (POST/GET ๋ฑ)
[์๋ฒ] โ ์๋ต OK + Access-Control-Allow-Origin
โ 7. ์ค๋ฌด CORS ์ค์ ์ (์๋ฒ ์ฝ๋)
Node.js Express
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "https://yourfrontend.com");
res.header("Access-Control-Allow-Methods", "GET,POST");
res.header("Access-Control-Allow-Headers", "Content-Type");
next();
});
Java Spring (์คํ๋ง CORS ์ค์ )
@CrossOrigin(origins = "https://yourfrontend.com")
@RestController
public class MyController {
...
}
โ 8. ๋ฉด์ ์ง๋ฌธ ์์ + ํด์ค
โ Q. CORS๋ ๋ฌด์์ด๊ณ , ์ ํ์ํฉ๋๊น?
โ A.
- CORS๋ ๋ค๋ฅธ ์ถ์ฒ์์ ์์์ ์์ฒญํ๋ Ajax ์์ฒญ์ ์ ํํ๊ฑฐ๋ ํ์ฉํ๋ ๋ณด์ ์ ์ฑ ์ ๋๋ค.
- ์ถ์ฒ๊ฐ ๋ค๋ฅด๋ฉด ๋ธ๋ผ์ฐ์ ๋ ์์ฒญ์ ์ฐจ๋จํ์ง๋ง,
- ์๋ฒ๊ฐ
Access-Control-Allow-Origin
ํค๋๋ก ํน์ ์ถ์ฒ๋ฅผ ๋ช ์์ ์ผ๋ก ํ์ฉํ๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ์์ฒญ์ ํ์ฉํฉ๋๋ค. POST
,PUT
์์ฒญ์ด๋ ์ปค์คํ ํค๋ ์ฌ์ฉ ์์๋ Preflight(OPTIONS) ์์ฒญ์ด ์ ํ๋ฉ๋๋ค.
โ ์ ์ฒด ์์ฝ ์นด๋
ํญ๋ชฉ | ์ค๋ช |
---|---|
CORS | ๋ค๋ฅธ ๋๋ฉ์ธ์์ ์ค๋ Ajax ์์ฒญ ํ์ฉ ์ ์ฑ |
Origin | ํ๋กํ ์ฝ + ๋๋ฉ์ธ + ํฌํธ |
Access-Control-Allow-Origin | ์๋ฒ๊ฐ ํ์ฉํ๋ ์ถ์ฒ ์ง์ |
Preflight | ์ํํ ์์ฒญ ์ ์ OPTIONS๋ก ์ฌ์ ํ๋ฝ ์์ฒญ |
์์ ํ ์์ฒญ | GET , POST + ๊ธฐ๋ณธ ํค๋๋ง ์ฐ๋ ์์ฒญ |
ํ์ฉ ์๋ต | CORS ํค๋๊ฐ ๋ฐ๋์ ์์ด์ผ ๋ธ๋ผ์ฐ์ ๊ฐ ํ์ฉ |
โ 5-2๋จ๊ณ: CSRF(Cross-Site Request Forgery) ๋ฐฉ์ง ์ ๋ต ์์ ์ ๋ณต
CSRF๋ ์ฌ์ฉ์์ ์๋์ ๋ฌด๊ดํ๊ฒ ์ ์์ ์ธ ์์ฒญ์ด ์๋์ผ๋ก ์๋ฒ์ ์ ์ก๋๋ ๊ณต๊ฒฉ์ ๋๋ค.
๋ธ๋ผ์ฐ์ ์ Ajax ๊ธฐ๋ฐ ์ธ์ฆ์์ ๋ฐ๋์ ๋ฐฉ์ดํด์ผ ํ๋ ๋ณด์ ์ํ์ด์์.
โ 1. CSRF ๊ณต๊ฒฉ์ด๋?
๐ฆ ์ ์
Cross-Site Request Forgery = ์ฌ์ดํธ ๊ฐ ์์ฒญ ์์กฐ
๐ฆ๐ป ์ฌ์ด ์ค๋ช
๋ก๊ทธ์ธ๋ ์ฌ์ฉ์๊ฐ ๋ฌด์ฌ์ฝ ์ ์ฑ ์ฌ์ดํธ๋ฅผ ์ด์๋๋ฐ,
๊ทธ ์ฌ์ดํธ๊ฐ ๋ชฐ๋ ๋ด ์ฟ ํค๋ฅผ ์ฌ์ฉํด์ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด๋ฒ๋ฆฌ๋ ๊ณต๊ฒฉ!
โ ์์ ์๋๋ฆฌ์ค
- ์ฌ์ฉ์๊ฐ
bank.com
์ ๋ก๊ทธ์ธ (์ฟ ํค๋ก ์ธ์ฆ๋จ) - ๊ณต๊ฒฉ์๊ฐ
evil.com
์ ๊ฐ์ง ์์ฒญ์ ์จ๊ฒจ๋ - ์ฌ์ฉ์๊ฐ
evil.com
์ ๋ฐฉ๋ฌธํ์๋ง์, ๋ค์ ์์ฒญ์ด ์๋์ผ๋ก ์คํ๋จ:
<img src="https://bank.com/transfer?to=hacker&amount=10000">
โ ์ฟ ํค๋ ์๋์ผ๋ก ํจ๊ป ์ ์ก๋จ
โ ์ฌ์ฉ์๋ ์๋ฌด๊ฒ๋ ์ ํ๋๋ฐ ๋์ด ๋น ์ ธ๋๊ฐ!
โ 2. ์ ๋ฐ์ํ ๊น?
- ๋ธ๋ผ์ฐ์ ๋ ๊ฐ์ ์ฌ์ดํธ๋ก ๊ฐ๋ ์์ฒญ์ด๋ฉด ์ฟ ํค๋ฅผ ์๋์ผ๋ก ๋ฃ์ด์ค
- ์ ์ฑ ์ฌ์ดํธ๋ ๋์ผํ ์ฃผ์๋ก ์์ฒญํ๋ฉด ์ฟ ํค๊ฐ ๋ฐ๋ผ๊ฐ
- ์๋ฒ๋ ์์ฒญ์ด โ์ง์ง ์ฌ์ฉ์โ๋ผ๊ณ ์ฐฉ๊ฐํจ
โ 3. ๋ฐฉ์ง ๋ฐฉ๋ฒ โ : SameSite ์ฟ ํค ์ ์ฑ
๐ฆ SameSite ์์ฑ์ด๋?
๋ธ๋ผ์ฐ์ ๊ฐ ์ด ์ฟ ํค๋ ๋ค๋ฅธ ์ฌ์ดํธ์์ ์๋์ผ๋ก ๋ณด๋ด์ง ๋ง์๋ผ ๋ผ๊ณ ์ค์ ํ๋ ๋ฐฉ๋ฒ
SameSite ์ต์ 3๊ฐ์ง
์ค์ | ์ค๋ช |
---|---|
Strict |
๋ค๋ฅธ ์ฌ์ดํธ์์ ์ค๋ ์์ฒญ์๋ ์ ๋ ์ฟ ํค ์ ๋ณด๋ ๐ |
Lax |
GET ์์ฒญ๋ง ์ฟ ํค ๋ณด๋ (ํผ, ๋งํฌ ํด๋ฆญ ์ ๋) |
None |
๋ชจ๋ ์์ฒญ์ ์ฟ ํค ๋ณด๋ (๋จ Secure ํ์) โ ๏ธ |
โ ์ถ์ฒ ์ค์ (๋ณด์ ์ฐ์ )
Set-Cookie: sessionId=abc123; SameSite=Strict; Secure
โ ์ด๋ ๊ฒ ํ๋ฉด ์ธ๋ถ ์ฌ์ดํธ์์ Ajax ์์ฒญํด๋ ์ฟ ํค๊ฐ ๋ฐ๋ผ๊ฐ์ง ์์
โ CSRF ๋ฐฉ์ง ์๋ฃ! ๐ก๏ธ
โ 4. ๋ฐฉ์ง ๋ฐฉ๋ฒ โก: CSRF ํ ํฐ ์ฌ์ฉ
๐ฆ ๊ฐ๋
ํด๋ผ์ด์ธํธ(๋ธ๋ผ์ฐ์ )์ ์๋ฒ๊ฐ ๋ฐ๊ธํ CSRF ํ ํฐ์ ์จ๊ฒจ์ ์ ์ฅํ๊ณ ,
Ajax ์์ฒญ๋ง๋ค ํจ๊ป ๋ณด๋ด์ ์๋ฒ๊ฐ โ์ ์์ ์ธ ์์ฒญ์ธ์งโ๋ฅผ ํ์ธํ๋ ๋ฐฉ์
๐ฆ๐ป ์ฌ์ด ์ค๋ช
์ผ์ข ์ โ๋น๋ฐ ๋์ฅโ์ ์ฃผ๊ณ , ์์ฒญํ ๋๋ง๋ค ๋์ฅ์ ์ฐ์ด์ ๋ณด๋ธ๋ค๊ณ ๋ณด๋ฉด ๋ฉ๋๋ค.
๐ง ํ๋ฆ ์์
- ์ฌ์ฉ์๊ฐ ํ์ด์ง์ ์ ๊ทผํ๋ฉด ์๋ฒ๊ฐ CSRF ํ ํฐ์ ์์ฑํด์
<meta>
ํ๊ทธ์ ๋ฃ์
<meta name="csrf-token" content="abc123">
- Ajax ์์ฒญ ์ ์ด ๊ฐ์
ํค๋
๋ก ํจ๊ป ๋ณด๋
const csrf = document.querySelector("meta[name='csrf-token']").content;
fetch("/transfer", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": csrf // ๐ ํ ํฐ ์ ์ก
},
body: JSON.stringify({ to: "friend", amount: 100 })
});
- ์๋ฒ๋ ์ด ํ ํฐ์ด ์ ํจํ์ง ๊ฒ์ฌํ๊ณ ์์ฒญ์ ํ์ฉ
โ ์๋ฒ์์ ํ๋ ์ผ
// ์๋ฒ์์ ์ธ์
์ ์ ์ฅ๋ ํ ํฐ๊ณผ ์์ฒญ์์ ๋ฐ์ ํ ํฐ์ ๋น๊ต
if (req.headers['x-csrf-token'] !== req.session.csrfToken) {
return res.status(403).send("CSRF ๊ณต๊ฒฉ ํ์ง๋จ!");
}
โ 5. CSRF ํ ํฐ + SameSite ์กฐํฉ = ์ต๊ฐ ๋ณด์
์ ๋ต | ์ค๋ช |
---|---|
SameSite=Strict | ์ธ๋ถ ์์ฒญ์ ์ฟ ํค ์ ๋ณด๋ |
CSRF ํ ํฐ | ๋ด๋ถ ์์ฒญ์ด๋ผ๋ ํ ํฐ ํ์ธ ์ ๋๋ฉด ์ฐจ๋จ |
โ ๋ ๋ค ์ฐ๋ฉด ์ค์ ์์ด ์๋ฒฝ ๋ฐฉ์ด ๊ฐ๋ฅ!
โ 6. ์ค๋ฌด ํ: ํ๋ก ํธ์๋์์ ์์ ํ๊ฒ ํ ํฐ ๋ณด๋ด๋ ๋ฒ
- ์๋ฒ๊ฐ
<meta>
ํ๊ทธ ๋๋cookie
์ ํ ํฐ์ ๋ฃ์ด๋ - JavaScript๊ฐ Ajax ์์ฒญ์
X-CSRF-Token
ํค๋๋ก ํจ๊ป ์ ์ก - ์๋ฒ๋ ์ด ๊ฐ์ ์ธ์ ๋๋ DB์ ์ ์ฅ๋ ๊ฐ๊ณผ ๋น๊ต
โ ์ด๋ ๊ฒ ํ๋ฉด ํผ ์ ์ก๋, Ajax๋ ์์ ํ๊ฒ ๋์
โ ๋ฉด์ ์ง๋ฌธ ์์ + ํด์ค
โ Q. CSRF ๊ณต๊ฒฉ์ด๋ ๋ฌด์์ด๋ฉฐ, ์ด๋ฅผ ์ด๋ป๊ฒ ๋ฐฉ์ดํ์๊ฒ ์ต๋๊น?
โ A.
- ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ๋ ์ํ์์ ์ธ๋ถ ์ฌ์ดํธ๊ฐ ์ฟ ํค๋ฅผ ์๋์ผ๋ก ์ฌ์ฉํ๋ ์์ฒญ์ ๋ณด๋ด ์ฌ์ฉ์ ๋ชจ๋ฅด๊ฒ ์์ฒญ์ด ์คํ๋๋ ๊ณต๊ฒฉ์ ๋๋ค.
- ์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ๋ธ๋ผ์ฐ์ ๊ฐ ์ฟ ํค๋ฅผ ์๋์ผ๋ก ๋ณด๋ด์ง ์๋๋ก
SameSite=Strict
์์ฑ์ ์ค์ ํ๊ณ , - Ajax ์์ฒญ ์ ์๋ฒ๊ฐ ๋ฐ๊ธํ CSRF ํ ํฐ์ ํจ๊ป ์ ์กํ๊ณ ๊ฒ์ฆํ๋ ๋ฐฉ์์ ์ฌ์ฉํฉ๋๋ค.
โ ์ ์ฒด ์์ฝ ์นด๋
๊ฐ๋ | ์ค๋ช |
---|---|
CSRF | ๋ค๋ฅธ ์ฌ์ดํธ๊ฐ ์๋์ผ๋ก ์ธ์ฆ๋ ์์ฒญ์ ๋ณด๋ด๋ ๊ณต๊ฒฉ |
SameSite | ์ฟ ํค ์๋ ์ ์ก ์ ํ (Strict๊ฐ ๊ฐ์ฅ ์์ ) |
CSRF ํ ํฐ | ์๋ฒ๊ฐ ๋ฐ๊ธํ ๋์ฅ์ฒ๋ผ, ์์ฒญ๋ง๋ค ํจ๊ป ๋ณด๋ด์ ์ธ์ฆ |
๋ฐฉ์ด ์ ๋ต | SameSite + CSRF ํ ํฐ ์กฐํฉ |
Ajax ์ ์ฉ | ํค๋์ X-CSRF-Token ํฌํจํด์ ์๋ฒ ๊ฒ์ฆ ์ ๋ |
โ 5-3๋จ๊ณ: XSS(Cross-Site Scripting) ๋ฐฉ์ง์ ์์ ํ JSON ์๋ต ์ค๊ณ
๐ฆ XSS๋ ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๊ฐ์ด ์คํฌ๋ฆฝํธ๋ก ์คํ๋์ด ํดํน๋๋ ๋ํ์ ์ธ ๊ณต๊ฒฉ์ ๋๋ค.
์๋ฒ๊ฐ JSON ์๋ต์ ์ค ๋๋ XSS์ ๋งค์ฐ ์ฃผ์ํด์ผ ํฉ๋๋ค.
โ 1. XSS๋?
๐ ์ ์
XSS = Cross-Site Scripting
๋ค๋ฅธ ์ฌ๋์ ๋ธ๋ผ์ฐ์ ์์ ์๋ฐ์คํฌ๋ฆฝํธ๊ฐ ๊ฐ์ ๋ก ์คํ๋๊ฒ ๋ง๋๋ ๊ณต๊ฒฉ
๐ฆ๐ป ์ฌ์ด ์
<!-- ์ฌ์ฉ์๊ฐ ์
๋ ฅ -->
<script>alert("ํดํน๋จ")</script>
โ ์ด๊ฒ ๊ทธ๋๋ก ํ๋ฉด์ ์ถ๋ ฅ๋๋ฉด?
โ ๐ฅ ๋ค๋ฅธ ์ฌ์ฉ์์ ํ๋ฉด์์ ๊ฒฝ๊ณ ์ฐฝ์ด ๋จ๊ฑฐ๋,
โ ๐ต๏ธโโ๏ธ ์ฟ ํค๊ฐ ํ์ณ์ง๊ณ , ๋น๋ฐ๋ฒํธ๊ฐ ํ์ทจ๋ ์ ์์ด์.
โ ํนํ ์ํํ ๊ฒฝ์ฐ
- ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๋ด์ฉ์ด ์๋ฒ์ ์ ์ฅ๋จ
- ๋ค๋ฅธ ์ฌ์ฉ์๊ฐ ๊ทธ ๋ด์ฉ์ HTML๋ก ๋ณผ ๋
<script>
๊ฐ ๊ทธ๋๋ก ์คํ๋จ - ๋๋ Ajax๋ก ๋ฐ์์จ JSON ๋ฐ์ดํฐ์ ์คํฌ๋ฆฝํธ๊ฐ ํฌํจ๋ผ ์์ผ๋ฉด?
โ ๋ฐ๋ก JSON XSS๊ฐ ํฐ์ง ์ ์์ด์!
โ 2. JSON ์๋ต์ ์ ์ํํ ๊น?
๐ฆ ์์ (์ํํ ์๋ต)
{
"username": "<script>alert('XSS')</script>"
}
๐จ๐ปโ๐ซ ์ด๊ฑธ HTML์ ์ด๋ ๊ฒ ์ถ๋ ฅํ๋ฉด?
<p>${data.username}</p>
โ ๊ฒฐ๊ณผ:
<p><script>alert('XSS')</script></p>
โ ๐ฅ ์๋ฐ์คํฌ๋ฆฝํธ ์คํ๋จ โ ๋ธ๋ผ์ฐ์ ํดํน๋จ
โ 3. ์์ ํ JSON ์๋ต ์ค๊ณ ๋ฐฉ๋ฒ
โ 1) Content-Type ๋ช ํํ ์ค์
Content-Type: application/json
- ๋ธ๋ผ์ฐ์ ์๊ฒ โ์ด๊ฑด ์คํฌ๋ฆฝํธ๊ฐ ์๋๋ผ JSON์ด์ผ!โ ๋ผ๊ณ ์๋ ค์ฃผ๋ ๊ฒ
- โ
text/html
์ด๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ HTML์ฒ๋ผ ๋ ๋๋งํ๋ ค ํจ โ ์ํ
โ 2) ๋ฌธ์์ด ์ด์ค์ผ์ดํ(escape) ์ฒ๋ฆฌ
๐ฆ ์ด์ค์ผ์ดํ๋?
โ, <, > ๋ฑ HTML์์ ์ํํ ๋ฌธ์๋ฅผ ๋ค๋ฅธ ์ฝ๋๋ก ๋ฐ๊ฟ์ ๋ฌดํดํํ๋ ๊ฒ
์์
์๋ ๋ฌธ์ | ์ด์ค์ผ์ดํ |
---|---|
< |
\u003c |
> |
\u003e |
& |
\u0026 |
' |
\u0027 |
๐ฆ ์์ ํ JSON ์์
{
"username": "\u003cscript\u003ealert('XSS')\u003c/script\u003e"
}
โ ๋ธ๋ผ์ฐ์ ๋ ์ด๊ฑธ ์คํฌ๋ฆฝํธ๊ฐ ์๋ ๋จ์ ๋ฌธ์์ด๋ก ์ฒ๋ฆฌ
โ โ ์ ๋ ์คํ๋์ง ์์!
โ 4. ์๋ฒ์์ ์์ ํ๊ฒ JSON ์๋ตํ๋ ๋ฐฉ๋ฒ
Node.js (Express)
res.setHeader("Content-Type", "application/json");
res.send(JSON.stringify(data)
.replace(/</g, "\\u003c")
.replace(/>/g, "\\u003e")
.replace(/&/g, "\\u0026")
.replace(/'/g, "\\u0027"));
Java (Spring)
// Jackson JSON ์ค์
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper()
.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
}
โ 5. ์ถ๊ฐ ๋ณด์: Content Security Policy (CSP)
๐ฆ CSP๋?
๋ธ๋ผ์ฐ์ ์๊ฒ โ์ด ์ฌ์ดํธ์์ ์ธ๋ถ ์คํฌ๋ฆฝํธ ์คํ ๊ธ์งํด์ค!โ๋ผ๊ณ ๋ช ๋ นํ๋ ๋ณด์ ํค๋
์์
Content-Security-Policy: default-src 'self';
'self'
โ ๋ด ๋๋ฉ์ธ ์ธ์๋ ์คํฌ๋ฆฝํธ ์คํ ๊ธ์ง<script src="http://hacker.com/xss.js">
โ ์ฐจ๋จ๋จ โ
โ 6. ๋ฉด์ ์ง๋ฌธ ์์ + ํด์ค
โ Q. JSON ์๋ต์์๋ XSS๊ฐ ๋ฐ์ํ ์ ์๋์? ๊ทธ๋ ๋ค๋ฉด ์ด๋ป๊ฒ ๋ฐฉ์งํ๋์?
โ A.
- JSON ์๋ต ๋ด์ฉ์
<script>
๊ฐ์ ํ๊ทธ๊ฐ ํฌํจ๋๋ฉด, ์ด๋ฅผ ํ๋ฉด์ ์ถ๋ ฅํ๋ ์๊ฐ XSS๊ฐ ์คํ๋ ์ ์์ต๋๋ค. - ์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์๋ฒ๋
Content-Type: application/json
์ ๋ช ์ํ๊ณ , - JSON ๋ฌธ์์ด ๋ด
<
,>
,&
,'
๋ฑ์ ํน์๋ฌธ์๋ฅผ ์ ๋์ฝ๋๋ก ์ด์ค์ผ์ดํ ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค. - ๋ํ, ๋ธ๋ผ์ฐ์ ์๋
Content-Security-Policy
๋ฅผ ์ ์ฉํด ์ธ๋ถ ์คํฌ๋ฆฝํธ ์คํ์ ์ฐจ๋จํ๋ ๊ฒ๋ ํจ๊ณผ์ ์ ๋๋ค.
โ ์ ์ฒด ์์ฝ ์นด๋
ํญ๋ชฉ | ์ค๋ช |
---|---|
XSS | ์ ์ฑ ์คํฌ๋ฆฝํธ๊ฐ ์ฌ์ฉ์ ๋ธ๋ผ์ฐ์ ์์ ์คํ๋๋ ๊ณต๊ฒฉ |
JSON XSS | JSON ์์ <script> ๊ฐ ํฌํจ๋์ด ์์ผ๋ฉด ์ํ |
Content-Type | application/json ์ผ๋ก ๋ช
ํํ ์ค์ |
์ด์ค์ผ์ดํ | < , > , & ๋ฑ์ ์์ ํ ์ฝ๋๋ก ๋ณํ |
CSP | ์ธ๋ถ ์คํฌ๋ฆฝํธ ์คํ ์ฐจ๋จ ๋ณด์ ์ ์ฑ |