infiltr8 Lab
Home/ CVE/ 웹·서버/ WSO2 API Manager JWT 서명검증 우회
CVE · 웹·서버 · 인증우회 · CVE-2026-5430
심층 분석 연구 분석

CVE-2026-5430 (WSO2 API Manager JWT 서명검증 우회) 분석

WSO2 API Manager 등 4개 제품의 REST API 인증에서, 지원되지 않는 서명 알고리즘의 JWT가 만드는 예외를 인터셉터가 그냥 삼켜버려 인증 자체가 우회되는 취약점입니다(CVSS 3.1 = 10.0/9.8). 이 글은 원 어드바이저리를 재구성하고, 취약 버전의 실제 소스와 패치 diff를 직접 열어 "검증 로직은 멀쩡한데 왜 뚫리는가"를 코드 레벨로 분석합니다.

이 글의 코드 발췌(JWTUtil.java, OAuthAuthenticationInterceptor.java, GlobalThrowableMapper.java, OAuthJwtAuthenticatorImpl.java, JWTValidatorImpl.java, OAuthAuthenticationInterceptorException.java)는 wso2/carbon-apimgt 오픈소스(Apache License 2.0) 저장소의 실제 소스입니다. 취약 버전 코드는 패치 PR #13752 병합 커밋(4914da24315062f383bf9c729971d8338df6529b)의 첫 번째 부모 커밋(52ae133a3264e5485b26cf015147533ccddeeda6, GitHub raw로 2026-09-17 직접 fetch), 패치 후 코드는 병합 커밋 자체를 기준으로 라인 단위로 직접 대조했습니다. 패치 diff도 GitHub API(pulls/13752/files)의 파일별 patch를 그대로 받아 확인했습니다. 다만 "인증을 우회한 요청이 실제로 어느 코드 지점에서 위조 토큰의 admin 클레임을 최종 신뢰해 관리자 권한 동작을 수행하는가"의 마지막 연결 고리는 이 세션에서 REST 리소스 구현체까지 전부 추적하지 않았고, WSO2 공식 어드바이저리와 watchTowr/보도매체의 기술 설명을 근거로 재구성했습니다 — 그 연결 부분은 2차 출처 기반임을 밝힙니다. 또한 CVSS 점수는 어드바이저리 수치를 그대로 옮긴 것이 아니라 CVSS 3.1 공식 산식으로 이 세션에서 직접 재계산해 일치를 확인했습니다.

infiltr8 team 직접 분석 #jwt-alg-confusion#exception-swallowing#auth-bypass#wso2-apim
무엇WSO2 API Manager·API Control Plane·Traffic Manager·Universal Gateway의 REST API가 사용하는 JWT 인증이, 지원되지 않는 서명 알고리즘의 토큰이 만드는 예외를 인터셉터가 그냥 삼켜버려 우회되는 취약점입니다(CVSS 3.1 = 단일 테넌트 9.8 / 멀티 테넌트 10.0).
원리서명을 검증하는 JWTUtil은 실제로 이상한 알고리즘을 만나면 예외를 던져 정상적으로 거부합니다. 문제는 그 예외를 받은 인터셉터가 로그만 남기고 아무것도 다시 던지지 않은 채 정상 종료한다는 것입니다.
핵심인터셉터가 아무것도 던지지 않고 끝나면 Apache CXF의 인터셉터 체인은 "막지 않았으니 통과"로 간주해 실제 API 리소스 메서드 호출까지 그대로 진행합니다 — 위조된 admin 클레임을 가진 토큰이 검증되지 않은 채 관리자 권한 동작에 도달합니다.

01개요

CVE-2026-5430은 WSO2 API Manager(오픈소스 API 게이트웨이·관리 플랫폼)의 REST API(Admin/Publisher/DevPortal 등)가 사용하는 JWT(JSON Web Token) 인증에서, 지원되지 않는 서명 알고리즘의 토큰이 발생시키는 예외를 인증 인터셉터가 요청 거부로 연결하지 못하고 그냥 통과시키는 결함입니다. WSO2가 이 취약점에 매긴 자체 어드바이저리 번호는 WSO2-2026-5328로, CVE 번호(CVE-2026-5430)와는 다른 벤더 내부 식별자입니다.

