Edgewall Software

Ticket #4049: trac-0.10-csrf.patch

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

A version for 0.10-stable, by far not as elegant.

  • 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 
    18 from trac.util.html import Markup, Fragment, escape 
     20from trac.util.html import Markup, Fragment, escape, _EMPTY_TAGS 
    1921from trac.util.text import to_unicode 
    2022 
    2123 
     
    274276        cs.parseStr(string) 
    275277        return cs 
    276278 
    277     def render(self, template): 
     279    def render(self, template, form_token=''): 
    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        from cStringIO import StringIO 
     293        out = StringIO() 
     294        injector = FormTokenInjector(form_token+'x', out) 
     295        injector.feed(template.render()) 
     296        return out.getvalue() 
    291297 
     298 
     299class FormTokenInjector(HTMLParser): 
     300    """Identify and protect forms from CSRF attacks 
     301 
     302    This filter works by adding a input type=hidden field to POST forms. 
     303    """ 
     304    def __init__(self, form_token, out): 
     305        HTMLParser.__init__(self) 
     306        self.out = out 
     307        self.token = form_token 
     308 
     309    def handle_starttag(self, tag, attrs): 
     310        strs = [] 
     311        attr_map = {} 
     312        for name, val in attrs: 
     313            attr_map[name] = val 
     314            strs.append('%s="%s"' % (name, val)) 
     315        chunk = '<' + tag 
     316        if strs: 
     317            chunk += ' ' + ' '.join(strs) 
     318        if tag in _EMPTY_TAGS: 
     319            chunk += '/' 
     320        chunk += '>' 
     321        self.out.write(chunk) 
     322        if tag == 'form' and attr_map.get('method', '').lower() == 'post': 
     323            self.out.write('<input type="hidden" name="__FORM_TOKEN"' 
     324                           ' value="%s"/>' % self.token) 
     325 
     326    def handle_entityref(self, name): 
     327        self.out.write('&%s;' % name) 
     328 
     329    def handle_data(self, data): 
     330        self.out.write(escape(data, quotes=False)) 
     331 
     332    def handle_endtag(self, tag): 
     333        if tag not in _EMPTY_TAGS: 
     334            self.out.write('</' + tag + '>') 
     335 
     336 
    292337if __name__ == '__main__': 
    293338    import doctest, sys 
    294339    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