AccessToken 저장 위치에 대한 고찰 (당신의 토큰은 안전하신가요?)

Nine·2022년 11월 5일
2

왜 고찰함?

때는 바야흐로 프로젝트 달록에 카테고리 관리자 기능이 없던 시절....

아래처럼 카테고리에 일정을 추가할 수 있는 사람은 카테고리를 생성한 사람뿐이었습니다.

달록 사용자들이 관심있어 할 만한 카테고리들을 만들어 일정을 추가하기 위해 카테고리를 생성한 팀원의 accessToken을 받아 로그인을 했습니다.

🤦‍♂️ 순간 띵 하면서 남의 accessToken을 탈취하면 어떤 일이든 할 수 있겠다는 위험을 깨달았어요.

발생할 수 있는 위협에 대해서 알아보고 현재 달록 프로젝트에서는 어떤지 찾아봤어요.

검색해보니 XSS, CSRF 같은 취약점들이 발생할 수 있겠더라구요.

그래서 먼저 보안 위협에 대해 공부하고 프로젝트에서 고찰해봤어요.


JWT

저희 달록 서비스에서는 JWT를 사용하고 있어요.

JWT란?

JSON 포맷을 이용하여 사용자에 대한 속성을 저장하는 Web Token입니다.

왜 JWT 썼나요?

  • 수평확장이 좋아요.
  • 세션 저장소가 따로 필요 없어서 DB 접근 시간을 줄일 수 있어요.

어떤 위협이 있을까요?

XSS (Cross Site Script)

스크립트를 통해 html이나 js를 동작시켜 사용자의 인증 정보를 탈취해요.

🥵 XSS 예시

  1. 공격자가 유저 정보(accessToken)를 탈취해오는 악성 스크립트를 사이트에 삽입해요.

  2. 사용자가 해당 악성 스크립트가 들어있는 페이지를 들어가요.

  3. 스크립트가 실행되어 유저 정보가 탈취됩니다 (⊙ˍ⊙)

🤦‍♂️ XSS에 취약한 친구들

브라우저 저장소(localStorage, sessionStorage)가 가장 취약해요.

개발자들이 쓰기도 쉽지만 공격자들도 쓰기 쉽죠ㅎㅎ;;

Storage.getItem('...')

✋ 올~ 쿠키는 HttpOnly를 붙여주면 스크립트로 접근할 수 없어서 XSS 공격에서는 안전해요.

Set-Cookie: 쿠키명=쿠키값; path=/; HttpOnly

🔨 XSS 해결방법

1) Escape

  • React는 기본적으로 Escape를 진행해줍니다.
  • 공격자가 악성 스크립트를 넣어도 실행되지 않도록 escape를 진행해요.
  • 하지만 공격자가 svg 등에 악성 스크립트를 삽입한다면 escape 또한 무용지물이예요.

2) 삽입 가능한 파일을 제한해요.

  • svg 파일을 입력하지 못하게 막는 방법도 있겠네요.

3) 애초에 브라우저 저장소에는 민감한 정보를 저장하지 말아요.

  • 가장 확실한 방법이겠네요.

CSRF (Corss Site Request Forgery)

인증된 유저가 의도하지 않게 서버에 공격을 하게 만들어요.

🥵 CSRF 예시

  1. 사용자가 정상적인 웹 페이지에 로그인하여 쿠키가 저장됩니다.

  2. 이후 사용자가 (악성 페이지 인지를 못하고) 악성 웹 서비스에 들어갑니다.

  3. 악성 페이지는 만들어졌던 쿠키로 서버에 악의적인 요청(DB의 수정/삭제, 회원정보 수정 등)을 보냅니다 (⊙ˍ⊙)

🤦‍♂️ CSRF에 취약한 친구들

쿠키가 취약해요.

HttpOnly 옵션을 주어서 XSS 공격을 막았지만 CSRF는 다른 이야기죠(⊙o⊙)

🔨 CSRF 해결방법

1) HTTPS일 때에만 쿠키를 보냅시다.

  • HTTPS 프로토콜을 사용하여 데이터를 암호화하면 좋겠어요.
  • secure 접미사를 붙입시다.
Set-Cookie: 쿠키명=쿠키값; path=/; secure

