module
Xssmaze::Beacon
Overview
Execution oracle.
Every scoring path in this lab measures reflection: a scanner asks whether
its string came back in the HTML and calls that a finding. That is a proxy,
and a poor one — it scores a harmlessly-escaped echo as a hit, and it is
blind to the ~160 dom endpoints where the payload never reaches the server
response at all. The beacon replaces the proxy with proof: a payload has to
actually run to reach /beacon/<token>, so a hit in the log is a true
positive that no amount of string matching can fake.
/basic/level1/?query=<img src=/beacon/run1 onerror=fetch('/beacon/run1')> then: GET /beacon/log?token=run1
The recorded Referer is the payoff — it names the maze page that executed,
so a headless harness can fire one token for a whole sweep and still
attribute every hit to the endpoint that produced it.
Routes:
ANY /beacon/
This is instrumentation, not a maze, and it is deliberately boring: it never
calls Xssmaze.push (the catalog is a benchmark denominator and must not
grow a non-maze entry), it answers only image/gif and application/json,
and nothing it records is ever echoed into an HTML response.
Defined in:
mazes/beacon.crConstant Summary
-
GIF_1X1 =
Bytes[71, 73, 70, 56, 57, 97, 1, 0, 1, 0, 128, 0, 0, 0, 0, 0, 255, 255, 255, 33, 249, 4, 1, 0, 0, 0, 0, 44, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, 2, 68, 1, 0, 59] -
The canonical 43-byte transparent 1x1 GIF: the smallest thing an
<img src=...>can load without drawing anything on the page under test. -
MAX_HITS =
100 -
MAX_TOKENS =
1000 -
A fuzzer pointing at
/beacon/<random>must not be able to exhaust memory, so the log is bounded on all three axes it can grow along: distinct tokens, stored hits per token, and the length kept from each attacker-set header. -
MAX_VALUE =
256 -
RESERVED_TOKENS =
["log"] of ::String -
/beacon/logis the log API for every verb, so a token by that name could never be read back. Refusing it up front beats letting a benchmark discover the collision from a permanently empty result. -
TOKEN_PATTERN =
/\A[A-Za-z0-9_-]{1,64}\z/
Class Method Summary
- .clear
-
.headers(env) : Nil
no-storebecause a cached beacon is a silently lost signal, and the wildcard CORS header because the payload firing it may well be running on another origin. - .hit(env) : String
- .json(env) : Nil
- .log_json
- .preflight(env) : String
-
.record(token : String, method : String, referer : String | Nil, user_agent : String | Nil) : Bool
Returns false only when the token cap refused a brand new token.
- .reject(env) : String
- .token_json(token : String)
- .valid_token?(token : String) : Bool
Class Method Detail
no-store because a cached beacon is a silently lost signal, and the
wildcard CORS header because the payload firing it may well be running on
another origin. nosniff keeps a browser from ever second-guessing the
JSON content type and parsing a recorded Referer as markup.
Returns false only when the token cap refused a brand new token. Tokens already in the log keep recording, so a benchmark run in flight is never cut off by someone else fuzzing the beacon.