๐Ÿ”ฅ CSRF(Cross-Site Request Forgery)๋ž€?

์„ํ˜„ยท2025๋…„ 2์›” 17์ผ
0

Insight

๋ชฉ๋ก ๋ณด๊ธฐ
20/43

๐Ÿค” "๋‚ด๊ฐ€ ํด๋ฆญํ–ˆ์„ ๋ฟ์ธ๋ฐ, ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋ฐ”๋€Œ์—ˆ๋‹ค?!"

์–ผ๋งˆ ์ „ ํŒ€์›๊ณผ ๋ณด์•ˆ ๊ด€๋ จ ์ด์•ผ๊ธฐ๋ฅผ ๋‚˜๋ˆ„๋‹ค๊ฐ€ CSRF(Cross-Site Request Forgery, ํฌ๋กœ์Šค ์‚ฌ์ดํŠธ ์š”์ฒญ ์œ„์กฐ) ๊ณต๊ฒฉ์— ๋Œ€ํ•œ ํฅ๋ฏธ๋กœ์šด ์‚ฌ๋ก€๋ฅผ ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ, ์ด๋ฉ”์ผ์„ ์ด์šฉํ•œ CSRF ๊ณต๊ฒฉ์ด ์–ผ๋งˆ๋‚˜ ์œ„ํ˜‘์ ์ธ์ง€ ์•Œ๊ฒŒ ๋˜์—ˆ์ฃ .

"์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฉ”์ผ๋กœ ๋ฐ›์€ '๊ธด๊ธ‰ ๊ณ„์ • ์—…๋ฐ์ดํŠธ ํ•„์š”!'๋ผ๋Š” ๋งํฌ๋ฅผ ํด๋ฆญํ–ˆ๋Œ€. ๊ทธ๋Ÿฐ๋ฐ ๊ทธ ์ˆœ๊ฐ„ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๊ณ , ๋ณธ์ธ์€ ์•„๋ฌด ์กฐ์ž‘๋„ ์•ˆ ํ–ˆ๋‹ค๊ณ  ํ•˜๋”๋ผ."

์ด ๋ง์„ ๋“ฃ๊ณ  ์ €๋Š” '์ด๊ฒŒ ๊ฐ€๋Šฅํ•ด?' ์‹ถ์—ˆ๋Š”๋ฐ, ์•Œ๊ณ  ๋ณด๋‹ˆ CSRF ๊ณต๊ฒฉ์ด ๋”ฑ ์ด๋Ÿฐ ๋ฐฉ์‹์ด๋”๊ตฐ์š”. ์‚ฌ์‹ค ์ด ๊ณต๊ฒฉ์€ ์˜ค๋ž˜์ „๋ถ€ํ„ฐ ์•Œ๋ ค์ง„ ์œ ๋ช…ํ•œ ๊ณต๊ฒฉ์ด์ง€๋งŒ, ์ƒ๊ฐํ•ด๋ณด๋ฉด ์ •๋ง ๋˜‘๋˜‘ํ•œ ๋ฐฉ๋ฒ•์ด๋ผ๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ์‹ ๋ขฐ๋ฅผ ์ด์šฉํ•ด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž๋™์œผ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋„๋ก ์„ค๊ณ„๋œ๋‹ค๋Š” ์ ์ด ์ฐธ ๊ต๋ฌ˜ํ•˜์ฃ .

์˜ค๋Š˜์€ CSRF๊ฐ€ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ๋ง‰๋Š” ๋ฐฉ๋ฒ•๊นŒ์ง€ ์ •๋ฆฌํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๐Ÿš€


๐ŸŽญ CSRF ๊ณต๊ฒฉ์ด๋ž€?

CSRF ๊ณต๊ฒฉ์€ ์‚ฌ์šฉ์ž๊ฐ€ ์˜๋„ํ•˜์ง€ ์•Š์€ ์š”์ฒญ์„ ํŠน์ • ์›น์‚ฌ์ดํŠธ์— ๋ณด๋‚ด๋„๋ก ์œ ๋„ํ•˜๋Š” ๊ณต๊ฒฉ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ๋ณดํ†ต ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ๋œ ์ƒํƒœ์—์„œ ์•…์„ฑ ์‚ฌ์ดํŠธ๋ฅผ ๋ฐฉ๋ฌธํ•˜๋ฉด, ๊ณต๊ฒฉ์ž๊ฐ€ ๋ชฐ๋ž˜ ์‚ฌ์šฉ์ž ๊ณ„์ •์œผ๋กœ ์กฐ์ž‘๋œ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๋ฐฉ์‹์œผ๋กœ ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค.

