Skip to content

[#2837] - Null path normalization in getPathWithinApplication causes filter chain bypass#2836

Open
xaccefy wants to merge 3 commits into
apache:mainfrom
xaccefy:fix-null-normalization
Open

[#2837] - Null path normalization in getPathWithinApplication causes filter chain bypass#2836
xaccefy wants to merge 3 commits into
apache:mainfrom
xaccefy:fix-null-normalization

Conversation

@xaccefy

@xaccefy xaccefy commented Jul 21, 2026

Copy link
Copy Markdown

Problem

WebUtils.getPathWithinApplication() can return null when normalize() fails (path traverses above root, e.g. /../). This null propagates through PathMatchingFilterChainResolver.getChain() which matches zero patterns, causing AbstractShiroFilter to fall back to the original container FilterChain — bypassing all Shiro filters.

This was introduced in commit b90f918 (Shiro 1.5.3, CVE-2020-11989 fix) when normalize() was added to getPathWithinApplication() without a null guard, and has remained unhandled through all subsequent versions (1.5.3 → 3.0.0).

Fix

Two layers:

  1. WebUtils.getPathWithinApplication(): Return "/" instead of null when normalize() fails.
  2. PathMatchingFilterChainResolver.getChain(): Safety net — fall back to /** chain when requestURI is null/empty.

Tests

  • NullNormalizationBypassPocTest — 4 tests for the null-normalization scenario
  • WebUtilsTest — traversal-above-root paths
  • PathMatchingFilterChainResolverTest — catch-all fallback integration test

Impact

Prevents the class of auth bypass vulnerabilities caused by null path propagation through the filter chain.

fixes #2837

When WebUtils.getPathWithinApplication() receives a path that
normalize() resolves to null (path traversal above root, e.g.
getServletPath()=/ followed by getPathInfo()=../), the method
returned null. This caused PathMatchingFilterChainResolver.getChain()
to fail matching any pattern including the required /** catch-all,
causing AbstractShiroFilter.getExecutionChain() to pass the request
to the original container FilterChain without any Shiro filtering.

This bypass meant global filters such as InvalidRequestFilter never
ran, enabling auth bypass via path traversal.

Fix:
- WebUtils.getPathWithinApplication(): return '/' instead of null
  when normalize() fails, so the path always matches /**.
- PathMatchingFilterChainResolver.getChain(): safety net that
  falls back to the /** chain when requestURI is null or empty
  (defense-in-depth for subclasses that override resolution).

Tests added for both the WebUtils fix and the resolver fallback.
Copilot AI review requested due to automatic review settings July 21, 2026 09:23
@xaccefy xaccefy changed the title fix: prevent filter chain bypass when path normalizes to null [#2837] - Null path normalization in getPathWithinApplication causes filter chain bypass Jul 21, 2026
@github-actions github-actions Bot added java Pull requests that update Java code tests groovy labels Jul 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a security-relevant edge case where WebUtils.getPathWithinApplication() could return null (when normalize() fails on traversal-above-root paths), which could propagate into PathMatchingFilterChainResolver.getChain() and result in Shiro returning null and falling back to the container chain (bypassing Shiro filters).

Changes:

  • Guard WebUtils.getPathWithinApplication() so normalization failures don’t return null.
  • Add a “catch-all” fallback in PathMatchingFilterChainResolver.getChain() for null/empty request paths.
  • Add regression tests covering traversal-above-root normalization behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
web/src/main/java/org/apache/shiro/web/util/WebUtils.java Prevents null path propagation from normalization failures.
web/src/main/java/org/apache/shiro/web/filter/mgt/PathMatchingFilterChainResolver.java Adds a safety-net to avoid returning null when a catch-all chain is available and the request path is missing.
web/src/test/groovy/org/apache/shiro/web/util/WebUtilsTest.groovy Adds tests for traversal-above-root inputs.
web/src/test/java/org/apache/shiro/web/filter/mgt/PathMatchingFilterChainResolverTest.java Adds regression coverage to ensure traversal-above-root no longer results in a null resolved chain when /** exists.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread web/src/main/java/org/apache/shiro/web/util/WebUtils.java
Comment on lines +294 to +306
/**
* Verifies that path traversal above root (where normalize returns null)
* no longer bypasses Shiro. The path is normalized to "/" and can match
* the /** catch-all chain if one exists.
*/
@Test
void testPathTraversalAboveRootFallsBackToCatchAll() {
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
FilterChain chain = mock(FilterChain.class);

// Create the /** catch-all chain (as ShiroFactoryBean would)
resolver.getFilterChainManager().createChain("/**", "anon");
@lprimak lprimak self-assigned this Jul 22, 2026
@lprimak lprimak added this to the 3.0.1 milestone Jul 22, 2026
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 22, 2026 19:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment on lines +153 to +157
// If no chain matched and the requestURI is null/empty, fall back to the catch-all chain if one exists.
// This ensures global filters (e.g. InvalidRequestFilter) always run when the request path cannot be resolved.
if (requestURI == null || "".equals(requestURI)) {
NamedFilterList catchAllChain = filterChainManager.getChain("/**");
if (catchAllChain != null) {

@lprimak lprimak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution.

Can you please restore the issue template? It is required for copyright purposes.

See comments below as well.

*/
public static String getPathWithinApplication(HttpServletRequest request) {
return normalize(removeSemicolon(getServletPath(request) + getPathInfo(request)));
String path = normalize(removeSemicolon(getServletPath(request) + getPathInfo(request)));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is very risky, and possibly introduces bypass vectors.
I think this needs to be reverted

}
}

// If no chain matched and the requestURI is null/empty, fall back to the catch-all chain if one exists.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change also needs to be introduced in support/guice/src/main/java/org/apache/shiro/guice/web/SimpleFilterChainResolver.java

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 22, 2026 19:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

groovy java Pull requests that update Java code pending-cla tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Null path normalization in getPathWithinApplication causes complete filter chain bypass

3 participants