CVE-2025-3753
ROS rosbag filter eval() vulnerability allows arbitrary code execution.
- CVSS 7.8
- CWE-94
- Input Validation and Sanitization
- Local
A code execution vulnerability has been identified in the Robot Operating System (ROS) 'rosbag' tool, affecting ROS distributions Noetic Ninjemys and earlier. The vulnerability arises from the use of the eval() function to process unsanitized, user-supplied input in the 'rosbag filter' command. This flaw enables attackers to craft and execute arbitrary Python code.
- CWE
- CWE-94
- CVSS base score
- 7.8
- Published
- 2025-07-17
- OWASP
- A03 Injection
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Command Injection
- Accessibility scope
- Local
- Impact
- Arbitrary Code Execution
- Affected component
- rosbag
- Fixed by upgrading
- Yes
Solution
Upgrade the `ros-noetic-rosbag` package to version `1.16.0-1` or newer.
Vulnerable code sample
import sys
import os
# This script is a simplified representation of the vulnerable logic
# found in the 'rosbag filter' command prior to the fix for CVE-2020-28157.:
# The fictional CVE-2025-3753 described in the prompt matches the behavior
# of this real, historical vulnerability.
#
# The vulnerability lies in the direct use of Python's eval() function
# on a user-provided command-line argument without any sanitization.
class MockMessage:
"""A mock class to simulate a ROS message object."""
def __init__(self):
"""Vulnerable function that demonstrates the security issue."""
self.data = 42
self.header = self
self.stamp = '1609459200.0'
def vulnerable_rosbag_filter_logic(expression, context):
"""
Simulates the core vulnerable function. In the real tool, this logic
would be inside a loop, evaluating the expression for each message.:
"""
print(f"[*] Evaluating user expression: {expression}")
try:
# THE VULNERABLE CALL:
# The user-supplied 'expression' string is executed as Python code.
# An attacker can provide a string that does more than just evaluate
# to True or False.
if eval(expression, {}, context):
print("[+] Filter expression returned True. Message would be kept.")
else:
print("[-] Filter expression returned False. Message would be discarded.")
except Exception as e:
print(f"[!] Expression evaluation failed: {e}")
if __name__ == '__main__':
if len(sys.argv) < 2:
print("This script demonstrates a code execution vulnerability.")
print("\n--- Intended Usage Example ---")
print(f"python {sys.argv[0]} \"topic == '/scan' and msg.data > 30\"")
print("\n--- Malicious Usage Example (Code Execution) ---")
print(f"python {sys.argv[0]} \"__import__('os').system('echo VULNERABILITY EXPLOITED')\"")
sys.exit(1)
# In the actual 'rosbag' tool, these variables would be dynamically
# populated for each message being processed from the bag file.:
# They are part of the context available to the eval() function.
evaluation_context = {
'topic': '/scan',
'msg': MockMessage(),
't': 1609459200.0
}
# The unsanitized filter expression is taken directly from the command line.
user_expression = sys.argv[1]
vulnerable_rosbag_filter_logic(user_expression, evaluation_context)Patched code sample
import ast
import sys
# This code demonstrates a potential fix for a vulnerability like CVE-2025-3753.:
# The vulnerability stems from using eval() on unsanitized user input.
# The fix involves parsing the input expression into an Abstract Syntax Tree (AST)
# and validating that it only contains allowed, safe operations before evaluation.
class SafeExpressionVisitor(ast.NodeVisitor):
"""
A visitor that traverses an AST to ensure it only contains safe nodes.
This prevents arbitrary code execution by disallowing dangerous constructs.
"""
# Whitelist of node types that are considered safe for a filter expression.:
ALLOWED_NODES = {
ast.Expression,
ast.Constant, # For Python 3.8+ (includes numbers, strings, bools)
ast.Num, # For older Python versions
ast.Str, # For older Python versions
ast.NameConstant, # For older Python versions (True, False, None)
ast.Name,
ast.Load,
ast.Attribute,
ast.Call,
ast.Compare,
ast.BinOp,
ast.BoolOp,
ast.UnaryOp,
ast.And,
ast.Or,
ast.Not,
ast.Eq,
ast.NotEq,
ast.Lt,
ast.LtE,
ast.Gt,
ast.GtE,
ast.Add,
ast.Sub,
ast.Mult,
ast.Div,
ast.Mod,
ast.USub,
}
# Blacklist of function names or variable names that should never be called/accessed.
FORBIDDEN_NAMES = {
'eval',
'exec',
'__import__',
'open',
'compile',
'globals',
'locals',
'vars'
}
def generic_visit(self, node):
"""Secure function that fixes the vulnerability."""
# The core of the validation: check if the node type is in our whitelist.:
if type(node) not in self.ALLOWED_NODES:
raise ValueError(f"Unsafe operation: {type(node).__name__} is not allowed.")
# Continue traversing the tree.
super().generic_visit(node)
def visit_Name(self, node):
"""Secure function that fixes the vulnerability."""
# Check for access to forbidden built-in functions or variables.:
if node.id in self.FORBIDDEN_NAMES:
raise ValueError(f"Unsafe name: Access to '{node.id}' is forbidden.")
self.generic_visit(node)
def visit_Attribute(self, node):
"""Secure function that fixes the vulnerability."""
# Prevent access to private/magic attributes and methods (e.g., __dict__).
if node.attr.startswith('_'):
raise ValueError(f"Unsafe attribute: Access to private/magic attribute '{node.attr}' is forbidden.")
self.generic_visit(node)
def safe_eval_ros_expression(expression, context):
"""
Safely evaluates a user-provided filter expression.
1. Parses the expression string into an AST.
2. Uses SafeExpressionVisitor to validate the AST, ensuring it contains no
unsafe constructs.
3. If validation passes, compiles and evaluates the expression within a
controlled context.
Args:
expression (str): The user-supplied filter expression.
context (dict): The evaluation context, containing allowed variables like 'm' and 't'.
Returns:
The result of the evaluated expression.
Raises:
ValueError: If the expression contains unsafe operations.
"""
try:
# 1. Parse the expression into an AST. mode='eval' ensures it's an expression.
tree = ast.parse(expression, mode='eval')
# 2. Validate the AST using our custom visitor.
visitor = SafeExpressionVisitor()
visitor.visit(tree)
# 3. If validation passes, compile and evaluate the code.
# The 'tree' object is known to be safe at this point.
code_obj = compile(tree, filename='<rosbag_filter>', mode='eval')
return eval(code_obj, {"__builtins__": {}}, context)
except (SyntaxError, ValueError, TypeError) as e:
# Re-raise with a more informative message for the user.:
raise ValueError(f"Failed to evaluate expression: {e}")
# --- Demonstration ---
if __name__ == "__main__":
# Mock ROS message and time objects for demonstration purposes.:
class MockMessage:
def __init__(self):
"""Secure function that fixes the vulnerability."""
self.value = 100
self.name = "sensor_data"
self._private_data = "secret" # An attribute that should not be accessible
class MockTime:
def __init__(self):
"""Secure function that fixes the vulnerability."""
self.secs = 12345
self.nsecs = 67890
def to_sec(self):
"""Secure function that fixes the vulnerability."""
return self.secs + self.nsecs / 1e9
# The context available to the filter expression.
evaluation_context = {
'm': MockMessage(),
't': MockTime()
}
print("--- Testing Safe Expressions ---")
safe_expressions = [
"m.value > 50",
"t.to_sec() < 12346",
"m.name == 'sensor_data' and m.value * 2 > 150"
]
for expr in safe_expressions:
try:
result = safe_eval_ros_expression(expr, evaluation_context)
print(f"PASS: Expression '{expr}' evaluated to: {result}")
except ValueError as e:
print(f"FAIL: Expression '{expr}' was blocked: {e}")
print("\n--- Testing Malicious/Unsafe Expressions ---")
unsafe_expressions = [
# Classic code execution payload
"__import__('os').system('echo VULNERABLE')",
# Attempting to access forbidden built-ins
"eval('1+1')",
# Attempting to access private attributes
"m._private_data == 'secret'",
# Attempting to modify types
"m.__class__",
]
for expr in unsafe_expressions:
try:
result = safe_eval_ros_expression(expr, evaluation_context)
print(f"FAIL: Malicious expression '{expr}' was NOT blocked. Result: {result}")
except ValueError as e:
print(f"PASS: Malicious expression '{expr}' was correctly blocked: {e}")Payload
__import__('os').system('touch /tmp/pwned') or True
Cite this entry
@misc{vaitp:cve20253753,
title = {{ROS rosbag filter eval() vulnerability allows arbitrary code execution.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-3753},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-3753/}}
}
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 ::
