CVE-2022-23949 (published 2022-09-21T19:15Z)

In Keylime before 6.3.0, unsanitized UUIDs can be passed by a rogue agent and can lead to log spoofing on the verifier and registrar.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)

Weakness information:
cwe_id: CWE-290
cwe_name: Authentication Bypass by Spoofing
description: This attack-focused weakness is caused by incorrectly implemented authentication schemes that are subject to spoofing attacks.
extended_description: 
            
            
         
         

Fix information:
repo_url: https://github.com/keylime/keylime
commit_details: repo_url: https://github.com/keylime/keylime
author: Alberto Planas
author_date: 2022-01-17 15:04:26+01:00
msg: validators: add uuid and agent_id validators

Add valid_uuid and valid_agent_id validators, and tests for both.

Signed-off-by: Alberto Planas <aplanas@suse.com>
repository: repo_url: https://github.com/keylime/keylime
repo_name: keylime/keylime
description: A CNCF Project to Bootstrap & Maintain Trust on the Edge / Cloud and IoT
repo_language: Python
owner: keylime
file_changes: filename: validators.py
change_type: ModificationType.MODIFY
diff: @@ -32,3 +32,29 @@ def valid_hex(value):
     except Exception:
         return False
     return True
+
+
+def valid_uuid(uuid: str) -> bool:
+    """Check if the string is a valid UUID."""
+    valid = False
+    try:
+        valid = bool(
+            re.fullmatch(
+                r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}",
+                uuid,
+                re.I,
+            )
+        )
+    except Exception:
+        pass
+    return valid
+
+
+def valid_agent_id(agent_id: str) -> bool:
+    """Check if agent_id is valid."""
+    valid = False
+    try:
+        valid = bool(re.fullmatch(r"[\w.-]+", agent_id))
+    except Exception:
+        pass
+    return valid

programming_language: Python
filename: test_validators.py
change_type: ModificationType.MODIFY
diff: @@ -78,5 +78,49 @@ class TestValidHex(unittest.TestCase):
         self.assertFalse(validators.valid_hex("123xyz"))
 
 
+class TestValidUUID(unittest.TestCase):
+    """Tests for valid_uuid."""
+
+    def test_none(self):
+        """Check that None is not valid."""
+        self.assertFalse(validators.valid_uuid(None))
+
+    def test_empty(self):
+        """Check that the empty string is not valid."""
+        self.assertFalse(validators.valid_uuid(""))
+
+    def test_valid(self):
+        """Check a valid UUID that mix upper and lower case."""
+        self.assertTrue(validators.valid_uuid("74a93e15-da24-4ff1-ABC0-55beed02a16a"))
+
+    def test_invalid(self):
+        """Check an invalid UUID that mix upper and lower case."""
+        self.assertFalse(validators.valid_uuid("some text"))
+
+
+class TestValidAgentID(unittest.TestCase):
+    """Tests for valid_agent_id."""
+
+    def test_none(self):
+        """Check that None is not valid."""
+        self.assertFalse(validators.valid_agent_id(None))
+
+    def test_empty(self):
+        """Check that the empty string is not valid."""
+        self.assertFalse(validators.valid_agent_id(""))
+
+    def test_valid_uuid(self):
+        """Check a valid UUID that mix upper and lower case."""
+        self.assertTrue(validators.valid_agent_id("74a93e15-da24-4ff1-ABC0-55beed02a16a"))
+
+    def test_valid_hostname(self):
+        """Check a valid hostname that mix upper and lower case."""
+        self.assertTrue(validators.valid_agent_id("my-Hostname.example.com"))
+
+    def test_invalid(self):
+        """Check an invalid user ID with non valid characters."""
+        self.assertFalse(validators.valid_agent_id("rm -fr *"))
+
+
 if __name__ == "__main__":
     unittest.main()

programming_language: Python
repo_url: https://github.com/keylime/keylime
commit_details: repo_url: https://github.com/keylime/keylime
author: Alberto Planas
author_date: 2022-01-17 14:33:20+01:00
msg: validators: create validators module

Move from the config module some validators, and create the validators
module.  Add tests for them.

