CreaCaptcha

rest-context.php

Root helpers for the question "what request is this?" — which PATH does it address, and is it served by the REST API?

Both answers used to be reconstructed ad hoc at every call site, and both were wrong in the same way: they trusted a value that looks authoritative but is not (?rest_route= for REST-ness, wp_parse_url() for the path).

Audit module 27, findings BK-1 / BK-2 / CM-7: two guards derived REST-ness from the raw, client-controlled rest_route request parameter — the interceptor via a bare isset(), the rate limiter via str_contains(). Both were bypassable by simply appending ?rest_route= (BK-1) or ?rest_route=…challenge (BK-2) to a request that WordPress never serves through the REST API at all. The same value-independent-existence pattern had already come back once as a regression in the login gate. It therefore lives here exactly once, and every consumer calls into it.

Table of Contents

Functions

creationell_captcha_request_path()  : string
Returns the path portion of the current request, the way WordPress core derives it — NOT the way a URL parser would.
creationell_captcha_request_target()  : string
Der aktuelle Request als relativer Ziel-URI: Pfad aus `creationell_captcha_request_path()`, Query aus derselben Zeichenkette.
creationell_captcha_current_rest_route()  : string
Returns the REST route the current request actually addresses.
creationell_captcha_normalize_rest_route()  : string
Normalises a raw `rest_route` value into the plugin's canonical route form.

Functions

creationell_captcha_request_path()

Returns the path portion of the current request, the way WordPress core derives it — NOT the way a URL parser would.

creationell_captcha_request_path() : string

The return value is still percent-encoded (the "wire spelling"); callers that need the decoded form apply rawurldecode() to it exactly once, after this function has split off the query.

WHY THIS EXISTS (audit module 27, finding C1)

Every call site used to run wp_parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ). wp_parse_url() is a URL parser, and REQUEST_URI is not a URL — it is a request target. For a target that starts with // the parser treats the string as a scheme-relative URL (it prepends placeholder:, see wp-includes/http.php), so the first segment becomes the HOST:

REQUEST_URI      wp_parse_url(…, PHP_URL_PATH)   WP::parse_request()
/kontakt/        '/kontakt/'                     kontakt
//kontakt/       '/'                             kontakt   ← same page
///kontakt/      ''                              kontakt   ← same page

Core reduces the target in WP::parse_request() with list( $req_uri ) = explode( '?', $_SERVER['REQUEST_URI'] ); $req_uri = trim( $req_uri, '/' ); (wp-includes/class-wp.php) and therefore routes all three spellings to the same page. The guards saw / or '', matched no pattern, and let the request through: POST //kontakt was a one-character bypass of the entire interceptor path guard.

WHAT THIS DOES

  1. Split off query and fragment BEFORE anything is decoded, so a percent-encoded ? inside the path cannot smuggle a query string in.
  2. Strip scheme and authority if the target arrived in absolute form (POST http://example.com/kontakt HTTP/1.1, RFC 9112 §3.2.2). Core does not do this and simply 404s such a request, but wp_parse_url() did — so keeping it guards MORE than core routes, never less. Dropping it would have taken protection away that 1.0.2 had.
  3. Collapse leading slashes to exactly one — the same reduction core's trim( $req_uri, '/' ) performs. INNER double slashes are kept, because core keeps them too, and the TRAILING slash is kept, because existing patterns are written against the spelling wp_parse_url() produced.
Tags
since
1.1.0
Return values
string —

Request path with exactly one leading slash, still encoded.

creationell_captcha_request_target()

Der aktuelle Request als relativer Ziel-URI: Pfad aus `creationell_captcha_request_path()`, Query aus derselben Zeichenkette.

creationell_captcha_request_target() : string

WARUM DAS EINE EIGENE FUNKTION IST (Nachlese Modul 27, Strang N1; Umfang erweitert im Re-Review und in der Nachlese N6)

remove_query_arg() OHNE URL-Argument und add_query_arg() ohne URL- Argument sind dieselbe Mechanik: beide delegieren an add_query_arg(), und dessen Zweig count( $args ) < 3 (bzw. < 2 bei der Array-Form) liest $_SERVER['REQUEST_URI'] ROH. Sie leiten also eine Pfadangabe aus dem Request ab, ohne das sichtbar zu tun — deshalb standen die betroffenen Stellen in keinem der vier Review-Berichte.

Gemessen wurde das am Ausblenden-Link des Härtungs-Hinweises: bei REQUEST_URI = "//wp-admin/admin.php?page=…" rendert er ein protokollrelatives href="//wp-admin/admin.php?…"; esc_url() reicht jede mit / beginnende Zeichenkette unverändert durch. Der Browser liest das als Host wp-admin, der Klick kommt nie an. Dieselbe Konstruktion stand am „↻ Aktualisieren"-Link der Statistik-Seite.

Die Query kommt bewusst aus DERSELBEN Zeichenkette wie der Pfad, damit beide Hälften zusammenpassen und nicht die eine aus REQUEST_URI und die andere aus QUERY_STRING stammt (die beiden können auseinanderlaufen, etwa hinter einer Rewrite-Regel). Und der Rückgabewert ist bewusst RELATIV: er ersetzt genau das, was der Kern ohne URL-Argument selbst eingesetzt hätte.

VOLLZÄHLIGKEIT: Seit der Nachlese N6 übergibt jeder add_query_arg()/remove_query_arg()-Aufruf im ausgelieferten Code eine explizite URL — entweder diese Funktion, eine $base_url aus menu_page_url() oder admin_url(). Nachgezählt wird das nicht von Hand, sondern von tests/test-hardening-migration.php (Abschnitt „Bestandsaufnahme"), das alle Aufrufe unter includes/ auszählt und rot wird, sobald einer wieder ohne URL-Argument dasteht. Ein früherer Stand dieses Docblocks nannte die Redirect-Funktion der Härtungs-Migration „die neunte und letzte Stelle im Plugin, die eine Pfadangabe aus dem Request ableitet" — das war messbar falsch und ist hiermit ersetzt.

Tags
since
1.1.0
Return values
string —

Relativer Ziel-URI (führender Schrägstrich, genau einer).

creationell_captcha_current_rest_route()

Returns the REST route the current request actually addresses.

creationell_captcha_current_rest_route() : string

Returns a normalised route (leading slash, no trailing slash, no query part) — for example /creationell-captcha/v1/challenge — or the empty string when the request is not served by the REST API.

The function is deliberately conservative: whenever it cannot establish that WordPress core will hand the request to the REST server, it returns the empty string. Both consumers fail closed on that answer (the interceptor guards the request, the rate limiter counts it), so under-detection is safe while over-detection hands out a bypass.

It must work at init priority 0/1, i.e. long before parse_request has populated $GLOBALS['wp']->query_vars and before core defines the REST_REQUEST constant, so it reconstructs core's own decision from the request instead of reading either of those.

Tags
since
1.1.0
Return values
string —

Normalised REST route, or '' when this is not a REST request.

creationell_captcha_normalize_rest_route()

Normalises a raw `rest_route` value into the plugin's canonical route form.

creationell_captcha_normalize_rest_route(string $raw) : string

Returns '' for every value that does not address a concrete REST route.

Parameters
$raw : string

Raw rest_route value as core would see it.

Tags
since
1.1.0
Return values
string —

Route with a leading slash and without a trailing one, or ''.


        
On this page

Search results