GHSA-4CHG-4752-W88R
Vulnerability from github – Published: 2026-07-13 17:58 – Updated: 2026-07-13 17:58Summary
An unauthenticated attacker can coerce the server into issuing HTTP requests to an attacker-chosen host by spoofing the X Forwarded-Host (and X-Forwarded-Proto) request headers. The forwarded host is used, without validation, to build the URL that server_info_update() fetches with cURL, resulting in a Server-Side Request Forgery (SSRF) that requires no authentication.
Affected component
- File:
includes/ini.php— functionserver_info_update()(cURL sink) - File:
vendor/vinades/nukeviet/Core/Server.php—standardizeHost()and the forwarded-header handling in the constructor (source of the tainted host) - Trigger:
POSTrequest containing the field__serverInfoUpdate=1, handled early inincludes/ini.phpbefore any authentication.
Details
NukeViet\Core\Server derives original_host / original_protocol from the X-Forwarded-Host / X-Forwarded-Proto headers and exposes them via getOriginalHost() / getOriginalProtocol(). These values are attacker-controlled and were not validated against the site's configured domains (my_domains).
In server_info_update() the tainted host and scheme are concatenated directly into a cURL URL:
$proto = $nv_Server->getOriginalProtocol(); // from X-Forwarded-Proto
$host = $nv_Server->getOriginalHost(); // from X-Forwarded-Host
$ch = curl_init($proto . '://' . $host . NV_BASE_SITEURL . 'index.php?response_headers_detect=1');
curl_exec($ch);
Two factors made this reliably reachable:
- The
__serverInfoUpdatehandler runs very early inincludes/ini.php, before authentication, so the sink is reachable pre-auth. - The host sanitiser
standardizeHost()stripped a trailing port only with the regex(\:[0-9]+)$, which is bypassed by appending a slash (e.g.127.0.0.1:8081/): the string no longer ends in:digits, so the port survives and an arbitraryhost:portreaches the cURL call.
Proof of Concept
POST /index.php HTTP/1.1
Host: <victim>
X-Forwarded-Proto: http
X-Forwarded-Host: <attacker-controlled-host>:<port>/
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
__serverInfoUpdate=1
The server then issues a request to the attacker-supplied host, confirmed via an out-of-band interaction (DNS + HTTP) on a collaborator endpoint.
Impact
The SSRF is blind, HEAD-only, and uses a fixed request path (…/index.php?response_headers_detect=1):
- The fetched response is stored server-side in the
config_inicache and is not reflected to the attacker, so internal data cannot be exfiltrated directly. - Because the path is fixed and not attacker-controlled, cloud metadata endpoints (e.g.
169.254.169.254/latest/meta-data/...) cannot be reached, andgopher:///dict://request smuggling cannot inject arbitrary payloads.
What an attacker can do: unauthenticated internal host/port discovery (connection success/timing, with the port reachable through the regex bypass), and poisoning of the cached server_headers (the SSRF target's response headers are stored and applied to the site).
Severity
Rated High rather than Critical, because the blind + fixed-path + HEAD design of the sink prevents data exfiltration, cloud credential theft, and internal RCE.
- CVSS v3.1 Base Score: 7.2 (High)
- Vector:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N
Weakness
- Primary: CWE-918: Server-Side Request Forgery (SSRF)
- Contributing: CWE-20 (Improper Input Validation), CWE-644 (Improper Neutralization of HTTP Headers used by downstream components / trusting
X-Forwarded-*).
Remediation
Fixed by validating and normalising the forwarded values at the source and gating the request before the sink:
standardizeHost()now extracts the host withparse_url()(defeats the:port/bypass) and lower-cases it.X-Forwarded-Protois restricted to a{http, https}allow-list and falls back to the real server protocol otherwise.X-Forwarded-Portis validated as numeric and within range.- The incoming host is checked against
my_domainsbeforeincludes/ini.phpis reached; non-matching hosts are rejected/redirected, andserver_info_update()additionally re-validates its target host againstmy_domains(defense in depth).
Workaround
Configure the reverse proxy / web server to strip or override client-supplied X-Forwarded-Host, X-Forwarded-Proto, and X-Forwarded-Port headers, and ensure my_domains is configured with the site's canonical domain(s).
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "nukeviet/nukeviet"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.6.00"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-55372"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-13T17:58:44Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\nAn unauthenticated attacker can coerce the server into issuing HTTP requests to an attacker-chosen host by spoofing the `X Forwarded-Host` (and `X-Forwarded-Proto`) request headers. The forwarded host is used, without validation, to build the URL that `server_info_update()` fetches with cURL, resulting in a Server-Side Request Forgery (SSRF) that requires no authentication.\n\n## Affected component\n\n- File: `includes/ini.php` \u2014 function `server_info_update()` (cURL sink)\n- File: `vendor/vinades/nukeviet/Core/Server.php` \u2014 `standardizeHost()` and the forwarded-header handling in the constructor (source of the tainted host)\n- Trigger: `POST` request containing the field `__serverInfoUpdate=1`, handled early in `includes/ini.php` before any authentication.\n\n## Details\n\n`NukeViet\\Core\\Server` derives `original_host` / `original_protocol` from the `X-Forwarded-Host` / `X-Forwarded-Proto` headers and exposes them via `getOriginalHost()` / `getOriginalProtocol()`. These values are attacker-controlled and were not validated against the site\u0027s configured domains (`my_domains`).\n\nIn `server_info_update()` the tainted host and scheme are concatenated directly into a cURL URL:\n\n```php\n$proto = $nv_Server-\u003egetOriginalProtocol(); // from X-Forwarded-Proto\n$host = $nv_Server-\u003egetOriginalHost(); // from X-Forwarded-Host\n$ch = curl_init($proto . \u0027://\u0027 . $host . NV_BASE_SITEURL . \u0027index.php?response_headers_detect=1\u0027);\ncurl_exec($ch);\n```\n\nTwo factors made this reliably reachable:\n\n1. The `__serverInfoUpdate` handler runs very early in `includes/ini.php`, before authentication, so the sink is reachable pre-auth.\n2. The host sanitiser `standardizeHost()` stripped a trailing port only with the regex `(\\:[0-9]+)$`, which is bypassed by appending a slash (e.g. `127.0.0.1:8081/`): the string no longer ends in `:digits`, so the port survives and an arbitrary `host:port` reaches the cURL call.\n\n## Proof of Concept\n\n```http\nPOST /index.php HTTP/1.1\nHost: \u003cvictim\u003e\nX-Forwarded-Proto: http\nX-Forwarded-Host: \u003cattacker-controlled-host\u003e:\u003cport\u003e/\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 20\n\n__serverInfoUpdate=1\n```\n\nThe server then issues a request to the attacker-supplied host, confirmed via an out-of-band interaction (DNS + HTTP) on a collaborator endpoint.\n\n## Impact\n\nThe SSRF is **blind**, **HEAD-only**, and uses a **fixed request path** (`\u2026/index.php?response_headers_detect=1`):\n\n- The fetched response is stored server-side in the `config_ini` cache and is **not reflected** to the attacker, so internal data cannot be exfiltrated directly.\n- Because the path is fixed and not attacker-controlled, cloud metadata endpoints (e.g. `169.254.169.254/latest/meta-data/...`) cannot be reached, and `gopher://` / `dict://` request smuggling cannot inject arbitrary payloads.\n\nWhat an attacker **can** do: unauthenticated internal host/port discovery (connection success/timing, with the port reachable through the regex bypass), and poisoning of the cached `server_headers` (the SSRF target\u0027s response headers are stored and applied to the site).\n\n## Severity\n\nRated **High** rather than Critical, because the blind + fixed-path + HEAD design of the sink prevents data exfiltration, cloud credential theft, and internal RCE.\n\n- CVSS v3.1 Base Score: **7.2 (High)**\n- Vector: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N`\n\n## Weakness\n\n- Primary: **CWE-918: Server-Side Request Forgery (SSRF)**\n- Contributing: CWE-20 (Improper Input Validation), CWE-644 (Improper Neutralization of HTTP Headers used by downstream components / trusting `X-Forwarded-*`).\n\n## Remediation\n\nFixed by validating and normalising the forwarded values at the source and gating the request before the sink:\n\n- `standardizeHost()` now extracts the host with `parse_url()` (defeats the `:port/` bypass) and lower-cases it.\n- `X-Forwarded-Proto` is restricted to a `{http, https}` allow-list and falls back to the real server protocol otherwise.\n- `X-Forwarded-Port` is validated as numeric and within range.\n- The incoming host is checked against `my_domains` before `includes/ini.php` is reached; non-matching hosts are rejected/redirected, and `server_info_update()` additionally re-validates its target host against `my_domains` (defense in depth).\n\n## Workaround\n\nConfigure the reverse proxy / web server to strip or override client-supplied `X-Forwarded-Host`, `X-Forwarded-Proto`, and `X-Forwarded-Port` headers, and ensure `my_domains` is configured with the site\u0027s canonical domain(s).",
"id": "GHSA-4chg-4752-w88r",
"modified": "2026-07-13T17:58:44Z",
"published": "2026-07-13T17:58:44Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nukeviet/nukeviet/security/advisories/GHSA-4chg-4752-w88r"
},
{
"type": "PACKAGE",
"url": "https://github.com/nukeviet/nukeviet"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "NukeViet: Pre-authentication SSRF via X-Forwarded-Host"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.