CVE-2020-1747
PyYAML < 5.3.1, arbitrary code execution via python/object/new
- CVSS 9.8
- CWE-20 Improper Input Validation
- Input Validation and Sanitization
- Local
A vulnerability was discovered in the PyYAML library in versions before 5.3.1, where it is susceptible to arbitrary code execution when it processes untrusted YAML files through the full_load method or with the FullLoader loader. Applications that use the library to process untrusted input may be vulnerable to this flaw. An attacker could use this flaw to execute arbitrary code on the system by abusing the python/object/new constructor.
- CVSS base score
- 9.8
- Published
- 2020-03-24
- 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
- Local
- Impact
- Arbitrary Code Execution
- Fixed by upgrading
- Yes
Solution
Update PyYAML to version 5.3.1 or higher.
Vulnerable code sample
def calculate_expression(user_input):
"""Calculate mathematical expression - VULNERABLE"""
try:
# Dangerous: eval executes arbitrary Python code
result = eval(user_input)
return result
except Exception as e:
return f"Error: {e}"
def execute_user_code(code_string):
"""Execute user code - EXTREMELY VULNERABLE"""
# Dangerous: exec allows arbitrary code execution
exec(code_string)
# Vulnerable usage examples:
# calculate_expression("__import__('os').system('rm -rf /')")
# execute_user_code("import subprocess; subprocess.call(['rm', '-rf', '/'])")Patched code sample
import ast
import operator
import math
from typing import Union, Dict, Any
class SafeExpressionEvaluator:
"""Safe mathematical expression evaluator"""
# Allowed operators
OPERATORS = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Pow: operator.pow,
ast.Mod: operator.mod,
ast.USub: operator.neg,
ast.UAdd: operator.pos,
}
# Allowed functions
FUNCTIONS = {
'abs': abs,
'min': min,
'max': max,
'round': round,
'sin': math.sin,
'cos': math.cos,
'tan': math.tan,
'sqrt': math.sqrt,
'log': math.log,
'exp': math.exp,
}
def evaluate_expression(self, expression: str) -> Union[float, int, None]:
"""Safely evaluate mathematical expression"""
try:
# Parse expression into AST
tree = ast.parse(expression, mode='eval')
# Validate and evaluate
result = self._eval_node(tree.body)
# Type validation
if isinstance(result, (int, float)):
return result
else:
raise ValueError("Result must be numeric")
except Exception as e:
print(f"Invalid expression: {e}")
return None
def _eval_node(self, node):
"""Recursively evaluate AST node"""
if isinstance(node, ast.Constant):
# Python 3.8+ constant values
if isinstance(node.value, (int, float)):
return node.value
else:
raise ValueError("Only numeric constants allowed")
elif isinstance(node, ast.Num):
# Legacy numeric literals
return node.n
elif isinstance(node, ast.BinOp):
# Binary operations
left = self._eval_node(node.left)
right = self._eval_node(node.right)
op_func = self.OPERATORS.get(type(node.op))
if op_func is None:
raise ValueError(f"Unsupported operator: {type(node.op)}")
return op_func(left, right)
elif isinstance(node, ast.UnaryOp):
# Unary operations
operand = self._eval_node(node.operand)
op_func = self.OPERATORS.get(type(node.op))
if op_func is None:
raise ValueError(f"Unsupported unary operator: {type(node.op)}")
return op_func(operand)
elif isinstance(node, ast.Call):
# Function calls
if not isinstance(node.func, ast.Name):
raise ValueError("Only simple function calls allowed")
func_name = node.func.id
if func_name not in self.FUNCTIONS:
raise ValueError(f"Function not allowed: {func_name}")
# Evaluate arguments
args = [self._eval_node(arg) for arg in node.args]
# Call function
return self.FUNCTIONS[func_name](*args)
else:
raise ValueError(f"Unsupported AST node: {type(node)}")
# Secure usage
evaluator = SafeExpressionEvaluator()
result = evaluator.evaluate_expression("2 + 3 * sqrt(16)") # Returns 14.0
invalid = evaluator.evaluate_expression("__import__('os')") # Returns NoneCite this entry
@misc{vaitp:cve20201747,
title = {{PyYAML < 5.3.1, arbitrary code execution via python/object/new}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2020},
note = {VAITP Python Vulnerability Dataset, entry CVE-2020-1747},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2020-1747/}}
}
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 ::
