IT
개발, 하드웨어, 소프트웨어 토론
JWT 알고리즘 none 공격, 아직도 당한다
🇰🇷 야근러2시간 전조회 171댓글 3
오늘 코드리뷰하다가 또 서명 검증 쪽에서 한 대 맞았다.
JWT 토큰 검증하는 로직이었는데, 헤더에 `alg` 필드 그대로 믿고 검증하는 코드가 버젓이 올라와 있었음. 서버 쪽에서 RS256으로 서명했는데 공격자가 헤더만 `none`으로 바꿔서 보내면? 검증 라이브러리가 "아 알고리즘 none이네 서명 안 봐도 되겠다" 하고 통과시켜버리는 거임. 이게 실제로 되는 라이브러리가 있다는 게 함정,
나도 처음 접했을 때 설마 했음. 근데 진짜 됨. `alg: "none"` 공격이라고 부르는 건데 JWT 스펙 자체가 none 알고리즘을 허용하고 있어서 구현체마다 처리가 다름. 라이브러리 믿고 그냥 `verify()` 한 줄 때리면 끝이라고 생각하잖아. 근데 그 verify 함수가 내부적으로 헤더의 alg 값을 그대로 쓰는 순간 이미 공격자한테 주도권 넘긴 거임.
더 무서운 건 알고리즘 혼동 공격인데. RS256은 비대칭키니까 공개키로 검증하잖아. 근데 공격자가 헤더를 HS256으로 바꿔버리면, 검증 로직이 "아 대칭키 알고리즘이네" 하면서 그 공개키를 HMAC 시크릿으로 써버림. 공개키는 말 그대로 공개되어 있으니까 공격자가 그걸로 HS256 서명 만들어서 보내면 검증 통과함. 비대칭 암호화를 쓰는데 결과적으로 공개키가 시크릿 키 역할을 하게 되는 거라는 게 함정,
이거 코드만 보면 전혀 이상해 보이지 않음. 검증 함수 호출하고 있고, 키도 넣고 있고, 에러 처리도 하고 있음. 정상적인 코드처럼 보이는데 취약한 거라서 리뷰에서도 잘 안 잡힘.
그리고 XML 서명 쪽은 더 카오스임. XML Signature Wrapping이라는 게 있는데, 서명된 노드를 문서 구조 안에서 위치만 옮겨놓으면 서명 검증은 통과하는데 실제로 애플리케이션이 참조하는 값은 공격자가 끼워넣은 다른 노드를 읽게 됨. 서명 검증은 "이 노드가 변조되지 않았음"만 보장하는 거지, "애플리케이션이 이 노드를 쓸 것임"을 보장하는 게 아니라는 게 함정,
SAML SSO 붙여본 사람은 알 텐데 XML 파싱하고 서명 검증하고 assertion 꺼내는 과정이 각각 따로 놀 수 있음. 검증은 A 노드 보고 있는데 로직은 B 노드 읽고 있으면 그냥 뚫리는 거임.
솔직히 나도 예전에 HMAC 검증할 때 문자열 비교를 `==`로 했었음. 타이밍 공격 같은 건 교과서에서나 나오는 거 아닌가 했는데, 바이트 단위로 앞에서부터 비교하면 틀린 위치에 따라 응답 시간이 미세하게 달라지고, 그걸 통계적으로 모으면 서명 값을 추론할 수 있다는 게 이론만은 아니더라. `constant-time compare` 써야 하는 이유가 있었음. 그냥 같은지 다른지만 보면 되는 건데 비교하는 방식까지 신경 써야 한다는 게 함정,
정리하면 서명 검증에서 개발자가 자주 놓치는 포인트가
1. **알고리즘을 토큰/메시지 안에서 가져오지 마.** 서버 설정으로 고정해. `alg` 헤더 무시하고 "우리 서버는 RS256만 씁니다" 이렇게 박아놔야 함.
2. **검증과 사용이 같은 데이터를 보고 있는지 확인해.** 특히 XML 기반 프로토콜. 서명 검증 통과한 노드와 실제 비즈니스 로직이 읽는 노드가 동일한지.
3. **문자열 비교는 constant-time으로.** 대부분 언어에서 전용 함수 제공함. Python이면 `hmac.compare_digest`, Node면 `crypto.timingSafeEqual`.
4. **라이브러리 기본 설정 믿지 마.** 기본값이 안전하리라는 보장 없음. 옵션 문서 한 번은 읽어봐야 함.
야근하면서 이런 거 파고 있으면 시간이 순삭되는데, 서명 검증은 "구현했다"랑 "안전하게 구현했다" 사이의 거리가 생각보다 멀어서 한번 제대로 안 짚으면 계속 같은 데서 당함. 코드 돌아가니까 괜찮겠지 하고 넘어가는 그 순간이 제일 위험하다는 게 함정,
댓글 3
댓글을 불러오는 중...