CVE-2022-3174: Missing Encryption of Sensitive Data (CWE-311)
value: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute in GitHub repository ikus060/rdiffweb prior to 2.4.2.
CWE description: The product does not encrypt sensitive or critical information before storage or transmission.
CVSS v3 base score: 7.5
Published: 2022-09-13T10:15Z
Repository: https://github.com/ikus060/rdiffweb
Language: Python
Files changed: README.md, rdiffweb/controller/tests/test_secure_headers.py, rdiffweb/rdw_app.py, rdiffweb/tools/secure_headers.py
Commit date: 2022-09-12 10:55:40 -0400
Commit message: Use 'Secure' Attribute with Sensitive Cookie in HTTPS Session #209

Diff: diff --git a/README.md b/README.md
index 90c4b6f4..48c87bdc 100644
--- a/README.md
+++ b/README.md
@@ -105,13 +105,21 @@ Bug reports should be reported on the Rdiffweb Gitlab at https://gitlab.com/ikus
 
 Professional support for Rdiffweb is available by contacting [IKUS Soft](https://www.ikus-soft.com/en/support/#form).
 
 # Changelog
 
+## 2.4.2 (2022-09-08)
+
+This releases include a security fix. If you are using an earlier version, you should upgrade to this release immediately.
+
+* Use 'Secure' Attribute with Sensitive Cookie in HTTPS Session. [CVE-2022-3174](https://nvd.nist.gov/vuln/detail/CVE-2022-3174) #209
+
 ## 2.4.1 (2022-09-08)
 
-* Add Clickjacking Defense
+This releases include a security fix. If you are using an earlier version, you should upgrade to this release immediately.
+
+* Add Clickjacking Defense [CVE-2022-3167](https://nvd.nist.gov/vuln/detail/CVE-2022-3167)
 * Drop Ubuntu Hirsute & Impish (End-of-life)
 
 ## 2.4.0 (2022-06-21)
 
 This new release brings a lot of improvement since the last version, multiple bug fixes
diff --git a/rdiffweb/controller/tests/test_csrf.py b/rdiffweb/controller/tests/test_secure_headers.py
similarity index 79%
rename from rdiffweb/controller/tests/test_csrf.py
rename to rdiffweb/controller/tests/test_secure_headers.py
index af58d0c2..58063fe3 100644
--- a/rdiffweb/controller/tests/test_csrf.py
+++ b/rdiffweb/controller/tests/test_secure_headers.py
@@ -20,30 +20,46 @@ Created on Oct 20, 2021
 @author: Patrik Dufresne
 """
 import rdiffweb.test
 
 
-class CsrfTest(rdiffweb.test.WebCase):
+class SecureHeadersTest(rdiffweb.test.WebCase):
 
     login = True
 
-    def test_samesite_lax(self):
+    def test_cookie_samesite_lax(self):
         # Given a request made to rdiffweb
         # When receiving the response
         self.getPage('/')
         # Then the header contains Set-Cookie with SameSite=Lax
         cookie = self.assertHeader('Set-Cookie')
         self.assertIn('SameSite=Lax', cookie)
 
-    def test_samesite_lax_without_session(self):
+    def test_cookie_samesite_lax_without_session(self):
         # Given not a client sending no cookie
         self.cookies = None
         # When a query is made to a static path (without session)
         self.getPage('/static/blue.css')
         # Then Set-Cookie is not defined.
         self.assertNoHeader('Set-Cookie')
 
+    def test_cookie_with_https(self):
+        # Given an https request made to rdiffweb
+        self.getPage('/', headers=[('X-Forwarded-Proto', 'https')])
+        # When receiving the response
+        # Then the header contains Set-Cookie with Secure
+        cookie = self.assertHeader('Set-Cookie')
+        self.assertIn('Secure', cookie)
+
+    def test_cookie_with_http(self):
+        # Given an https request made to rdiffweb
+        self.getPage('/')
+        # When receiving the response
+        # Then the header contains Set-Cookie with Secure
+        cookie = self.assertHeader('Set-Cookie')
+        self.assertNotIn('Secure', cookie)
+
     def test_get_with_wrong_origin(self):
         # Given a GET request made to rdiffweb
         # When the request is made using a different origin
         self.getPage('/', headers=[('Origin', 'http://www.examples.com')])
         # Then the response status it 200 OK.
diff --git a/rdiffweb/rdw_app.py b/rdiffweb/rdw_app.py
index f2ccacc8..317b90ff 100644
--- a/rdiffweb/rdw_app.py
+++ b/rdiffweb/rdw_app.py
@@ -36,11 +36,11 @@ import rdiffweb.tools.auth_form
 import rdiffweb.tools.currentuser
 import rdiffweb.tools.errors
 import rdiffweb.tools.i18n
 import rdiffweb.tools.proxy
 import rdiffweb.tools.ratelimit
-import rdiffweb.tools.security
+import rdiffweb.tools.secure_headers
 from rdiffweb.controller import Controller
 from rdiffweb.controller.api import ApiPage
 from rdiffweb.controller.dispatch import static  # noqa
 from rdiffweb.controller.page_admin import AdminPage
 from rdiffweb.controller.page_browse import BrowsePage
@@ -61,10 +61,11 @@ from rdiffweb.core.store import Store
 # Define the logger
 logger = logging.getLogger(__name__)
 
 
 @cherrypy.tools.proxy()
+@cherrypy.tools.secure_headers()
 class Root(LocationsPage):
     def __init__(self):
         self.login = LoginPage()
         self.logout = LogoutPage()
         self.browse = BrowsePage()
@@ -168,11 +169,10 @@ class RdiffwebApp(Application):
                 'tools.auth_basic.realm': 'rdiffweb',
                 'tools.auth_basic.checkpassword': self._checkpassword,
                 'tools.auth_form.on': True,
                 'tools.currentuser.on': True,
                 'tools.currentuser.userobj': lambda username: self.store.get_user(username),
-                'tools.csrf.on': True,
                 'tools.i18n.on': True,
                 'tools.i18n.default': 'en_US',
                 'tools.i18n.mo_dir': pkg_resources.resource_filename('rdiffweb', 'locales'),  # @UndefinedVariable
                 'tools.i18n.domain': 'messages',
                 'tools.encode.on': True,
diff --git a/rdiffweb/tools/security.py b/rdiffweb/tools/secure_headers.py
similarity index 81%
rename from rdiffweb/tools/security.py
rename to rdiffweb/tools/secure_headers.py
index 6fe62659..202fc26b 100644
--- a/rdiffweb/tools/security.py
+++ b/rdiffweb/tools/secure_headers.py
@@ -30,25 +30,26 @@ logger = logging.getLogger(__name__)
 #
 if not http.cookies.Morsel().isReservedKey("samesite"):
     http.cookies.Morsel._reserved['samesite'] = 'SameSite'
 
 
-class CsrfAuth(HandlerTool):
+class SecureHeaders(HandlerTool):
     """
     This tool provide CSRF mitigation.
 
     * Define X-Frame-Options = DENY
     * Define Cookies SameSite=Lax
+    * Define Cookies Secure when https is detected
     * Validate `Origin` and `Referer` on POST, PUT, PATCH, DELETE
 
     Ref.:
     https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
     https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html
     """
 
     def __init__(self):
-        HandlerTool.__init__(self, self.run, name='csrf')
+        HandlerTool.__init__(self, self.run, name='secure_headers')
         # Make sure to run before authform (priority 71)
         self._priority = 71
 
     def _setup(self):
         cherrypy.request.hooks.attach('before_finalize', self._set_headers)
@@ -56,21 +57,27 @@ class CsrfAuth(HandlerTool):
 
     def _set_headers(self):
         response = cherrypy.serving.response
         # Define X-Frame-Options to avoid Clickjacking
         response.headers['X-Frame-Options'] = 'DENY'
-        # Awaiting bug fix in cherrypy
-        # https://github.com/cherrypy/cherrypy/issues/1767
-        # Force SameSite to Lax
+
+        # Enforce security on cookies
         cookie = response.cookie.get('session_id', None)
         if cookie:
+            # Awaiting bug fix in cherrypy
+            # https://github.com/cherrypy/cherrypy/issues/1767
+            # Force SameSite to Lax
             cookie['samesite'] = 'Lax'
+            # Check if https is enabled
+            https = cherrypy.request.base.startswith('https')
+            if https:
+                cookie['secure'] = 1
 
     def run(self):
         if cherrypy.request.method in ['POST', 'PUT', 'PATCH', 'DELETE']:
             # Check if Origin matches our target.
             origin = cherrypy.request.headers.get('Origin', None)
             if origin and not origin.startswith(cherrypy.request.base):
                 raise cherrypy.HTTPError(403, 'Unexpected Origin header')
 
 
-cherrypy.tools.csrf = CsrfAuth()
+cherrypy.tools.secure_headers = SecureHeaders()

Vulnerable code: * Add Clickjacking Defense
class CsrfTest(rdiffweb.test.WebCase):
    def test_samesite_lax(self):
    def test_samesite_lax_without_session(self):
import rdiffweb.tools.security
                'tools.csrf.on': True,
class CsrfAuth(HandlerTool):
        HandlerTool.__init__(self, self.run, name='csrf')
        # Awaiting bug fix in cherrypy
        # https://github.com/cherrypy/cherrypy/issues/1767
        # Force SameSite to Lax
cherrypy.tools.csrf = CsrfAuth()
Fixed code: ## 2.4.2 (2022-09-08)

This releases include a security fix. If you are using an earlier version, you should upgrade to this release immediately.

* Use 'Secure' Attribute with Sensitive Cookie in HTTPS Session. [CVE-2022-3174](https://nvd.nist.gov/vuln/detail/CVE-2022-3174) #209

This releases include a security fix. If you are using an earlier version, you should upgrade to this release immediately.

* Add Clickjacking Defense [CVE-2022-3167](https://nvd.nist.gov/vuln/detail/CVE-2022-3167)
class SecureHeadersTest(rdiffweb.test.WebCase):
    def test_cookie_samesite_lax(self):
    def test_cookie_samesite_lax_without_session(self):
    def test_cookie_with_https(self):
        # Given an https request made to rdiffweb
        self.getPage('/', headers=[('X-Forwarded-Proto', 'https')])
        # When receiving the response
        # Then the header contains Set-Cookie with Secure
        cookie = self.assertHeader('Set-Cookie')
        self.assertIn('Secure', cookie)

    def test_cookie_with_http(self):
        # Given an https request made to rdiffweb
        self.getPage('/')
        # When receiving the response
        # Then the header contains Set-Cookie with Secure
        cookie = self.assertHeader('Set-Cookie')
        self.assertNotIn('Secure', cookie)

import rdiffweb.tools.secure_headers
@cherrypy.tools.secure_headers()
class SecureHeaders(HandlerTool):
    * Define Cookies Secure when https is detected
        HandlerTool.__init__(self, self.run, name='secure_headers')

        # Enforce security on cookies
            # Awaiting bug fix in cherrypy
            # https://github.com/cherrypy/cherrypy/issues/1767
            # Force SameSite to Lax
            # Check if https is enabled
            https = cherrypy.request.base.startswith('https')
            if https:
                cookie['secure'] = 1
cherrypy.tools.secure_headers = SecureHeaders()