From 1d65c1bca06c0c0cbaae004fd18e2e361713dcd6 Mon Sep 17 00:00:00 2001
From: Lukas Braun <lukas.braun@fau.de>
Date: Wed, 25 Jul 2018 19:01:54 +0200
Subject: [PATCH] Check Origin header before accept/edit/delete

For some protection against CSRF attacks, check if the Origin header is
the weburl we are listening on before handling POSTs to
moderation/{edit,accept}/<doc>.
If the request does not contain an Origin header (which should never be
the case for POST requests in modern browsers), a warning is printed and
the request handled anyway.

It is probably a good idea to implement some CSRF token mechanism to
authenticate requests as well, I'm not sure how robust this Origin
checking stuff really is.
---
 util/web.py  | 11 ++++++++++-
 web/views.py |  6 +++++-
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/util/web.py b/util/web.py
index db5fbac..b051c9a 100644
--- a/util/web.py
+++ b/util/web.py
@@ -6,8 +6,9 @@ from jinja2.exceptions import TemplateNotFound
 
 from werkzeug import Local, LocalManager, Response
 from werkzeug.routing import Map, Rule
-from werkzeug.exceptions import BadRequest
+from werkzeug.exceptions import BadRequest, Forbidden
 
+import sys
 import os
 import netaddr
 
@@ -117,6 +118,14 @@ def remote_is_internal_network(request):
     return False
 
 
+def same_origin_only(request):
+    try:
+        if request.headers['Origin'] != config.weburl:
+            raise Forbidden(description="Same-Origin check failed")
+    except KeyError:
+        sys.stderr.write("WARNING: No Origin header in request for protected endpoint!\n");
+
+
 available_themes = set([it[1] for it in config.themes_by_url])
 available_themes.add(config.default_theme)
 jinja_env = Environment(loader=ThemeSupportTemplateLoader(available_themes), cache_size=0)
diff --git a/web/views.py b/web/views.py
index 3743283..35c2b05 100644
--- a/web/views.py
+++ b/web/views.py
@@ -23,7 +23,7 @@ import datetime
 
 from util.web import expose, render_template, profify, completion_hints, \
     update_meta_from_request, theme_for_url, local, \
-    remote_is_internal_network
+    remote_is_internal_network, same_origin_only
 from util.readMetaData import readMeta
 from util.general import canonicalize_title, trim_pdf, empty_metadata
 from util.univis import get_univis_metadata
@@ -243,6 +243,8 @@ def upload(request):
 def edit_submit(request, document):
     failed = False
     if request.method == 'POST':
+        same_origin_only(request)
+
         if document in public_pdf_db:
             entry = public_pdf_db[document]
 
@@ -310,6 +312,8 @@ def moderate(request, failed=None):
 @expose('/moderation/accept/<document>/')
 def edit_article(request, document):
     if request.method == 'POST':
+        same_origin_only(request)
+
         if document in moderation_queue_db:
             entry = moderation_queue_db[document]
             result = update_meta_from_request(entry, request.form)
-- 
GitLab