All vulnerabilities
GHSA-7m9j-h553-397x

yeswiki/yeswiki: Missing HTTP Signature replay protection allows indefinite re-execution of captured ActivityPub requests

yeswiki/yeswiki
CVSS
6.5Medium
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed in4.7.0Fixed versionGitHub
Published12 Oct 2026Updated 12 Oct 2026GitHub

What fixes it

  • 4.7.0yeswiki/yeswiki, composerGitHubcomposer require "yeswiki/yeswiki:^4.7.0"

A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.

Description

Summary

HttpSignatureService::verifySignature() validates the cryptographic integrity of HTTP Signature requests and the body digest, but never checks the Date header against the current server time. It also maintains no nonce tracking or seen-signature deduplication. A captured valid HTTP Signature request can be replayed at any future time to re-execute the original ActivityPub action on behalf of the original signer, as long as the signer's public key remains at its published URL.

Root Cause

The verifySignature() function includes the Date header in the signed data reconstruction (line 144) but only uses it for signature verification, never comparing its value to the current time. The function has no calls to time(), strtotime(), DateTime, or any temporal comparison. No nonce, request-ID deduplication, or seen-signature caching exists anywhere in the HttpSignatureService class or the inbox processing pipeline.

Affected function: HttpSignatureService::verifySignature
File: tools/bazar/services/HttpSignatureService.php (lines 96-157)

The function performs four checks: (1) Signature header presence (line 98),
(2) signature field completeness (line 106), (3) cryptographic signature verification
via openssl_verify (line 148), and (4) body digest matching (line 152). None of these
checks include temporal validation. The Date header value flows through signature
reconstruction at line 144 but is never compared to current time.

Impact

An attacker who has access to historical HTTP request headers (through reverse proxy logs, application-level debug logging, WAF/IDS logs, or shared hosting co-tenancy) can capture a valid HTTP Signature request and replay it at any future time. The replay window is unlimited because ActivityPub keys are generated once and persist indefinitely in practice.

The attacker can re-execute any previously valid ActivityPub action: a replayed Delete removes an entry, a replayed Update overwrites current content with captured old content, a replayed Follow re-establishes an unwanted subscription, and a replayed Undo reverses a legitimate action. The assertOwns() checks added by GHSA-rm6r-grfg-4v78 do not prevent replay because they verify identity (who signed), not temporality (when they signed); a replayed request from a legitimate actor passes all ownership checks.

Proof of Concept

Prerequisites: Access to a valid HTTP POST request with Signature, Date, Digest, and
Content-Type headers sent to the /api/forms/{formId}/actor/inbox endpoint. Capture can
occur from reverse proxy logs, WAF logs, application debug output, or shared hosting
co-tenancy where PHP error logs are accessible.
  1. Capture a valid signed request from server logs or shared hosting access:
POST /api/forms/2/actor/inbox HTTP/1.1
Host: wiki.example
Date: Mon, 01 Sep 2026 12:00:00 GMT
Content-Type: application/activity+json
Digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=
Signature: keyId="https://remote.example/actors/alice#main-key",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="base64sig..."

{"type":"Delete","actor":"https://remote.example/actors/alice","object":"https://remote.example/entries/42"}
  1. Replay the identical request days, weeks, or months later using curl or any HTTP client.
Result: verifySignature() succeeds because the public key is still at the keyId URL,
the signed data reconstructs identically, and no Date freshness check rejects the
stale request. The Delete action re-executes, removing the entry even if it was
recreated after the original deletion.

Attack Chain

  1. Exposure: The inbox endpoint /api/forms/{formId}/actor/inbox is public (ACL: {"public"}), accessible to any network party without authentication.
  2. Control: The attacker controls when to replay the captured request. The request content (headers, body, signature) is the original legitimate content. Timing control enables strategic disruption.
  3. Path: The replayed POST reaches ApiController, which calls verifySignature() (HttpSignatureService.php:96), then passes the activity to processActivity() (ActivityPubService.php:157).
  4. Guard: verifySignature() checks cryptographic validity (line 148) and digest integrity (line 152) but performs no Date freshness validation. processActivity() checks actor binding (line 162) and ownership (lines 196, 210, 224), all of which pass because the request IS from the legitimate actor.
  5. Primitive: Re-execute any previously valid ActivityPub action: Delete, Update, Create, Follow, or Undo.
  6. Result: Entry deletion, content reversion to old state, unwanted subscription restoration, or legitimate action reversal. Persistent disruption is possible through repeated replay of captured Delete signatures.

Bypass Evidence

No prior fix or guard exists for HTTP Signature replay protection in YesWiki. CVE-2026-52767 (GHSA-mv28-wj57-f57g) addressed the openssl_verify return-value handling (changing !openssl_verify(...) to openssl_verify(...) !== 1). GHSA-rm6r-grfg-4v78 addressed cross-actor authorization. Neither addresses temporal validation or replay.

The HTTP Signatures specification (draft-cavage-http-signatures-12, Section 2.1.4) explicitly recommends Date freshness validation: "a receiver SHOULD verify that the Date header is less than a configurable time in the past." Mastodon, the dominant ActivityPub implementation, enforces a 12-hour Date freshness window. YesWiki's own generateSignature() method emits a Date header (line 46), confirming awareness that Date is part of the protocol, but the verifier never checks it.

Strongest disproof attempted: "The content of a replayed request is old valid content, not attacker-chosen new content, so integrity impact is low." This was considered but rejected: the attacker controls timing, enabling strategic disruption (replaying a Delete before a critical event); persistent replay of captured Delete signatures creates a denial loop; and within the scope of captured activities, the integrity and availability impact is complete.

Affected Versions

  • Ecosystem: Composer (Packagist)
  • Package: yeswiki/yeswiki
  • Confirmed affected range: >= 4.6.2, <= 4.6.7
  • Latest stable release checked: 4.6.7 (Packagist)
  • Fix status: not fixed

The HttpSignatureService::verifySignature() function was introduced alongside ActivityPub support in v4.6.2 and has never included Date freshness validation in any released version. The prerelease v5.0.0-alpha1 (reorganized to src/Federation/Service/HttpSignatureService.php) also lacks replay protection.

Suggested Fix

Add Date header freshness validation in verifySignature(): parse the Date header value and reject requests where the date is older than a configurable window (recommended: 30 seconds to 5 minutes, per the HTTP Signatures specification). The check should be performed after signature verification succeeds but before returning the verified actor identity.

For defense-in-depth, consider requiring that digest be listed in the signed headers (preventing body modification on replay when the remote server omits digest from the signature). Activity-ID deduplication (storing processed activity IDs with a TTL) provides an additional layer against replay within the freshness window.

No interim mitigation is available without a code change, since the vulnerability is in the signature verification logic itself.

Reported by zx (GitHub: @manus-pi).

Text from GitHub Advisory Database

Affected versions 1

  • yeswiki/yeswiki, composer>= 4.6.2, < 4.7.0GitHub