Listmonk SSTI: When Helper Libraries Help You Get Hacked
Jan 6, 2026·5 min read
Executive Summary (TL;DR)
Listmonk included the 'Sprig' template library without sanitization. This allowed any user with permission to create email templates to use the `{{ env }}` function. This function dumps the server's environment variables, handing over DB passwords, AWS keys, and config secrets to anyone who can preview an email. Fixed in v5.0.2.
A critical Server-Side Template Injection (SSTI) vulnerability in listmonk allows authenticated users to read host environment variables via the Sprig template library, exposing database credentials and cloud secrets.
The Hook: The Newsletter That Knew Too Much
Listmonk is the go-to self-hosted solution for newsletters. It’s fast, written in Go, and generally robust. But like many modern Go applications, it relies on the text/template and html/template packages to render dynamic content. To make life easier for users who want to do fancy things in their email subjects—like capitalizing names or formatting dates—developers often reach for Sprig.
Sprig is a collection of over 100 template functions for Go. It's incredibly useful. It's also dangerous if you blindly trust it. The developers of listmonk imported the entire Sprig feature set into the user-accessible template engine. This is akin to giving a house guest the keys to your car just because they asked for a ride to the store. You gave them way more access than the context required.
This vulnerability (CVE-2025-49136) isn't a complex buffer overflow or a race condition. It's a classic case of "Default Configurations Considered Harmful." By enabling Sprig's default function map, listmonk accidentally gave every marketing intern with a login the ability to read the server's deepest secrets.
The Flaw: A Dangerous Import
The root cause lies in how listmonk initializes its template engine. Go's template engine allows developers to pass a FuncMap—a map of function names to actual Go functions that can be called inside the {{ }} delimiters. Sprig provides a convenient method called GenericFuncMap() which returns all its supported functions.
Among innocent helpers like upper (uppercase) or date, Sprig includes env and expandenv. These functions are designed for DevOps tools (like Helm) where reading environment variables is a feature, not a bug. In a multi-tenant or multi-user web application, however, env is catastrophic.
The vulnerability exists because listmonk took this map and applied it directly to the campaign rendering context. There was no filter list, no deny list, just a raw copy-paste of functionality. This means the template engine went from being a text formatter to a system inspection tool.
The Code: The Smoking Gun
Let's look at the code. In the vulnerable versions (4.0.0 through 5.0.1), the manager initialized the function map roughly like this:
// The Vulnerable Implementation
func (m *Manager) makeGenericFuncMap() template.FuncMap {
// Load all Sprig functions, including dangerous ones
funcs := sprig.GenericFuncMap()
// Merge with internal listmonk functions
// ...
return funcs
}The fix, applied in commit d27d2c32cf3af2d0b24e29ea5a686ba149b49b3e, is a textbook example of "sanitize your inputs" (or in this case, your imports). The developers explicitly delete the dangerous keys from the map before returning it.
// The Fixed Implementation (v5.0.2)
func (m *Manager) makeGenericFuncMap() template.FuncMap {
sprigFuncs := sprig.GenericFuncMap()
// explicit kill list
delete(sprigFuncs, "env")
delete(sprigFuncs, "expandenv")
maps.Copy(funcs, sprigFuncs)
return funcs
}It’s a simple three-line change that closes a massive security hole. It highlights the importance of auditing third-party libraries not just for CVEs, but for features that become vulnerabilities in your specific context.
The Exploit: Reading the Keys to the Kingdom
Exploiting this is trivially easy. You don't need to be a binary ninja; you just need a login with "Campaign" permissions. The attack vector leverages the "Preview" functionality found in the campaign editor.
Step 1: Log in as a low-privileged user.
Step 2: Create a new Campaign.
Step 3: In the body of the email, insert the following Sprig payload:
Whoops, I found your database password:
{{ env "LISTMONK_db__password" }}
And your AWS keys:
{{ env "AWS_SECRET_ACCESS_KEY" }}Step 4: Click the "Preview" tab (the eye icon).
The Result: The backend Go code processes the template. When it hits {{ env ... }}, it executes os.Getenv(...) on the server and pastes the result directly into the HTML preview. The attacker now has full credentials to the backend database or cloud provider.
[!NOTE] A Metasploit module (
gather/listmonk_env_disclosure) already automates this, cycling through common environment variable names likePOSTGRES_PASSWORD,AWS_ACCESS_KEY_ID, andLISTMONK_app__admin_password.
The Impact: Why This is Critical
In modern containerized environments (Docker, Kubernetes), configuration is almost exclusively passed via Environment Variables. This is factor III of the "12-Factor App" methodology. While this is great for DevOps, it means that os.Getenv is effectively root access to the application's configuration.
By exploiting this, an attacker gets:
- Database Access:
LISTMONK_db__passwordallows direct connection to the Postgres DB, leading to data exfiltration (user emails, PII) or modification. - SMTP Credentials:
LISTMONK_smtp__passwordallows the attacker to send spam/phishing emails from your trusted domain. - Cloud Lateral Movement: If the listmonk instance is running on AWS/GCP and keys are in the env (common in ECS/Lambda), the attacker pivots to your cloud infrastructure.
While this isn't direct Remote Code Execution (RCE) via the template itself, the leaked credentials usually provide five different ways to achieve RCE within minutes.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
listmonk knadh | >= 4.0.0, < 5.0.2 | 5.0.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-1336 (Template Injection) |
| CVSS v3.1 | 9.1 (Critical) |
| Attack Vector | Network (Authenticated) |
| EPSS Score | 0.35141 (High) |
| Exploit Status | Weaponized (Metasploit) |
| Affected Component | Sprig Template Library Integration |
MITRE ATT&CK Mapping
Improper Neutralization of Special Elements Used in a Template Engine (SSTI)
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.