|
|
8cf1bd |
diff --git a/Mailman/Cgi/admindb.py b/Mailman/Cgi/admindb.py
|
|
|
8cf1bd |
index d187332..defc58d 100644
|
|
|
8cf1bd |
--- a/Mailman/Cgi/admindb.py
|
|
|
8cf1bd |
+++ b/Mailman/Cgi/admindb.py
|
|
|
8cf1bd |
@@ -39,6 +39,7 @@ from Mailman.ListAdmin import readMessage
|
|
|
8cf1bd |
from Mailman.Cgi import Auth
|
|
|
8cf1bd |
from Mailman.htmlformat import *
|
|
|
8cf1bd |
from Mailman.Logging.Syslog import syslog
|
|
|
8cf1bd |
+from Mailman.CSRFcheck import csrf_check
|
|
|
8cf1bd |
|
|
|
8cf1bd |
EMPTYSTRING = ''
|
|
|
8cf1bd |
NL = '\n'
|
|
|
8cf1bd |
@@ -51,6 +52,9 @@ i18n.set_language(mm_cfg.DEFAULT_SERVER_LANGUAGE)
|
|
|
8cf1bd |
EXCERPT_HEIGHT = 10
|
|
|
8cf1bd |
EXCERPT_WIDTH = 76
|
|
|
8cf1bd |
|
|
|
8cf1bd |
+AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin,
|
|
|
8cf1bd |
+ mm_cfg.AuthListModerator)
|
|
|
8cf1bd |
+
|
|
|
8cf1bd |
|
|
|
8cf1bd |
|
|
|
8cf1bd |
def helds_by_sender(mlist):
|
|
|
8cf1bd |
@@ -100,6 +104,18 @@ def main():
|
|
|
8cf1bd |
# Make sure the user is authorized to see this page.
|
|
|
8cf1bd |
cgidata = cgi.FieldStorage(keep_blank_values=1)
|
|
|
8cf1bd |
|
|
|
8cf1bd |
+ # CSRF check
|
|
|
8cf1bd |
+ safe_params = ['adminpw', 'admlogin', 'msgid', 'sender', 'details']
|
|
|
8cf1bd |
+ params = cgidata.keys()
|
|
|
8cf1bd |
+ if set(params) - set(safe_params):
|
|
|
8cf1bd |
+ csrf_checked = csrf_check(mlist, cgidata.getvalue('csrf_token'))
|
|
|
8cf1bd |
+ else:
|
|
|
8cf1bd |
+ csrf_checked = True
|
|
|
8cf1bd |
+ # if password is present, void cookie to force password authentication.
|
|
|
8cf1bd |
+ if cgidata.getvalue('adminpw'):
|
|
|
8cf1bd |
+ os.environ['HTTP_COOKIE'] = ''
|
|
|
8cf1bd |
+ csrf_checked = True
|
|
|
8cf1bd |
+
|
|
|
8cf1bd |
if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin,
|
|
|
8cf1bd |
mm_cfg.AuthListModerator,
|
|
|
8cf1bd |
mm_cfg.AuthSiteAdmin),
|
|
|
8cf1bd |
@@ -177,7 +193,11 @@ def main():
|
|
|
8cf1bd |
elif not details:
|
|
|
8cf1bd |
# This is a form submission
|
|
|
8cf1bd |
doc.SetTitle(_('%(realname)s Administrative Database Results'))
|
|
|
8cf1bd |
- process_form(mlist, doc, cgidata)
|
|
|
8cf1bd |
+ if csrf_checked:
|
|
|
8cf1bd |
+ process_form(mlist, doc, cgidata)
|
|
|
8cf1bd |
+ else:
|
|
|
8cf1bd |
+ doc.addError(
|
|
|
8cf1bd |
+ _('The form lifetime has expired. (request forgery check)'))
|
|
|
8cf1bd |
# Now print the results and we're done. Short circuit for when there
|
|
|
8cf1bd |
# are no pending requests, but be sure to save the results!
|
|
|
8cf1bd |
admindburl = mlist.GetScriptURL('admindb', absolute=1)
|
|
|
8cf1bd |
@@ -198,7 +218,7 @@ def main():
|
|
|
8cf1bd |
mlist.Save()
|
|
|
8cf1bd |
return
|
|
|
8cf1bd |
|
|
|
8cf1bd |
- form = Form(admindburl)
|
|
|
8cf1bd |
+ form = Form(admindburl, mlist=mlist, contexts=AUTH_CONTEXTS)
|
|
|
8cf1bd |
# Add the instructions template
|
|
|
8cf1bd |
if details == 'instructions':
|
|
|
8cf1bd |
doc.AddItem(Header(
|
|
|
8cf1bd |
diff --git a/Mailman/Cgi/edithtml.py b/Mailman/Cgi/edithtml.py
|
|
|
8cf1bd |
index ee1ccd0..5c9416e 100644
|
|
|
8cf1bd |
--- a/Mailman/Cgi/edithtml.py
|
|
|
8cf1bd |
+++ b/Mailman/Cgi/edithtml.py
|
|
|
8cf1bd |
@@ -30,9 +30,12 @@ from Mailman import Errors
|
|
|
8cf1bd |
from Mailman.Cgi import Auth
|
|
|
8cf1bd |
from Mailman.Logging.Syslog import syslog
|
|
|
8cf1bd |
from Mailman import i18n
|
|
|
8cf1bd |
+from Mailman.CSRFcheck import csrf_check
|
|
|
8cf1bd |
|
|
|
8cf1bd |
_ = i18n._
|
|
|
8cf1bd |
|
|
|
8cf1bd |
+AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin)
|
|
|
8cf1bd |
+
|
|
|
8cf1bd |
|
|
|
8cf1bd |
|
|
|
8cf1bd |
def main():
|
|
|
8cf1bd |
@@ -82,6 +85,18 @@ def main():
|
|
|
8cf1bd |
# Must be authenticated to get any farther
|
|
|
8cf1bd |
cgidata = cgi.FieldStorage()
|
|
|
8cf1bd |
|
|
|
8cf1bd |
+ # CSRF check
|
|
|
8cf1bd |
+ safe_params = ['VARHELP', 'adminpw', 'admlogin']
|
|
|
8cf1bd |
+ params = cgidata.keys()
|
|
|
8cf1bd |
+ if set(params) - set(safe_params):
|
|
|
8cf1bd |
+ csrf_checked = csrf_check(mlist, cgidata.getvalue('csrf_token'))
|
|
|
8cf1bd |
+ else:
|
|
|
8cf1bd |
+ csrf_checked = True
|
|
|
8cf1bd |
+ # if password is present, void cookie to force password authentication.
|
|
|
8cf1bd |
+ if cgidata.getvalue('adminpw'):
|
|
|
8cf1bd |
+ os.environ['HTTP_COOKIE'] = ''
|
|
|
8cf1bd |
+ csrf_checked = True
|
|
|
8cf1bd |
+
|
|
|
8cf1bd |
# Editing the html for a list is limited to the list admin and site admin.
|
|
|
8cf1bd |
if not mlist.WebAuthenticate((mm_cfg.AuthListAdmin,
|
|
|
8cf1bd |
mm_cfg.AuthSiteAdmin),
|
|
|
8cf1bd |
@@ -126,7 +141,11 @@ def main():
|
|
|
8cf1bd |
|
|
|
8cf1bd |
try:
|
|
|
8cf1bd |
if cgidata.keys():
|
|
|
8cf1bd |
- ChangeHTML(mlist, cgidata, template_name, doc)
|
|
|
8cf1bd |
+ if csrf_checked:
|
|
|
8cf1bd |
+ ChangeHTML(mlist, cgidata, template_name, doc)
|
|
|
8cf1bd |
+ else:
|
|
|
8cf1bd |
+ doc.addError(
|
|
|
8cf1bd |
+ _('The form lifetime has expired. (request forgery check)'))
|
|
|
8cf1bd |
FormatHTML(mlist, doc, template_name, template_info)
|
|
|
8cf1bd |
finally:
|
|
|
8cf1bd |
doc.AddItem(mlist.GetMailmanFooter())
|
|
|
8cf1bd |
@@ -145,7 +164,8 @@ def FormatHTML(mlist, doc, template_name, template_info):
|
|
|
8cf1bd |
doc.AddItem(FontSize("+1", link))
|
|
|
8cf1bd |
doc.AddItem('')
|
|
|
8cf1bd |
doc.AddItem(' ')
|
|
|
8cf1bd |
- form = Form(mlist.GetScriptURL('edithtml') + '/' + template_name)
|
|
|
8cf1bd |
+ form = Form(mlist.GetScriptURL('edithtml') + '/' + template_name,
|
|
|
8cf1bd |
+ mlist=mlist, contexts=AUTH_CONTEXTS)
|
|
|
8cf1bd |
text = Utils.maketext(template_name, raw=1, mlist=mlist)
|
|
|
8cf1bd |
# MAS: Don't websafe twice. TextArea does it.
|
|
|
8cf1bd |
form.AddItem(TextArea('html_code', text, rows=40, cols=75))
|
|
|
8cf1bd |
diff --git a/Mailman/Cgi/options.py b/Mailman/Cgi/options.py
|
|
|
8cf1bd |
index ae701a7..328d9d5 100644
|
|
|
8cf1bd |
--- a/Mailman/Cgi/options.py
|
|
|
8cf1bd |
+++ b/Mailman/Cgi/options.py
|
|
|
8cf1bd |
@@ -33,6 +33,7 @@ from Mailman import MemberAdaptor
|
|
|
8cf1bd |
from Mailman import i18n
|
|
|
8cf1bd |
from Mailman.htmlformat import *
|
|
|
8cf1bd |
from Mailman.Logging.Syslog import syslog
|
|
|
8cf1bd |
+from Mailman.CSRFcheck import csrf_check
|
|
|
8cf1bd |
|
|
|
8cf1bd |
SLASH = '/'
|
|
|
8cf1bd |
SETLANGUAGE = -1
|
|
|
8cf1bd |
@@ -47,6 +48,8 @@ except NameError:
|
|
|
8cf1bd |
True = 1
|
|
|
8cf1bd |
False = 0
|
|
|
8cf1bd |
|
|
|
8cf1bd |
+AUTH_CONTEXTS = (mm_cfg.AuthListAdmin, mm_cfg.AuthSiteAdmin,
|
|
|
8cf1bd |
+ mm_cfg.AuthListModerator, mm_cfg.AuthUser)
|
|
|
8cf1bd |
|
|
|
8cf1bd |
|
|
|
8cf1bd |
def main():
|
|
|
8cf1bd |
@@ -88,6 +91,19 @@ def main():
|
|
|
8cf1bd |
# The total contents of the user's response
|
|
|
8cf1bd |
cgidata = cgi.FieldStorage(keep_blank_values=1)
|
|
|
8cf1bd |
|
|
|
8cf1bd |
+ # CSRF check
|
|
|
8cf1bd |
+ safe_params = ['displang-button', 'language', 'email', 'password', 'login',
|
|
|
8cf1bd |
+ 'login-unsub', 'login-remind', 'VARHELP', 'UserOptions']
|
|
|
8cf1bd |
+ params = cgidata.keys()
|
|
|
8cf1bd |
+ if set(params) - set(safe_params):
|
|
|
8cf1bd |
+ csrf_checked = csrf_check(mlist, cgidata.getvalue('csrf_token'))
|
|
|
8cf1bd |
+ else:
|
|
|
8cf1bd |
+ csrf_checked = True
|
|
|
8cf1bd |
+ # if password is present, void cookie to force password authentication.
|
|
|
8cf1bd |
+ if cgidata.getvalue('password'):
|
|
|
8cf1bd |
+ os.environ['HTTP_COOKIE'] = ''
|
|
|
8cf1bd |
+ csrf_checked = True
|
|
|
8cf1bd |
+
|
|
|
8cf1bd |
# Set the language for the page. If we're coming from the listinfo cgi,
|
|
|
8cf1bd |
# we might have a 'language' key in the cgi data. That was an explicit
|
|
|
8cf1bd |
# preference to view the page in, so we should honor that here. If that's
|
|
|
8cf1bd |
@@ -269,6 +285,15 @@ def main():
|
|
|
8cf1bd |
# options. The first set of checks does not require the list to be
|
|
|
8cf1bd |
# locked.
|
|
|
8cf1bd |
|
|
|
8cf1bd |
+ # Before going further, get the result of CSRF check and do nothing
|
|
|
8cf1bd |
+ # if it has failed.
|
|
|
8cf1bd |
+ if csrf_checked == False:
|
|
|
8cf1bd |
+ doc.addError(
|
|
|
8cf1bd |
+ _('The form lifetime has expired. (request forgery check)'))
|
|
|
8cf1bd |
+ options_page(mlist, doc, user, cpuser, userlang)
|
|
|
8cf1bd |
+ print doc.Format()
|
|
|
8cf1bd |
+ return
|
|
|
8cf1bd |
+
|
|
|
8cf1bd |
if cgidata.has_key('logout'):
|
|
|
8cf1bd |
print mlist.ZapCookie(mm_cfg.AuthUser, user)
|
|
|
8cf1bd |
loginpage(mlist, doc, user, language)
|
|
|
8cf1bd |
@@ -779,7 +804,8 @@ def options_page(mlist, doc, user, cpuser, userlang, message=''):
|
|
|
8cf1bd |
mlist.FormatButton('othersubs',
|
|
|
8cf1bd |
_('List my other subscriptions')))
|
|
|
8cf1bd |
replacements['<mm-form-start>'] = (
|
|
|
8cf1bd |
- mlist.FormatFormStart('options', user))
|
|
|
8cf1bd |
+ mlist.FormatFormStart('options', user, mlist=mlist,
|
|
|
8cf1bd |
+ contexts=AUTH_CONTEXTS, user=user))
|
|
|
8cf1bd |
replacements['<mm-user>'] = user
|
|
|
8cf1bd |
replacements['<mm-presentable-user>'] = presentable_user
|
|
|
8cf1bd |
replacements['<mm-email-my-pw>'] = mlist.FormatButton(
|
|
|
8cf1bd |
diff --git a/Mailman/HTMLFormatter.py b/Mailman/HTMLFormatter.py
|
|
|
8cf1bd |
index dad51e7..3afe4bb 100644
|
|
|
8cf1bd |
--- a/Mailman/HTMLFormatter.py
|
|
|
8cf1bd |
+++ b/Mailman/HTMLFormatter.py
|
|
|
8cf1bd |
@@ -28,6 +28,8 @@ from Mailman.htmlformat import *
|
|
|
8cf1bd |
|
|
|
8cf1bd |
from Mailman.i18n import _
|
|
|
8cf1bd |
|
|
|
8cf1bd |
+from Mailman.CSRFcheck import csrf_token
|
|
|
8cf1bd |
+
|
|
|
8cf1bd |
|
|
|
8cf1bd |
EMPTYSTRING = ''
|
|
|
8cf1bd |
BR = ' '
|
|
|
8cf1bd |
@@ -314,12 +316,17 @@ class HTMLFormatter:
|
|
|
8cf1bd |
container.AddItem("</center>")
|
|
|
8cf1bd |
return container
|
|
|
8cf1bd |
|
|
|
8cf1bd |
- def FormatFormStart(self, name, extra=''):
|
|
|
8cf1bd |
+ def FormatFormStart(self, name, extra='',
|
|
|
8cf1bd |
+ mlist=None, contexts=None, user=None):
|
|
|
8cf1bd |
base_url = self.GetScriptURL(name)
|
|
|
8cf1bd |
if extra:
|
|
|
8cf1bd |
full_url = "%s/%s" % (base_url, extra)
|
|
|
8cf1bd |
else:
|
|
|
8cf1bd |
full_url = base_url
|
|
|
8cf1bd |
+ if mlist:
|
|
|
8cf1bd |
+ return ("""<form method="POST" action="%s">
|
|
|
8cf1bd |
+<input type="hidden" name="csrf_token" value="%s">"""
|
|
|
8cf1bd |
+ % (full_url, csrf_token(mlist, contexts, user)))
|
|
|
8cf1bd |
return ('<FORM Method=POST ACTION="%s">' % full_url)
|
|
|
8cf1bd |
|
|
|
8cf1bd |
def FormatArchiveAnchor(self):
|
|
|
8cf1bd |
diff --git a/Mailman/htmlformat.py b/Mailman/htmlformat.py
|
|
|
8cf1bd |
index 2387096..e2ad2e4 100644
|
|
|
8cf1bd |
--- a/Mailman/htmlformat.py
|
|
|
8cf1bd |
+++ b/Mailman/htmlformat.py
|
|
|
8cf1bd |
@@ -405,13 +405,14 @@ class Center(StdContainer):
|
|
|
8cf1bd |
|
|
|
8cf1bd |
class Form(Container):
|
|
|
8cf1bd |
def __init__(self, action='', method='POST', encoding=None,
|
|
|
8cf1bd |
- mlist=None, contexts=None, *items):
|
|
|
8cf1bd |
+ mlist=None, contexts=None, user=None, *items):
|
|
|
8cf1bd |
apply(Container.__init__, (self,) + items)
|
|
|
8cf1bd |
self.action = action
|
|
|
8cf1bd |
self.method = method
|
|
|
8cf1bd |
self.encoding = encoding
|
|
|
8cf1bd |
self.mlist = mlist
|
|
|
8cf1bd |
self.contexts = contexts
|
|
|
8cf1bd |
+ self.user = user
|
|
|
8cf1bd |
|
|
|
8cf1bd |
def set_action(self, action):
|
|
|
8cf1bd |
self.action = action
|
|
|
8cf1bd |
@@ -426,7 +427,7 @@ class Form(Container):
|
|
|
8cf1bd |
if self.mlist:
|
|
|
8cf1bd |
output = output + \
|
|
|
8cf1bd |
'<input type="hidden" name="csrf_token" value="%s">\n' \
|
|
|
8cf1bd |
- % csrf_token(self.mlist, self.contexts)
|
|
|
8cf1bd |
+ % csrf_token(self.mlist, self.contexts, self.user)
|
|
|
8cf1bd |
output = output + Container.Format(self, indent+2)
|
|
|
8cf1bd |
output = '%s\n%s</FORM>\n' % (output, spaces)
|
|
|
8cf1bd |
return output
|