
Discovering CVE-2025-22381: Host Header Injection in the Aggie Open-Source Project
CVE-2025-22381: Host Header Injection in Aggie
Detailed analysis and Proof-of-Concept for CVE-2025-22381, a Host Header Injection vulnerability discovered in the Aggie Open-Source Project.
Vulnerability Overview+
CVE ID: CVE-2025-22381
Published: October 2025 (MITRE assignment)
Disclosed publicly: February 2026
Reporter: Anas Abderrahman Benbarek
Discovery Date: September 17, 2025
Affected Project: TID-Lab/aggie
Affected Versions: All versions (including 2.6.1 and earlier; no fix applied as of February 2026)
Severity: Medium to High (estimated CVSS ~7.1–7.5)
Impact: Enables phishing attacks leading to password reset token theft and potential account takeover.
Background
I spend a fair amount of time reviewing open source node.js projects on GitHub, especially ones that handle authentication flows In September 2025 while looking through the Aggie repository, I noticed something that immediately stood out in the pasword reset logic. What started as routine code reading ended up becoming CVE-2025-22381 — a classic Host Header Injection vulnerability that allows an attacker to control the domain in pasword reset emails.
How I Found It
i cloned the repository and started reading files under lib/api/, focusing on anything related to authentication and email generation.
The file lib/api/reset-password.js contains the endpoint logic for /reset-password. The critical part is inside the sendEmail helper:
function sendEmail(user, req, callback) { var token = encodeToken(user);
mailer.sendFromTemplate({ template: 'forgotPassword', user: user, token: token, host: req.headers.host, // ← vulnerable protocol: req.protocol, acceptLanguage: req.headers['accept-language'] }, callback); }
The line host: req.headers.host is the problem. In express, req.headers.host comes directly from the Host HTTP header, which is entirely attacker-controlled. There is no validation, no whitelist, no fallback to a trusted domain from configuration.
Initial Confirmation
I quickly set up a local instance following the README instructions (Ubuntu, nvm, npm install, secrets.json with test SMTP), started the server, and trigered a pasword reset. The generated email link used localhost:3000 as expected.
Then I replayed the request with a manipulated Host header:
curl -X POST http://localhost:3000/reset-password
-H "Host: evil-phish.example"
-d "email=[email protected]"
The email (captured via MailHog) contained: http://evil-phish.example/reset-password?token=...
Proof positive. The application trusts the client-supplied Host header when building the reset link.
How the Attack Actually Works
Attacker sends a password reset request for a victim s email address, but sets the Host header to a domain they control (e.g. evil-phish.example).
Aggie generates a legitimate reset token (server-side, time-limited, encrypted with the config secret).
The email is sent containing a link to the attacker s domain instead of the real one.
Victim receives the email and clicks the link (phishing success condition).
The victim lands on the attacker s server.
The attacker s server can:
Simply display a fake “reset failed” page and silently discard the token, or
Capture the token from the query string (via server-side logging or JavaScript), or
Proxy the request to the real Aggie instance, capture the token, and forward the user to the legitimate reset page (so the victim doesn t immediately notice anything wrong).
Attacker later uses the captured token on the real domain to reset the victim s pasword.
The key point: Host header injection alone does not let the attacker use the token directly. The attacker still needs the victim to visit the malicious link so the token reaches the attacker s infrastructure. That s why this is a phishing-enabling vulnerability rather than a direct account takeover without user interaction.
Technical Severity & Impact
This is a medium-to-high severity issue depending on context:
AV:N: Network reachable
PR:N: No privileges required
AC:L: Low complexity
UI:R: Requires user interaction
S:C: Scope can change (impact extends to the victim’s account on the legitimate domain)
C:L / I:H: Confidentiality & Integrity impact on the victim’s account
Many databases list it with CVSS ~7.1–7.5 range I personally consider it serious in production environments where Aggie is used for sensitive monitoring (elections, crises), as successful phishing here can lead to full account takeover.
Proof-of-Concept (Detailed & Reproducible)
Environment
Ubuntu 18.04/20.04 (as recommended)
Node 12.16 (per .nvmrc)
MailHog: Running locally for email capture (docker run -d -p 8025:8025 -p 1025:1025 mailhog/mailhog)
Aggie configured with email.transport pointing to localhost:1025
Step-by-step
Clone & start Aggie:
git clone https://github.com/TID-Lab/aggie.git cd aggie nvm install npm install cp config/secrets.json.example config/secrets.json edit secrets.json → set adminPassword, add test SMTP if needed npm start
Create a test user via the web UI or directly in MongoDB.
Trigger malicious reset:
curl -i -X POST http://localhost:3000/reset-password
-H "Host: evil-phish.example"
-H "Content-Type: application/x-www-form-urlencoded"
-d "email=[email protected]"
Open MailHog: Inspect the sent email. The reset link will point to http://evil-phish.example/reset-password?token=...
Disclosure Timeline
Sep 17, 2025: Discovered + local PoC
Sep 17, 2025: Emailed [email protected] with full details and PoC
Sep 17, 2025: Submitted to MITRE (service request 1926730 / MCID15453119)
Oct 9, 2025: MITRE assigned CVE-2025-22381
Oct–Dec 2025: No public patch or response observed
Feb 2026: Public disclosure (this article)
Recommended Fix
Code Change
Replace the vulnerable line with a trusted value in lib/api/reset-password.js:
// In lib/api/reset-password.js, inside sendEmail() const config = require('../../config/secrets').get();
// Option A: Hard trust config value (recommended for single-domain) const host = config.appHost || 'localhost:3000';
// Then use it: mailer.sendFromTemplate({ template: 'forgotPassword', user: user, token: token, host: host, // Use the trusted variable protocol: config.environment === 'production' ? 'https' : req.protocol, acceptLanguage: req.headers['accept-language'] }, callback);
Configuration
Add to secrets.json:
"appHost": "https://your-real-domain.com"
Closing Thoughts
host header injection remains surprisingly common in 2025–2026 especially in projects that were started years ago and haven’t been heavily audited. Aggie is a valuable tool for civic tech and crisis monitoring — I hope the maintainers apply a fix soon.