VAITP Dataset

← Back to the dataset

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.

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 ::