From 9b1a65fe3936c4d3fe237775e54f0249b740f23e Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Jan 10 2024 09:30:38 +0000
Subject: Check the HTTP Referer header on all requests


The referer was only checked in WSGIExecutioner classes:

 - jsonserver
 - KerberosWSGIExecutioner
 - xmlserver
 - jsonserver_kerb

This left /i18n_messages, /session/login_kerberos,
/session/login_x509, /session/login_password,
/session/change_password and /session/sync_token unprotected
against CSRF attacks.

CVE-2023-5455

Signed-off-by: Rob Crittenden <rcritten@redhat.com>

---

diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py
index 4e8a08b..3555014 100644
--- a/ipaserver/rpcserver.py
+++ b/ipaserver/rpcserver.py
@@ -156,6 +156,19 @@ _success_template = """<html>
 </html>"""
 
 class HTTP_Status(plugable.Plugin):
+    def check_referer(self, environ):
+        if "HTTP_REFERER" not in environ:
+            logger.error("Rejecting request with missing Referer")
+            return False
+        if (not environ["HTTP_REFERER"].startswith(
+                "https://%s/ipa" % self.api.env.host)
+                and not self.env.in_tree):
+            logger.error("Rejecting request with bad Referer %s",
+                         environ["HTTP_REFERER"])
+            return False
+        logger.debug("Valid Referer %s", environ["HTTP_REFERER"])
+        return True
+
     def not_found(self, environ, start_response, url, message):
         """
         Return a 404 Not Found error.
@@ -331,9 +344,6 @@ class wsgi_dispatch(Executioner, HTTP_Status):
         self.__apps[key] = app
 
 
-
-
-
 class WSGIExecutioner(Executioner):
     """
     Base class for execution backends with a WSGI application interface.
@@ -897,6 +907,9 @@ class jsonserver_session(jsonserver, KerberosSession):
 
         logger.debug('WSGI jsonserver_session.__call__:')
 
+        if not self.check_referer(environ):
+            return self.bad_request(environ, start_response, 'denied')
+
         # Redirect to login if no Kerberos credentials
         ccache_name = self.get_environ_creds(environ)
         if ccache_name is None:
@@ -949,6 +962,9 @@ class KerberosLogin(Backend, KerberosSession):
     def __call__(self, environ, start_response):
         logger.debug('WSGI KerberosLogin.__call__:')
 
+        if not self.check_referer(environ):
+            return self.bad_request(environ, start_response, 'denied')
+
         # Redirect to login if no Kerberos credentials
         user_ccache_name = self.get_environ_creds(environ)
         if user_ccache_name is None:
@@ -967,6 +983,9 @@ class login_x509(KerberosLogin):
     def __call__(self, environ, start_response):
         logger.debug('WSGI login_x509.__call__:')
 
+        if not self.check_referer(environ):
+            return self.bad_request(environ, start_response, 'denied')
+
         if 'KRB5CCNAME' not in environ:
             return self.unauthorized(
                 environ, start_response, 'KRB5CCNAME not set',
@@ -1015,6 +1034,9 @@ class login_password(Backend, KerberosSession):
 
         logger.debug('WSGI login_password.__call__:')
 
+        if not self.check_referer(environ):
+            return self.bad_request(environ, start_response, 'denied')
+
         # Get the user and password parameters from the request
         content_type = environ.get('CONTENT_TYPE', '').lower()
         if not content_type.startswith('application/x-www-form-urlencoded'):
@@ -1147,6 +1169,9 @@ class change_password(Backend, HTTP_Status):
     def __call__(self, environ, start_response):
         logger.info('WSGI change_password.__call__:')
 
+        if not self.check_referer(environ):
+            return self.bad_request(environ, start_response, 'denied')
+
         # Get the user and password parameters from the request
         content_type = environ.get('CONTENT_TYPE', '').lower()
         if not content_type.startswith('application/x-www-form-urlencoded'):
@@ -1364,6 +1389,9 @@ class xmlserver_session(xmlserver, KerberosSession):
 
         logger.debug('WSGI xmlserver_session.__call__:')
 
+        if not self.check_referer(environ):
+            return self.bad_request(environ, start_response, 'denied')
+
         ccache_name = environ.get('KRB5CCNAME')
 
         # Redirect to /ipa/xml if no Kerberos credentials

