Controlling user input in dynamic code evaluation is crucial to prevent security vulnerabilities such as code injection. When you need to evaluate user-provided code dynamically, consider the following best practices to enhance security:
-
Avoid
eval()if Possible:- Whenever possible, try to avoid using
eval()or similar functions that execute arbitrary code. There are often safer alternatives, such as using predefined functions, parsing input with libraries likeast, or employing other domain-specific solutions.
- Whenever possible, try to avoid using
-
Use Safe Alternatives:
- If you need to evaluate expressions, consider using safer alternatives like
ast.literal_eval()for evaluating literals or other parsers that are designed to handle specific input formats.
import ast user_input = "42" result = ast.literal_eval(user_input) - If you need to evaluate expressions, consider using safer alternatives like
-
Whitelisting Allowed Functions:
- If you must allow dynamic code execution, consider using a whitelist of allowed functions or operations. Only permit specific functions that are safe for your application context.
allowed_functions = {'math.sqrt', 'custom_function'} def execute_user_code(user_input): if user_input in allowed_functions: result = eval(user_input) return result else: raise ValueError("Invalid function.") -
Restricting Imports:
- Limit the ability to import certain modules by providing a restricted
globalsdictionary toeval().
restricted_globals = {'__builtins__': None, 'math': math} user_input = "math.sqrt(25)" result = eval(user_input, restricted_globals) - Limit the ability to import certain modules by providing a restricted
-
Implement Code Reviews:
- If possible, have code reviews for any dynamically evaluated code. This can help catch potential security issues and ensure that the code adheres to established guidelines.
-
Validate and Sanitize Input:
- Validate and sanitize user input before allowing it to be evaluated. Ensure that the input adheres to the expected format and doesn’t contain malicious code.
import re def sanitize_input(user_input): if re.match(r'^[a-zA-Z0-9_]+$', user_input): return user_input else: raise ValueError("Invalid input.") -
Use Safe Execution Environments:
- Consider using sandboxing or safe execution environments to run user-provided code. Libraries like
execjsprovide a way to execute JavaScript code in a controlled environment.
- Consider using sandboxing or safe execution environments to run user-provided code. Libraries like
Remember that allowing dynamic code execution introduces security risks, and it’s essential to carefully evaluate the necessity of such functionality in your application. If possible, explore alternative approaches that provide the required flexibility without exposing your application to potential vulnerabilities.
Pickle module security
The pickle module in Python is used for serializing and deserializing Python objects. While it is a convenient way to save and load data structures, it also poses security risks, especially when dealing with untrusted or unauthenticated data. The primary security concerns with the pickle module are related to arbitrary code execution and deserialization attacks.
Here are some security considerations when using the pickle module:
-
Arbitrary Code Execution:
- The
picklemodule can execute arbitrary Python code during deserialization. If an attacker can control the pickled data, they might inject malicious code that gets executed when the data is loaded usingpickle.loads().
import pickle # Unsafe usage malicious_data = b'\x80\x04\x95\x13\x01\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x07my_func\x94\x93\x94.' # Loading data without proper validation loaded_object = pickle.loads(malicious_data)In the above example, if
my_funccontains malicious code, it will be executed when the data is loaded. - The
-
Deserialization Attacks:
- Deserialization attacks involve manipulating serialized data to abuse the application’s logic. Attackers might modify pickled data to exploit vulnerabilities or compromise the application.
import pickle def load_data(serialized_data): # Unsafe deserialization data = pickle.loads(serialized_data) # Process the data return dataProper input validation and data integrity checks should be performed before deserializing data.
-
Avoid Untrusted Pickle Data:
- Do not unpickle data from untrusted or unauthenticated sources. Only unpickle data that comes from trusted sources or has been properly validated.
-
Use Safe Alternatives:
- If possible, consider using safer serialization formats, such as JSON or XML, which do not execute arbitrary code during deserialization. The
jsonmodule in Python is a safer alternative for many use cases.
import json # Safer serialization and deserialization using JSON data = {'key': 'value'} serialized_data = json.dumps(data) loaded_data = json.loads(serialized_data) - If possible, consider using safer serialization formats, such as JSON or XML, which do not execute arbitrary code during deserialization. The
-
Secure the Environment:
- If you must use
pickle, consider running the deserialization code in a restricted environment or sandbox to minimize the impact of potential attacks.
import pickle def load_data(serialized_data): # Safer deserialization in a restricted environment with open('safe_module.py', 'w') as f: f.write('def safe_function(): return "Safe data"') restricted_globals = {'safe_function': None} data = pickle.loads(serialized_data, restricted_globals) # Process the data return data - If you must use
The pickle module is powerful, it should be used cautiously, especially with untrusted data. If possible, prefer safer serialization alternatives that do not execute arbitrary code during deserialization. When using pickle, implement proper input validation, data integrity checks, and consider running the deserialization process in a restricted environment.
This code reads user input, assumes it’s a filename, and then attempts to load the contents of the file using both the pickle and yaml modules. However, both of these approaches can be insecure, and I’ll explain why:
Using pickle (insecure):
import os
import pickle
user_input = input()
with open(user_input, 'rb') as file:
contents = pickle.load(file) # insecure
-
Security Risk:
- Using
pickle.load()can be insecure, especially when loading data from untrusted or unauthenticated sources. Pickle allows for the execution of arbitrary code during deserialization, which can lead to security vulnerabilities.
- Using
-
Potential Exploits:
- If an attacker can control the contents of the file, they may craft a malicious pickle file that executes arbitrary code when loaded, leading to code injection vulnerabilities.
-
Mitigation:
- To make this more secure, you should avoid using
picklefor loading data from untrusted sources. If you must usepickle, consider running the deserialization code in a restricted environment or use safer alternatives.
- To make this more secure, you should avoid using
Using yaml (insecure):
import os
import yaml
user_input = input()
with open(user_input, 'rb') as file:
contents = yaml.load(file) # insecure
-
Security Risk:
- Using
yaml.load()without proper security measures can be insecure. YAML is a powerful data serialization format, and loading untrusted YAML data can lead to arbitrary code execution.
- Using
-
Potential Exploits:
- An attacker might manipulate the YAML file to execute code, leading to security vulnerabilities.
-
Mitigation:
- To make this more secure, avoid loading YAML data from untrusted sources. If you need to load YAML data, consider using the
safe_loadmethod, which restricts the loaded objects to basic Python types.
- To make this more secure, avoid loading YAML data from untrusted sources. If you need to load YAML data, consider using the
Example of a more secure use of yaml:
import os
import yaml
user_input = input()
with open(user_input, 'rb') as file:
contents = yaml.safe_load(file) # safer
- Safer Alternative for YAML:
- Use
yaml.safe_load()instead ofyaml.load()to restrict the loaded objects to basic Python types and avoid executing arbitrary code.
- Use
When loading data from user-provided or untrusted sources, it’s essential to be cautious about the serialization and deserialization methods used. pickle and some deserialization methods in yaml can be insecure if not used carefully, and it’s crucial to implement proper security measures to mitigate potential risks.
A security vulnerability related to the pickle module. Let’s break down the code and explain the security issue:
import os
import pickle
class Vulnerable(object):
def __reduce__(self):
# Note: this will only list files in your directory.
return (os.system, ('ls',))
def serialize_exploit():
shellcode = pickle.dumps(Vulnerable())
return shellcode
def insecure_deserialize(exploit_code):
pickle.loads(exploit_code)
if __name__ == '__main__':
shellcode = serialize_exploit()
print('Obtaining files...')
insecure_deserialize(shellcode)
-
VulnerableClass:- The
Vulnerableclass is defined with a__reduce__method. This method is a special method used by thepicklemodule for customization during object serialization and deserialization.
- The
-
__reduce__Method:- The
__reduce__method is overridden in theVulnerableclass to return a tuple(os.system, ('ls',)). This means that when an instance ofVulnerableis pickled and later unpickled, it will execute theos.system('ls')command, listing the files in the current directory.
- The
-
serialize_exploitFunction:- The
serialize_exploitfunction usespickle.dumpsto serialize an instance of theVulnerableclass. This results in a serialized representation of the object containing the__reduce__method.
- The
-
insecure_deserializeFunction:- The
insecure_deserializefunction usespickle.loadsto deserialize the provided data. Since the serialized data comes from an instance ofVulnerablewith a malicious__reduce__method, it executes theos.system('ls')command upon deserialization.
- The
-
if __name__ == '__main__':Block:- In the main block, the
serialize_exploitfunction is called to obtain the serialized shellcode. The shellcode is then deserialized using theinsecure_deserializefunction, resulting in the execution of theos.system('ls')command.
- In the main block, the
Security Issue:
This code demonstrates a classic example of a security vulnerability known as arbitrary code execution during deserialization. The use of __reduce__ in the Vulnerable class allows an attacker to inject arbitrary code that gets executed when the object is deserialized.
Mitigation:
To prevent such vulnerabilities:
-
Avoid Using
__reduce__: Avoid defining__reduce__methods unless absolutely necessary, especially in classes that might be serialized and deserialized from untrusted sources. -
Sanitize and Validate Input: When using
pickleor similar serialization libraries, ensure that the data being deserialized is from a trusted source. Validate and sanitize input to prevent code injection. -
Use Safe Deserialization Methods: If you need to deserialize data, consider using safer alternatives such as JSON or YAML, or use methods like
ast.literal_eval()oryaml.safe_load(). -
Restrict Permissions: When dealing with serialization or deserialization, restrict permissions appropriately to minimize the impact of potential attacks.
It’s important to be cautious when using serialization and deserialization, especially when dealing with untrusted input. Avoiding potentially dangerous features like __reduce__ and validating user input are essential practices for writing secure code.
To mitigate malicious code execution, we could use methods such as new chroot or sandbox. For example, the following script represent a new chroot, preventing code execution on the root folder itself.
Let’s break down the code and explain the security issue:
import os
import pickle
from contextlib import contextmanager
class ShellSystemChroot(object):
def __reduce__(self):
# This will list contents of the root / folder
return (os.system, ('ls /',))
@contextmanager
def system_chroot():
"""A simple chroot"""
os.chroot('/')
yield
def serialize():
with system_chroot():
shellcode = pickle.dumps(ShellSystemChroot())
return shellcode
def deserialize(exploit_code):
with system_chroot():
pickle.loads(exploit_code)
if __name__ == '__main__':
shellcode = serialize()
print('Obtaining files...')
deserialize(shellcode)
Explanation:
-
ShellSystemChrootClass:- Similar to the previous example, the
ShellSystemChrootclass defines a__reduce__method. This method returns a tuple(os.system, ('ls /',)), indicating that when an instance of this class is pickled and later unpickled, it will execute theos.system('ls /')command, listing the contents of the root/folder.
- Similar to the previous example, the
-
system_chrootContext Manager:- The
system_chrootfunction is a context manager that usesos.chroot('/')to change the root directory to/temporarily. This context manager is used during both serialization and deserialization to create a chroot environment.
- The
-
serializeFunction:- The
serializefunction uses thesystem_chrootcontext manager during the pickling process to create a chroot environment. It then serializes an instance of theShellSystemChrootclass.
- The
-
deserializeFunction:- The
deserializefunction uses thesystem_chrootcontext manager during the unpickling process to create a chroot environment. It then attempts to deserialize the provided exploit code usingpickle.loads.
- The
-
if __name__ == '__main__':Block:- In the main block, the
serializefunction is called to obtain the serialized shellcode. The shellcode is then deserialized using thedeserializefunction, resulting in the execution of theos.system('ls /')command within a chroot environment.
- In the main block, the
Security Issue:
This code exhibits a security vulnerability known as arbitrary code execution during deserialization, similar to the previous example. The use of the __reduce__ method allows an attacker to inject arbitrary code that gets executed when the object is deserialized. In this case, it executes a potentially harmful command (ls /) within a chroot environment.
Mitigation:
To address this issue, you should follow similar mitigation strategies as mentioned in the previous example:
- Avoid defining
__reduce__methods unless absolutely necessary. - Sanitize and validate input to prevent code injection.
- Use safer serialization alternatives if possible.
- Restrict permissions appropriately when dealing with serialization or deserialization.
If you need to use pickle for serialization, consider using a whitelist of allowed classes or functions and carefully validating the input data. Always exercise caution when working with code that can execute arbitrary commands, especially when dealing with untrusted data.