Commits

Anonymous committed af38a93

0.10-stable branch: Applying CSRF-patch from #4049.

  • Participants
  • Parent commits 07dfd8c
  • Branches 0.10-stable

Comments (0)

Files changed (3)

     authname = None
     perm = None
     session = None
+    form_token = None
 
     def __init__(self, environ, start_response):
         """Create the request wrapper.
             content_type = 'text/plain'
             data = str(self.hdf)
         else:
-            data = self.hdf.render(template)
+            data = self.hdf.render(template, self.form_token)
 
         self.send_response(status)
         self.send_header('Cache-control', 'must-revalidate')

trac/web/clearsilver.py

 #
 # Author: Christopher Lenz <cmlenz@gmx.de>
 
+from HTMLParser import HTMLParser
+
 from trac.core import TracError
 from trac.util.html import Markup, Fragment, escape
 from trac.util.text import to_unicode
         cs.parseStr(string)
         return cs
 
-    def render(self, template):
+    def render(self, template, form_token=None):
         """Render the HDF using the given template.
 
         The template parameter can be either an already parse neo_cs.CS
             import neo_cs
             template = neo_cs.CS(self.hdf)
             template.parseFile(filename)
-        return template.render()
+
+        if form_token:
+            from cStringIO import StringIO
+            out = StringIO()
+            injector = FormTokenInjector(form_token, out)
+            injector.feed(template.render())
+            return out.getvalue()
+        else:
+            return template.render()
+
+
+class FormTokenInjector(HTMLParser):
+    """Identify and protect forms from CSRF attacks
+
+    This filter works by adding a input type=hidden field to POST forms.
+    """
+    def __init__(self, form_token, out):
+        HTMLParser.__init__(self)
+        self.out = out
+        self.token = form_token
+
+    def handle_starttag(self, tag, attrs):
+        self.out.write(self.get_starttag_text())
+        if tag.lower() == 'form':
+            for name, value in attrs:
+                if name.lower() == 'method' and value.lower() == 'post':
+                    self.out.write('<input type="hidden" name="__FORM_TOKEN"'
+                                   ' value="%s"/>' % self.token)
+                    break
+                    
+    def handle_startendtag(self, tag, attrs):
+        self.out.write(self.get_starttag_text())
+        
+    def handle_charref(self, name):
+        self.out.write('&#%s;' % name)
+
+    def handle_entityref(self, name):
+        self.out.write('&%s;' % name)
+
+    def handle_comment(self, data):
+        self.out.write('<!--%s-->' % data)
+
+    def handle_decl(self, data):
+        self.out.write('<!%s>' % data)
+
+    def handle_pi(self, data):
+        self.out.write('<?%s?>' % data)
+
+    def handle_data(self, data):
+        self.out.write(data)
+
+    def handle_endtag(self, tag):
+        self.out.write('</' + tag + '>')
 
 
 if __name__ == '__main__':
                 req.authname = self.authenticate(req)
                 req.perm = PermissionCache(self.env, req.authname)
                 req.session = Session(self.env, req)
+                req.form_token = self._get_form_token(req)
             except:
                 anonymous_request = True
                 early_error = sys.exc_info()
         try:
             try:
                 try:
+                    # Protect against CSRF attacks.
+                    if (req.method == 'POST' and
+                        req.args.get('__FORM_TOKEN') != req.form_token):
+                        raise TracError('Missing or invalid form token '
+                                        'Do you have cookies enabled?')
+
                     resp = chosen_handler.process_request(req)
                     if resp:
                         template, content_type = \
                                                             content_type)
         return template, content_type
 
+    def _get_form_token(self, req):
+        """Used to protect against CSRF.
+
+        The 'trac_auth' cookie is a good and strong shared secret, only
+        known by the user it belongs to and Trac itself.
+
+        The session id is our second best option, not as reliable since
+        it will change on each request if the user has cookies disabled in
+        his/her browser.
+        """
+        if req.incookie.has_key('trac_auth'):
+            return req.incookie['trac_auth'].value
+        else:
+            return req.session.sid
+
 
 def dispatch_request(environ, start_response):
     """Main entry point for the Trac web interface.