CVE-2024-27305 (published 2024-03-12T21:15Z)

aiosmtpd is a reimplementation of the Python stdlib smtpd.py based on asyncio. aiosmtpd is vulnerable to inbound SMTP smuggling. SMTP smuggling is a novel vulnerability based on not so novel interpretation differences of the SMTP protocol. By exploiting SMTP smuggling, an attacker may send smuggle/spoof e-mails with fake sender addresses, allowing advanced phishing attacks. This issue is also existed in other SMTP software like Postfix. With the right SMTP server constellation, an attacker can send spoofed e-mails to inbound/receiving aiosmtpd instances. This issue has been addressed in version 1.4.5. Users are advised to upgrade. There are no known workarounds for this vulnerability.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/aio-libs/aiosmtpd
commit_details: repo_url: https://github.com/aio-libs/aiosmtpd
author: Login
author_date: 2024-03-02 15:55:13+01:00
msg: Merge pull request from GHSA-pr2m-px7j-xg65

* SMTP Smuggling Fix

Adapted adherence to RFC 5321 § 2.3.8 to fix SMTP smuggling issues (https://www.rfc-editor.org/rfc/rfc5321#section-2.3.8)

* Apply suggestions from code review

Co-authored-by: Sam Bull <git@sambull.org>

* Add files via upload

* Update test_smtpsmuggling.py

---------

Co-authored-by: Sam Bull <git@sambull.org>
repository: repo_url: https://github.com/aio-libs/aiosmtpd
repo_name: aio-libs/aiosmtpd
description: A reimplementation of the Python stdlib smtpd.py based on asyncio.
repo_language: Python
owner: aio-libs
file_changes: filename: smtp.py
change_type: ModificationType.MODIFY
diff: @@ -87,7 +87,7 @@ DATA_SIZE_DEFAULT = 2**25  # Where does this number come from, I wonder...
 EMPTY_BARR = bytearray()
 EMPTYBYTES = b''
 MISSING = _Missing.MISSING
-NEWLINE = '\n'
+NEWLINE = '\r\n'
 VALID_AUTHMECH = re.compile(r"[A-Z0-9_-]+\Z")
 
 # https://tools.ietf.org/html/rfc3207.html#page-3
@@ -1427,9 +1427,10 @@ class SMTP(asyncio.StreamReaderProtocol):
             # Since eof_received cancels this coroutine,
             # readuntil() can never raise asyncio.IncompleteReadError.
             try:
-                line: bytes = await self._reader.readuntil()
+                # https://datatracker.ietf.org/doc/html/rfc5321#section-2.3.8
+                line: bytes = await self._reader.readuntil(b'\r\n')
                 log.debug('DATA readline: %s', line)
-                assert line.endswith(b'\n')
+                assert line.endswith(b'\r\n')
             except asyncio.CancelledError:
                 # The connection got reset during the DATA command.
                 log.info('Connection lost during DATA')
@@ -1446,7 +1447,7 @@ class SMTP(asyncio.StreamReaderProtocol):
                 data *= 0
                 # Drain the stream anyways
                 line = await self._reader.read(e.consumed)
-                assert not line.endswith(b'\n')
+                assert not line.endswith(b'\r\n')
             # A lone dot in a line signals the end of DATA.
             if not line_fragments and line == b'.\r\n':
                 break
@@ -1458,7 +1459,7 @@ class SMTP(asyncio.StreamReaderProtocol):
                 # Discard data immediately to prevent memory pressure
                 data *= 0
             line_fragments.append(line)
-            if line.endswith(b'\n'):
+            if line.endswith(b'\r\n'):
                 # Record data only if state is "NOMINAL"
                 if state == _DataState.NOMINAL:
                     line = EMPTY_BARR.join(line_fragments)

programming_language: Python
filename: test_smtpsmuggling.py
change_type: ModificationType.ADD
diff: @@ -0,0 +1,79 @@
+# Copyright 2014-2021 The aiosmtpd Developers
+# SPDX-License-Identifier: Apache-2.0
+
+"""Test SMTP smuggling."""
+
+from email.mime.text import MIMEText
+from smtplib import SMTP, SMTP_SSL
+from typing import Generator, Union
+
+import pytest
+import smtplib
+
+from aiosmtpd.controller import Controller
+from aiosmtpd.testing.helpers import ReceivingHandler
+from aiosmtpd.testing.statuscodes import SMTP_STATUS_CODES as S
+
+from aiosmtpd.smtp import SMTP as Server
+from aiosmtpd.smtp import Session as ServerSession
+from aiosmtpd.smtp import Envelope
+
+from .conftest import Global, controller_data, handler_data
+
+from aiosmtpd.testing.helpers import (
+    ReceivingHandler
+)
+
+def new_data(self, msg):
+        self.putcmd("data")
+
+        (code, repl) = self.getreply()
+        if self.debuglevel > 0:
+            self._print_debug('data:', (code, repl))
+        if code != 354:
+            raise SMTPDataError(code, repl)
+        else:
+            ##### Patching input encoding so we can send raw messages
+            #if isinstance(msg, str):
+            #    msg = smtplib._fix_eols(msg).encode('ascii')
+            #q = smtplib._quote_periods(msg)
+            #if q[-2:] != smtplib.bCRLF:
+            #    q = q + smtplib.bCRLF
+            #q = q + b"." + smtplib.bCRLF
+            q = msg
+            self.send(q)
+            (code, msg) = self.getreply()
+            if self.debuglevel > 0:
+                self._print_debug('data:', (code, msg))
+            return (code, msg)
+
+def return_unchanged(data):
+    return data
+
+class TestSmuggling:
+    @handler_data(class_=ReceivingHandler)
+    def test_smtp_smuggling(self, plain_controller, client):
+        smtplib._fix_eols = return_unchanged
+        smtplib._quote_periods = return_unchanged
+        smtplib.SMTP.data = new_data
+
+        handler = plain_controller.handler
+        sender = "sender@example.com"
+        recipients = ["rcpt1@example.com"]
+        resp = client.helo("example.com")
+        assert resp == S.S250_FQDN
+        # Trying SMTP smuggling with a fake \n.\r\n end-of-data sequence.
+        message_data = b"""\
+From: Anne Person <anne@example.com>\r\n\
+To: Bart Person <bart@example.com>\r\n\
+Subject: A test\r\n\
+Message-ID: <ant>\r\n\
+\r\n\
+Testing\
+\n.\r\n\
+NO SMUGGLING
+\r\n.\r\n\
+"""
+        results = client.sendmail(sender, recipients, message_data)
+        client.quit()
+        assert b"NO SMUGGLING" in handler.box[0].content

programming_language: Python