Commit Graph
3 Commits
Author SHA1 Message Date
shadowdaoandClaude Opus 5 18750861b4 fix(haproxy): close open redirect in wp-admin edge gate
The redirect target is built by regsub-rewriting `path`, which only
replaces the matched "/wp-admin/.*" substring -- anything before it
survives untouched. Three request forms turn that survival into an
off-site Location header: a protocol-relative "//evil/wp-admin/x.php",
a browser-normalized "/\evil/wp-admin/x.php", and an RFC 7230
absolute-form request target. Without this gate those paths simply
404 against WordPress; the gate itself is what would have exposed a
fleet-wide phishing primitive.

Adds a positive wp_admin_safe_path ACL (path_reg ^/[^/\\]) requiring a
well-formed absolute path, required alongside the existing conditions
on the redirect rule. A path that fails it is simply not redirected
and falls through to the backend -- pre-gate behavior, so no
regression. set-var is left unguarded since it only computes a
variable; the redirect is what emits the header, so guarding it is
sufficient.

Verified against real HAProxy 3.0.11: the naive two-backslash form
fails to compile (config-line word parsing collapses "\\" to one
backslash before PCRE sees it, leaving an unterminated class); four
backslashes are required in the template so PCRE receives the
intended single-backslash class member. Confirmed live, via a
differential test against the pre-fix rule, that both the // and /\
vectors previously produced off-site Location headers and now do not,
while normal root and subdirectory-install redirects are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 08:19:02 -07:00
shadowdaoandClaude Opus 5 6b0b5893b6 fix(haproxy): repair wp-admin edge gate redirect + anchor allowlist
Two defects in the wp-admin edge gate (2171bed, 704be38):

1. HAProxy 3.0.11 rejects the inline regsub redirect
   (regsub((^|/)wp-admin/.*,\1wp-login.php)) with "invalid arg 2 in
   converter 'regsub': missing arguments". Verified this is a
   converter-argument-parenthesis-counting limitation -- the inner
   "(^|/)" grouping parens are misread as closing the outer regsub()
   call, and neither quoting nor backslash-escaping the parens helps.
   Since HTTP paths always start with "/", the group is unnecessary:
   compute the login URL in its own set-var, matching the literal
   substring "/wp-admin/" (no group, no backreference) and replacing
   it with the literal "/wp-login.php" -- regsub only replaces the
   matched substring, so a subdirectory-install prefix survives
   untouched.

2. wp_admin_allowed used a bare path_end suffix match
   (/admin-ajax.php etc), so /wp-admin/evil/admin-ajax.php matched
   both wp_admin_path and the allowlist and sailed through the gate
   ungated. Anchored each entry to /wp-admin/<file>.

Verified against real HAProxy 3.0.11-1+deb13u3: haproxy -c exit 0,
and live curl against the real generated config's literal lines
confirms root-install and subdirectory-install redirects, the
anchored-allowlist fix, cookie exemption, and non-wp-admin passthrough
all behave correctly.

Extends scripts/test-wpadmin-gate.py with regression tests for the
anchored allowlist and the set-var ordering/no-inline-regsub guard.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 08:02:00 -07:00
shadowdao 704be38882 feat(haproxy): gate unauthenticated wp-admin requests at the edge
Redirect /wp-admin/* to the site's login page when no wordpress_logged_in_
cookie is present, so unauthenticated requests never boot PHP. Identity-based
rather than rate-based, so it is unaffected by how widely an attack is
distributed. Allowlists the paths that legitimately serve unauthenticated
visitors, including the css/js the login page itself loads.
2026-08-14 07:45:52 -07:00