Edgewall Software

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

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

An updated version of the 0.10-stable patch.

  • trac/web/api.py

     
    127127    authname = None 
    128128    perm = None 
    129129    session = None 
     130    form_token = None 
    130131 
    131132    def __init__(self, environ, start_response): 
    132133        """Create the request wrapper. 
     
    350351            content_type = 'text/plain' 
    351352            data = str(self.hdf) 
    352353        else: 
    353             data = self.hdf.render(template) 
     354            data = self.hdf.render(template, self.form_token) 
    354355 
    355356        self.send_response(status) 
    356357        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                    break 
     320                     
     321    def handle_startendtag(self, tag, attrs): 
     322        self.out.write(self.get_starttag_text()) 
     323         
     324    def handle_charref(self, name): 
     325        self.out.write('&#%s;' % name) 
     326 
     327    def handle_entityref(self, name): 
     328        self.out.write('&%s;' % name) 
     329 
     330    def handle_comment(self, data): 
     331        self.out.write('<!--%s-->' % data) 
     332 
     333    def handle_decl(self, data): 
     334        self.out.write('<!%s>' % data) 
     335 
     336    def handle_pi(self, data): 
     337        self.out.write('<?%s?>' % data) 
     338 
     339    def handle_data(self, data): 
     340        self.out.write(data) 
     341 
     342    def handle_endtag(self, tag): 
     343        self.out.write('</' + tag + '>') 
     344 
     345 
    292346if __name__ == '__main__': 
    293347    import doctest, sys 
    294348    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                    if (req.method == 'POST' and 
     227                        req.args.get('__FORM_TOKEN') != req.form_token): 
     228                        raise TracError('Missing or invalid form token ' 
     229                                        'Do you have cookies enabled?') 
     230 
    224231                    resp = chosen_handler.process_request(req) 
    225232                    if resp: 
    226233                        template, content_type = \ 
     
    257264                                                            content_type) 
    258265        return template, content_type 
    259266 
     267    def _get_form_token(self, req): 
     268        """Used to protect against CSRF. 
    260269 
     270        The 'trac_auth' cookie is a good and strong shared secret, only 
     271        known by the user it belongs to and Trac itself. 
     272 
     273        The session id is our second best option, not as reliable since 
     274        it will change on each request if the user has cookies disabled in 
     275        his/her browser. 
     276        """ 
     277        if req.incookie.has_key('trac_auth'): 
     278            return req.incookie['trac_auth'].value 
     279        else: 
     280            return req.session.sid 
     281 
     282 
    261283def dispatch_request(environ, start_response): 
    262284    """Main entry point for the Trac web interface. 
    263285