👍 secure 접미사를 사용하면 브라우저는 HTTPS가 아닌 통신에서 쿠키를 전송하지 않아요.

2) 서버에게 요청을 보낼 수 있는 도메인(유효한 사이트)을 특정해요.

  • 서버는 Origin을 지정하여 원하는 도메인에서만 request를 수용할 수 있어요.
  • 악성 사이트는 허용되지 않은 도메인이니 유효하지 않은 요청이라고 판단할 수 있어요.
  • 😭 하지만 Request의 Referer은 얼마든지 위조가 가능해요ㅠㅠ
    • 그래도 위조가 되면 악성 사이트가 아닌 위조된 도메인으로 응답을 보내서 그나마 괜찮아요.

3) 쿠키에 담긴 토큰 단독으로 DB 수정/삭제가 되도록 하지 맙시다.

  • 서버가 Origin을 특정해도 Request의 Referer을 수정하면 응답은 받을 수는 없어도 요청은 가능합니다.
  • 만약 그 요청이 회원 정보를 변경한다거나 DB의 정보를 수정/삭제하는 경우라면 문제가 되겠죠.

그러면 토큰은 어디에 저장해야 안전할까요?

많은 고민을 진행한 결과 localStorage와 sessionStorage를 아예 사용하지 않는 방안을 생각하게 되었습니다.

React에서 자동으로 escape를 진행시켜준다고 해도 제가 알지 못하는 어떤 위협들이 존재할지 모르기 때문이죠.

  • 추가적으로 refreshToken을 반드시 도입해야겠더라구요. 아래에서 더 살펴봅시다!

accessToken을 javascript 코드에 저장하자

localStorage와 sessionStorage는 스크립트 코드로 브라우저 상에서도 너무나도 쉽게 탈취될 수 있어요.

  • no localStorage, no sessionStorage

httpOnly Cookie에 accessToken을 사용한다면 스크립트로 인한 직접적인 탈취는 막을 수 있겠지만 DB를 수정/삭제하는 요청 자체를 보낼 수는 있습니다.

  • no Cookie

👍 그래서 브라우저에서 쉽게 접근할 수 없는 javascript 변수에 accessToken을 담기로 했어요.


refreshToken을 httpOnly secure Cookie에 저장

refreshToken을 httpOnly Secure Cookie에 저장한다고 해봅시다.

✌ httpOnly

  • 덕분에 브라우저 상에서 스크립트로 인한 탈취를 막을 수 있어요. (XSS 방어)

✌ Secure

  • 덕분에 https 요청에서만 쿠키를 보내게 되어 데이터를 암호화하여 보낼 수 있어요.

🤔 CSRF에서는 어떨까요?

  • 🥵쿠키는 브라우저에서 공용으로 사용되기 때문에 악성 사이트에서 접근 가능해요. 이 경우 쿠키에 담긴 refreshToken으로 서버에 어떤 요청을 보낼 수 있겠죠.

    • 하지만 문제 없습니다. 서버에서 Access-Control-Allow-Origin 설정을 하여 특정 도메인에서 오는 요청들만 받을 수 있거든요!
  • 🥵물론 악성사이트에서 Referrer를 쉽게 수정하여 서버의 Origin 허용점을 쉽게 뚫을 수는 있어요.

    • 하지만 또 문제 없어요. RefreshToken은 오직 accessToken을 재발급 받는데에만 사용됩니다. accessToken을 response로 돌려받을 때에는 수정된 Referrer(원래 유효한 도메인)로 가기 때문에 악성 사이트는 실제로 accessToken을 받을 수 없답니다.

🎉 아하 이러니 refreshToken을 도입해야겠군요.

단점은?

하지만 늘 장점만 존재하는 것은 아니죠. 반드시 trade-off가 있기 마련입니다.

웹 서비스에 처음 접근할 때, 새로고침할 때에는 refreshToken을 사용하여 accessToken을 반드시 받아야하기 때문에 이 부분에서 네트워크 요청이 필히 발생하게 됩니다.

이로 인해 사용자는 초기 로딩이 느리다고 느낄 수 있겠죠.

위의 방법이 최선이 아님을 인지하고 더 나은 방향을 찾아봐야겠다고 생각이 드네요.

profile
함께 웃어야 행복한 개발자 장호영입니다😃

0개의 댓글