CVE-2019-11340: Improper Input Validation (CWE-20)
value: util/emailutils.py in Matrix Sydent before 1.0.2 mishandles registration restrictions that are based on e-mail domain, if the allowed_local_3pids option is enabled. This occurs because of potentially unwanted behavior in Python, in which an email.utils.parseaddr call on user@bad.example.net@good.example.com returns the user@bad.example.net substring.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 5.9
Published: 2019-04-19T14:29Z
Repository: https://github.com/matrix-org/sydent
Language: Python
Files changed: sydent/util/emailutils.py
Commit date: 2019-04-18 17:20:03 +0100
Commit message: Require that parsed mail addresses match raw input

Python's email parser turns malformed addresses into valid ones
by removing anything after and including the second '@'. This
meant that sydent sent emails to the validated address but stored
the raw address as validated.

This rejects any requests where the parsed email address does not
match the raw input.

Diff: diff --git a/sydent/util/emailutils.py b/sydent/util/emailutils.py
index 2ae5fdc..7b3b92c 100644
--- a/sydent/util/emailutils.py
+++ b/sydent/util/emailutils.py
@@ -53,16 +53,17 @@ def sendEmail(sydent, templateName, mailTo, substitutions):
         for k,v in allSubstitutions.items():
             allSubstitutions[k] = v.decode('utf8')
             allSubstitutions[k+"_forhtml"] = cgi.escape(v.decode('utf8'))
             allSubstitutions[k+"_forurl"] = urllib.quote(v)
 
-        mailString = open(mailTemplateFile).read() % allSubstitutions
-        rawFrom = email.utils.parseaddr(mailFrom)[1]
-        rawTo = email.utils.parseaddr(mailTo)[1]
-        if rawFrom == '' or rawTo == '':
+        mailString = open(mailTemplateFile).read().decode('utf8') % allSubstitutions
+        parsedFrom = email.utils.parseaddr(mailFrom)[1]
+        parsedTo = email.utils.parseaddr(mailTo)[1]
+        if parsedFrom == '' or parsedTo == '':
             logger.info("Couldn't parse from / to address %s / %s", mailFrom, mailTo)
             raise EmailAddressException()
+
         mailServer = sydent.cfg.get('email', 'email.smtphost')
         mailPort = sydent.cfg.get('email', 'email.smtpport')
         mailUsername = sydent.cfg.get('email', 'email.smtpusername')
         mailPassword = sydent.cfg.get('email', 'email.smtppassword')
         mailTLSMode = sydent.cfg.get('email', 'email.tlsmode')
@@ -75,11 +76,16 @@ def sendEmail(sydent, templateName, mailTo, substitutions):
                 smtp.starttls()
             else:
                 smtp = smtplib.SMTP(mailServer, mailPort, myHostname)
             if mailUsername != '':
                 smtp.login(mailUsername, mailPassword)
-            smtp.sendmail(rawFrom, rawTo, mailString.encode('utf-8'))
+
+            # We're using the parsing above to do basic validation, but instead of
+            # failing it may munge the address it returns. So we should *not* use
+            # that parsed address, as it may not match any validation done
+            # elsewhere.
+            smtp.sendmail(mailFrom, mailTo, mailString.encode('utf-8'))
             smtp.quit()
         except Exception as origException:
             twisted.python.log.err()
             ese = EmailSendException()
             ese.cause = origException

Vulnerable code:         mailString = open(mailTemplateFile).read() % allSubstitutions
        rawFrom = email.utils.parseaddr(mailFrom)[1]
        rawTo = email.utils.parseaddr(mailTo)[1]
        if rawFrom == '' or rawTo == '':
            smtp.sendmail(rawFrom, rawTo, mailString.encode('utf-8'))
Fixed code:         mailString = open(mailTemplateFile).read().decode('utf8') % allSubstitutions
        parsedFrom = email.utils.parseaddr(mailFrom)[1]
        parsedTo = email.utils.parseaddr(mailTo)[1]
        if parsedFrom == '' or parsedTo == '':


            # We're using the parsing above to do basic validation, but instead of
            # failing it may munge the address it returns. So we should *not* use
            # that parsed address, as it may not match any validation done
            # elsewhere.
            smtp.sendmail(mailFrom, mailTo, mailString.encode('utf-8'))