๐Ÿ“Œ CSRF ๊ณต๊ฒฉ ์˜ˆ์‹œ

1๏ธโƒฃ ํ”ผํ•ด์ž๊ฐ€ google.com์— ๋กœ๊ทธ์ธํ•œ ์ƒํƒœ

์‚ฌ์šฉ์ž๊ฐ€ maeil-mail.com์— ๋กœ๊ทธ์ธํ•˜๋ฉด, ์„œ๋ฒ„๋Š” ์„ธ์…˜ ์ฟ ํ‚ค(Session Cookie)๋ฅผ ์ด์šฉํ•ด ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•ฉ๋‹ˆ๋‹ค.

2๏ธโƒฃ ๊ณต๊ฒฉ์ž๊ฐ€ ์•…์„ฑ ์‚ฌ์ดํŠธ๋ฅผ ๋งŒ๋“ค์–ด ํ”ผํ•ด์ž๋ฅผ ์œ ๋„

๊ณต๊ฒฉ์ž๋Š” ์‚ฌ์šฉ์ž๋ฅผ ์•…์„ฑ ์‚ฌ์ดํŠธ๋กœ ์œ ๋„ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, "๊ธด๊ธ‰: ๊ท€ํ•˜์˜ ๊ณ„์ •์ด ํ•ดํ‚น ์œ„ํ—˜์— ์ฒ˜ํ•ด ์žˆ์Šต๋‹ˆ๋‹ค! ์ฆ‰์‹œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ณ€๊ฒฝํ•˜์„ธ์š”."๋ผ๋Š” ์ด๋ฉ”์ผ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฉ”์ผ ๋‚ด ๋งํฌ๋ฅผ ํด๋ฆญํ•˜๋ฉด, ์•…์„ฑ ์‚ฌ์ดํŠธ๋กœ ์ด๋™ํ•˜๊ฒŒ ๋˜๊ณ  ๊ทธ ์ˆœ๊ฐ„ CSRF ๊ณต๊ฒฉ์ด ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค.

3๏ธโƒฃ ์‚ฌ์šฉ์ž๊ฐ€ ์•…์„ฑ ์‚ฌ์ดํŠธ๋ฅผ ๋ฐฉ๋ฌธํ•˜๋ฉด ์ž๋™์œผ๋กœ ์š”์ฒญ ๋ฐœ์ƒ

์•…์„ฑ ์‚ฌ์ดํŠธ์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

<img src="https://google.com/member/changePassword?newValue=Hacked123" />

์‚ฌ์šฉ์ž๊ฐ€ ์ด ํŽ˜์ด์ง€๋ฅผ ๋ฐฉ๋ฌธํ•˜๋Š” ์ˆœ๊ฐ„, ๋ธŒ๋ผ์šฐ์ €๋Š” ์ž๋™์œผ๋กœ ์ด URL์„ ์š”์ฒญํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

4๏ธโƒฃ ํ”ผํ•ด์ž์˜ ์„ธ์…˜ ์ฟ ํ‚ค๊ฐ€ ์ž๋™์œผ๋กœ ์ „์†ก๋จ

๋ธŒ๋ผ์šฐ์ €๋Š” ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ์„ธ์…˜ ์ฟ ํ‚ค๋ฅผ ์ž๋™์œผ๋กœ ํฌํ•จํ•˜๋ฏ€๋กœ, ์„œ๋ฒ„๋Š” "์ด ์š”์ฒญ์ด ์ •์ƒ์ ์ธ ์‚ฌ์šฉ์ž ์š”์ฒญ์ด๊ตฌ๋‚˜"๋ผ๊ณ  ์ฐฉ๊ฐํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