Signed-off-by: Alberto Planas <aplanas@suse.com>
repository: repo_url: https://github.com/keylime/keylime
repo_name: keylime/keylime
description: A CNCF Project to Bootstrap & Maintain Trust on the Edge / Cloud and IoT
repo_language: Python
owner: keylime
file_changes: filename: cloud_verifier_common.py
change_type: ModificationType.MODIFY
diff: @@ -16,7 +16,7 @@ from keylime.agentstates import AgentAttestStates
 from keylime.failure import Failure, Component
 from keylime.tpm.tpm_main import tpm
 from keylime.tpm.tpm_abstract import TPM_Utilities
-from keylime.common import algorithms
+from keylime.common import algorithms, validators
 from keylime import ima_file_signatures
 
 # setup logging
@@ -300,7 +300,7 @@ def validate_agent_data(agent_data):
     lists = json.loads(agent_data['allowlist'])
 
     # Validate exlude list contains valid regular expressions
-    is_valid, _, err_msg = config.valid_exclude_list(lists.get('exclude'))
+    is_valid, _, err_msg = validators.valid_exclude_list(lists.get('exclude'))
     if not is_valid:
         err_msg += " Exclude list regex is misformatted. Please correct the issue and try again."
 

programming_language: Python
filename: validators.py
change_type: ModificationType.ADD
diff: @@ -0,0 +1,34 @@
+"""Validators module."""
+import re
+
+
+def valid_regex(regex):
+    """Check if string is a valid regular expression."""
+    if regex is None:
+        return True, None, None
+
+    try:
+        compiled_regex = re.compile(regex)
+    except re.error as regex_err:
+        err = "Invalid regex: " + regex_err.msg + "."
+        return False, None, err
+
+    return True, compiled_regex, None
+
+
+def valid_exclude_list(exclude_list):
+    """Check if the list is composed of valid regex."""
+    if not exclude_list:
+        return True, None, None
+
+    combined_regex = "(" + ")|(".join(exclude_list) + ")"
+    return valid_regex(combined_regex)
+
+
+def valid_hex(value):
+    """Check if the string is a valid hex number representation."""
+    try:
+        int(value, 16)
+    except Exception:
+        return False
+    return True

programming_language: Python
filename: config.py
change_type: ModificationType.MODIFY
diff: @@ -5,7 +5,6 @@ Copyright 2017 Massachusetts Institute of Technology.
 import os
 import os.path
 import configparser
-import re
 from typing import Optional
 
 import yaml
@@ -179,37 +178,6 @@ def yaml_to_dict(arry, add_newlines=True, logger=None) -> Optional[dict]:
     return None
 
 
-def valid_exclude_list(exclude_list):
-    if not exclude_list:
-        return True, None, None
-
-    combined_regex = "(" + ")|(".join(exclude_list) + ")"
-    return valid_regex(combined_regex)
-
-
-def valid_regex(regex):
-    if regex is None:
-        return True, None, None
-
-    try:
-        compiled_regex = re.compile(regex)
-    except re.error as regex_err:
-        err = "Invalid regex: " + regex_err.msg + "."
-        return False, None, err
-
-    return True, compiled_regex, None
-
-
-def valid_hex(value: str):
-    if not value.isalnum():
-        return False
-    try:
-        int(value, 16)
-        return True
-    except ValueError:
-        return False
-
-
 if STUB_IMA:
     IMA_ML = '../scripts/ima/ascii_runtime_measurements'
 else:

programming_language: Python
filename: ima.py
change_type: ModificationType.MODIFY
diff: @@ -15,11 +15,11 @@ import functools
 
 from keylime import config
 from keylime import gpg
-from keylime import keylime_logging
 from keylime import ima_ast
-from keylime.agentstates import AgentAttestState
 from keylime import ima_file_signatures
-from keylime.common.algorithms import Hash
+from keylime import keylime_logging
+from keylime.agentstates import AgentAttestState
+from keylime.common import algorithms, validators
 from keylime.failure import Failure, Component
 
 
