CVE-2022-24798: Improper Removal of Sensitive Information Before Storage or Transfer (CWE-212)
value: Internet Routing Registry daemon version 4 is an IRR database server, processing IRR objects in the RPSL format. IRRd did not always filter password hashes in query responses relating to `mntner` objects and database exports. This may have allowed adversaries to retrieve some of these hashes, perform a brute-force search for the clear-text passphrase, and use these to make unauthorised changes to affected IRR objects. This issue only affected instances that process password hashes, which means it is limited to IRRd instances that serve authoritative databases. IRRd instances operating solely as mirrors of other IRR databases are not affected. This has been fixed in IRRd 4.2.3 and the main branch. Versions in the 4.1.x series never were affected. Users of the 4.2.x series are strongly recommended to upgrade. There are no known workarounds for this issue.
CWE description: The product stores, transfers, or shares a resource that contains sensitive information, but it does not properly remove that information before the product makes the resource available to unauthorized actors.
CVSS v3 base score: 7.5
Published: 2022-03-31T23:15Z
Repository: https://github.com/irrdnet/irrd
Language: Python
Files changed: irrd/server/graphql/resolvers.py, irrd/utils/text.py
Commit date: 2022-03-31 11:43:41 +0200
Commit message: Correct missing password hash removal

Auth has removal was broken in a number of cases for all 4.2 versions:
- All queries that returned the object text for a mntner when
  all hash names (MD5-PW etc.) in the text were lower or mixed case
- Queries for the auth attribute in GraphQL queries
- Queries for the objectText for journal entries in GraphQL queries

Further details in 4.2.3 release notes.

Note that this commit only has the fix and tests or coverage may
fail without the subsequent update to the tests.

Diff: diff --git a/irrd/server/graphql/resolvers.py b/irrd/server/graphql/resolvers.py
index 9fe6bb4..684b907 100644
--- a/irrd/server/graphql/resolvers.py
+++ b/irrd/server/graphql/resolvers.py
@@ -185,10 +185,12 @@ def resolve_rpsl_object_journal(rpsl_object, info: GraphQLResolveInfo):
     for row in database_handler.execute_query(query, refresh_on_error=True):
         response = {snake_to_camel_case(k): v for k, v in row.items()}
         response['operation'] = response['operation'].name
         if response['origin']:
             response['origin'] = response['origin'].name
+        if response['objectText']:
+            response['objectText'] = remove_auth_hashes(response['objectText'])
         yield response
 
 
 def _rpsl_db_query_to_graphql_out(query: RPSLDatabaseQuery, info: GraphQLResolveInfo):
     """
@@ -219,11 +221,11 @@ def _rpsl_db_query_to_graphql_out(query: RPSLDatabaseQuery, info: GraphQLResolve
             graphql_result['asn'] = row['asn_first']
 
         object_type = resolve_rpsl_object_type(row)
         for key, value in row.get('parsed_data', dict()).items():
             if key == 'auth':
-                value = remove_auth_hashes(value)
+                value = [remove_auth_hashes(v) for v in value]
             graphql_type = schema.graphql_types[object_type][key]
             if graphql_type == 'String' and isinstance(value, list):
                 value = '\n'.join(value)
             graphql_result[snake_to_camel_case(key)] = value
         yield graphql_result
diff --git a/irrd/utils/text.py b/irrd/utils/text.py
index c62e97d..12af06b 100644
--- a/irrd/utils/text.py
+++ b/irrd/utils/text.py
@@ -10,11 +10,12 @@ re_remove_last_modified = re.compile(r'^last-modified: [^\n]+\n', flags=re.MULTI
 
 def remove_auth_hashes(input: Optional[str]):
     if not input:
         return input
     # If there are no hashes, skip the RE for performance.
-    if not any([pw_hash in input for pw_hash in PASSWORD_HASHERS_ALL.keys()]):
+    input_lower = input.lower()
+    if not any([pw_hash.lower() in input_lower for pw_hash in PASSWORD_HASHERS_ALL.keys()]):
         return input
     return re_remove_passwords.sub(r'\1 %s  # Filtered for security' % PASSWORD_HASH_DUMMY_VALUE, input)
 
 
 def remove_last_modified(rpsl_text: str):

Vulnerable code:                 value = remove_auth_hashes(value)
    if not any([pw_hash in input for pw_hash in PASSWORD_HASHERS_ALL.keys()]):
Fixed code:         if response['objectText']:
            response['objectText'] = remove_auth_hashes(response['objectText'])
                value = [remove_auth_hashes(v) for v in value]
    input_lower = input.lower()
    if not any([pw_hash.lower() in input_lower for pw_hash in PASSWORD_HASHERS_ALL.keys()]):