개념잡기

웹 공격으로 생각해보는 JWT

gucoding 2025. 6. 2. 23:40

CSRF(Cross-Site Request Fogery)

피해자가 서버로 공격자의 의도가 담긴 요청을 하게 만드는 공격

  • 로그인된 사용자의 요청에 공격자가 원하는 요청으로 유도하는 공격 방법입니다.

공격 가능 예시를 살펴보겠습니다.

잘못쓴 GET 요청

게시판 서비스에서 모 개발자가 게시글을 삭제하는 URI를 다음과 같이 정의했습니다.

GET https://gucoding.com/post/delete/1

모 개발자는 해당 요청을 작성자 본인만 보낼 수 있게 설계해서 안전하다고 생각했습니다. 그러나 이것은 틀렸습니다.

공격자는 작성자 본인은 의도하지 않게 요청을 보내도록 의도할 수 있습니다.

예를들어, 공격자가 공격을 위한 게시글을 하나 올립니다.

해당 게시글에 이미지를 첨부하는데, 이 때 이미지 태그에

<img src="https://gucoding.com/post/delete/1" />

다음 html 태그를 첨부한다고 했을 때, 해당 post를 올린 작성자가 게시글을 조회하는 행동만으로 게시글을 삭제할 수 있게됩니다.

모 개발자는 GET 요청에 행위를 포함하지 않아야겠다고 깨달았습니다.

Form 요청 태그

모 개발자는 정신을 차리고 황급하게 게시글 삭제를 form 요청으로 바꿨습니다. 그러나 얼마못가 공격자에게 당하고 맙니다.

<html>
<head>
    <title>흥미로운 뉴스 기사</title>
</head>
<body>
    <h1>새로운 뉴스 기사를 확인하세요!</h1>
    <img src="https://example.com/images/news.jpg" alt="뉴스">

    <form id="csrfDeleteForm" action="https://gucoding.com/post/1" method="DELETE">
        <input type="hidden" name="postId" value="123">
    </form>

    <script>
        // 페이지 로드 시 자동으로 폼 제출 (예: 뉴스 기사 로딩 중 백그라운드에서 실행)
        window.onload = function() {
            document.getElementById('csrfDeleteForm').submit();
        };
    </script>
</body>
</html>

위와 마찬가지로 해당 게시글의 주인이 해당 기사를 조회하는 것 만으로도 본인의 게시글을 지우도록 만들 수 있습니다.

이에 대한 해결방안으로 CSRF 토큰이 존재합니다. 스프링 시큐리티에서는 필터체인 등록 시, csrf() 설정이 default로 잡혀있습니다.

<form action="https://example.com/api/updateProfile" method="POST">
    <input type="hidden" name="csrf_token" value="[유효한_CSRF_토큰]"> <button type="submit">내 정보 업데이트</button>
</form>

해당 설정은 로그인한 유저에게 고유의 난수 토큰을 발급해주고 hidden 파라미터로 담은 요청만을 정상적인 요청으로 처리합니다.

난 왜 CSRF 보안로직을 쓴적이 없을까?

지금까지 경험에 의하면 프론트분들은 액세스 토큰을 메모리 혹은 로컬 스토리지에 저장하곤 했습니다. 왜냐하면 JWT 방식으로 인증했기 때문입니다.

만약에 쿠키-세션 방식으로 인증한다면 대부분의 쿠키는 브라우저에 의해 매 요청마다 필수적으로 전송이 됩니다.

💡

SameSite 옵션에 따라 동일 사이트 요청에서만 전송된다거나,
Secure 옵션으로 HTTPS 연결에서만 전송된다거나,

Domain 옵션으로 특정 도메인만 유효한다던가 하는 등

공격자는 이미 인증되고 있는 환경에서 악성요청을 동작하게만 구현하면되니 다소 쉬운 환경입니다.

제가 포스팅을 하게 된 계기가 액세스토큰도 쿠키에 두면 더 안전한거 아닌가?라는 생각으로 시작하게 됐는데요, 액세스토큰을 쿠키에 두지 않는 이유 중 하나가 아닐까하네요.

💡

물론 CSRF 공격을 SameSite 옵션을 줘서 방지한다면 다시금 쿠키에 저장하는게 안전하지 않을까? 라는 생각이 들었습니다.

  • strict : 현재 브라우저 URL과 쿠키 도메인과 일치해야 전송가능
  • Lax : 현재 브라우저 URL과 달라도 일부케이스(e.g.링크)에서는 접근 허용

그러나 제 경험상 프론트와 백엔드가 각자 배포해서 소통함으로 samesite=NONE 옵션을 줄 수 밖에 없었습니다.

얕게 알아본결과 리버스 프록시를 통해서 동일 도메인 환경을 구축가능한걸로 보입니다.

server {
    listen 80;
    server_name example.com;

    # 프론트엔드 정적 파일 서빙 (Nginx 서버 자체에 프론트엔드 빌드 파일이 있을 경우)
    location / {
        root /var/www/frontend_app; # Nginx 서버의 로컬 경로
        try_files $uri $uri/ /index.html;
    }

    # API 요청을 원격 백엔드 서버로 프록시
    location /api/ {
        # 백엔드 서버의 내부 IP 주소 또는 도메인 이름과 포트
        proxy_pass http://192.168.1.100:8080/; 
        # 또는 도메인 이름: http://api.example.com/;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

쿠키에 보안옵션을 빡세게 줄 수 있어도,

작은 프로젝트에서 개발시간을 지체하면서까지 그러고 싶지는 않은것도 하나의 이유일 것 같습니다.

XSS (Cross Site Scripting)

클라이언트 측에 스크립트를 삽입해서 정상적인 유저가 해당 로직을 실행하도록 하는 공격

@Controller
public class MyController {

    @GetMapping("/search/{query}")
    public String search(@PathVariable String query, Model model) {
        model.addAttribute("query", query);
        return "search";
    }
}

{query} 에 악성 스크립트를 담아 보낼 때, 개발자가 단순표기가 아니라 스크립트가 실행될 수 있게 만든다면 문제가 됩니다.

물론 저는 Spring MVC를 사용할 것 같지는 않아 깊게 살펴보지는 않고 관련 링크를 남기면서 끝내겠습니다.

https://youtu.be/YbuSLo5SKRg?si=98qBBNZQ6z1vg5wX

'개념잡기' 카테고리의 다른 글

가비지 컬렉션  (1) 2025.07.16
캐시  (1) 2025.05.28