@@ -223,15 +223,14 @@ def _process_measurement_list(agentAttestState, lines, hash_alg, lists=None, m2w
         allow_list = None
         exclude_list = None
 
-    ima_log_hash_alg = Hash.SHA1
+    ima_log_hash_alg = algorithms.Hash.SHA1
     if allow_list is not None:
         try:
-            ima_log_hash_alg = Hash(allow_list["ima"]["log_hash_alg"])
+            ima_log_hash_alg = algorithms.Hash(allow_list["ima"]["log_hash_alg"])
         except ValueError:
             logger.warning("Specified IMA log hash algorithm %s is not a valid algorithm! Defaulting to SHA1.",
                            allow_list["ima"]["log_hash_alg"])
 
-
     if boot_aggregates and allow_list:
         if 'boot_aggregate' not in allow_list['hashes'] :
             allow_list['hashes']['boot_aggregate'] = []
@@ -240,7 +239,7 @@ def _process_measurement_list(agentAttestState, lines, hash_alg, lists=None, m2w
                 if val not in allow_list['hashes']['boot_aggregate'] :
                     allow_list['hashes']['boot_aggregate'].append(val)
 
-    is_valid, compiled_regex, err_msg = config.valid_exclude_list(exclude_list)
+    is_valid, compiled_regex, err_msg = validators.valid_exclude_list(exclude_list)
     if not is_valid:
         # This should not happen as the exclude list has already been validated
         # by the verifier before acceping it. This is a safety net just in case.
@@ -312,7 +311,7 @@ def _process_measurement_list(agentAttestState, lines, hash_alg, lists=None, m2w
 
 
 def process_measurement_list(agentAttestState, lines, lists=None, m2w=None, pcrval=None, ima_keyrings=None,
-                             boot_aggregates=None, hash_alg=Hash.SHA1):
+                             boot_aggregates=None, hash_alg=algorithms.Hash.SHA1):
     failure = Failure(Component.IMA)
     try:
         running_hash, failure = _process_measurement_list(agentAttestState, lines, hash_alg, lists=lists, m2w=m2w,

programming_language: Python
filename: keylime_agent.py
change_type: ModificationType.MODIFY
diff: @@ -43,7 +43,7 @@ from keylime import registrar_client
 from keylime import secure_mount
 from keylime import web_util
 from keylime import api_version as keylime_api_version
-from keylime.common import algorithms
+from keylime.common import algorithms, validators
 from keylime.tpm.tpm_main import tpm
 from keylime.tpm.tpm_abstract import TPM_Utilities
 from keylime.tpm.tpm2_objects import pubkey_from_tpm2b_public
@@ -131,7 +131,7 @@ class Handler(BaseHTTPRequestHandler):
 
             # Sanitization assurance (for tpm.run() tasks below)
             if not (nonce.isalnum() and
-                    (pcrmask is None or config.valid_hex(pcrmask)) and
+                    (pcrmask is None or validators.valid_hex(pcrmask)) and
                     ima_ml_entry.isalnum()):
                 logger.warning('GET quote returning 400 response. parameters should be strictly alphanumeric')
                 web_util.echo_json_response(

programming_language: Python
filename: test_validators.py
change_type: ModificationType.ADD
diff: @@ -0,0 +1,82 @@
+import unittest
+
+from keylime.common import validators
+
+
+class TestValidRegex(unittest.TestCase):
+    """Tests for valid_regex."""
+
+    def test_none(self):
+        """Check that None is a valid regex."""
+        self.assertEqual(validators.valid_regex(None), (True, None, None))
+
+    def test_valid(self):
+        """Check a well formed regex."""
+        value = validators.valid_regex(r"a.*")
+        self.assertTrue(value[0])
+        self.assertEqual(value[1].pattern, r"a.*")
+        self.assertEqual(value[2], None)
+
+    def test_invalid(self):
+        """Check a not valid regex."""
+        value = validators.valid_regex(r"a[")
+        self.assertEqual(
+            value, (False, None, "Invalid regex: unterminated character set.")
+        )
+
+
+class TestValidExcludeList(unittest.TestCase):
+    """Tests for valid_exclude_list."""
+
+    def test_none(self):
+        """Check that the empty list is valid."""
+        self.assertEqual(validators.valid_exclude_list(None), (True, None, None))
+
+    def test_single(self):
+        """Check a single exclude list element."""
+        value = validators.valid_exclude_list([r"a.*"])
+        self.assertTrue(value[0])
+        self.assertEqual(value[1].pattern, r"(a.*)")
+        self.assertEqual(value[2], None)
+
+    def test_multi(self):
+        """Check a multiple elements exclude list."""
+        value = validators.valid_exclude_list([r"a.*", r"b.*"])
+        self.assertTrue(value[0])
+        self.assertEqual(value[1].pattern, r"(a.*)|(b.*)")
+        self.assertEqual(value[2], None)
+
+    def test_invalid(self):
+        """Check an invalid exclude list."""
+        value = validators.valid_exclude_list([r"a["])
+        self.assertEqual(
+            value, (False, None, "Invalid regex: unterminated character set.")
+        )
+
+
+class TestValidHex(unittest.TestCase):
+    """Tests for valid_hex."""
+
+    def test_none(self):
+        """Check that None is not valid."""
+        self.assertFalse(validators.valid_hex(None))
+
+    def test_empty(self):
+        """Check that the empty string is not valid."""
+        self.assertFalse(validators.valid_hex(""))
+
+    def test_valid_lower(self):
+        """Check a valid lower case hexadecimal number."""
+        self.assertTrue(validators.valid_hex("123abc"))
+
+    def test_valid_upper(self):
+        """Check a valid upper case hexadecimal number."""
+        self.assertTrue(validators.valid_hex("123ABC"))
+
+    def test_invalid(self):
+        """Check and invalid hexadecimal number."""
+        self.assertFalse(validators.valid_hex("123xyz"))
+
+
+if __name__ == "__main__":
+    unittest.main()

programming_language: Python
repo_url: https://github.com/keylime/keylime
commit_details: repo_url: https://github.com/keylime/keylime
author: Alberto Planas
author_date: 2022-01-17 16:39:35+01:00
msg: Validate user ID in all public interfaces

The user ID is read from the config file, or from some public REST API.
We should validate that is composed with valid set of chars.

Signed-off-by: Alberto Planas <aplanas@suse.com>
repository: repo_url: https://github.com/keylime/keylime
repo_name: keylime/keylime
description: A CNCF Project to Bootstrap & Maintain Trust on the Edge / Cloud and IoT
repo_language: Python
owner: keylime
file_changes: filename: cloud_verifier_tornado.py
change_type: ModificationType.MODIFY
diff: @@ -18,7 +18,7 @@ from keylime import config
 from keylime import json
 from keylime import registrar_client
 from keylime.agentstates import AgentAttestStates
-from keylime.common import states
+from keylime.common import states, validators
 from keylime.db.verifier_db import VerfierMain
 from keylime.db.verifier_db import VerifierAllowlist
 from keylime.db.keylime_db import DBEngineManager, SessionManager
@@ -264,6 +264,13 @@ class AgentsHandler(BaseHandler):
         agent_id = rest_params["agents"]
 
         if (agent_id is not None) and (agent_id != ''):
+            # If the agent ID is not valid (wrong set of characters),
+            # just do nothing.
+            if not validators.valid_agent_id(agent_id):
+                web_util.echo_json_response(self, 400, "agent_id not not valid")
+                logger.error("GET received an invalid agent ID: %s", agent_id)
+                return
+
             try:
                 agent = session.query(VerfierMain).filter_by(
                     agent_id=agent_id).one_or_none()
@@ -330,6 +337,13 @@ class AgentsHandler(BaseHandler):
             logger.warning('DELETE returning 400 response. uri not supported: %s', self.request.path)
             return
 
+        # If the agent ID is not valid (wrong set of characters), just
+        # do nothing.
+        if not validators.valid_agent_id(agent_id):
+            web_util.echo_json_response(self, 400, "agent_id not not valid")
+            logger.error("DELETE received an invalid agent ID: %s", agent_id)
+            return
+
         try:
             agent = session.query(VerfierMain).filter_by(
                 agent_id=agent_id).first()
@@ -396,6 +410,13 @@ class AgentsHandler(BaseHandler):
             agent_id = rest_params["agents"]
 
             if agent_id is not None:
+                # If the agent ID is not valid (wrong set of
+                # characters), just do nothing.
+                if not validators.valid_agent_id(agent_id):
+                    web_util.echo_json_response(self, 400, "agent_id not not valid")
+                    logger.error("POST received an invalid agent ID: %s", agent_id)
+                    return
+
                 content_length = len(self.request.body)
                 if content_length == 0:
                     web_util.echo_json_response(
@@ -534,6 +555,14 @@ class AgentsHandler(BaseHandler):
             if agent_id is None:
                 web_util.echo_json_response(self, 400, "uri not supported")
                 logger.warning("PUT returning 400 response. uri not supported")
+
+            # If the agent ID is not valid (wrong set of characters),
+            # just do nothing.
+            if not validators.valid_agent_id(agent_id):
+                web_util.echo_json_response(self, 400, "agent_id not not valid")
+                logger.error("PUT received an invalid agent ID: %s", agent_id)
+                return
+
             try:
                 verifier_id = config.get('cloud_verifier', 'cloudverifier_id', fallback=cloud_verifier_common.DEFAULT_VERIFIER_ID)
                 agent = session.query(VerfierMain).filter_by(

programming_language: Python
filename: keylime_agent.py
change_type: ModificationType.MODIFY
diff: @@ -676,6 +676,11 @@ def main():
         agent_uuid = os.getenv("KEYLIME_AGENT_UUID", None)
         if agent_uuid is None:
             raise RuntimeError("Env variable KEYLIME_AGENT_UUID is empty, but agent_uuid is set to 'environment'")
+    elif not validators.valid_uuid(agent_uuid):
+        raise RuntimeError("The UUID is not valid")
+
+    if not validators.valid_agent_id(agent_uuid):
+        raise RuntimeError("The agent ID set via agent uuid parameter use invalid characters")
 
     if config.STUB_VTPM and config.TPM_CANNED_VALUES is not None:
         # Use canned values for stubbing

programming_language: Python
filename: registrar_common.py
change_type: ModificationType.MODIFY
diff: @@ -17,6 +17,7 @@ from sqlalchemy.orm.exc import NoResultFound
 from cryptography.hazmat.backends import default_backend
 from cryptography.x509 import load_der_x509_certificate
 
+from keylime.common import validators
 from keylime.db.registrar_db import RegistrarMain
 from keylime.db.keylime_db import DBEngineManager, SessionManager
 from keylime import config
@@ -74,6 +75,13 @@ class ProtectedHandler(BaseHTTPRequestHandler, SessionManager):
         agent_id = rest_params["agents"]
 
         if agent_id is not None:
+            # If the agent ID is not valid (wrong set of characters),
+            # just do nothing.
+            if not validators.valid_agent_id(agent_id):
+                web_util.echo_json_response(self, 400, "agent_id not not valid")
+                logger.error("GET received an invalid agent ID: %s", agent_id)
+                return
+
             try:
                 agent = session.query(RegistrarMain).filter_by(
                     agent_id=agent_id).first()
@@ -150,6 +158,13 @@ class ProtectedHandler(BaseHTTPRequestHandler, SessionManager):
         agent_id = rest_params["agents"]
 
         if agent_id is not None:
+            # If the agent ID is not valid (wrong set of characters),
+            # just do nothing.
+            if not validators.valid_agent_id(agent_id):
+                web_util.echo_json_response(self, 400, "agent_id not not valid")
+                logger.error("DELETE received an invalid agent ID: %s", agent_id)
+                return
+
             if session.query(RegistrarMain).filter_by(agent_id=agent_id).delete():
                 # send response
                 try:
@@ -233,6 +248,13 @@ class UnprotectedHandler(BaseHTTPRequestHandler, SessionManager):
             logger.warning('POST agent returning 400 response. agent id not found in uri %s', self.path)
             return
 
+        # If the agent ID is not valid (wrong set of characters), just
+        # do nothing.
+        if not validators.valid_agent_id(agent_id):
+            web_util.echo_json_response(self, 400, "agent id not valid")
+            logger.error("POST received an invalid agent ID: %s", agent_id)
+            return
+
         try:
             content_length = int(self.headers.get('Content-Length', 0))
             if content_length == 0:
@@ -410,6 +432,13 @@ class UnprotectedHandler(BaseHTTPRequestHandler, SessionManager):
             logger.warning('PUT agent returning 400 response. agent id not found in uri %s', self.path)
             return
 
+        # If the agent ID is not valid (wrong set of characters), just
+        # do nothing.
+        if not validators.valid_agent_id(agent_id):
+            web_util.echo_json_response(self, 400, "agent_id not not valid")
+            logger.error("PUT received an invalid agent ID: %s", agent_id)
+            return
+
         try:
             content_length = int(self.headers.get('Content-Length', 0))
             if content_length == 0:

programming_language: Python
filename: tenant.py
change_type: ModificationType.MODIFY
diff: @@ -34,7 +34,7 @@ from keylime import ima
 from keylime import crypto
 from keylime.cmd import user_data_encrypt
 from keylime import ca_util
-from keylime.common import algorithms
+from keylime.common import algorithms, validators
 from keylime import ima_file_signatures
 from keylime import measured_boot
 from keylime import gpg
@@ -1313,6 +1313,8 @@ def main(argv=sys.argv):
         if mytenant.agent_uuid.startswith('-----BEGIN PUBLIC KEY-----'):
             mytenant.agent_uuid = hashlib.sha256(
                 mytenant.agent_uuid).hexdigest()
+        if not validators.valid_agent_id(mytenant.agent_uuid):
+            raise UserError("The agent ID set via agent uuid parameter use invalid characters")
     else:
         logger.warning("Using default UUID d432fbb3-d2f1-4a97-9ef7-75bd81c00000")
         mytenant.agent_uuid = "d432fbb3-d2f1-4a97-9ef7-75bd81c00000"

programming_language: Python
filename: tenant_webapp.py
change_type: ModificationType.MODIFY
diff: @@ -16,7 +16,7 @@ import tornado.ioloop
 import tornado.web
 
 from keylime.requests_client import RequestsClient
-from keylime.common import states
+from keylime.common import validators, states
 from keylime import config
 from keylime import json
 from keylime import keylime_logging
@@ -395,6 +395,13 @@ class AgentsHandler(BaseHandler):
 
         agent_id = rest_params["agents"]
         if agent_id is not None:
+            # If the agent ID is not valid (wrong set of characters),
+            # just do nothing.
+            if not validators.valid_agent_id(agent_id):
+                web_util.echo_json_response(self, 400, "agent_id not not valid")
+                logger.error("GET received an invalid agent ID: %s", agent_id)
+                return
+
             # Handle request for specific agent data separately
             agents = await self.get_agent_state(agent_id)
             agents["id"] = agent_id
@@ -455,6 +462,12 @@ class AgentsHandler(BaseHandler):
             return
 
         agent_id = rest_params["agents"]
+        # If the agent ID is not valid (wrong set of characters), just
+        # do nothing.
+        if not validators.valid_agent_id(agent_id):
+            web_util.echo_json_response(self, 400, "agent_id not not valid")
+            logger.error("DELETE received an invalid agent ID: %s", agent_id)
+            return
 
         # let Tenant do dirty work of deleting agent
         mytenant = tenant.Tenant()
@@ -482,6 +495,12 @@ class AgentsHandler(BaseHandler):
             return
 
         agent_id = rest_params["agents"]
+        # If the agent ID is not valid (wrong set of characters), just
+        # do nothing.
+        if not validators.valid_agent_id(agent_id):
+            web_util.echo_json_response(self, 400, "agent_id not not valid")
+            logger.error("POST received an invalid agent ID: %s", agent_id)
+            return
 
         # Parse payload files (base64 data-uri)
         if self.get_argument("ptype", Agent_Init_Types.FILE, True) == Agent_Init_Types.FILE:
@@ -593,6 +612,12 @@ class AgentsHandler(BaseHandler):
             return
 
         agent_id = rest_params["agents"]
+        # If the agent ID is not valid (wrong set of characters), just
+        # do nothing.
+        if not validators.valid_agent_id(agent_id):
+            web_util.echo_json_response(self, 400, "agent_id not not valid")
+            logger.error("PUT received an invalid agent ID: %s", agent_id)
+            return
 
         # let Tenant do dirty work of reactivating agent
         mytenant = tenant.Tenant()

programming_language: Python