Ben Cooksley wrote:
>
>If Mailman were to implement basic CSRF protection for all POST requests
>that would also slow the attackers down I suspect (as they would have to
>make a GET request first and parse it).


I have implemented a simple version of what I think you requested in
your post at
<http://mail.python.org/pipermail/mailman-users/2012-October/074287.html>.

It is implemented by the attached patch against Mailman 2.1.15. The
patch will apply to versions 2.1.12 and later with at most line number
changes, For older versions, the hashing function
Mailman.Utils.sha_new doesn't exist and will need to be changed in the
patch to something else. Note that the patch only enables configuring
the token for the listinfo subscribe form. To actually enable
placement and checking of the token, one must assign a non-empty
string value to SUBSCRIBE_FORM_SECRET in mm_cfg.py. I.e.,

SUBSCRIBE_FORM_SECRET = 'Some site specific string'

The actual token is a hex digest of a sha hash of this string plus the
list's internal name plus the IP address of the caller.

A more secure token would include something more random such as the
time of day, but would be a bit more cumbersome to implement -
volunteers are welcome.

Let us know if this helps.

-- 
Mark Sapiro <m...@msapiro.net>        The highway is for gamblers,
San Francisco Bay Area, California    better use your sense - B. Dylan

--- f:/test-mailman-2.1/Mailman/Defaults.py     2012-10-21 12:25:32.265625000 
-0700
+++ f:/test-mailman/Mailman/Defaults.py 2012-11-18 15:35:27.906250000 -0800
@@ -111,6 +111,14 @@
 # Form lifetime is set against Cross Site Request Forgery.
 FORM_LIFETIME = hours(1)
 
+# If the following is set to a non-empty string, this string in combination
+# with the list name and the IP address of the requestor is used to create
+# a hidden hash as part of the subscribe form on the listinfo page. This
+# hash is checked upon form submission and the subscribe fails if it doesn't
+# match. I.e. the form posted must be first retrieved from the listinfo
+# CGI by the same IP that posts it.
+SUBSCRIBE_FORM_SECRET = None
+
 # Command that is used to convert text/html parts into plain text.  This
 # should output results to standard output.  %(filename)s will contain the
 # name of the temporary file that the program should operate on.
--- f:/test-mailman-2.1/Mailman/Cgi/listinfo.py 2010-09-05 07:38:30.000000000 
-0700
+++ f:/test-mailman/Mailman/Cgi/listinfo.py     2012-11-18 16:17:33.265625000 
-0800
@@ -184,6 +184,16 @@
     replacements['<mm-confirm-password>'] = mlist.FormatSecureBox('pw-conf')
     replacements['<mm-subscribe-form-start>'] = mlist.FormatFormStart(
         'subscribe')
+    if mm_cfg.SUBSCRIBE_FORM_SECRET:
+        replacements['<mm-subscribe-form-start>'] += (
+                    '<input type="hidden" name="sub_form_token" value="%s">\n' 
\
+                    % Utils.sha_new(mm_cfg.SUBSCRIBE_FORM_SECRET +
+                              mlist.internal_name() +
+                              os.environ.get('REMOTE_HOST',
+                                             os.environ.get('REMOTE_ADDR',
+                                                            'w.x.y.z'))
+                              ).hexdigest()
+                    )
     # Roster form substitutions
     replacements['<mm-roster-form-start>'] = mlist.FormatFormStart('roster')
     replacements['<mm-roster-option>'] = mlist.FormatRosterOptionForUser(lang)
--- f:/test-mailman-2.1/Mailman/Cgi/subscribe.py        2011-05-05 
13:36:42.000000000 -0700
+++ f:/test-mailman/Mailman/Cgi/subscribe.py    2012-11-18 16:18:31.140625000 
-0800
@@ -120,6 +120,13 @@
     remote = os.environ.get('REMOTE_HOST',
                             os.environ.get('REMOTE_ADDR',
                                            'unidentified origin'))
+    # Are we checking the hidden data?
+    if mm_cfg.SUBSCRIBE_FORM_SECRET:
+        token = Utils.sha_new(mm_cfg.SUBSCRIBE_FORM_SECRET +
+                              mlist.internal_name() +
+                              remote).hexdigest()
+        if token != cgidata.getvalue('sub_form_token', ''):
+            results.append(_('You must GET the form before submitting it.'))
     # Was an attempt made to subscribe the list to itself?
     if email == mlist.GetListEmail():
         syslog('mischief', 'Attempt to self subscribe %s: %s', email, remote)
------------------------------------------------------
Mailman-Users mailing list Mailman-Users@python.org
http://mail.python.org/mailman/listinfo/mailman-users
Mailman FAQ: http://wiki.list.org/x/AgA3
Security Policy: http://wiki.list.org/x/QIA9
Searchable Archives: http://www.mail-archive.com/mailman-users%40python.org/
Unsubscribe: 
http://mail.python.org/mailman/options/mailman-users/archive%40jab.org

Reply via email to