From 543c6b6b98e4996f90f4570e123632afef2e1fb4 Mon Sep 17 00:00:00 2001 From: kavix Date: Wed, 17 Jun 2026 19:34:39 +0530 Subject: [PATCH 1/5] Fix #28038: Reject multiple Authorization headers with 400 Bad Request --- .../valve/IdentityContextCreatorValve.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java index 99c40a8f8eb8..7d2fccfdbc1b 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java @@ -28,8 +28,10 @@ import org.wso2.carbon.identity.core.internal.context.OrganizationResolver; import java.io.IOException; +import java.util.Enumeration; import javax.servlet.ServletException; +import javax.servlet.http.HttpServletResponse; public class IdentityContextCreatorValve extends ValveBase { @@ -47,6 +49,19 @@ public IdentityContextCreatorValve() { @Override public void invoke(Request request, Response response) throws IOException, ServletException { + Enumeration authHeaders = request.getHeaders("Authorization"); + if (authHeaders != null) { + int authHeaderCount = 0; + while (authHeaders.hasMoreElements()) { + authHeaders.nextElement(); + authHeaderCount++; + if (authHeaderCount > 1) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Multiple Authorization headers are not allowed."); + return; + } + } + } + try { initIdentityContext(); initRequest(request); From c71c20eb55c4691ba8619dddea4d9f093db115ba Mon Sep 17 00:00:00 2001 From: kavix Date: Wed, 17 Jun 2026 20:01:45 +0530 Subject: [PATCH 2/5] Fix #28038: Add unit test and comments for Authorization header validation --- .../valve/IdentityContextCreatorValve.java | 6 ++ .../IdentityContextCreatorValveTest.java | 82 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValveTest.java diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java index 7d2fccfdbc1b..a944c51d00db 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java @@ -49,6 +49,9 @@ public IdentityContextCreatorValve() { @Override public void invoke(Request request, Response response) throws IOException, ServletException { + // Enforce RFC 9110 §11.6.1: The Authorization header is a single-value field. + // Rejecting requests with multiple Authorization headers early at the transport/valve layer + // prevents potential header manipulation and security vulnerabilities. Enumeration authHeaders = request.getHeaders("Authorization"); if (authHeaders != null) { int authHeaderCount = 0; @@ -56,6 +59,9 @@ public void invoke(Request request, Response response) throws IOException, Servl authHeaders.nextElement(); authHeaderCount++; if (authHeaderCount > 1) { + if (LOG.isDebugEnabled()) { + LOG.debug("Rejecting request with multiple Authorization headers for URI: " + request.getRequestURI()); + } response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Multiple Authorization headers are not allowed."); return; } diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValveTest.java b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValveTest.java new file mode 100644 index 000000000000..984ead7244c9 --- /dev/null +++ b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValveTest.java @@ -0,0 +1,82 @@ +package org.wso2.carbon.identity.core.context.valve; + +import org.apache.catalina.Valve; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import javax.servlet.http.HttpServletResponse; +import java.util.Enumeration; +import java.util.Vector; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class IdentityContextCreatorValveTest { + + private IdentityContextCreatorValve identityContextCreatorValve; + + @Mock + private Valve nextValve; + + private AutoCloseable closeable; + + @BeforeMethod + public void setUp() { + closeable = MockitoAnnotations.openMocks(this); + identityContextCreatorValve = new IdentityContextCreatorValve(); + identityContextCreatorValve.setNext(nextValve); + } + + @AfterMethod + public void tearDown() throws Exception { + if (closeable != null) { + closeable.close(); + } + } + + @Test + public void testInvokeWithMultipleAuthorizationHeaders() throws Exception { + Vector headers = new Vector<>(); + headers.add("Bearer token1"); + headers.add("Bearer token2"); + final Enumeration headerEnum = headers.elements(); + + Request request = new Request(null) { + @Override + public Enumeration getHeaders(String name) { + if ("Authorization".equals(name)) { + return headerEnum; + } + return super.getHeaders(name); + } + + @Override + public String getRequestURI() { + return "/oauth2/userinfo"; + } + }; + + final int[] errorStatus = new int[1]; + final String[] errorMessage = new String[1]; + Response response = new Response() { + @Override + public void sendError(int status, String message) throws java.io.IOException { + errorStatus[0] = status; + errorMessage[0] = message; + } + }; + + identityContextCreatorValve.invoke(request, response); + + org.testng.Assert.assertEquals(errorStatus[0], HttpServletResponse.SC_BAD_REQUEST); + org.testng.Assert.assertEquals(errorMessage[0], "Multiple Authorization headers are not allowed."); + verify(nextValve, never()).invoke(any(Request.class), any(Response.class)); + } +} From d387af41a557a0167d776693dbbee1b9584838f6 Mon Sep 17 00:00:00 2001 From: kavix Date: Wed, 17 Jun 2026 20:14:10 +0530 Subject: [PATCH 3/5] Update debug log message and fix test dependencies --- .../org.wso2.carbon.identity.core/pom.xml | 12 ++++++++++++ .../context/valve/IdentityContextCreatorValve.java | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/components/identity-core/org.wso2.carbon.identity.core/pom.xml b/components/identity-core/org.wso2.carbon.identity.core/pom.xml index 43ce964c798d..930814a589e9 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/pom.xml +++ b/components/identity-core/org.wso2.carbon.identity.core/pom.xml @@ -165,6 +165,18 @@ h2 test + + org.apache.tomcat + tomcat-coyote + ${apache.tomcat-catalina.version} + test + + + org.apache.tomcat + tomcat-util + ${apache.tomcat-catalina.version} + test + diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java index a944c51d00db..2fb561f515dd 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java @@ -60,7 +60,7 @@ public void invoke(Request request, Response response) throws IOException, Servl authHeaderCount++; if (authHeaderCount > 1) { if (LOG.isDebugEnabled()) { - LOG.debug("Rejecting request with multiple Authorization headers for URI: " + request.getRequestURI()); + LOG.debug("Multiple Authorization headers detected."); } response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Multiple Authorization headers are not allowed."); return; From c15af386952f5b8de187bc8eb84242fa62dcfc09 Mon Sep 17 00:00:00 2001 From: kavix Date: Wed, 17 Jun 2026 20:43:46 +0530 Subject: [PATCH 4/5] Change debug log to warn log for multiple Authorization headers --- .../core/context/valve/IdentityContextCreatorValve.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java index 2fb561f515dd..28bddf8245f1 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java @@ -59,9 +59,7 @@ public void invoke(Request request, Response response) throws IOException, Servl authHeaders.nextElement(); authHeaderCount++; if (authHeaderCount > 1) { - if (LOG.isDebugEnabled()) { - LOG.debug("Multiple Authorization headers detected."); - } + LOG.warn("Multiple Authorization headers detected."); response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Multiple Authorization headers are not allowed."); return; } From 131cc5f1bb3f54a80fe03025eaec374d87ea9277 Mon Sep 17 00:00:00 2001 From: kavix Date: Wed, 17 Jun 2026 20:49:39 +0530 Subject: [PATCH 5/5] Move header validation inside try block to ensure IdentityContext cleanup --- .../valve/IdentityContextCreatorValve.java | 30 +++++++++---------- .../IdentityContextCreatorValveTest.java | 1 + 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java index 28bddf8245f1..1178a6990145 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/main/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValve.java @@ -49,24 +49,24 @@ public IdentityContextCreatorValve() { @Override public void invoke(Request request, Response response) throws IOException, ServletException { - // Enforce RFC 9110 §11.6.1: The Authorization header is a single-value field. - // Rejecting requests with multiple Authorization headers early at the transport/valve layer - // prevents potential header manipulation and security vulnerabilities. - Enumeration authHeaders = request.getHeaders("Authorization"); - if (authHeaders != null) { - int authHeaderCount = 0; - while (authHeaders.hasMoreElements()) { - authHeaders.nextElement(); - authHeaderCount++; - if (authHeaderCount > 1) { - LOG.warn("Multiple Authorization headers detected."); - response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Multiple Authorization headers are not allowed."); - return; + try { + // Enforce RFC 9110 §11.6.1: The Authorization header is a single-value field. + // Rejecting requests with multiple Authorization headers early at the transport/valve layer + // prevents potential header manipulation and security vulnerabilities. + Enumeration authHeaders = request.getHeaders("Authorization"); + if (authHeaders != null) { + int authHeaderCount = 0; + while (authHeaders.hasMoreElements()) { + authHeaders.nextElement(); + authHeaderCount++; + if (authHeaderCount > 1) { + LOG.warn("Multiple Authorization headers detected."); + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Multiple Authorization headers are not allowed."); + return; + } } } - } - try { initIdentityContext(); initRequest(request); initAccessTokenIssuedOrganization(request.getRequestURI()); diff --git a/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValveTest.java b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValveTest.java index 984ead7244c9..bafdb19e7644 100644 --- a/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValveTest.java +++ b/components/identity-core/org.wso2.carbon.identity.core/src/test/java/org/wso2/carbon/identity/core/context/valve/IdentityContextCreatorValveTest.java @@ -29,6 +29,7 @@ public class IdentityContextCreatorValveTest { @BeforeMethod public void setUp() { + System.setProperty("carbon.home", "."); closeable = MockitoAnnotations.openMocks(this); identityContextCreatorValve = new IdentityContextCreatorValve(); identityContextCreatorValve.setNext(nextValve);