공격자는 회원가입도 로그인도 필요 없이, RS256/RS384/RS512가 아닌 알고리즘으로 admin 클레임을 담은 JWT를 스스로 서명해 위조할 수 있습니다. 이 토큰이 인증을 우회해 통과하면, watchTowr의 표현대로 "모든 API 백엔드 엔드포인트와 그 자격 증명, 컨슈머 키·시크릿"까지 접근 범위에 들어갑니다.

발견자는 Hacktron Team이며, WSO2는 어드바이저리를 2026-05-03에 공개했습니다. 이후 2026-09-13, 위협 인텔리전스 업체 watchTowr의 허니팟 네트워크가 admin 권한이 박힌 위조 JWT가 실제로 유입되는 것을 포착해 실공격 관측(active in-the-wild exploitation)이 확인됐습니다.

CVSS: 9.8 (단일 테넌트, CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) / 10.0 (멀티 테넌트, Scope Changed — S:C)

WSO2 어드비저리 자체엔 CWE가 명시되지 않았습니다. 보도매체 다수는 이를 CWE-347(부적절한 암호서명 검증)로 분류합니다 — 이 글이 소스 대조로 직접 짚는 더 정밀한 근본원인(CWE-703, 예외적 조건의 부적절한 처리)은 §04 참고.

02영향받는 버전

대상취약 버전안전 버전
WSO2 API Manager 4.6.0update level 21 미만update level 21 이상
WSO2 API Manager 4.5.0update level 57 미만update level 57 이상
WSO2 API Manager 4.4.0update level 72 미만update level 72 이상
WSO2 API Manager 4.3.0update level 108 미만update level 108 이상
WSO2 API Manager 4.2.0update level 197 미만update level 197 이상
WSO2 API Manager 4.1.0update level 257 미만update level 257 이상
WSO2 API Control Plane 4.6.0update level 22 미만update level 22 이상
WSO2 API Control Plane 4.5.0update level 58 미만update level 58 이상
WSO2 Traffic Manager 4.6.0update level 21 미만update level 21 이상
WSO2 Traffic Manager 4.5.0update level 56 미만update level 56 이상
WSO2 Universal Gateway 4.6.0update level 21 미만update level 21 이상
WSO2 Universal Gateway 4.5.0update level 57 미만update level 57 이상

03기술적 배경 — CXF 인터셉터 체인과 "막지 않으면 통과"라는 기본값

WSO2 API Manager의 관리용 REST API(Admin·Publisher·DevPortal 등)는 Apache CXF의 JAX-RS 구현 위에서 동작합니다. CXF는 실제 리소스 메서드(예: 사용자 생성 API 구현체)를 호출하기 전, 여러 단계(Phase)별로 등록된 인터셉터를 순서대로 거치게 합니다 — 인증은 그중 PRE_INVOKE 단계에 등록된 OAuthAuthenticationInterceptor가 담당합니다.

이 인터셉터는 AbstractPhaseInterceptor를 상속하는데, 이 계열 인터셉터의 동작 방식이 이번 취약점의 핵심 전제입니다 — handleMessage()가 명시적으로 예외를 던지거나 체인을 중단시키지 않는 한, CXF는 이 인터셉터를 "통과"로 간주하고 다음 단계(결국 실제 리소스 메서드 호출)로 그대로 진행합니다. 즉 이 설계는 "의심되면 막는다(fail-closed)"가 아니라 "막으라고 말하지 않으면 통과시킨다(fail-open)"는 기본값 위에 서 있습니다.

인증 자체는 요청의 Authorization: Bearer <JWT> 헤더를 OAuthJwtAuthenticatorImpl.authenticate()가 받아, 서명 검증을 JWTValidatorImpl.validateSignature() → JWTUtil.verifyTokenSignature()로 위임하는 구조입니다. JWT 헤더에는 서명에 쓴 알고리즘(alg)이 자체 명시되는데, WSO2는 RS256·RS384·RS512(RSA 계열) 서명만 신뢰하도록 이 값을 검사합니다 — 그 외 알고리즘(예: 대칭키 HS256, 무서명 none 등)이면 검증을 거부해야 합니다.

