본문 바로가기

블로그

LG CNS 기술블로그 DX Lounge에서 최신 IT 소식을 만나보세요!

보안

[보안동향] 인증, ‘토큰’ 방식 새롭게 뜬다! 안전한 사용법은?

2021.10.14

IT 서비스가 개인정보, 결제정보와 같이 민감한 정보를 다루기 시작하면서 보안과 프라이버시에 대한 관심이 높아졌습니다. 이로 인해 허가된 사용자에게만 정보를 제공하는 인증 절차가 필요하게 됐죠. 현재 대부분의 클라이언트-서버 방식의 서비스는 세션 기반의 인증 방법을 사용하고 있습니다. 그러나 최근 들어 토큰 기반의 인증 방법을 사용하는 서비스가 빠르게 늘어나고 있습니다. 이번 글에서는 토큰 기반의 인증 방법 중 가장 많이 사용되는 JWT((JSON Web Token)에 대해 살펴보겠습니다.

우선 세션 기반 인증과 토큰 기반 인증을 알아보도록 하겠습니다.

세션 기반 인증 vs. 토큰 기반 인증

세션기반인증 

애플리케이션 통신에 가장 많이 쓰이는 통신 방법은 HTTP입니다. 하지만 HTTP는 무상태성(Stateless) 프로토콜로 누가 요청을 했는지, 인증된 클라이언트인지 확인할 수 없는데요. 그래서 나온 방법이 서버 측에 클라이언트의 접속 상태를 저장하는 세션 기반의 인증입니다.

Client(web browser), Server(Web Server), Session Storage(file, DB, memory)

세션 기반 인증의 기본적인 프로세스는 다음과 같습니다. 사용자의 인증(ID/PW)이 완료되면 서버는 세션을 발급합니다. 이후 서버는 세션 값을 포함한 클라이언트의 상태를 세션 스토리지에 저장합니다. 세션 스토리지로 파일, DB, 메모리가 사용될 수 있습니다. 클라이언트 또한 자신의 세션 값을 클라이언트에 저장(주로 쿠키)합니다. 클라이언트는 데이터 요청 시 쿠키에 저장된 세션 값을 함께 보내며, 서버에서는 이 세션 값을 세션 스토리지를 통해 검증한 후 데이터 응답을 합니다.

토큰기반인증 

토큰은 사용자 인증을 위한 정보를 서명한 것입니다. 세션 기반의 인증은 클라이언트의 상태를 서버 내에 저장하며, 요청마다 세션 스토리지에 저장된 유효한 세션인지 확인해야 합니다. 반면 토큰 기반의 인증 방법은 토큰에 사용자 인증을 위한 정보가 담겨있기 때문에 서버에 사용자 정보를 서버에 저장하지 않고, 전달받은 토큰의 서명과 데이터를 검증하는 것만으로 인증이 가능합니다.

사용자의 인증(ID/PW)이 완료되면 서버는 비밀키 또는 공개/개인 키를 이용해 서명한 토큰을 클라이언트에게 전달합니다. 그리고 데이터 요청 시 클라이언트는 토큰을 포함합니다(주로 헤더). 서버는 토큰의 서명 값을 이용하여 토큰이 유효한지 검증하는데요. 유효한 토큰인 경우 요청에 응답합니다. 토큰 발급 시 토큰 내 권한 정보를 추가하여 권한에 맞는 데이터 응답도 가능합니다.

세션 기반 인증과 토큰 기반 인증의 차이점 


정보 포함 : 세션 값 자체에는 정보가 포함되지 않지만, 토큰값에는 정보가 포함됩니다. 토큰에는 정보가 포함되어 있기 때문에 일반적으로 길이가 더 깁니다.


상태 정보 미저장: 세션 방식은 상태 정보를 서버 내에 저장하고 있습니다. 이에 비해 토큰은 기본적으로 서버에 클라이언트 상태를 저장하지 않는 무상태성 방식입니다. 서버에 정보를 저장할 필요가 없기 때문에 서버를 확장하는데 제약이 적죠. 또한, 저장 없이 유효한 토큰인지 검증만 필요하기 때문에 다른 플랫폼, 서비스 간에 사용하기도 편리합니다.

JWT(JSON Web Token) 설명

JWT(JSON Web Token)는 현재 토큰 인증에서 가장 많이 사용되는 인터넷 표준인데요. 애플리케이션의 액세스 토큰을 만드는 데 주로 사용됩니다. 데이터들이 JSON 형태로 작성되며, 데이터를 비밀키 또는 공개/개인 키로 서명해 사용합니다. 이를 통해 JWT는 인가와 정보 교환을 위해 사용될 수 있죠. 사용자에게 발급된 토큰으로 인가된 서버 리소스에 접근할 수 있습니다. 또한, 공개/개인 키를 사용해 서명함으로써 송신자를 확인할 수 있으며, 데이터의 무결성도 확인할 수 있습니다.

구조


JWT는 크게 헤더, 페이로드, 서명 세 개의 파트로 구성되어 있습니다.

1. 헤더
헤더는 보편적으로 토큰 타입을 명시하는 “typ”와 HMAC, SHA256 등의 서명 알고리즘을 적는 “alg”로 이루어져 있습니다.

2. 페이로드

페이로드에는 토큰에 담을 데이터가 들어있습니다. 이 데이터 하나를 JWT에서는 claim이라고 부르는데요. claim에는 registered claims, public claims, private claims 세 가지 종류가 있습니다.

Registered claims는 서비스에 필요한 정보가 아닌 토큰에 관한 정보를 담기 위해 이미 등록된 클레임입니다. 반드시 사용할 필요 없이 선택적으로 사용할 수 있습니다.

Public claims는 사용자가 정의할 수 있는 클레임입니다. 이 클레임은 충돌 방지를 위해 IANA(Internet Assigned Numbers Authority) JSON 웹 토큰 레지스트리에 정의하거나, 충돌 방지 네임스페이스를 포함하는 URI로 클레임 이름을 정의해야 합니다.

Private claims는 토큰 사용자 간(서버-클라이언트) 정보 공유를 위해 만들어 사용하는 클레임으로 충돌이 발생할 수 있으니 주의해야 합니다.

3. 서명

서명 부분은 헤더와 페이로드를 서명한 값입니다. 시크릿 키를 이용해 base64 url로 인코딩한 헤더와 페이로드를 헤더에 규정된 해싱 알고리즘으로 서명합니다. 서버는 이 서명과 전달된 헤더, 페이로드를 같은 알고리즘으로 해시한 값이 동일한 것을 확인함으로써 유효한 토큰인지 검사합니다.

특징 

SWT(단순 웹 토큰)는 대칭키만을 이용하여 HMAC 알고리즘으로 서명할 수 있는 반면에, JWT는 x.509 형식의 public/private 키를 사용할 수 있습니다. SAML(Security Assertion Markup Language)방식도 public/private 키를 사용할 수 있지만, 보안의 허점 없이 디지털 서명을 사용하기에는JWT가 더 쉽습니다.

JWT는 다른 토큰 방식인 SAML에 비교해 사이즈가 작다는 장점을 갖고 있습니다. SAML은 XML 형식을 사용하나, JWT는 JSON 형식을 사용해 더 간결하기 때문에 HTTP 환경에서 잘 전달될 수 있습니다. SAML은 토큰과 기본 프로토콜의 구조를 정의할 수 있으며, 기업에서 많이 사용됩니다. 이와 다르게 JWT는 토큰 구조만을 정의할 수 있는데요. 인터넷의 여러 플랫폼에서 사용되며 특히 모바일에서 많이 사용됩니다.

JWT 관련 취약점 및 안전한 사용

정보노출


JWT의 페이로드에는 클라이언트와 서버가 사용할 데이터를 저장합니다. 이 데이터는 암호화되지 않고, base64로 인코딩한 값입니다. base64로 인코딩된 값은 누구나 디코딩해 데이터를 확인할 수 있습니다. 따라서 페이로드에는 중요 정보, 민감 정보 등을 포함하지 않아야 합니다.

위의 이미지와 같이 사용자의 인증 정보(비밀번호), 금융 정보(카드 번호)와 같은 중요 정보를 페이로드에 담고 있는 경우, 토큰이 노출되는 순간 중요 정보들도 쉽게 노출되게 됩니다.
따라서, JWT을 통한 불필요한 정보 노출을 방지하기 위해 페이로드 내 최소한의 정보만을 포함해야 합니다. 최소한의 정보란 사용자 검증에 필요한 최소한의 식별 및 권한 정보를 말할 수 있습니다.

None algorithm


JWT의 알고리즘 타입 중에 “none”이 있습니다. “none” 알고리즘은 서명 값 검증을 하지 않습니다. 따라서 none 알고리즘을 포함하는 헤더와 페이로드를 작성해 누구나 토큰을 생성할 수 있고, 악의적으로 사용이 가능합니다.

none 알고리즘을 허용할 경우 위의 이미지와 같이 악의적 사용자가 서명 없이 토큰을 생성해 인증과 인과 절차를 우회하여 데이터에 접근할 수 있습니다. 따라서 아래와 같이 none 알고리즘을 이용한 서명 없는 토큰은 사용할 수 없도록 조치해야 합니다.

근래의 라이브러리들은 none 알고리즘을 금지하고 있지만, 일부 라이브러리는 유효한 토큰으로 처리하기 때문에 시스템 구현 시 none 알고리즘은 사용하지 않도록 조치가 필요합니다.

서명 키 획득

토큰 기반 인증에서는 발급된 토큰을 저장하여 검증하는 것이 아니라 토큰의 헤더, 데이터와 서명을 이용하여 검증합니다. 그렇기 때문에 키값을 획득하면, 페이로드의 데이터를 변조해 유효한 토큰을 생성할 수 있습니다. 이를 통해 인증 우회, 권한획득 등의 악의적인 행위가 가능하죠. 아래의 코드와 Dictionary를 이용하여 키값을 알아낼 수 있습니다.

관리자로 로그인한 사용자만 사용자 목록 조회가 가능한 간단한 시스템으로 예를 들어 보겠습니다. 일반 사용자 계정으로 로그인을 하여 아래와 같이 JWT 토큰을 발급받았습니다.

발급된 토큰을 이용해 사용자 목록을 조회해봤지만, 관리자가 아니라는 응답을 받았습니다. 토큰을 base64로 디코딩해 살펴보면 페이로드 내 권한 관련 정보가 있는 것을 확인할 수 있습니다.

관리자 권한을 가진 유효한 토큰을 생성하기 위해서는 서명에 사용되는 비밀키를 알아야 합니다. 사전 대입 공격을 통해 발급된 토큰의 비밀 키(“testkey”)를 획득했습니다.

페이로드의 권한 관련 데이터를 일반 사용자(“role”: “User”)에서 관리자(“role”: “admin”)로 변경했습니다. 그리고 헤더와 페이로드를 획득한 비밀 키와 함께 HS256 알고리즘으로 서명했습니다. 이 토큰으로 관리자 권한이 필요한 사용자 목록 조회도 성공했습니다.

위의 내용과 같이 사용자가 토큰의 비밀키를 획득해 원하는 토큰을 발급하지 못하게 하기 위해서는 안전한 비밀키를 사용해야 합니다. 알파벳과 특수문자, 숫자의 조합으로 의미가 없는 문자열을 만들어 복잡도 높은 비밀키를 사용하는 것이 위험을 줄일 수 있습니다.

토큰 유효기간 및 파기


기본적으로 토큰이 서버에 저장되지 않고 토큰의 서명을 이용해 검증하면, 토큰의 만료 시점이 될 때까지 사용이 가능합니다. 따라서 별도의 토큰 파기 절차가 없는 경우가 있습니다. 하지만 이는 토큰이 탈취되었을 때, 토큰이 만료될 때까지 악의적으로 사용될 위험이 있습니다. 그래서 위험을 줄이기 위해 사용자가 토큰을 파기할 수 있는 기능을 구현하는 것이 필요합니다.


토큰이 탈취돼 악의적으로 사용되는 위험을 줄이기 위한 또 하나의 방법은 Access 토큰과 Refresh 토큰을 이용하는 것입니다. 서버에 데이터를 요청할 때는 Access 토큰을 이용하며, 이 토큰의 유효 시간을 짧게 설정합니다. 유효 시간이 짧아 토큰이 만료되면, 클라이언트는 Refresh 토큰을 이용하여 서버에 새로운 Access 토큰을 요청하여 발급받습니다.

이때 주의해야 할 점은 Refresh 토큰은 Access 토큰 발급을 위한 요청에만 포함돼야 합니다.
토큰의 파기 절차, 유효 기간 설정, Refresh 토큰 적용은 모든 애플리케이션에서 필요한 조치는 아닙니다. 애플리케이션 서비스의 특성과 정보의 중요도, 리소스 사용량 등을 고려해 적용해야 합니다.

지금까지 토큰 기반 인증 기법 중 JWT에 대해 살펴보았습니다. 기존에 사용 중이던 세션 기반의 인증에 비교해 JWT만이 가지는 편리성이 주목을 받고 있습니다. 하지만 이 글을 통해 알 수 있듯이 JWT도 취약점이 존재하기 때문에 페이로드 내에 최소한의 정보를 담고, none 알고리즘을 이용한 서명 없는 토큰은 사용할 수 없게 해야 합니다. 그리고 복잡도 높은 비밀키를 사용해 토큰을 암호화하고, 유효 기간과 파기를 적절하게 적용해 사용하기를 권장합니다.

글 ㅣ LG CNS RED팀

[출처]
[1] IETF RFC 7519 : JSON Web Token (JWT)
[2] Jwt.io. 2021. JWT.IO. [online] Available at: http://jwt.io/>

챗봇과 대화를 할 수 있어요