** ๐Ÿ“Œ 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. ์™œ ์ด๋Ÿฐ ์ œํ•œ์ด ์žˆ์„๊นŒ?

๋ณด์•ˆ์„ ์œ„ํ•ด์„œ์˜ˆ์š”! ๐Ÿ˜จ

์˜ˆ์‹œ

๐Ÿ‘‰ ๊ทธ๋ž˜์„œ ๋ธŒ๋ผ์šฐ์ €๋Š” ๋‹ค๋ฅธ ์ถœ์ฒ˜์˜ 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 ์š”์ฒญ ํ—ˆ์šฉ ์ •์ฑ…
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 = ์‚ฌ์ดํŠธ ๊ฐ„ ์š”์ฒญ ์œ„์กฐ


๐Ÿ‘ฆ๐Ÿป ์‰ฌ์šด ์„ค๋ช…

๋กœ๊ทธ์ธ๋œ ์‚ฌ์šฉ์ž๊ฐ€ ๋ฌด์‹ฌ์ฝ” ์•…์„ฑ ์‚ฌ์ดํŠธ๋ฅผ ์—ด์—ˆ๋Š”๋ฐ,

๊ทธ ์‚ฌ์ดํŠธ๊ฐ€ ๋ชฐ๋ž˜ ๋‚ด ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด๋ฒ„๋ฆฌ๋Š” ๊ณต๊ฒฉ!


โ— ์˜ˆ์‹œ ์‹œ๋‚˜๋ฆฌ์˜ค

  1. ์‚ฌ์šฉ์ž๊ฐ€ bank.com์— ๋กœ๊ทธ์ธ (์ฟ ํ‚ค๋กœ ์ธ์ฆ๋จ)
  2. ๊ณต๊ฒฉ์ž๊ฐ€ evil.com์— ๊ฐ€์งœ ์š”์ฒญ์„ ์ˆจ๊ฒจ๋‘ 
  3. ์‚ฌ์šฉ์ž๊ฐ€ 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 ์š”์ฒญ๋งˆ๋‹ค ํ•จ๊ป˜ ๋ณด๋‚ด์„œ ์„œ๋ฒ„๊ฐ€ โ€œ์ •์ƒ์ ์ธ ์š”์ฒญ์ธ์ง€โ€๋ฅผ ํ™•์ธํ•˜๋Š” ๋ฐฉ์‹


๐Ÿ‘ฆ๐Ÿป ์‰ฌ์šด ์„ค๋ช…

์ผ์ข…์˜ โ€œ๋น„๋ฐ€ ๋„์žฅโ€์„ ์ฃผ๊ณ , ์š”์ฒญํ•  ๋•Œ๋งˆ๋‹ค ๋„์žฅ์„ ์ฐ์–ด์„œ ๋ณด๋‚ธ๋‹ค๊ณ  ๋ณด๋ฉด ๋ฉ๋‹ˆ๋‹ค.


๐Ÿ”ง ํ๋ฆ„ ์ˆœ์„œ

  1. ์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€์— ์ ‘๊ทผํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ CSRF ํ† ํฐ์„ ์ƒ์„ฑํ•ด์„œ <meta> ํƒœ๊ทธ์— ๋„ฃ์Œ
<meta name="csrf-token" content="abc123">
  1. 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 })
});
  1. ์„œ๋ฒ„๋Š” ์ด ํ† ํฐ์ด ์œ ํšจํ•œ์ง€ ๊ฒ€์‚ฌํ•˜๊ณ  ์š”์ฒญ์„ ํ—ˆ์šฉ

โœ… ์„œ๋ฒ„์—์„œ ํ•˜๋Š” ์ผ

// ์„œ๋ฒ„์—์„œ ์„ธ์…˜์— ์ €์žฅ๋œ ํ† ํฐ๊ณผ ์š”์ฒญ์—์„œ ๋ฐ›์€ ํ† ํฐ์„ ๋น„๊ต
if (req.headers['x-csrf-token'] !== req.session.csrfToken) {
  return res.status(403).send("CSRF ๊ณต๊ฒฉ ํƒ์ง€๋จ!");
}

