CVE-2021-29432: Insufficient Information (NVD-CWE-noinfo)
value: Sydent is a reference matrix identity server. A malicious user could abuse Sydent to send out arbitrary emails from the Sydent email address. This could be used to construct plausible phishing emails, for example. This issue has been fixed in 4469d1d.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 5.7
Published: 2021-04-15T21:15Z
Repository: https://github.com/matrix-org/sydent
Language: Other
Files changed: res/matrix-org/invite_template.eml, res/matrix-org/verification_template.eml, res/vector-im/invite_template.eml, res/vector-im/verification_template.eml, sydent/http/servlets/store_invite_servlet.py, sydent/util/emailutils.py
Commit date: 2021-04-09 12:20:45 +0000
Commit message: Randomise multipart boundary, and include mxids in invite email notifs

Diff: diff --git a/res/matrix-org/invite_template.eml b/res/matrix-org/invite_template.eml
index 826006c8..bd01964b 100644
--- a/res/matrix-org/invite_template.eml
+++ b/res/matrix-org/invite_template.eml
@@ -2,23 +2,24 @@ Date: %(date)s
 From: %(from)s
 To: %(to)s
 Message-ID: %(messageid)s
 Subject: %(subject_header_value)s
 MIME-Version: 1.0
-Content-Type: multipart/alternative; 
-	boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
+Content-Type: multipart/alternative;
+	boundary="%(multipart_boundary)s"
 
---7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
+--%(multipart_boundary)s
 Content-Type: text/plain; charset=UTF-8
 Content-Disposition: inline
 
 Hi,
 
-%(sender_display_name)s has invited you into a room %(bracketed_room_name)s on
-Matrix. To join the conversation, either pick a Matrix client from
-https://matrix.org/docs/projects/try-matrix-now.html or use the single-click
-link below to join via Element (requires Chrome, Firefox, Safari, iOS or Android)
+%(sender_display_name)s %(bracketed_verified_sender)shas invited you into a room
+%(bracketed_room_name)son Matrix. To join the conversation, either pick a
+Matrix client from https://matrix.org/docs/projects/try-matrix-now.html or use
+the single-click link below to join via Element (requires Chrome, Firefox,
+Safari, iOS or Android)
 
 %(web_client_location)/#/room/%(room_id_forurl)s?email=%(to_forurl)s&signurl=https%%3A%%2F%%2Fmatrix.org%%2F_matrix%%2Fidentity%%2Fapi%%2Fv1%%2Fsign-ed25519%%3Ftoken%%3D%(token)s%%26private_key%%3D%(ephemeral_private_key)s&room_name=%(room_name_forurl)s&room_avatar_url=%(room_avatar_url_forurl)s&inviter_name=%(sender_display_name_forurl)s&guest_access_token=%(guest_access_token_forurl)s&guest_user_id=%(guest_user_id_forurl)s
 
 
 About Matrix:
@@ -36,16 +37,11 @@ create new communication solutions or extend the capabilities and reach of exist
 
 Thanks,
 
 Matrix
 
---7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
-Content-Type: multipart/related;
-	boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR";
-	type="text/html"
-
---M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR
+--%(multipart_boundary)s
 Content-Type: text/html; charset=UTF-8
 Content-Disposition: inline
 
 <!doctype html>
 <html lang="en">
@@ -66,10 +62,14 @@ pre, code {
     font-size: 12pt;
     width: 100%%;
     padding: 20px;
 }
 