여기까지 정리하면 이 API의 안전은 정확히 한 가지에 달려 있습니다 — 알고리즘 검증이 실패했을 때, 그 실패가 정말로 CXF 체인을 멈추는가. 다음 절에서 이 연결이 어디서 끊기는지 봅니다.

04취약점 발생 원인

이 취약점은 "서명 검증이 허술해서"가 아니라 "검증 실패를 처리하는 방식이 잘못돼서" 뚫립니다. 미흡점 세 개가 순서대로 맞물립니다 — 검증 로직(W1)은 실제로 이상 알고리즘을 정상적으로 거부하지만, 그 거부 신호를 받은 인터셉터(W2)가 신호를 소거해버리고, 이 소거가 완전한 우회로 이어지는 것은 CXF 인터셉터 체인의 기본 설계(W3) 때문입니다.

1"RSA 계열이 아니면 거부한다" — 서명 검증 자체는 실제로 이상 알고리즘을 거부한다

핵심

JWTUtil.verifyTokenSignature(jwt, alias)는 토큰 헤더의 alg가 RS256/RS384/RS512가 아니면 예외를 던져 정상적으로 거부합니다. 문제는 이 절이 아니라, 그 예외를 누가 어떻게 받느냐입니다.

이 메서드는 truststore에서 인증서를 찾고, 그 인증서로 서명을 검증하기 전에 먼저 JWT 헤더의 알고리즘을 확인합니다. RSA 계열이 아니면 verifyTokenSignature(jwt, publicKey)(실제 암호학적 서명 검증)까지 가지도 않고, 그 자리에서 즉시 APIManagementException을 던집니다 — 이 자체는 정상적인 방어입니다.

public static boolean verifyTokenSignature(SignedJWT jwt, String alias) throws APIManagementException { Certificate publicCert = null; //Read the client-truststore.jks into a KeyStore try { publicCert = APIUtil.getCertificateFromParentTrustStore(alias); } catch (APIManagementException e) { throw new APIManagementException("Error retrieving certificate from truststore ",e); } if (publicCert != null) { JWSAlgorithm algorithm = jwt.getHeader().getAlgorithm(); if ((JWSAlgorithm.RS256.equals(algorithm) || JWSAlgorithm.RS512.equals(algorithm) || JWSAlgorithm.RS384.equals(algorithm))) { return verifyTokenSignature(jwt, (RSAPublicKey) publicCert.getPublicKey()); } else { log.error("Public key is not RSA"); throw new APIManagementException("Public key is not RSA"); } } else { log.error("Couldn't find a public certificate with alias " + alias + " to verify the signature"); throw new APIManagementException( "Couldn't find a public certificate with alias " + alias + " to verify the signature"); } }

wso2/carbon-apimgt 실제 소스(패치 전) — JWTUtil.java:107-131 · verifyTokenSignature(SignedJWT, String) (부모 커밋 52ae133).

왜 이렇게 됐나이 함수만 떼어 보면 완벽합니다 — RSA 계열이 아니면 실제 암호학적 검증까지 가지 않고 즉시 예외로 거부합니다. 붉게 표시한 부분(122-125행)이 바로 그 거부 지점입니다 — 다음 절의 질문은 "이 예외를 받은 쪽이 정말로 요청을 막는가"입니다.

2"예외가 났으니 거부됐겠지" — 인터셉터가 예외를 잡고 그냥 지나간다

핵심

OAuthAuthenticationInterceptor.handleMessage()는 인증 체인에서 올라온 APIManagementException을 catch 하지만, 이를 실제 요청 거부로 연결하지 않고 로그만 남긴 뒤 그대로 리턴합니다.

W1의 예외는 곧바로 이 인터셉터로 올라오지 않습니다 — OAuthJwtAuthenticatorImpl.authenticate()가 JWTValidatorImpl.validateSignature()를 거쳐 JWTUtil.verifyTokenSignature()를 호출하고, 이 호출 경로의 어느 메서드도 APIManagementException을 흡수하지 않은 채 그대로 위로 전파합니다. 이 예외를 처음으로 catch하는 곳이 바로 인터셉터의 handleMessage()입니다.

if (authenticator.authenticate(authContext)) { inMessage = JWTAuthenticationUtils.addToMessageContext(inMessage, authContext); if (logger.isDebugEnabled()) { logger.debug("Request has been Authenticated , authentication type : "+ authenticationType); } logAuditOperation(inMessage); } else { logger.error("Failed to Authenticate , authentication type : "+ authenticationType); throw new AuthenticationException("Unauthenticated request"); } } catch (APIManagementException e) { logger.error("Authentication Failure " + e.getMessage()); return; }

