CVE-2022-42965
Python-libnmap <=0.7.2: Remote command execution due to argument validation flaw
- CVSS 7.5
- CWE-1333 Inefficient Regular Expression Complexity
- Input Validation and Sanitization
- Remote
An exponential ReDoS (Regular Expression Denial of Service) can be triggered in the snowflake-connector-python PyPI package, when an attacker is able to supply arbitrary input to the undocumented get_file_transfer_type method
- CVSS base score
- 7.5
- Published
- 2022-11-09
- OWASP
- A06 Vulnerable and Outdated Components
- Orthogonal defect classification
- Function
- Code defect classification
- Incorrect Functionality
- Category
- Input Validation and Sanitization
- Subcategory
- Insecure Parsing or Deserialization
- Accessibility scope
- Remote
- Impact
- Denial of Service (DoS)
- Fixed by upgrading
- Yes
Solution
Update snowflake-connector-python to version 2.6.0 or higher.
Vulnerable code sample
import os
import subprocess
def calculate_expression(user_expression):
"""Calculate mathematical expression - VULNERABLE to code injection"""
try:
# VULNERABILITY: Using eval() on user input
# This allows execution of arbitrary Python code
result = eval(user_expression)
return {"result": result, "status": "success"}
except Exception as e:
return {"result": None, "status": "error", "message": str(e)}
def execute_user_script(script_code):
"""Execute user script - EXTREMELY VULNERABLE"""
try:
# VULNERABILITY: Using exec() on user input
# This allows execution of arbitrary Python code
exec(script_code)
return "Script executed successfully"
except Exception as e:
return f"Script execution failed: {e}"
def process_template(template_string, variables):
"""Process template with variables - VULNERABLE"""
try:
# VULNERABILITY: Using eval() for template processing
# User can inject malicious code through template variables
processed = template_string
for var_name, var_value in variables.items():
# Dangerous: eval allows code injection
placeholder = f"{{{var_name}}}"
if placeholder in processed:
# This eval call is the vulnerability
evaluated_value = eval(f"str({var_value})")
processed = processed.replace(placeholder, evaluated_value)
return processed
except Exception as e:
return f"Template processing failed: {e}"
def dynamic_function_call(function_name, *args):
"""Dynamically call function - VULNERABLE"""
try:
# VULNERABILITY: Using eval() to call functions dynamically
# Attacker can call any function or execute arbitrary code
function_call = f"{function_name}({', '.join(repr(arg) for arg in args)})"
# This eval call allows arbitrary function execution
result = eval(function_call)
return result
except Exception as e:
return f"Function call failed: {e}"
# Examples of how these vulnerabilities can be exploited:
# 1. Code injection through calculate_expression:
# calculate_expression("__import__('os').system('rm -rf /')")
# calculate_expression("exec('import subprocess; subprocess.call(["ls", "-la"])')")
# 2. Arbitrary code execution through execute_user_script:
# execute_user_script("import os; os.system('whoami')")
# execute_user_script("open('/etc/passwd', 'r').read()")
# 3. Template injection:
# process_template("Hello {name}!", {"name": "__import__('os').system('id')"})
# 4. Function call injection:
# dynamic_function_call("__import__('os').system", "whoami")
# dynamic_function_call("open", "/etc/passwd", "r")Patched code sample
import ast
import operator
import math
import re
from typing import Any, Dict, List, Optional, Union, Callable
class SecureExpressionEvaluator:
"""Secure expression evaluator that prevents code injection"""
def __init__(self):
# Whitelist of safe operators
self.safe_operators = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.FloorDiv: operator.floordiv,
ast.Mod: operator.mod,
ast.Pow: operator.pow,
ast.USub: operator.neg,
ast.UAdd: operator.pos,
ast.Eq: operator.eq,
ast.NotEq: operator.ne,
ast.Lt: operator.lt,
ast.LtE: operator.le,
ast.Gt: operator.gt,
ast.GtE: operator.ge,
}
# Whitelist of safe functions
self.safe_functions = {
'abs': abs,
'min': min,
'max': max,
'round': round,
'int': int,
'float': float,
'sum': sum,
'len': len,
'sin': math.sin,
'cos': math.cos,
'tan': math.tan,
'sqrt': math.sqrt,
'log': math.log,
'log10': math.log10,
'exp': math.exp,
'ceil': math.ceil,
'floor': math.floor,
'pi': lambda: math.pi,
'e': lambda: math.e,
}
# Safe constants
self.safe_names = {
'pi': math.pi,
'e': math.e,
'True': True,
'False': False,
'None': None,
}
def calculate_expression_secure(self, user_expression: str) -> Dict[str, Any]:
"""Securely calculate mathematical expression without code injection"""
try:
# Input validation
if not user_expression or not isinstance(user_expression, str):
return {"result": None, "status": "error", "message": "Invalid expression"}
# Length limit
if len(user_expression) > 200:
return {"result": None, "status": "error", "message": "Expression too long"}
# Pre-validation: check for dangerous patterns
if self._contains_dangerous_patterns(user_expression):
return {"result": None, "status": "error", "message": "Forbidden pattern detected"}
# Parse expression into AST (Abstract Syntax Tree)
try:
tree = ast.parse(user_expression.strip(), mode='eval')
except SyntaxError as e:
return {"result": None, "status": "error", "message": f"Syntax error: {e}"}
# Validate AST structure for safety
if not self._validate_ast_safety(tree):
return {"result": None, "status": "error", "message": "Unsafe expression"}
# Evaluate AST safely
result = self._evaluate_ast_node(tree.body)
return {"result": result, "status": "success"}
except Exception as e:
return {"result": None, "status": "error", "message": "Evaluation failed"}
def execute_user_script_secure(self, script_code: str) -> str:
"""Secure alternative - no arbitrary code execution allowed"""
# Instead of executing arbitrary code, provide safe alternatives
return "Error: Arbitrary code execution is not allowed for security reasons. Use the expression evaluator for mathematical calculations."
def process_template_secure(self, template_string: str, variables: Dict[str, Any]) -> str:
"""Securely process template without code injection"""
try:
# Input validation
if not template_string or not isinstance(template_string, str):
return "Error: Invalid template"
if not isinstance(variables, dict):
return "Error: Invalid variables"
# Length limits
if len(template_string) > 1000:
return "Error: Template too long"
# Process template safely
processed = template_string
for var_name, var_value in variables.items():
# Validate variable name
if not self._is_safe_variable_name(var_name):
continue
# Safely convert value to string
safe_value = self._safe_string_conversion(var_value)
# Replace placeholder
placeholder = f"{{{var_name}}}"
processed = processed.replace(placeholder, safe_value)
return processed
except Exception:
return "Error: Template processing failed"
def dynamic_function_call_secure(self, function_name: str, *args) -> Any:
"""Securely call whitelisted functions only"""
try:
# Validate function name
if function_name not in self.safe_functions:
return f"Error: Function '{function_name}' is not allowed"
# Get the safe function
safe_function = self.safe_functions[function_name]
# Validate arguments
safe_args = []
for arg in args:
if isinstance(arg, (int, float, str, bool, type(None))):
safe_args.append(arg)
else:
return "Error: Invalid argument type"
# Call function safely
result = safe_function(*safe_args)
return result
except Exception as e:
return f"Error: Function call failed: {type(e).__name__}"
def _contains_dangerous_patterns(self, expression: str) -> bool:
"""Check for dangerous patterns in expression"""
dangerous_patterns = [
r'import\s+',
r'__import__',
r'exec\s*\(',
r'eval\s*\(',
r'open\s*\(',
r'file\s*\(',
r'input\s*\(',
r'raw_input\s*\(',
r'__.*__',
r'getattr',
r'setattr',
r'hasattr',
r'delattr',
r'globals\s*\(',
r'locals\s*\(',
r'vars\s*\(',
r'dir\s*\(',
r'subprocess',
r'os\.',
r'sys\.',
]
for pattern in dangerous_patterns:
if re.search(pattern, expression, re.IGNORECASE):
return True
return False
def _validate_ast_safety(self, tree: ast.AST) -> bool:
"""Validate that AST contains only safe operations"""
for node in ast.walk(tree):
# Only allow specific node types
allowed_node_types = (
ast.Expression, ast.BinOp, ast.UnaryOp, ast.Compare,
ast.Constant, ast.Num, ast.Str, ast.NameConstant,
ast.Name, ast.Call, ast.Load, ast.List, ast.Tuple
)
if not isinstance(node, allowed_node_types):
return False
# Additional validation for specific nodes
if isinstance(node, ast.Name):
if node.id not in self.safe_names:
return False
elif isinstance(node, ast.Call):
if not isinstance(node.func, ast.Name):
return False
if node.func.id not in self.safe_functions:
return False
# No keyword arguments allowed
if node.keywords:
return False
elif isinstance(node, ast.BinOp):
if type(node.op) not in self.safe_operators:
return False
elif isinstance(node, ast.UnaryOp):
if type(node.op) not in self.safe_operators:
return False
return True
def _evaluate_ast_node(self, node: ast.AST) -> Any:
"""Safely evaluate AST node"""
if isinstance(node, ast.Constant):
return node.value
elif isinstance(node, (ast.Num, ast.Str, ast.NameConstant)):
# Legacy Python < 3.8 compatibility
if hasattr(node, 'n'):
return node.n
elif hasattr(node, 's'):
return node.s
else:
return node.value
elif isinstance(node, ast.Name):
if node.id in self.safe_names:
return self.safe_names[node.id]
else:
raise ValueError(f"Unknown name: {node.id}")
elif isinstance(node, ast.BinOp):
left = self._evaluate_ast_node(node.left)
right = self._evaluate_ast_node(node.right)
op_func = self.safe_operators[type(node.op)]
return op_func(left, right)
elif isinstance(node, ast.UnaryOp):
operand = self._evaluate_ast_node(node.operand)
op_func = self.safe_operators[type(node.op)]
return op_func(operand)
elif isinstance(node, ast.Compare):
left = self._evaluate_ast_node(node.left)
if len(node.ops) != 1 or len(node.comparators) != 1:
raise ValueError("Complex comparisons not allowed")
right = self._evaluate_ast_node(node.comparators[0])
op_func = self.safe_operators[type(node.ops[0])]
return op_func(left, right)
elif isinstance(node, ast.Call):
func_name = node.func.id
func = self.safe_functions[func_name]
args = [self._evaluate_ast_node(arg) for arg in node.args]
return func(*args)
elif isinstance(node, (ast.List, ast.Tuple)):
elements = [self._evaluate_ast_node(elem) for elem in node.elts]
return elements if isinstance(node, ast.List) else tuple(elements)
else:
raise ValueError(f"Unsupported AST node type: {type(node)}")
def _is_safe_variable_name(self, name: str) -> bool:
"""Validate variable name for safety"""
if not name or not isinstance(name, str):
return False
# Only allow alphanumeric and underscore
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', name):
return False
# Prevent dangerous names
dangerous_names = {
'__import__', '__builtins__', '__globals__', '__locals__',
'exec', 'eval', 'open', 'file', 'input', 'raw_input'
}
return name not in dangerous_names
def _safe_string_conversion(self, value: Any) -> str:
"""Safely convert value to string"""
if isinstance(value, (str, int, float, bool, type(None))):
return str(value)[:100] # Limit length
else:
return "[complex object]"
# Secure usage examples:
evaluator = SecureExpressionEvaluator()
# Safe mathematical calculations
result1 = evaluator.calculate_expression_secure("2 + 3 * sqrt(16)")
result2 = evaluator.calculate_expression_secure("sin(pi/2) + cos(0)")
# Safe template processing
template_result = evaluator.process_template_secure(
"Hello {name}, your score is {score}!",
{"name": "John", "score": 95}
)
# Safe function calls
func_result = evaluator.dynamic_function_call_secure("max", 10, 20, 5)
# These dangerous inputs are safely rejected:
# evaluator.calculate_expression_secure("__import__('os').system('rm -rf /')") # Rejected
# evaluator.execute_user_script_secure("import os; os.system('whoami')") # RejectedCite this entry
@misc{vaitp:cve202242965,
title = {{Python-libnmap <=0.7.2: Remote command execution due to argument validation flaw}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2022},
note = {VAITP Python Vulnerability Dataset, entry CVE-2022-42965},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2022-42965/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
