Q> JWT(JSON Web Token) 개발시 보안 고려사항에 대한 문의

회사에서 정보보안 업무를 담당하고 있습니다. 회사에서 추진하는 차세대 시스템 개발 및 구축사업에 정보보안 업무를 담당하게 되었습니다.
개발환경은 Microservice Architecture기반으로 각종 서비스들을 재개발할 계획이며 정보보안 담당자로써 각종 보안정책이나 요구사항 등을 제공해야 하는 입장입니다.
특히 모든 클라이언트 인증 및 권한을 JWT Token방식을 채택한다고 하는데, 개발조직에게 제시할수 있는 JWT개발시 중요 원칙이 필요해서 도움을 요청합니다.

A>

<> JWT(JSON Web Token) 개발시 보안 원칙

Microservice Architecture기반의 개발이 많아지면서 독립적인 Microservice들 간의 클라이언트(사용자 또는 시스템) 인증 및 권한을 위해 사용되는 주요 기술들을 요약하면 아래와 같다.
- JSON Web Token(JWT)
- OAuth 2,0
- API Gateway(중앙집중식 인증관리)
- Service Mesh
- Mutual TLS(mTLS)
- IAM Platform

이중에서 JWT기술은 대표적인 기술이며 개발 현장에서 JWT형식의 Token을 생성하여 Session에 넣어서 사용하는 것이 일반적이다.
그리고 JWT 포멧의 길이가 제한적이다 보니, Custom Header 또는 Custom Payload로 확장해서 사용하는 것이 일반적이다.
그러다 보니, 기존의 JWT위변조, JWT서명 위변조, JWT서명우회, 비밀키 Brute force, 서명키 삽입 등과 같은 취약점 들이 더욱 자주 발견되곤 한다.
또한 최근에는 HTTP Custom Header 에 SQL Injection 취약점들이 알려졌으면 공격 난이도 상당히 낮은 기법이다. 이 취약점은 일반 사용자로 인증을 받은 이후에 권한상승과 같은 2차 공격으로 진행할 수 있지만, 이 공격을 잘 응용하면 Authorization 필드 값이 없어도(즉, 인증이 되지 않은 상태)에서도 SQL Injection 공격이 먹히는 경우도 있다.
그렇다면, JWT개발시 개발자에게 제시해야 하는 주요한 보안 원칙을 정리하면 아래와 같다.

1. JWT토근의 무결성 보장을 위한 서명을 적용한다.

- JWT 포멧은 Header, Payload, Signature로 구성되어 있다.
- Signature는 Header와 Payload를 합한 값을 비밀키로 암호화하여 생성된다.
- JWT라이브러리를 사용하여 토큰을 생성할때, 비밀키를 제공하여 자동적으로 암호화하도록 구현해야 한다.
- 또한, 서버에서 JWT토근 검증기능을 구현하는 것이 중요하다.
- Node.js의 경우, jsonwebtoken 라이브러리는 verify()와 decode()라는 두개의 메쏘드를 제공하는데, 간혹 decode()만 사용하여 토큰을 전달하고 검증하지 않는 경우가 있다.
- 서버에서 JWT검증하는 기능이 없으면 공격자가 Burp에 JWT extension과 같은 편리한 도구를 이용하여 JWT Payload를 임의로 변조, 재서명으로 JWT를 변조하여 인증을 우회하거나 권한을 상승할 수 있다.

2. JWT Token의 만료기간을 설정한다.

- JWT의 Payload부분에 exp필드를 추가하여 만료기간을 설정하고 서버가 JWT 검증하도록 구현한다.
- 클라이언트의 재로그인 불편함을 상쇄하기 위해 Access Token이외에 Refresh Token을 추가로 생성하여 클라이언트에게 제공하도록 한다.
- 즉, 클라이언트는 2개의 Token을 가지고 있다가 Access Toek의 만료기간이 지나면 Refresh Token을 서버에 전송하여 클라이언트가 다시 로그인하지 않도록 하는 것이다.
- 결국은 Access Token의 유효기간은 짧게 설정하고 Refresh Token의 유효기간은 길게 설정하여 클라이언트 인증을 강화하고 사용자 불편을 최소화하는 것이다.
- 금융권 API 보안점검 가이드에 따르면 Access Token은 10분 이용가능 횟수는 1회로 제한하도록 가이드 되어 있다.
- Refresh Token에 대한 가이드는 없지만 통상 1일~2주까지 다양하므로 서비스의 특성을 고려하여 정책을 설정한다.

