Skip to content

Fix NGINX HTTP response splitting in multisite subdirectory rewrite - #1640

Merged
retlehs merged 2 commits into
masterfrom
fix/nginx-response-splitting
Mar 5, 2026
Merged

retlehs merged 2 commits into
masterfrom
fix/nginx-response-splitting

Conversation

@retlehs

@retlehs retlehs commented Mar 4, 2026

Copy link
Copy Markdown
Member

Summary

  • Use $request_uri instead of $uri in the multisite subdirectory wp-admin rewrite rule to prevent HTTP response splitting via CRLF injection
  • $uri is decoded by nginx, allowing injected %0d%0a sequences to become raw CRLF characters in the Location header
  • $request_uri preserves the original encoded request, preventing the injection

Closes #1548

🤖 Generated with Claude Code

Use $request_uri instead of $uri in the wp-admin rewrite rule to
prevent HTTP response splitting via CRLF injection.

Closes #1548

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@retlehs

retlehs commented Mar 4, 2026

Copy link
Copy Markdown
Member Author

CI failure here is expected: this is an XPASS(strict) from the template regression tests after the fix in this PR.

The test is still marked xfail(strict=True), so pytest intentionally fails when it starts passing.

Cleanup is tracked in #1648, which removes the xfail markers once this PR is merged.

@retlehs

retlehs commented Mar 4, 2026

Copy link
Copy Markdown
Member Author

Local verification

Branch tested: fix/nginx-response-splitting
Environment: Trellis local development VM (Lima)

Multisite setup

Configured Bedrock for multisite subdirectory mode per Trellis docs:

  1. Added multisite constants to site/config/application.php before Config::apply():

    Config::define('WP_ALLOW_MULTISITE', true);
    Config::define('MULTISITE', true);
    Config::define('SUBDOMAIN_INSTALL', false);
    Config::define('DOMAIN_CURRENT_SITE', env('DOMAIN_CURRENT_SITE'));
    Config::define('PATH_CURRENT_SITE', env('PATH_CURRENT_SITE') ?: '/');
    Config::define('SITE_ID_CURRENT_SITE', env('SITE_ID_CURRENT_SITE') ?: 1);
    Config::define('BLOG_ID_CURRENT_SITE', env('BLOG_ID_CURRENT_SITE') ?: 1);
  2. Updated group_vars/development/wordpress_sites.yml:

    multisite:
      enabled: true
      subdomains: false
  3. Provisioned and installed multisite:

    trellis provision development
    trellis vm shell -- wp core multisite-install --url="http://example.test" --title="Example Multisite" --admin_user="admin" --admin_password="password" --admin_email="admin@example.test"
    trellis vm shell -- wp site create --slug=site2 --title="Site 2"
  4. Verified network:

    $ trellis vm shell -- wp site list --fields=blog_id,url
    blog_id  url
    1        http://example.test/
    2        http://example.test/site2/
    

Config diff

Before (master):

461:    rewrite /wp-admin$ $scheme://$host$uri/ permanent;

After (fix/nginx-response-splitting):

461:    rewrite /wp-admin$ $scheme://$host$request_uri/ permanent;

Runtime: CRLF injection test

$ curl -sI "http://example.test/wp/%0d%0asplitting/wp-admin"

Before (master) — vulnerable, response is split into two:

HTTP/1.1 301 Moved Permanently
Server: nginx
Location: http://example.test/wp/

HTTP/1.1 301 Moved Permanently
Server: nginx
Location: http://example.test/wp/

After (fix) — single response, %0d%0a stays encoded:

HTTP/1.1 301 Moved Permanently
Server: nginx
Location: http://example.test/wp/%0d%0asplitting/wp-admin/
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
X-UA-Compatible: IE=Edge
Content-Security-Policy: frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN
X-Robots-Tag: noindex, nofollow

Runtime: site2 wp-admin redirect (sanity check)

$ curl -sI "http://example.test/site2/wp-admin"

HTTP/1.1 301 Moved Permanently
Location: http://example.test/site2/wp-admin/

Normal redirect behavior preserved after fix.

Summary

Before (master) After (fix)
Config $uri $request_uri
CRLF injection Two split responses (vulnerable) Single response, encoded (safe)
site2/wp-admin 301 → correct Location 301 → correct Location

Conclusion: Verified locally — $uri replaced with $request_uri in multisite subdirectory rewrite. HTTP response splitting is no longer reproducible.

@retlehs retlehs self-assigned this Mar 4, 2026
@retlehs
retlehs requested a review from swalkinshaw March 4, 2026 19:56
@retlehs
retlehs merged commit 531c138 into master Mar 5, 2026
1 of 2 checks passed
@retlehs
retlehs deleted the fix/nginx-response-splitting branch March 5, 2026 04:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NGINX HTTP Response Splittng for Multisites running in subdirectories

2 participants