Skip to content

v2.34.1 — one challenge, many visitors

Choose a tag to compare

@sean-e-dietrich sean-e-dietrich released this 30 Sep 06:24
· 32 commits to 2.x since this release
7a5a417

v2.34.1

A challenge page served from a cache locked visitors out, one after another.

Every response the firewall writes is a decision about one visitor: a block, a lockdown refusal, a redirect, a challenge, a pass token. The challenge page sent only Cache-Control: no-store, and some CDNs, Pantheon's Global CDN among them, cache a response marked only that. A cached challenge page gives every visitor to that URL the same single-use ALTCHA challenge. The first to solve it got through; everyone after was refused and sent back to the same cached page, in a loop that lasted as long as the cache entry.

PHP 8.1–8.5 supported. No new configuration keys, and no configuration changes are required.

What changes on upgrade

  • Every response the firewall writes carries a full set of no-cache headers: the challenge page, the challenge verify answer, block, lockdown and redirect.

    Cache-Control: private, no-store, no-cache, must-revalidate, max-age=0
    Pragma: no-cache
    Expires: 0
    Surrogate-Control: no-store
    CDN-Cache-Control: no-store
    

    They replace any Cache-Control the host set earlier in the request. That's more restrictive than before, never less. A site that deliberately cached a block or the challenge page will stop, and that's the fix.

  • A new warning in the log when a spent challenge solution comes back from a different client. See below.

  • In mode: exception you write the response yourself, so add Kanopi\Firewall\Utility\NoStore::HEADERS to it. The examples in Error Handling now do; they used to show no-store alone.

Fixed

  • Firewall responses could be cached (#417, #418). Before this release:

    Response Sent
    Challenge page, lockdown, redirect Cache-Control: no-store only
    Block no cache header at all
    Challenge verify answer, the one setting the pass cookie no cache header at all

    All of them now send the set above. private and max-age=0 are what shared caches reliably honour, and Surrogate-Control and CDN-Cache-Control are for CDNs that read those in preference to Cache-Control.

  • A cached challenge page is now visible in the logs. It used to look like visitors simply failing a challenge. A spent solution now records which client spent it, hashed just as the solution is. When the same solution comes back from a different client, which is what a cached page looks like, the firewall logs:

    warning: A spent challenge solution came back from a different client; the challenge page is
             probably being cached by a CDN or proxy, so visitors share one single-use challenge
    

    The same client twice is a double click or a replay and stays at info, as it was.

  • A double click posted the same solution twice. The challenge page now ignores Continue while a submission is in flight and disables the button until it returns. Before, the second post was refused, and its failure handling raced the first one's successful redirect. After a refusal, each provider still decides what the button does next: math re-enables it, ALTCHA fetches a new challenge, and Turnstile and reCAPTCHA wait for a new token.

Found with a downstream report against kanopi/basic-firewall-wp on Pantheon.

Full changelog: v2.34.0...v2.34.1