Secure JWT and Slinding Sessions

Sessions 이란?

Sessions은 컴퓨팅에서 비슷하지만 여러 의미로 사용되는 용어입니다. 일반적으로 상태를 의미한다고 보면 될 것 같고, 웹에서는 HTTP가 비 연결형 프로토콜이기 때문에 서버가 기존에 접속했던 클라이언트인지 확인할 수 있는 수단으로 사용됩니다. (파일 쿠키랑 비슷하죠. 다만 처리에선 약간 다르긴합니다.)

아무튼 이러한 Sessions 정보는 서버가 별도의 스토리지 등을 통해 Sessions 값을 보관 및 접속 시 마다 비교하고, 명식적인 로그아웃 등이 있을 때 폐기하는 형태로 인증이 있는 웹 서비스에서 사용됩니다.

MPA와 SPA

옛날에는 전통적인 형태의 웹 서비스 구조인 MPA(Multiple Page App)가 많았다고 하면 최근엔 SPA(Single Page App), 즉 여러 페이지를 가지지 않고 단일 페이지에서 렌더링을 갱신하는 형태의 웹이 점점 많아지고 있는 상태입니다. 물론 각각 MPA와 SPA는 장/단점을 가지겠지만, 페이지 이동이 없이 단일 페이지에서 서비스를 처리하는 SPA가 모바일에 가까운 느낌을 사용자에게 줄 수 있기 때문에 선호되고 있는 상태입니다.

MPA의 경우 페이지가 이동되면 매번 웹 요청을 발생하기 때문에 서버에서 세션을 통해 사용자를 식별하고 Response로 전달해줄 페이지를 인증 여부애 따라 다르게 구성해줄 수 있습니다. SPA는 페이지 이동을 하지 않는 구조로 인해서 대다수가 사용자의 세션을 체크하지 않는 Stateless 서비스입니다. 그래서 JWT나 Access/Refresh Token등의 인증 토큰을 발급하는 형태로 사용자를 식별하고 관리합니다.

Stateless service 와 JWT

위에서도 이야기했지만 페이지 이동을 하지 않는 구조로 인해서 SPA의 대부분은 Stateless 한 서비스를 구성하고 있고, 보통 이러한 구조에선 JWT(JSON Web Tokens)를 통해 인증을 구현하곤 합니다.

(JWT 자체가 궁금하신 분은 RFC7519를 읽어주세요!)

JWT의 인증 토큰으로서의 적합성 여부는 아직도 의견 충돌이 있긴 하지만 JWT 자체가 토큰 내부에서 유효시간을 통해 토큰이 최대 사용될 수 있는 시간을 지정할 수 있고, 이에 대한 Signature를 같이 생성하고 검증하기 때문에 별다른 서버 로직 없이 쉽게 인증 서비스를 구현할 수 있어 많이 사용되고 있습니다. 또한 Access/Refresh Token 형태와 다르게 서버에서 토큰 값을 저장하고 검증할 필요가 없기 때문에 백엔드 구성에서 Cache나 DB등의 사용을 줄일 수 있어 구조적으로 심플해집니다.

How to Secure JWT

솔직히 JWT에 대한 공격 기법이 최근들어 다시 한번 연구되고 있는 상태라서 다시 한번 체크해보고 대응방안을 고려해볼 필요가 있습니다. 안전하게 JWT를 사용하기 위한 대표적인 방법들은 아래와 같습니다.

짧은 만료시간

JWT 자체가 토큰 내부에 만료 시간을 저장하고 서버가 만료 여부만 체크하기 때문에 명시적 로그아웃이나 브라우징 종료에 대해서 서버가 식별할 수 없습니다. 그래서 중요한 서비스인 경우 JWT 자체의 만료 시간을 짧게 가져가서 혹시라도 다른 취약점으로 인해 토큰이 노출되어도 추가적인 공격으로 이루어지기 어렵도록 할 수 있습니다.

None Algorithm

JWT의 Signature에 사용되는 알고리즘은 특정한 알고리즘(에를들면 HS256)이 지정되어야 합니다. 만약 none 상태로 사용된다면 쉽게 크랙할 수 있습니다.

Signature Secret의 복잡도

JWT는 내부 값과 유효기간등이 포함된 데이터에 대한 서명을 포함하고 있습니다. 이는 서버에서 발급 시 지정한 알고리즘에 따라서 서명 후 JWT 토큰에 포함되며 서버에서 유효기간과 함께 서명의 정상 여부를 검증하게 됩니다.

보통 Secret을 지정하지 않고 서명하는 경우, Secret에 대한 크랙 정도의 리스크가 있습니다. 제가 예전에 JWT 테스팅을 위해서 jwt-hack을 만들고 여러번 테스트 했을때도 JWT 자체가 네트워크 트래픽이 발생하지 않고도 Signature와 Secret의 매핑 여부를 체크할 수 있는지라 많은 수의 workdlist를 빠르게 crack 해볼 수 있었던 것 같습니다.

아무튼 한번 깨진 secret은 서비스 전체의 JWT 토큰들에 영향을 끼칠 수 있기 떄문에 굉장히 잘 관리되어야 합니다.

내부에 중요정보를 저장하지 않음

JWT는 Base64로 인코딩된 JSON 형태의 값입니다. Base64기 떄문에 쉽게 디코딩해서 볼 수 있고 당연히 중요한 정보는 JWT에 담지 않는 것이 원칙입니다.

Other attack technic

예전에는 위 내용 정도면 뭐 나름 안전하게 사용할 수 있었으나 nahamcon에서 나왔던 JWT 공격 방식과 같이 공격 기법이 계속 나오는 상태입니다. (제가 따로 블로그에 정리한 줄 알았는데, 안했었네요.. 회사에서만 했었나 🤨…)

아무튼 공격 방법 자체가 계속 생기고 있어서 최근 기술 동향은 파악하고 서비스에 관련 결함이 있다면 수정하는 식으로 추가적인 대응이 필요하겠네요. jku&x5u 는 아래 문서를 한번 읽어보시면 아하 하실겁니다 :D

https://www.slideshare.net/snyff/jwt-jku-x5u

Sliding sessions

슬라이딩 세션은 이러한 Stateless 서비스에서의 보안성을 위해 버려지는 편의성을 잡기 위한 세션 전략입니다. 서비스를 계속 사용하는 유저에게는 만료되기 전에 자동으로 만료 기한을 연장시켜주는 방법입니다. 사용자가 웹 페이지에서 활동하는건 JS단에서 쉽게 이벤트 핸들러로 감지할 수 있기 때문에 이러한 코드들을 통해 사용자의 현재 액션 여부를 지속적으로 체크하고 활동 중이라고 판단되면 세션이 만료 시간이 되기 전에 만료시간을 갱신한 토큰을 다시 받아오는 형태로 만료 시간을 연장할 수 있습니다.

예를들면..이런식으로 구성할 수 있겠네요.

function updateJWT(){
  // JWT를 갱신하는 함수를 하나 만들고...
  // 물론 이는 서버로 요청해서 받아와야겠죠.
  // 단 너무 잦은 요청이 부담스럽다면 현재 JWT의 유효시간을 보고 갱신해도 됩니다.
  // 예를들면.. 만기가 다와갈 때
}

// window 전체에 onmouseenter를 걸어서 updateJWT가 호출되도록 합니다.
// 그러면 사용자가 마우스 액션이 있을 때 JWT를 자동으로 갱신 시켜줄 수 있습니다.
// 비슷하게 키보드도 있겠네요.
window.addEventListener("mouseenter", updateJWT, false);

References