diff options
-rw-r--r-- | Mailman/CSRFcheck.py | 22 | ||||
-rw-r--r-- | Mailman/Cgi/options.py | 25 | ||||
-rw-r--r-- | Mailman/SecurityManager.py | 1 | ||||
-rw-r--r-- | NEWS | 12 |
4 files changed, 43 insertions, 17 deletions
diff --git a/Mailman/CSRFcheck.py b/Mailman/CSRFcheck.py index a1e78d98..24e3e11b 100644 --- a/Mailman/CSRFcheck.py +++ b/Mailman/CSRFcheck.py @@ -18,11 +18,13 @@ """ Cross-Site Request Forgery checker """ import time +import urllib import marshal import binascii from Mailman import mm_cfg -from Mailman.Utils import sha_new +from Mailman.Logging.Syslog import syslog +from Mailman.Utils import UnobscureEmail, sha_new keydict = { 'user': mm_cfg.AuthUser, @@ -37,6 +39,10 @@ keydict = { def csrf_token(mlist, contexts, user=None): """ create token by mailman cookie generation algorithm """ + if user: + # Unmunge a munged email address. + user = UnobscureEmail(urllib.unquote(user)) + for context in contexts: key, secret = mlist.AuthContextInfo(context, user) if key: @@ -49,9 +55,8 @@ def csrf_token(mlist, contexts, user=None): token = binascii.hexlify(marshal.dumps((issued, keymac))) return token -def csrf_check(mlist, token): +def csrf_check(mlist, token, options_user=None): """ check token by mailman cookie validation algorithm """ - try: issued, keymac = marshal.loads(binascii.unhexlify(token)) key, received_mac = keymac.split(':', 1) @@ -62,6 +67,17 @@ def csrf_check(mlist, token): key, user = key.split('+', 1) else: user = None + if user: + # This is for CVE-2021-42097. The token is a user token because + # of the fix for CVE-2021-42096 but it must match the user for + # whom the options page is requested. + raw_user = UnobscureEmail(urllib.unquote(user)) + if options_user and options_user != raw_user: + syslog('mischief', + 'Form for user %s submitted with CSRF token ' + 'issued for %s.', + options_user, raw_user) + return False context = keydict.get(key) key, secret = mlist.AuthContextInfo(context, user) assert key diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py index 1037f8f9..60b7d9b6 100644 --- a/Mailman/Cgi/options.py +++ b/Mailman/Cgi/options.py @@ -54,9 +54,6 @@ except NameError: True = 1 False = 0 -AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin, - mm_cfg.AuthListModerator, mm_cfg.AuthUser) - def main(): global _ @@ -124,15 +121,6 @@ def main(): print doc.Format() return - if set(params) - set(safe_params): - csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token')) - else: - csrf_checked = True - # if password is present, void cookie to force password authentication. - if cgidata.getfirst('password'): - os.environ['HTTP_COOKIE'] = '' - csrf_checked = True - # Set the language for the page. If we're coming from the listinfo cgi, # we might have a 'language' key in the cgi data. That was an explicit # preference to view the page in, so we should honor that here. If that's @@ -169,6 +157,16 @@ def main(): user = user[-1].strip() # Avoid cross-site scripting attacks + if set(params) - set(safe_params): + csrf_checked = csrf_check(mlist, cgidata.getfirst('csrf_token'), + Utils.UnobscureEmail(urllib.unquote(user))) + else: + csrf_checked = True + # if password is present, void cookie to force password authentication. + if cgidata.getfirst('password'): + os.environ['HTTP_COOKIE'] = '' + csrf_checked = True + safeuser = Utils.websafe(user) try: Utils.ValidateEmail(user) @@ -871,8 +869,9 @@ def options_page(mlist, doc, user, cpuser, userlang, message=''): mlist.FormatButton('othersubs', _('List my other subscriptions'))) replacements['<mm-form-start>'] = ( + # Always make the CSRF token for the user. CVE-2021-42096 mlist.FormatFormStart('options', user, mlist=mlist, - contexts=AUTH_CONTEXTS, user=user)) + contexts=[mm_cfg.AuthUser], user=user)) replacements['<mm-user>'] = user replacements['<mm-presentable-user>'] = presentable_user replacements['<mm-email-my-pw>'] = mlist.FormatButton( diff --git a/Mailman/SecurityManager.py b/Mailman/SecurityManager.py index 9b7f03f3..e9e5ce53 100644 --- a/Mailman/SecurityManager.py +++ b/Mailman/SecurityManager.py @@ -104,6 +104,7 @@ class SecurityManager: if user is None: # A bad system error raise TypeError, 'No user supplied for AuthUser context' + user = Utils.UnobscureEmail(urllib.unquote(user)) secret = self.getMemberPassword(user) userdata = urllib.quote(Utils.ObscureEmail(user), safe='') key += 'user+%s' % userdata @@ -5,7 +5,17 @@ Copyright (C) 1998-2020 by the Free Software Foundation, Inc. Here is a history of user visible changes to Mailman. -2.1.35 (xx-xxx-xxxx) +2.1.35 (19-Oct-2021) + + Security + + - A potential for for a list member to carry out an off-line brute force + attack to obtain the list admin password has been reported by Andre + Protas, Richard Cloke and Andy Nuttall of Apple. This is fixed. + CVE-2021-42096 (LP:#1947639) + + - A CSRF attack via the user options page could allow takeover of a users + account. This is fixed. CVE-2021-42097 (LP:#1947640) Bug Fixes and other patches |