# HG changeset patch # User Goffi # Date 1499706631 -7200 # Node ID dabecab10faa6c4a71fc0a577bd6b5e098d9072c # Parent 4f7cb6335a33e036576121c3354141f218a21ede server (pages): impleted CSRF protection: A basic CSRF protection has been implemented using CSRF token. The token is created on session creation, and checked on data post. The process should be fully automatic, and a hidden field is added in forms in sat_templates when csrf_token is present in template data (require to import input/form.html with context). If token is wrong on absent, an unauthorized error page is returned (and client ip is logged). Also don't use anymore inlineCallbacks in _on_data_post, as StopIteration exception are catched by inlineCallbacks, resulting in bad behaviour. As a further security, getPostedDate raise a KeyError instead of StopIteration is a specific key is looked for and missing. Added HTTP_SEE_OTHER status code in constants. diff -r 4f7cb6335a33 -r dabecab10faa src/server/constants.py --- a/src/server/constants.py Sun Jul 09 22:28:40 2017 +0200 +++ b/src/server/constants.py Mon Jul 10 19:10:31 2017 +0200 @@ -63,6 +63,7 @@ HTTP_METHOD_POST = u'POST' ## HTTP codes ## + HTTP_SEE_OTHER = 303 HTTP_BAD_REQUEST = 400 HTTP_UNAUTHORIZED = 401 HTTP_NOT_FOUND = 404 diff -r 4f7cb6335a33 -r dabecab10faa src/server/server.py --- a/src/server/server.py Sun Jul 09 22:28:40 2017 +0200 +++ b/src/server/server.py Mon Jul 10 19:10:31 2017 +0200 @@ -1690,14 +1690,32 @@ msg = failure_)) self.pageError(request, C.HTTP_INTERNAL_ERROR) - @defer.inlineCallbacks - def _on_data_post(self, dummy, request): - yield defer.maybeDeferred(self.on_data_post, self, request) - request.setResponseCode(303) + def _on_data_post_redirect(self, ret, request): + """called when page's on_data_post has been called successfuly + + this method redirect to the same page, using Post/Redirect/Get pattern + HTTP status code "See Other" (303) is the recommanded code in this case + """ + request.setResponseCode(C.HTTP_SEE_OTHER) request.setHeader("location", request.uri) request.finish() raise failure.Failure(exceptions.CancelError(u'Post/Redirect/Get is used')) + def _on_data_post(self, dummy, request): + csrf_token = self.host.getSessionData(request, session_iface.ISATSession).csrf_token + try: + given_csrf = self.getPostedData(request, u'csrf_token') + except KeyError: + given_csrf = None + if given_csrf is None or given_csrf != csrf_token: + log.warning(_(u"invalid CSRF token, hack attempt? URL: {url}, IP: {ip}").format( + url=request.uri, + ip=request.getClientIP())) + self.pageError(request, C.HTTP_UNAUTHORIZED) + d = defer.maybeDeferred(self.on_data_post, self, request) + d.addCallback(self._on_data_post_redirect, request) + return d + def getPostedData(self, request, keys, multiple=False): """get data from a POST request and decode it @@ -1709,6 +1727,7 @@ @param multiple(bool): True if multiple values are possible/expected if False, the first value is returned @return (iterator[unicode], list[iterator[unicode], unicode, list[unicode]): values received for this(these) key(s) + @raise KeyError: one specific key has been requested, and it is missing """ if isinstance(keys, basestring): keys = [keys] @@ -1722,7 +1741,10 @@ if multiple: ret.append(gen) else: - ret.append(next(gen)) + try: + ret.append(next(gen)) + except StopIteration: + return KeyError(key) return ret[0] if get_first else ret @@ -1768,7 +1790,8 @@ """Main method to handle the workflow of a LiberviaPage""" # template_data are the variables passed to template if not hasattr(request, 'template_data'): - request.template_data = {} + csrf_token = self.host.getSessionData(request, session_iface.ISATSession).csrf_token + request.template_data = {'csrf_token': csrf_token} # XXX: here is the code which need to be executed once # at the beginning of the request hanling diff -r 4f7cb6335a33 -r dabecab10faa src/server/session_iface.py --- a/src/server/session_iface.py Sun Jul 09 22:28:40 2017 +0200 +++ b/src/server/session_iface.py Mon Jul 10 19:10:31 2017 +0200 @@ -35,6 +35,7 @@ self.jid = None self.uuid = unicode(shortuuid.uuid()) self.identities = data_objects.Identities() + self.csrf_token = unicode(shortuuid.uuid()) class ISATGuestSession(Interface):