3. 중요정보(패스워드, 개인식별정보 등)을 Header, Payload에 포함시키지 않는다.

- JWT포멧의 제한적인 길이떄문에 Payload에 항목을 추가해서 사용하는 경우가 많다.
- JWT는 평문으로 되어 있으며, “.”를 구분자로 Header, Payload, Signature를 쉽게 식별 할수 있으며, Burp에 JWT Extentiond을 설치하면 아주 손쉽게 그 내용을 Decoding하고 변조공격도 시도할 수 있다.
- 특히, 경우에 따라서는 Header에 변수를 인증과 관련된 데이터를 추가하여 구현하는 경우도 있는데, 이런 경우, 기존의 referer를 이용한 HTTP Header SQL Injection 취약점과 유사한 공격이 먹히는 경우가 있다.
- 따라서, Header 또는 Payload에 패스워드나 개인식별정보와 같은 중요정보를 포함시키는 것은 아주 위험한 생각이다.

4. 키관리시스템을 이용하여 비밀키를 정기적으로 교체한다.

- JWT를 서명하는데 사용되는 서버의 비밀키는 잘 알려진 Brute force공격에 노출될수 있다.
- 다만 키값을 변경하는 것은 모든 JWT token을 무효화시키는 문제가 발생할 수 있으므로 키 교체전략을 잘 짜야한다.
- 그러나 JWT Token은 클라이언트에 평문으로 존재하기 때문에 공격자에게는 시간싸움일 뿐이므로 정기적인 키교체 전략은 키관리시스템과 같은 자동화의 도움을 받는 것이 효율적이다.

5. 안전성이 확인된 강력한 암호화 알고리즘을 사용한다.

- 서버가 JWT Token을 발행할때 사용하는 암호화 알고리즘은 HS256과 RS256이 대표적이다.
- JWT Token을 디코딩하면 Header 부분의 “alg”필드에서 사용된 암호화 알고리즘을 확인할 수 있다.
- JWT 라이브러리를 사용할때 알고리즘을 선택할 수 있으며, HS256보다는 RS256과 같은 강력한 알고리즘 선택이 필수적이다.

6. Token Whitelisting 또는 Blacklisting을 구현하여 만료된 JWT를 차단한다.

- JWT Token은 탈취, 로그아웃했지만 유효기간이 남아있는 Token의 부정사용 등과 같은 공격에 노출될수 있다.
- 이런 취약점을 방어하기 위하여 서버에서 Token Whitelisting 또는 Blacklist 기능을 구현하여 부정사용되지 않도록 해야한다.
- 또한, 유효기간이 지나지 않았어도 Blacklist에 등록된 Token은 자동 차단되도록 구현하는 것이 중요하다. Token Whitelisting 또는 Blacklisting은 금융권 API 보안점검 가이드에 명시되지는 않았지만 전자금융거래에는 반드시 필요한 내용이다.
- 오픈소스 API Gateway인 Kong을 이용하는 경우 커뮤니티에서 제공하는 플러그인을 활용할 수 있으며, AWS API Gateway를 사용하는 경우에는 AWS Lambda와 조합하여 사용자 정의 Authorizer를 작성함으로써 구현할 수 있다.
- API Gateway에서 Whitelisting 또는 Blacklisting기능을 제공하지 않는 경우에는 인증서비스나 백앤드에서 해당기능을 구현하여 API Gateway와 연동시키는 방법도 있다.
JWT와 관련된 취약점 이슈는 지속적으로 나타나고 있으므로 위에서 살펴본 보안강화방안 이외에도 최신 취약점 동향, 공격사례 등을 모니터링하여 보안강화방안을 마련할 필요가 있다.