wso2/carbon-apimgt 실제 소스(패치 전) — OAuthAuthenticationInterceptor.java:98-111 · handleMessage() (부모 커밋 52ae133).

왜 이렇게 됐나바로 위 throw new AuthenticationException(...) 분기(106행)는 뒤에서 볼 GlobalThrowableMapper가 401로 정확히 매핑합니다. 하지만 바로 아래 catch 블록(108-110행)은 똑같이 "인증 실패"인 상황인데도 예외를 로그만 남기고 삼킵니다. handleMessage()가 아무것도 던지지 않고 정상 종료되면, CXF의 PRE_INVOKE 인터셉터 체인은 "이 인터셉터를 통과했다"고 보고 다음 단계로 그대로 진행합니다. 참고로 이 파일에는 authenticator == null일 때 쓰이는 두 번째 인증 경로(147-149행 부근, authenticatorMap을 통해 기본 JWT/Opaque 인증을 수행하는 경로 — 이번 CVE의 실제 취약 경로)에도 완전히 동일한 패턴(로그만 남기고 아무것도 다시 던지지 않음)이 있었습니다. 패치는 이 두 catch 블록을 모두 고칩니다.

3"막지 않으면 통과" — 안전장치는 있었지만 신호가 거기까지 가지 않았다

핵심

GlobalThrowableMapper는 AuthenticationException·APIManagementException을 정확한 HTTP 오류로 바꾸는 매핑 로직을 이미 갖고 있었습니다. 문제는 W2가 이 매핑을 발동시킬 아무것도 다시 던지지 않았다는 것입니다.

CXF의 JAX-RS 계층에서 리소스 메서드 호출 중(또는 이번처럼 인터셉터 단계에서) 예외가 실제로 던져지면, ExceptionMapper인 GlobalThrowableMapper.toResponse()가 이를 받아 적절한 HTTP 상태 코드로 변환합니다. 아래는 그중 인증 실패를 401로 매핑하는 부분입니다 — 이 코드는 이번 패치로 바뀌지 않았습니다(원래도 정상 동작).

if (e instanceof AuthenticationException) { ErrorDTO errorDetail = new ErrorDTO(); errorDetail.setCode((long)401); errorDetail.setMoreInfo(""); errorDetail.setMessage(""); errorDetail.setDescription(e.getMessage()); return Response .status(Response.Status.UNAUTHORIZED) .type(MediaType.APPLICATION_JSON_TYPE) .entity(errorDetail) .build(); }

wso2/carbon-apimgt 실제 소스(패치 전·후 동일 — 이 분기는 이번 커밋에서 변경되지 않았습니다) — GlobalThrowableMapper.java:128-139.

왜 이렇게 됐나이 매핑 로직은 처음부터 옳았습니다 — 무언가 AuthenticationException이나 APIManagementException으로 이 코드에 도달하기만 하면 401을 정확히 돌려줬을 것입니다. 하지만 CXF의 PRE_INVOKE 인터셉터 체인은 "명시적으로 막으라고 말하지 않으면 통과시킨다"는 기본값(fail-open) 위에 있어서, W2에서 아무것도 던지지 않는 순간 이 안전장치 자체가 호출될 기회를 잃습니다. 안전장치가 없었던 게 아니라, 그 안전장치를 켜는 스위치가 눌리지 않은 것입니다.

분석 결론

무게 중심은 W2(인터셉터의 예외 삼킴) 하나입니다.