โœ… ๊ฒฐ๊ณผ: ํ”ผํ•ด์ž์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ž๋™์œผ๋กœ ![](https://velog.velcdn.com/images/sh1623/post/076646cc-d233-4e33-8301-ad22203112dd/image.png) Hacked123์œผ๋กœ ๋ณ€๊ฒฝ๋จ ๐Ÿ˜ฑ


๐Ÿ”’ CSRF ๊ณต๊ฒฉ ๋ฐฉ์–ด ๋ฐฉ๋ฒ•

์ด์ œ ์ด ๋ฌธ์ œ๋ฅผ ์–ด๋–ป๊ฒŒ ๋ง‰์„ ์ˆ˜ ์žˆ์„์ง€ ์‚ดํŽด๋ณผ๊นŒ์š”?

๐Ÿ›ก๏ธ 1. CSRF ํ† ํฐ ์‚ฌ์šฉํ•˜๊ธฐ (๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ๋ฐฉ๋ฒ•!)

์„œ๋ฒ„๊ฐ€ ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด CSRF ํ† ํฐ์„ ํฌํ•จํ•˜๋„๋ก ๊ฐ•์ œํ•˜๋ฉด ๊ณต๊ฒฉ์ž๋Š” ์ด๋ฅผ ์šฐํšŒํ•˜๊ธฐ ์–ด๋ ค์›Œ์ง‘๋‹ˆ๋‹ค.

โœ” CSRF ํ† ํฐ ์ ์šฉ ๋ฐฉ์‹:

  • ์„œ๋ฒ„๋Š” ํผ์„ ๋ Œ๋”๋งํ•  ๋•Œ ๋žœ๋คํ•œ CSRF ํ† ํฐ์„ ์ƒ์„ฑํ•˜์—ฌ HTML ๋‚ด๋ถ€์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ, ์ด ํ† ํฐ๋„ ํ•จ๊ป˜ ์ „์†กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์„œ๋ฒ„๋Š” ์š”์ฒญ์„ ๋ฐ›์„ ๋•Œ ํ† ํฐ์ด ์œ ํšจํ•œ์ง€ ๊ฒ€์ฆํ•˜๊ณ , ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ์š”์ฒญ์„ ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“Œ CSRF ํ† ํฐ์„ ํ™œ์šฉํ•œ HTML ์˜ˆ์ œ

<form action="/update-profile" method="POST">
    <input type="hidden" name="csrf_token" value="randomToken1234" />
    <input type="text" name="username" />
    <button type="submit">Update</button>
</form>

๐Ÿ“Œ ์„œ๋ฒ„์—์„œ CSRF ํ† ํฐ ๊ฒ€์ฆ (Java Spring ์˜ˆ์ œ)

@PostMapping("/update-profile")
public ResponseEntity<?> updateProfile(@RequestParam String username,
                                       @RequestParam String csrfToken,
                                       HttpSession session) {
    String sessionToken = (String) session.getAttribute("csrf_token");
    if (!csrfToken.equals(sessionToken)) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Invalid CSRF Token");
    }
    // ์ •์ƒ ์ฒ˜๋ฆฌ
    return ResponseEntity.ok("Profile updated");
}

๐Ÿ›ก๏ธ 2. SameSite ์ฟ ํ‚ค ์†์„ฑ ์„ค์ •ํ•˜๊ธฐ

์ตœ๊ทผ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ์ฟ ํ‚ค์˜ SameSite ์†์„ฑ์„ ์ด์šฉํ•ด CSRF ๊ณต๊ฒฉ์„ ๋ง‰์„ ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์„ค์ •ํ•˜๋ฉด ํฌ๋กœ์Šค ์‚ฌ์ดํŠธ ์š”์ฒญ์—์„œ๋Š” ์ฟ ํ‚ค๊ฐ€ ์ „์†ก๋˜์ง€ ์•Š๋„๋ก ์ œํ•œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“Œ SameSite ์„ค์ • ์˜ˆ์ œ (Spring Boot)

Cookie sessionCookie = new Cookie("SESSION_ID", sessionId);
sessionCookie.setHttpOnly(true);
sessionCookie.setSecure(true);
sessionCookie.setPath("/");
sessionCookie.setAttribute("SameSite", "Strict");