+#low-contrast {
+    color: #666666
+}
+
 #inner {
     width: 640px;
 }
 
 .header {
@@ -100,11 +100,11 @@ pre, code {
                         </tr>
                     </table>
 
 <p>Hi,</p>
 
-<p>%(sender_display_name_forhtml)s has invited you into a room %(bracketed_room_name_forhtml)s on
+<p>%(sender_display_name_forhtml)s <span class="low-contrast">%(bracketed_verified_sender_for_html)s</span> has invited you into a room %(bracketed_room_name_forhtml)s on
 Matrix. To join the conversation, either <a href="https://matrix.org/docs/projects/try-matrix-now.html">pick a Matrix client</a> or use the single-click
 link below to join via Element (requires
 <a href="https://www.google.com/chrome">Chrome</a>,
 <a href="https://www.getfirefox.com">Firefox</a> or
 <a href="https://www.apple.com/safari">Safari</a> on the web,
@@ -137,8 +137,7 @@ create new communication solutions or extend the capabilities and reach of exist
                 <td> </td>
             </tr>
         </table>
     </body>
 </html>
---M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR--
 
---7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ--
+--%(multipart_boundary)s--
diff --git a/res/matrix-org/verification_template.eml b/res/matrix-org/verification_template.eml
index 7c8c91e2..d98e4d9e 100644
--- a/res/matrix-org/verification_template.eml
+++ b/res/matrix-org/verification_template.eml
@@ -2,14 +2,14 @@ Date: %(date)s
 From: %(from)s
 To: %(to)s
 Message-ID: %(messageid)s
 Subject: Confirm your email address for Matrix
 MIME-Version: 1.0
-Content-Type: multipart/alternative; 
-	boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
+Content-Type: multipart/alternative;
+	boundary="%(multipart_boundary)s"
 
---7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
+--%(multipart_boundary)s
 Content-Type: text/plain; charset=UTF-8
 Content-Disposition: inline
 
 Hello,
 
@@ -33,11 +33,11 @@ subscribing to data whilst tracking the conversation history.
 
 Matrix defines the standard, and provides open source reference implementations of
 Matrix-compatible Servers, Clients, Client SDKs and Application Services to help you
 create new communication solutions or extend the capabilities and reach of existing ones.
 
---7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
+--%(multipart_boundary)s
 Content-Type: text/html; charset=UTF-8
 Content-Disposition: inline
 
 <!DOCTYPE html>
 <html>
@@ -83,6 +83,6 @@ Matrix-compatible Servers, Clients, Client SDKs and Application Services to help
 create new communication solutions or extend the capabilities and reach of existing ones.</p>
 
 </body>
 </html>
 
---7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ--
+--%(multipart_boundary)s--
diff --git a/res/vector-im/invite_template.eml b/res/vector-im/invite_template.eml
index 38b98347..7b081330 100644
--- a/res/vector-im/invite_template.eml
+++ b/res/vector-im/invite_template.eml
@@ -2,21 +2,22 @@ Date: %(date)s
 From: %(from)s
 To: %(to)s
 Message-ID: %(messageid)s
 Subject: %(subject_header_value)s
 MIME-Version: 1.0
-Content-Type: multipart/alternative; 
-	boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
+Content-Type: multipart/alternative;
+	boundary="%(multipart_boundary)s"
 
---7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
+--%(multipart_boundary)s
 Content-Type: text/plain; charset=UTF-8
 Content-Disposition: inline
 
 Hi,
 
-%(sender_display_name)s has invited you into a room %(bracketed_room_name)s on
-Element. To join the conversation please follow the link below.
+%(sender_display_name)s %(bracketed_verified_sender)shas invited you into a room
+%(bracketed_room_name)son Element. To join the conversation please follow the
+link below.
 
 %(web_client_location)/#/room/%(room_id_forurl)s?email=%(to_forurl)s&signurl=https%%3A%%2F%%2Fvector.im%%2F_matrix%%2Fidentity%%2Fapi%%2Fv1%%2Fsign-ed25519%%3Ftoken%%3D%(token)s%%26private_key%%3D%(ephemeral_private_key)s&room_name=%(room_name_forurl)s&room_avatar_url=%(room_avatar_url_forurl)s&inviter_name=%(sender_display_name_forurl)s&guest_access_token=%(guest_access_token_forurl)s&guest_user_id=%(guest_user_id_forurl)s
 
 Element is an open source collaboration app built on the Matrix.org
 open standard for interoperable communication: supporting group chat,
@@ -49,16 +50,11 @@ contribute to the code and everyone can benefit from the speed of community inno
 Made on Matrix - Element is built on top of Matrix. Matrix is an open network for secure,
 decentralized communication delivering a community of users, bridged networks,
 integrated bots and applications plus full end-to-end encryption. To learn more about
 Matrix visit https://matrix.org.
 
---7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
-Content-Type: multipart/related;
-	boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR";
-	type="text/html"
-
---M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR
+--%(multipart_boundary)s
 Content-Type: text/html; charset=UTF-8
 Content-Disposition: inline
 
 <!doctype html>
 <html lang="en">
@@ -79,10 +75,14 @@ pre, code {
     font-size: 12pt;
     width: 100%%;
     padding: 20px;
 }
 
+#low-contrast {
+    color: #666666
+}
+
 #inner {
     width: 640px;
 }
 
 .header {
@@ -121,12 +121,12 @@ pre, code {
                         </tr>
                     </table>
 
 <p>Hi,</p>
 
-<p>%(sender_display_name_forhtml)s has invited you into a room %(bracketed_room_name_forhtml)s on
-Element.</p>
+<p>%(sender_display_name_forhtml)s <span class="low-contrast">%(bracketed_verified_sender_for_html)s</span> has invited you into a
+room %(bracketed_room_name_forhtml)s on Element.</p>
 
 <p>
     <a
     href="https://app.element.io/#/room/%(room_id_forurl)s?email=%(to_forurl)s&signurl=https%%3A%%2F%%2Fvector.im%%2F_matrix%%2Fidentity%%2Fapi%%2Fv1%%2Fsign-ed25519%%3Ftoken%%3D%(token)s%%26private_key%%3D%(ephemeral_private_key)s&room_name=%(room_name_forurl)s&room_avatar_url=%(room_avatar_url_forurl)s&inviter_name=%(sender_display_name_forurl)s&guest_access_token=%(guest_access_token_forurl)s&guest_user_id=%(guest_user_id_forurl)s">Join the conversation.</a>
 </p>
@@ -171,8 +171,7 @@ Matrix visit https://matrix.org.</p>
                 <td> </td>
             </tr>
         </table>
     </body>
 </html>
---M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR--
 
---7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ--
+--%(multipart_boundary)s--
diff --git a/res/vector-im/verification_template.eml b/res/vector-im/verification_template.eml
index 76e21940..e13b7e2c 100644
--- a/res/vector-im/verification_template.eml
+++ b/res/vector-im/verification_template.eml
@@ -2,14 +2,14 @@ Date: %(date)s
 From: %(from)s
 To: %(to)s
 Message-ID: %(messageid)s
 Subject: Confirm your email address for Element
 MIME-Version: 1.0
-Content-Type: multipart/alternative; 
-	boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
+Content-Type: multipart/alternative;
+	boundary="%(multipart_boundary)s"
 
---7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
+--%(multipart_boundary)s
 Content-Type: text/plain; charset=UTF-8
 Content-Disposition: inline
 
 Hello there!
 
@@ -49,16 +49,11 @@ contribute to the code and everyone can benefit from the speed of community inno
 Made on Matrix - Element is built on top of Matrix. Matrix is an open network for secure,
 decentralized communication delivering a community of users, bridged networks,
 integrated bots and applications plus full end-to-end encryption. To learn more about
 Matrix visit https://matrix.org.
 
---7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
-Content-Type: multipart/related;
-	boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR";
-	type="text/html"
-
---M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR
+--%(multipart_boundary)s
 Content-Type: text/html; charset=UTF-8
 Content-Disposition: inline
 
 <!doctype html>
 <html lang="en">
@@ -169,8 +164,7 @@ pre, code {
                 <td> </td>
             </tr>
         </table>
     </body>
 </html>
---M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR--
 
---7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ--
+--%(multipart_boundary)s--
diff --git a/sydent/http/servlets/store_invite_servlet.py b/sydent/http/servlets/store_invite_servlet.py
index d661ea0f..3dce2a11 100644
--- a/sydent/http/servlets/store_invite_servlet.py
+++ b/sydent/http/servlets/store_invite_servlet.py
@@ -25,34 +25,37 @@ from twisted.web.resource import Resource
 from unpaddedbase64 import encode_base64
 
 from sydent.db.invite_tokens import JoinTokenStore
 from sydent.db.threepid_associations import GlobalAssociationStore
 
-from sydent.http.servlets import get_args, send_cors, jsonwrap
+from sydent.http.servlets import get_args, send_cors, jsonwrap, MatrixRestError
 from sydent.http.auth import authV2
 from sydent.util.emailutils import sendEmail
 
-
 class StoreInviteServlet(Resource):
     def __init__(self, syd, require_auth=False):
         self.sydent = syd
         self.random = random.SystemRandom()
         self.require_auth = require_auth
 
     @jsonwrap
     def render_POST(self, request):
         send_cors(request)
 
-        if self.require_auth:
-            authV2(self.sydent, request)
-
         args = get_args(request, ("medium", "address", "room_id", "sender",))
         medium = args["medium"]
         address = args["address"]
         roomId = args["room_id"]
         sender = args["sender"]
 
+        verified_sender = None
+        if self.require_auth:
+            account = authV2(self.sydent, request)
+            verified_sender = sender
+            if account.userId != sender:
+                raise MatrixRestError(403, "M_UNAUTHORIZED", "'sender' doesn't match")
+
         globalAssocStore = GlobalAssociationStore(self.sydent)
         mxid = globalAssocStore.getMxid(medium, address)
         if mxid:
             request.setResponseCode(400)
             return {
@@ -102,13 +105,17 @@ class StoreInviteServlet(Resource):
             'guest_access_token',
         ]
         for k in extra_substitutions:
             substitutions.setdefault(k, '')
 
+        substitutions["bracketed_verified_sender"] = ""
+        if verified_sender:
+            substitutions["bracketed_verified_sender"] = "(%s) " % (verified_sender,)
+
         substitutions["ephemeral_private_key"] = ephemeralPrivateKeyBase64
         if substitutions["room_name"] != '':
-            substitutions["bracketed_room_name"] = "(%s)" % substitutions["room_name"]
+            substitutions["bracketed_room_name"] = "(%s) " % substitutions["room_name"]
 
         substitutions["web_client_location"] = self.sydent.default_web_client_location
         if 'org.matrix.web_client_location' in substitutions:
             substitutions["web_client_location"] = substitutions.pop("org.matrix.web_client_location")
 
diff --git a/sydent/util/emailutils.py b/sydent/util/emailutils.py
index e8bebf07..56a18c9c 100644
--- a/sydent/util/emailutils.py
+++ b/sydent/util/emailutils.py
@@ -32,10 +32,11 @@ else:
     from html import escape
 
 import email.utils
 
 from sydent.util import time_msec
+from sydent.util.tokenutils import generateAlphanumericTokenOfLength
 
 logger = logging.getLogger(__name__)
 
 
 def sendEmail(sydent, templateFile, mailTo, substitutions):
@@ -72,10 +73,14 @@ def sendEmail(sydent, templateFile, mailTo, substitutions):
     for k, v in substitutions.items():
         allSubstitutions[k] = v
         allSubstitutions[k+"_forhtml"] = escape(v)
         allSubstitutions[k+"_forurl"] = urllib.parse.quote(v)
 
+    # We add randomize the multipart boundary to stop user input from
+    # conflicting with it.
+    allSubstitutions["multipart_boundary"] = generateAlphanumericTokenOfLength(32)
+
     mailString = open(templateFile).read() % 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)

Vulnerable code: Content-Type: multipart/alternative; 
	boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
%(sender_display_name)s has invited you into a room %(bracketed_room_name)s on
Matrix. To join the conversation, either pick a Matrix client from
https://matrix.org/docs/projects/try-matrix-now.html or use the single-click
link below to join via Element (requires Chrome, Firefox, Safari, iOS or Android)
Content-Type: multipart/related;
	boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR";
	type="text/html"

<p>%(sender_display_name_forhtml)s has invited you into a room %(bracketed_room_name_forhtml)s on
Content-Type: multipart/alternative; 
	boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
Content-Type: multipart/alternative; 
	boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
%(sender_display_name)s has invited you into a room %(bracketed_room_name)s on
Element. To join the conversation please follow the link below.
Content-Type: multipart/related;
	boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR";
	type="text/html"

<p>%(sender_display_name_forhtml)s has invited you into a room %(bracketed_room_name_forhtml)s on
Element.</p>
Content-Type: multipart/alternative; 
	boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
Content-Type: multipart/related;
	boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR";
	type="text/html"

from sydent.http.servlets import get_args, send_cors, jsonwrap

        if self.require_auth:
            authV2(self.sydent, request)

            substitutions["bracketed_room_name"] = "(%s)" % substitutions["room_name"]
Fixed code: Content-Type: multipart/alternative;
	boundary="%(multipart_boundary)s"
--%(multipart_boundary)s
%(sender_display_name)s %(bracketed_verified_sender)shas invited you into a room
%(bracketed_room_name)son Matrix. To join the conversation, either pick a
Matrix client from https://matrix.org/docs/projects/try-matrix-now.html or use
the single-click link below to join via Element (requires Chrome, Firefox,
Safari, iOS or Android)
--%(multipart_boundary)s
#low-contrast {
    color: #666666
}

<p>%(sender_display_name_forhtml)s <span class="low-contrast">%(bracketed_verified_sender_for_html)s</span> has invited you into a room %(bracketed_room_name_forhtml)s on
--%(multipart_boundary)s--
Content-Type: multipart/alternative;
	boundary="%(multipart_boundary)s"
--%(multipart_boundary)s
--%(multipart_boundary)s
--%(multipart_boundary)s--
Content-Type: multipart/alternative;
	boundary="%(multipart_boundary)s"
--%(multipart_boundary)s
%(sender_display_name)s %(bracketed_verified_sender)shas invited you into a room
%(bracketed_room_name)son Element. To join the conversation please follow the
link below.
--%(multipart_boundary)s
#low-contrast {
    color: #666666
}

<p>%(sender_display_name_forhtml)s <span class="low-contrast">%(bracketed_verified_sender_for_html)s</span> has invited you into a
room %(bracketed_room_name_forhtml)s on Element.</p>
--%(multipart_boundary)s--
Content-Type: multipart/alternative;
	boundary="%(multipart_boundary)s"
--%(multipart_boundary)s
--%(multipart_boundary)s
--%(multipart_boundary)s--
from sydent.http.servlets import get_args, send_cors, jsonwrap, MatrixRestError
        verified_sender = None
        if self.require_auth:
            account = authV2(self.sydent, request)
            verified_sender = sender
            if account.userId != sender:
                raise MatrixRestError(403, "M_UNAUTHORIZED", "'sender' doesn't match")

        substitutions["bracketed_verified_sender"] = ""
        if verified_sender:
            substitutions["bracketed_verified_sender"] = "(%s) " % (verified_sender,)

            substitutions["bracketed_room_name"] = "(%s) " % substitutions["room_name"]
from sydent.util.tokenutils import generateAlphanumericTokenOfLength
    # We add randomize the multipart boundary to stop user input from
    # conflicting with it.
    allSubstitutions["multipart_boundary"] = generateAlphanumericTokenOfLength(32)