โœ… 5. CSRF ํ† ํฐ + SameSite ์กฐํ•ฉ = ์ตœ๊ฐ• ๋ณด์•ˆ

์ „๋žต ์„ค๋ช…
SameSite=Strict ์™ธ๋ถ€ ์š”์ฒญ์—” ์ฟ ํ‚ค ์•ˆ ๋ณด๋ƒ„
CSRF ํ† ํฐ ๋‚ด๋ถ€ ์š”์ฒญ์ด๋ผ๋„ ํ† ํฐ ํ™•์ธ ์•ˆ ๋˜๋ฉด ์ฐจ๋‹จ

โ†’ ๋‘˜ ๋‹ค ์“ฐ๋ฉด ์‹ค์ˆ˜ ์—†์ด ์™„๋ฒฝ ๋ฐฉ์–ด ๊ฐ€๋Šฅ!


โœ… 6. ์‹ค๋ฌด ํŒ: ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ ํ† ํฐ ๋ณด๋‚ด๋Š” ๋ฒ•

โœ… ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํผ ์ „์†ก๋„, Ajax๋„ ์•ˆ์ „ํ•˜๊ฒŒ ๋™์ž‘


โœ… ๋ฉด์ ‘ ์งˆ๋ฌธ ์˜ˆ์‹œ + ํ•ด์„ค


โ“ Q. CSRF ๊ณต๊ฒฉ์ด๋ž€ ๋ฌด์—‡์ด๋ฉฐ, ์ด๋ฅผ ์–ด๋–ป๊ฒŒ ๋ฐฉ์–ดํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?

โœ… A.


โœ… ์ „์ฒด ์š”์•ฝ ์นด๋“œ

๊ฐœ๋… ์„ค๋ช…
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>

โ†’ ์ด๊ฒŒ ๊ทธ๋Œ€๋กœ ํ™”๋ฉด์— ์ถœ๋ ฅ๋˜๋ฉด?

โ†’ ๐Ÿ”ฅ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ํ™”๋ฉด์—์„œ ๊ฒฝ๊ณ ์ฐฝ์ด ๋œจ๊ฑฐ๋‚˜,

โ†’ ๐Ÿ•ต๏ธโ€โ™‚๏ธ ์ฟ ํ‚ค๊ฐ€ ํ›”์ณ์ง€๊ณ , ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ํƒˆ์ทจ๋  ์ˆ˜ ์žˆ์–ด์š”.


โ— ํŠนํžˆ ์œ„ํ—˜ํ•œ ๊ฒฝ์šฐ

โ†’ ๋ฐ”๋กœ 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

โœ… 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';

โœ… 6. ๋ฉด์ ‘ ์งˆ๋ฌธ ์˜ˆ์‹œ + ํ•ด์„ค


โ“ Q. JSON ์‘๋‹ต์—์„œ๋„ XSS๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‚˜์š”? ๊ทธ๋ ‡๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ฐฉ์ง€ํ•˜๋‚˜์š”?

โœ… A.


โœ… ์ „์ฒด ์š”์•ฝ ์นด๋“œ

ํ•ญ๋ชฉ ์„ค๋ช…
XSS ์•…์„ฑ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‚ฌ์šฉ์ž ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰๋˜๋Š” ๊ณต๊ฒฉ
JSON XSS JSON ์•ˆ์— <script>๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉด ์œ„ํ—˜
Content-Type application/json์œผ๋กœ ๋ช…ํ™•ํžˆ ์„ค์ •
์ด์Šค์ผ€์ดํ”„ <, >, & ๋“ฑ์„ ์•ˆ์ „ํ•œ ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜
CSP ์™ธ๋ถ€ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ ์ฐจ๋‹จ ๋ณด์•ˆ ์ •์ฑ