โœ” Strict: ํฌ๋กœ์Šค ์‚ฌ์ดํŠธ ์š”์ฒญ์—์„œ๋Š” ์ฟ ํ‚ค๊ฐ€ ์ ˆ๋Œ€ ์ „์†ก๋˜์ง€ ์•Š์Œ (๊ฐ€์žฅ ์•ˆ์ „!)
โœ” Lax: ํฌ๋กœ์Šค ์‚ฌ์ดํŠธ ์š”์ฒญ ์ค‘ GET ์š”์ฒญ์—์„œ๋Š” ์ฟ ํ‚ค๊ฐ€ ์ „์†ก๋จ (๋ณดํ†ต ๊ธฐ๋ณธ๊ฐ’)
โœ” None: ํฌ๋กœ์Šค ์‚ฌ์ดํŠธ์—์„œ๋„ ์ฟ ํ‚ค ์ „์†ก ํ—ˆ์šฉ (๋ณด์•ˆ ์ทจ์•ฝ!)

๐Ÿ›ก๏ธ 3. Referer ํ—ค๋” ๊ฒ€์ฆํ•˜๊ธฐ

์„œ๋ฒ„๊ฐ€ ์š”์ฒญ์„ ๋ฐ›์„ ๋•Œ Referer ํ—ค๋”๋ฅผ ํ™•์ธํ•˜์—ฌ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ์ถœ์ฒ˜์—์„œ ์™”๋Š”์ง€ ๊ฒ€์‚ฌํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด ๋ฐฉ๋ฒ•์€ ํ—ค๋” ์กฐ์ž‘์ด ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹จ๋…์œผ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ์—” ๋ถ€์กฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“Œ Referer ๊ฒ€์ฆ ์˜ˆ์ œ

String referer = request.getHeader("Referer");
if (referer == null || !referer.startsWith("https://maeil-mail.com")) {
    return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Invalid Referer");
}

๐Ÿ›ก๏ธ 4. CORS ์ •์ฑ… ๊ฐ•ํ™”ํ•˜๊ธฐ

CORS(Cross-Origin Resource Sharing) ์ •์ฑ…์„ ์ ์ ˆํžˆ ์„ค์ •ํ•˜๋ฉด ์™ธ๋ถ€ ์‚ฌ์ดํŠธ์—์„œ ๋‚ด๋ถ€ API๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ๋ชปํ•˜๋„๋ก ์ œํ•œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“Œ Spring Boot์—์„œ CORS ์„ค์ •ํ•˜๊ธฐ

@Configuration
public class CorsConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**").allowedOrigins("https://maeil-mail.com");
            }
        };
    }
}

๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌ

CSRF๋Š” ๋‹จ์ˆœํ•˜์ง€๋งŒ ๋ฌด์„ญ๊ฒŒ ์น˜๋ช…์ ์ธ ๊ณต๊ฒฉ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ ์ ˆํ•œ ๋ฐฉ์–ด ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋ฅผ ์ถฉ๋ถ„ํžˆ ๋ง‰์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ค‘์š”ํ•œ ๊ฑด, ๋‹ค์–‘ํ•œ ๋ณด์•ˆ ๋ฐฉ๋ฒ•์„ ์กฐํ•ฉํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ!

โœ” CSRF ํ† ํฐ์„ ํ™œ์šฉํ•ด ์š”์ฒญ์˜ ์ •๋‹น์„ฑ์„ ํ™•์ธํ•˜์ž.
โœ” SameSite ์ฟ ํ‚ค ์†์„ฑ์„ ์„ค์ •ํ•ด ํฌ๋กœ์Šค ์‚ฌ์ดํŠธ ์ฟ ํ‚ค ์ „์†ก์„ ๋ง‰์ž.
โœ” Referer ๊ฒ€์ฆ๊ณผ CORS ์ •์ฑ…์„ ๊ฐ•ํ™”ํ•ด ๋ถˆํ•„์š”ํ•œ ์š”์ฒญ์„ ์ฐจ๋‹จํ•˜์ž.

์ด์ œ CSRF์— ๋Œ€ํ•ด ์กฐ๊ธˆ ๋” ๋ช…ํ™•ํžˆ ์ดํ•ดํ•˜์…จ์„๊นŒ์š”? ๋ณด์•ˆ์€ ํ•œ์ˆœ๊ฐ„ ๋ฐฉ์‹ฌํ•˜๋ฉด ํฐ ํ”ผํ•ด๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฏธ๋ฆฌ ๋Œ€๋น„ํ•ด์„œ ์•ˆ์ „ํ•œ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด ๊ฐ‘์‹œ๋‹ค! ๐Ÿ”ฅ

0๊ฐœ์˜ ๋Œ“๊ธ€