Search for vulnerabilities
| Vulnerability ID | VCID-btzb-wm9n-9bc5 |
| Aliases |
CVE-2026-45013
GHSA-gf43-24g3-5hw2 |
| Summary | Apostrophe has a Weak Password Recovery Mechanism for Forgotten Password and Improper Input Validation ## Summary ApostropheCMS's password reset flow constructs the reset URL using `req.hostname`, which is derived directly from the attacker-controlled HTTP `Host` header when `apos.baseUrl` is not explicitly configured. An unauthenticated attacker who knows a victim's email address can send a crafted reset request that causes the application to email the victim a reset link pointing to the attacker's domain. When the victim clicks the link, the valid reset token is delivered to the attacker, enabling full account takeover. ## Affected Component `modules/@apostrophecms/login/index.js` — `resetRequest` route Precondition: `passwordReset: true` is set **and** `apos.baseUrl` is not configured. ## Vulnerability Details The `setPrefixUrls` middleware (i18n layer) builds `req.baseUrl` using `req.hostname`: ```js // Simplified from i18n middleware req.baseUrl = `${req.protocol}://${req.hostname}`; req.absoluteUrl = req.baseUrl + req.url; ``` The `resetRequest` handler then passes this tainted value directly into URL construction: ```js const parsed = new URL( req.absoluteUrl, // ← tainted by attacker's Host header self.apos.baseUrl ? undefined : `${req.protocol}://${req.hostname}${port}` // ← also tainted ); parsed.pathname = '/login'; parsed.searchParams.append('reset', reset); // real, valid token parsed.searchParams.append('email', user.email); await self.email(..., { url: parsed.toString() }, ...); // Email sent to victim with URL pointing to attacker-controlled domain ``` When `apos.baseUrl` is configured, it is used unconditionally and the attacker's `Host` header is ignored — that path is **not** vulnerable. ## Attack Scenario 1. Attacker identifies a valid user email (e.g. from the site's public interface). 2. Attacker sends: ``` POST /api/v1/login/reset-request Host: evil.attacker.com Content-Type: application/json {"email": "victim@example.com"} ``` 3. The application emails the victim: ``` Click here to reset your password: http://evil.attacker.com/login?reset=TOKEN&email=victim@example.com ``` 4. Victim clicks the link; attacker's server captures `TOKEN`. 5. Attacker calls the real target's reset endpoint with the captured token and sets a new password — full account takeover. ## Preconditions - `passwordReset: true` configured in login module options (opt-in) - `apos.baseUrl` is **not** set (common in development and some production deployments) - Attacker knows or can enumerate a valid account email ## Impact Full account takeover of any account whose email address is known to the attacker. No authentication or interaction beyond sending a single HTTP request is required from the attacker. The victim need only click a link in a legitimate-looking password reset email from their own site. ## Remediation **Operators (immediate):** Always set `apos.baseUrl` in your configuration: ```js // app.js or module configuration modules: { '@apostrophecms/express': { options: { baseUrl: 'https://yourdomain.com' } } } ``` **Framework fix (recommended):** The `resetRequest` route should refuse to proceed if `apos.baseUrl` is not configured, rather than falling back to the tainted `req.hostname`. Example: ```js // In resetRequest handler if (!self.apos.baseUrl) { throw self.apos.error( 'invalid', 'apos.baseUrl must be configured to enable password reset' ); } const parsed = new URL(self.loginUrl(), self.apos.baseUrl); ``` This eliminates the attacker-controlled input entirely from the URL construction path. ## References - [OWASP: Host Header Injection](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/17-Testing_for_Host_Header_Injection) - [CWE-640: Weak Password Recovery Mechanism for Forgotten Password](https://cwe.mitre.org/data/definitions/640.html) |
| Status | Published |
| Exploitability | None |
| Weighted Severity | None |
| Risk | None |
| Affected and Fixed Packages | Package Details |
| System | Score | Found at |
|---|---|---|
| cvssv3.1_qr | HIGH | https://github.com/advisories/GHSA-gf43-24g3-5hw2 |
| cvssv3.1 | 8.1 | https://github.com/apostrophecms/apostrophe |
| generic_textual | HIGH | https://github.com/apostrophecms/apostrophe |
| cvssv3.1 | 8.1 | https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-gf43-24g3-5hw2 |
| cvssv3.1_qr | HIGH | https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-gf43-24g3-5hw2 |
| generic_textual | HIGH | https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-gf43-24g3-5hw2 |
| Reference id | Reference type | URL |
|---|---|---|
| https://github.com/apostrophecms/apostrophe | ||
| https://github.com/apostrophecms/apostrophe/security/advisories/GHSA-gf43-24g3-5hw2 | ||
| GHSA-gf43-24g3-5hw2 | https://github.com/advisories/GHSA-gf43-24g3-5hw2 |
| Attack Vector (AV) | Attack Complexity (AC) | Privileges Required (PR) | User Interaction (UI) | Scope (S) | Confidentiality Impact (C) | Integrity Impact (I) | Availability Impact (A) |
|---|---|---|---|---|---|---|---|
network adjacent_network local physical |
low high |
none low high |
none required |
unchanged changed |
high low none |
high low none |
high low none |
| Attack Vector (AV) | Attack Complexity (AC) | Privileges Required (PR) | User Interaction (UI) | Scope (S) | Confidentiality Impact (C) | Integrity Impact (I) | Availability Impact (A) |
|---|---|---|---|---|---|---|---|
network adjacent_network local physical |
low high |
none low high |
none required |
unchanged changed |
high low none |
high low none |
high low none |
No EPSS data available for this vulnerability.
| Date | Actor | Action | Source | VulnerableCode Version |
|---|---|---|---|---|
| 2026-06-04T17:03:13.672713+00:00 | GithubOSV Importer | Import | https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-gf43-24g3-5hw2/GHSA-gf43-24g3-5hw2.json | 38.6.0 |