W1(알고리즘 검증)과 W3(HTTP 오류 매핑 인프라)는 각각 떼어 놓고 보면 정상 설계입니다 — "이상 알고리즘은 예외로 거부한다", "인증 예외는 401로 매핑한다"는 두 설계 모두 옳습니다. W2가 그 사이에서 신호를 끊어, W1이 만든 거부 신호가 W3에 끝내 도달하지 못합니다.

그래서 패치도 이 신호를 복구하는 데 집중합니다 — 새 예외 타입(OAuthAuthenticationInterceptorException)을 만들어 W2가 다시 던지게 하고, W3(GlobalThrowableMapper)가 이 타입도 인식하도록 조건 한 줄을 더합니다. W1의 알고리즘 검증 로직 자체는 바뀌지 않았습니다.

05공격 과정

5.1 공격 과정 요약

공격 흐름도
공격 흐름도 (직접 작성)

5.2 상세 공격 과정

이 취약점은 흔한 "서명 위조에 성공했다" 계열과 결이 다릅니다 — 공격자는 실제 서명 검증을 속이지 않습니다. 애초에 RSA 서명을 만들 필요조차 없습니다. 확인 저희가 직접 대조한 코드 경로에서, 알고리즘 검사(§04 W1)는 서명값을 들여다보기도 전에 예외를 던지므로 — 필요한 건 "RS256/384/512가 아닌 alg 값을 가진, 형식만 맞는 3-세그먼트 토큰" 하나뿐입니다.

분석 저희가 이번 세션에서 추적한 OAuthJwtAuthenticatorImpl.authenticate()의 호출 순서상, 서명 검증(validateSignature)은 스코프 클레임을 읽는 handleScopeValidation()보다 먼저 실행됩니다. 즉 이 우회 경로를 타는 요청은 서명 검증뿐 아니라 스코프(권한) 검증 자체가 아예 실행되지 않은 채 리소스 메서드에 도달할 가능성이 있습니다 — WSO2·watchTowr가 설명한 "admin 클레임을 넣은 위조 토큰"은 실제 공격에 쓰인 구체적인 방법이지만, 저희가 대조한 코드만 보면 이론적으로는 그보다 더 헐거운 조건(스코프 검증 자체의 부재)까지도 열려 있었을 수 있습니다. 이 마지막 한 문장은 리소스 구현체까지 끝까지 추적하지 않은 추론이며, WSO2가 별도로 확증하지 않은 부분입니다.

실제 작동하는 위조 토큰의 정확한 헤더·페이로드 값은 이 글에 싣지 않습니다(무해화 원칙) — 여기서는 "어떤 조건이 이 경로를 타는가"까지만 개념적으로 다룹니다.

5.3 미흡점 ↔ 공격 흐름 매핑

미흡점(§4)발화 지점(§5.2)여는 프리미티브→ 다음으로 잇는 것
W1 — 알고리즘 검증(정상 거부)JWTUtil.verifyTokenSignature() 호출(§8.2 ③단계)RSA 계열이 아닌 알고리즘에 대해 거부 신호(APIManagementException) 발생예외가 호출 스택을 그대로 거슬러 인터셉터까지 전파
W2 — 인터셉터의 예외 삼킴OAuthAuthenticationInterceptor.handleMessage()의 catch(§8.2 ④단계)거부 신호가 로그로만 소비되고 아무것도 다시 던지지 않음handleMessage()가 예외 없이 정상 종료
W3 — CXF 체인의 fail-open 기본값PRE_INVOKE 인터셉터 체인의 통과 판정(§8.2 ⑤단계)"막으라고 말하지 않으면 통과"라는 설계가 소거된 신호를 그대로 승인으로 해석위조 admin 클레임을 가진 요청이 실제 리소스 메서드에 도달

06취약점 패치

OAuthAuthenticationInterceptor.java·handleMessage() — 두 개의 catch(APIManagementException) 블록·PR #13752, 병합 커밋 4914da24315062f383bf9c729971d8338df6529b(2026-04-12)
logger.error("Authentication Failure " + e.getMessage()); return; String errorMsg = "Error while authenticating incoming request"; logger.error(errorMsg, e); throw new OAuthAuthenticationInterceptorException(errorMsg, e); ( authenticator == null 분기 — 실제 취약 경로 — 의 두 번째 catch 블록도 동일한 방식으로 수정 ) logger.error("Error while authenticating incoming request to API Manager REST API", e); String errorMsg = "Error while authenticating incoming request to API Manager REST API"; logger.error(errorMsg, e); throw new OAuthAuthenticationInterceptorException(errorMsg, e);

