Edgewall Software

Ticket #4049: trac-0.10-csrf.2.patch

File trac-0.10-csrf.2.patch, 5.1 KB (added by jonas, 2 years ago)

An updated version of the 0.10-stable patch.

  • trac/web/api.py

     
    350350            content_type = 'text/plain' 
    351351            data = str(self.hdf) 
    352352        else: 
    353             data = self.hdf.render(template) 
     353            data = self.hdf.render(template, self.form_token) 
    354354 
    355355        self.send_response(status) 
    356356        self.send_header('Cache-control', 'must-revalidate') 
  • trac/web/clearsilver.py

     
    1414# 
    1515# Author: Christopher Lenz <cmlenz@gmx.de> 
    1616 
     17from HTMLParser import HTMLParser 
     18 
    1719from trac.core import TracError 
    1820from trac.util.html import Markup, Fragment, escape 
    1921from trac.util.text import to_unicode 
     
    274276        cs.parseStr(string) 
    275277        return cs 
    276278 
    277     def render(self, template): 
     279    def render(self, template, form_token=None): 
    278280        """Render the HDF using the given template. 
    279281 
    280282        The template parameter can be either an already parse neo_cs.CS 
     
    286288            import neo_cs 
    287289            template = neo_cs.CS(self.hdf) 
    288290            template.parseFile(filename) 
    289         return template.render() 
    290291 
     292        if form_token: 
     293            from cStringIO import StringIO 
     294            out = StringIO() 
     295            injector = FormTokenInjector(form_token, out) 
     296            injector.feed(template.render()) 
     297            return out.getvalue() 
     298        else: 
     299            return template.render() 
    291300 
     301 
     302class FormTokenInjector(HTMLParser): 
     303    """Identify and protect forms from CSRF attacks 
     304 
     305    This filter works by adding a input type=hidden field to POST forms. 
     306    """ 
     307    def __init__(self, form_token, out): 
     308        HTMLParser.__init__(self) 
     309        self.out = out 
     310        self.token = form_token 
     311 
     312    def handle_starttag(self, tag, attrs): 
     313        self.out.write(self.get_starttag_text()) 
     314        if tag.lower() == 'form': 
     315            for name, value in attrs: 
     316                if name.lower() == 'method' and value.lower() == 'post': 
     317                    self.out.write('<input type="hidden" name="__FORM_TOKEN"' 
     318                                   ' value="%s"/>' % self.token) 
     319                     
     320    def handle_startendtag(self, tag, attrs): 
     321        self.out.write(self.get_starttag_text()) 
     322         
     323    def handle_charref(self, name): 
     324        self.out.write('&#%s;' % name) 
     325 
     326    def handle_entityref(self, name): 
     327        self.out.write('&%s;' % name) 
     328 
     329    def handle_comment(self, data): 
     330        self.out.write('<!--%s-->' % data) 
     331 
     332    def handle_decl(self, data): 
     333        self.out.write('<!--%s-->' % data) 
     334 
     335    def handle_pi(self, data): 
     336        self.out.write('<?%s>' % data) 
     337 
     338    def handle_data(self, data): 
     339        self.out.write(data) 
     340 
     341    def handle_endtag(self, tag): 
     342        self.out.write('</' + tag + '>') 
     343 
     344 
    292345if __name__ == '__main__': 
    293346    import doctest, sys 
    294347    doctest.testmod(sys.modules[__name__]) 
  • trac/web/main.py

     
    189189                req.authname = self.authenticate(req) 
    190190                req.perm = PermissionCache(self.env, req.authname) 
    191191                req.session = Session(self.env, req) 
     192                req.form_token = self._get_form_token(req) 
    192193            except: 
    193194                anonymous_request = True 
    194195                early_error = sys.exc_info() 
     
    221222        try: 
    222223            try: 
    223224                try: 
     225                    # Protect against CSRF attacks. 
     226                    # We can only block against such attacks if the user 
     227                    # is logged in or if we have an incoming session cookie. 
     228                    if (req.method == 'POST' and 
     229                        req.args.get('__FORM_TOKEN') != req.form_token and 
     230                        (req.incookie.has_key('trac_auth') or 
     231                         req.incookie.has_key('trac_session'))): 
     232                        raise TracError('Missing or invalid form token') 
     233 
    224234                    resp = chosen_handler.process_request(req) 
    225235                    if resp: 
    226236                        template, content_type = \ 
     
    257267                                                            content_type) 
    258268        return template, content_type 
    259269 
     270    def _get_form_token(self, req): 
     271        """Used to protect against CSRF. 
    260272 
     273        The 'trac_auth' cookie is a good and strong shared secret, only 
     274        known by the user it belongs to and Trac itself. 
     275 
     276        The session id is our second best option, not as reliable since 
     277        it will change on each request if the user has cookies disabled in 
     278        his/her browser. 
     279        """ 
     280        if req.incookie.has_key('trac_auth'): 
     281            return req.incookie['trac_auth'].value 
     282        else: 
     283            return req.session.sid 
     284 
     285 
    261286def dispatch_request(environ, start_response): 
    262287    """Main entry point for the Trac web interface. 
    263288