From 9573bdca55ddc5488066d3af525e41ed1d872ea6 Mon Sep 17 00:00:00 2001 From: NavaL Date: Wed, 24 Feb 2016 16:33:20 +0100 Subject: Backend and frontend protection against csrf attacks: - root resources changes the csrf token cookie everytime it is loaded, in particular during the intestitial load during login - it will also add that cookie on single user mode - initialize will still load all resources - but they you cant access them if the csrf token do not match - all ajax calls needs to add the token to the header - non ajax get requests do not need xsrf token validation - non ajax post will have to send the token in as a form input or in the content Issue #612 --- service/pixelated/resources/__init__.py | 4 ++ service/pixelated/resources/login_resource.py | 3 +- service/pixelated/resources/root_resource.py | 69 ++++++++++++++++++++------- 3 files changed, 57 insertions(+), 19 deletions(-) (limited to 'service/pixelated/resources') diff --git a/service/pixelated/resources/__init__.py b/service/pixelated/resources/__init__.py index 14ecac86..469c8bc8 100644 --- a/service/pixelated/resources/__init__.py +++ b/service/pixelated/resources/__init__.py @@ -99,3 +99,7 @@ class UnAuthorizedResource(Resource): def render_GET(self, request): request.setResponseCode(UNAUTHORIZED) return "Unauthorized!" + + def render_POST(self, request): + request.setResponseCode(UNAUTHORIZED) + return "Unauthorized!" diff --git a/service/pixelated/resources/login_resource.py b/service/pixelated/resources/login_resource.py index f1d9c1e3..aca266cf 100644 --- a/service/pixelated/resources/login_resource.py +++ b/service/pixelated/resources/login_resource.py @@ -20,14 +20,13 @@ from xml.sax import SAXParseException from twisted.cred import credentials from twisted.internet import defer +from twisted.python.filepath import FilePath from twisted.web import util -from twisted.web.error import FlattenerError from twisted.web.http import UNAUTHORIZED, OK from twisted.web.resource import IResource, NoResource from twisted.web.server import NOT_DONE_YET from twisted.web.static import File from twisted.web.template import Element, XMLFile, renderElement, renderer -from twisted.python.filepath import FilePath from pixelated.adapter.welcome_mail import add_welcome_mail from pixelated.resources import BaseResource, UnAuthorizedResource, IPixelatedSession diff --git a/service/pixelated/resources/root_resource.py b/service/pixelated/resources/root_resource.py index 6e619951..2c32ab0c 100644 --- a/service/pixelated/resources/root_resource.py +++ b/service/pixelated/resources/root_resource.py @@ -13,11 +13,12 @@ # # You should have received a copy of the GNU Affero General Public License # along with Pixelated. If not, see . - +import hashlib +import json import os from string import Template -from pixelated.resources import BaseResource +from pixelated.resources import BaseResource, UnAuthorizedResource from pixelated.resources.attachments_resource import AttachmentsResource from pixelated.resources.contacts_resource import ContactsResource from pixelated.resources.features_resource import FeaturesResource @@ -29,22 +30,22 @@ from pixelated.resources.mail_resource import MailResource from pixelated.resources.mails_resource import MailsResource from pixelated.resources.tags_resource import TagsResource from pixelated.resources.keys_resource import KeysResource -from twisted.web.resource import Resource from twisted.web.static import File +CSRF_TOKEN_LENGTH = 32 MODE_STARTUP = 1 MODE_RUNNING = 2 class RootResource(BaseResource): - def __init__(self, services_factory): BaseResource.__init__(self, services_factory) self._startup_assets_folder = self._get_startup_folder() self._static_folder = self._get_static_folder() self._html_template = open(os.path.join(self._static_folder, 'index.html')).read() self._services_factory = services_factory + self.child_resources = ChildResourcesMap() self._startup_mode() def _startup_mode(self): @@ -54,21 +55,39 @@ class RootResource(BaseResource): def getChild(self, path, request): if path == '': return self - return Resource.getChild(self, path, request) + if self._is_xsrf_valid(request): + return self.child_resources.get(path) + return UnAuthorizedResource() + + def _is_xsrf_valid(self, request): + xsrf_token = request.getCookie('XSRF-TOKEN') + + ajax_request = (request.getHeader('x-requested-with') == 'XMLHttpRequest') + if ajax_request: + xsrf_header = request.getHeader('x-xsrf-token') + return xsrf_header and xsrf_header == xsrf_token + + get_request = (request.method == 'GET') + if get_request: + return True + + csrf_input = request.args.get('csrftoken', [None])[0] or json.loads(request.content.read()).get('csrftoken', [None])[0] + return csrf_input and csrf_input == xsrf_token def initialize(self, portal=None, disclaimer_banner=None): - self.putChild('assets', File(self._static_folder)) - self.putChild('keys', KeysResource(self._services_factory)) - self.putChild(AttachmentsResource.BASE_URL, AttachmentsResource(self._services_factory)) - self.putChild('contacts', ContactsResource(self._services_factory)) - self.putChild('features', FeaturesResource(portal)) - self.putChild('tags', TagsResource(self._services_factory)) - self.putChild('mails', MailsResource(self._services_factory)) - self.putChild('mail', MailResource(self._services_factory)) - self.putChild('feedback', FeedbackResource(self._services_factory)) - self.putChild('user-settings', UserSettingsResource(self._services_factory)) - self.putChild(LoginResource.BASE_URL, LoginResource(self._services_factory, portal, disclaimer_banner=disclaimer_banner)) - self.putChild(LogoutResource.BASE_URL, LogoutResource(self._services_factory)) + self.child_resources.add('assets', File(self._static_folder)) + self.child_resources.add('keys', KeysResource(self._services_factory)) + self.child_resources.add(AttachmentsResource.BASE_URL, AttachmentsResource(self._services_factory)) + self.child_resources.add('contacts', ContactsResource(self._services_factory)) + self.child_resources.add('features', FeaturesResource(portal)) + self.child_resources.add('tags', TagsResource(self._services_factory)) + self.child_resources.add('mails', MailsResource(self._services_factory)) + self.child_resources.add('mail', MailResource(self._services_factory)) + self.child_resources.add('feedback', FeedbackResource(self._services_factory)) + self.child_resources.add('user-settings', UserSettingsResource(self._services_factory)) + self.child_resources.add(LoginResource.BASE_URL, + LoginResource(self._services_factory, portal, disclaimer_banner=disclaimer_banner)) + self.child_resources.add(LogoutResource.BASE_URL, LogoutResource(self._services_factory)) self._mode = MODE_RUNNING @@ -89,10 +108,26 @@ class RootResource(BaseResource): def _is_starting(self): return self._mode == MODE_STARTUP + def _add_csrf_cookie(self, request): + csrf_token = hashlib.sha256(os.urandom(CSRF_TOKEN_LENGTH)).hexdigest() + request.addCookie('XSRF-TOKEN', csrf_token) + def render_GET(self, request): + self._add_csrf_cookie(request) if self._is_starting(): return open(os.path.join(self._startup_assets_folder, 'Interstitial.html')).read() else: account_email = self.mail_service(request).account_email response = Template(self._html_template).safe_substitute(account_email=account_email) return str(response) + + +class ChildResourcesMap(object): + def __init__(self): + self._registry = {} + + def add(self, path, resource): + self._registry[path] = resource + + def get(self, path): + return self._registry.get(path) -- cgit v1.2.3