왜W2(예외를 삼키고 그냥 리턴/통과)가 근본원인이었기 때문입니다. 인증 체인에서 올라온 APIManagementException을 다시 던지지 않으면, CXF PRE_INVOKE 인터셉터 체인이 요청을 계속 진행시킨다는 사실이 핵심입니다.

어떻게① 새 예외 타입 OAuthAuthenticationInterceptorException(WebApplicationException 상속)을 신설하고, ② 두 catch 블록 모두 로그만 남기던 것을 이 예외를 던지도록 바꿉니다. WebApplicationException은 JAX-RS 표준 예외라 CXF가 이를 명시적인 요청 실패로 인식해 인터셉터 체인을 중단시킵니다. ③ GlobalThrowableMapper.toResponse()의 조건을 e instanceof APIManagementException || e instanceof OAuthAuthenticationInterceptorException으로 넓혀, 이 새 예외의 cause에 담긴 원래 APIManagementException(예: JWTUtil이 던진 것)까지 ExceptionUtils.getThrowableList()로 거슬러 올라가 정확한 HTTP 상태 코드로 매핑합니다. ④ JWTUtil.java도 별도로 "Public key is not RSA" 예외 생성 시 ExceptionCodes.ACCESS_TOKEN_INVALID를 붙여, ③의 매핑이 401(인증 실패)로 정확히 떨어지게 합니다 — 이 코드가 없었다면 예외는 던져지되 에러 코드가 비어 있어 매핑이 의도한 대로 되지 않았을 것입니다.

비고커밋 메시지·PR 제목은 "Improve exception handling"입니다. 실제로 이 PR이 건드린 4개 파일(JWTUtil.java, OAuthAuthenticationInterceptor.java, GlobalThrowableMapper.java, 신규 OAuthAuthenticationInterceptorException.java) 중 서명 검증 알고리즘 자체를 바꾸는 파일은 하나도 없습니다 — 제목 그대로 예외 처리 흐름이 진짜 원인이었다는 뜻입니다. 자매 저장소 product-apim의 PR #14167은 코드 수정이 아니라 통합 테스트 케이스(AdvancedConfigurationsTestCase.java) 갱신뿐입니다.

07대응방안

위 표에 정리한 제품별 update level 이상으로 갱신하는 것이 유일한 근본 해결책입니다. 커뮤니티 사용자는 GitHub PR #13752/#14167의 패치를 직접 적용하거나 이후 릴리스로 업그레이드하고, WSO2 지원 구독 고객은 WSO2 Update Manager로 표의 update level 이상으로 갱신합니다.

08왜 이게 반복되는가

이 사고와 저희가 함께 다룬 Issabel Framework 하드코딩 JWT 키 사례(CVE-2026-89026)는 둘 다 "위조 JWT로 관리자 권한을 탈취"라는 같은 결과에 이르지만, 근본원인은 정반대입니다 — Issabel은 검증에 쓰는 키 자체가 공개돼 서명 검증이 애초에 무의미했던 경우(CWE-321)였고, WSO2는 검증 로직·오류 매핑 인프라 모두 각각은 정상이었지만 그 사이 예외 처리 한 곳이 신호를 끊어버린 경우(CWE-703)입니다. "서명을 확인한다"는 문장 하나로는 안전을 보장할 수 없습니다 — 확인에 실패했을 때 정확히 무엇을 하는지까지가 그 문장의 일부라는 것을, 두 사례가 나란히 보여줍니다.

체크된 예외(checked exception)를 catch한 뒤 로그만 남기고 지나가는 코드는 Java 코드베이스에서 흔합니다 — 대부분은 무해하지만, 그 예외가 "이 요청을 거부해야 한다"는 보안 판단을 실어 나르는 통로일 때는 이야기가 다릅니다. 이번 사례처럼 인증 프레임워크가 "명시적으로 막지 않으면 통과"라는 fail-open 기본값을 갖고 있다면, catch 블록 하나의 누락이 곧 인증 전체의 누락으로 번집니다.

패치 완전성 평가. ① 근본원인을 막았나: 확인 — 같은 파일 안의 두 catch 블록 모두 동일한 패턴으로 수정됐고, 신호가 소거되는 지점이 남아 있지 않습니다. ② variant 여지: 분석 — 저장소 전체에서 JWTUtil.verifyTokenSignature류를 호출하는 지점을 찾아보면 API 게이트웨이(데이터플레인) 쪽의 GatewayUtils.java·InternalAPIKeyAuthenticator.java(Internal Key/MCP 인증 경로)도 있습니다 — 이 경로가 같은 "예외를 잡고도 명시적으로 거부하지 않는" 패턴을 공유하는지는 이번 세션에서 끝까지 추적하지 않았습니다. 정적 신호일 뿐이며 별도 patch-gap 후보로만 남깁니다. ③ silent/불완전 재패치: 분석 — product-apim의 PR #14167은 통합 테스트 갱신뿐이라 재패치가 필요했다는 신호로 보기 어렵고, 이 패치를 되짚는 후속 커밋도 확인되지 않았습니다. 종합하면 이번 패치는 REST API 인터셉터 경로에 한해 근본원인 제거로 판단되며, 게이트웨이 측 Internal Key 경로는 별도 확인이 필요한 가설로 남습니다.

서명 검증 로직도, 오류를 HTTP 코드로 바꾸는 인프라도 각각은 멀쩡했습니다. 문제는 그 둘 사이, 실패를 실패로 전달하는 단 하나의 통로였습니다 — 그래서 저희는 도구가 아니라 이 질문("이 예외가 정말로 요청을 막고 있는가")을 남깁니다.

09결론

CVE-2026-5430은 WSO2 API Manager 등 4개 제품의 REST API 인증에서, 지원되지 않는 서명 알고리즘의 JWT가 만드는 예외를 인터셉터가 로그만 남기고 그냥 통과시켜 발생하는 인증 우회입니다(CVSS 3.1 = 9.8~10.0). 서명 검증 로직도, HTTP 오류 매핑 인프라도 각각은 정상이었지만, 그 사이 인터셉터의 catch 블록 두 곳이 거부 신호를 소거해 CXF 인터셉터 체인의 "막지 않으면 통과" 기본값이 그대로 우회로 이어졌습니다. 패치는 새 예외 타입을 던지고 매핑 조건을 넓혀 이 신호를 복구합니다. 2026-09-13 watchTowr가 실공격을 관측했습니다.

참고 자료

이 글의 코드 발췌(JWTUtil.java, OAuthAuthenticationInterceptor.java, GlobalThrowableMapper.java, OAuthJwtAuthenticatorImpl.java, JWTValidatorImpl.java, OAuthAuthenticationInterceptorException.java)는 wso2/carbon-apimgt 오픈소스(Apache License 2.0) 저장소의 실제 소스입니다. 취약 버전 코드는 패치 PR #13752 병합 커밋(4914da24315062f383bf9c729971d8338df6529b)의 첫 번째 부모 커밋(52ae133a3264e5485b26cf015147533ccddeeda6, GitHub raw로 2026-09-17 직접 fetch), 패치 후 코드는 병합 커밋 자체를 기준으로 라인 단위로 직접 대조했습니다. 패치 diff도 GitHub API(pulls/13752/files)의 파일별 patch를 그대로 받아 확인했습니다. 다만 "인증을 우회한 요청이 실제로 어느 코드 지점에서 위조 토큰의 admin 클레임을 최종 신뢰해 관리자 권한 동작을 수행하는가"의 마지막 연결 고리는 이 세션에서 REST 리소스 구현체까지 전부 추적하지 않았고, WSO2 공식 어드바이저리와 watchTowr/보도매체의 기술 설명을 근거로 재구성했습니다 — 그 연결 부분은 2차 출처 기반임을 밝힙니다. 또한 CVSS 점수는 어드바이저리 수치를 그대로 옮긴 것이 아니라 CVSS 3.1 공식 산식으로 이 세션에서 직접 재계산해 일치를 확인했습니다.