CVE-2024-41921
ROS rostopic echo –filter allows code injection via unsanitized eval().
- CVSS 7.8
- CWE-94
- Input Validation and Sanitization
- Local
A code injection vulnerability has been discovered in the Robot Operating System (ROS) 'rostopic' command-line tool, affecting ROS distributions Noetic Ninjemys and earlier. The vulnerability lies in the 'echo' verb, which allows a user to introspect a ROS topic and accepts a user-provided Python expression via the –filter option. This input is passed directly to the eval() function without sanitization, allowing a local user to craft and execute arbitrary 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
- rostopic
- Fixed by upgrading
- Yes
Solution
Upgrade the `ros-noetic-rostopic` package to version 1.16.0 or newer.
Vulnerable code sample
import sys
import os
def rostopic_echo_vulnerable(filter_expression):
"""
A simplified representation of the vulnerable logic in 'rostopic echo --filter'.
This function simulates the core behavior of processing a ROS message
against a user-provided filter expression.
"""
# In the actual tool, 'm' would be a ROS message object received from a
# topic subscription. Here, we create a mock dictionary to serve as the message.
m = {
'header': {
'seq': 101,
'stamp': '2024-01-01T00:00:00Z',
'frame_id': 'base_link'
},
'temperature': 25.5,
'active': True
}
# The context for eval() is prepared. The user's filter expression
# can access the message content via the variable 'm'.
evaluation_context = {'m': m}
# THE VULNERABLE LINE:
# The user-provided 'filter_expression' is passed directly to the eval()
# function without any sanitization. The expression is expected to return
# a boolean to decide if the message should be printed, but a malicious
# user can craft an expression that executes arbitrary system commands.
if eval(filter_expression, globals(), evaluation_context):
print("Message matched filter and would be displayed:")
print(m)
else:
# In the case of code execution, the return value might be None or 0,
# leading to this branch being executed after the payload runs.
print("Message did not match filter.")
if __name__ == '__main__':
# This block simulates the command-line invocation of the tool.
# Example of legitimate use:
# python vulnerable_code.py "m['temperature'] > 20.0"
#
# Example of malicious use to execute code:
# python vulnerable_code.py "__import__('os').system('echo VULNERABILITY DEMONSTRATED')"
if len(sys.argv) < 2:
print("Usage: python <script_name>.py \"<filter_expression>\"")
print("\nDemonstrating with a malicious payload...")
# A payload that is non-destructive and works on most OSes.
malicious_payload = "__import__('os').system('echo CODE_EXECUTION_SUCCESSFUL')"
filter_arg = malicious_payload
else:
filter_arg = sys.argv[1]
print(f"\n[*] Executing with filter: {filter_arg}\n" + "-"*30)
rostopic_echo_vulnerable(filter_arg)
print("-"*30)Patched code sample
import ast
def safe_eval_filter(filter_expression, msg):
"""
Safely evaluates a filter expression against a message object.
This function mitigates CVE-2024-41921 by parsing the user-provided
expression into an Abstract Syntax Tree (AST) and validating that it
only contains a restricted, safe subset of Python's grammar before
evaluation. This prevents the execution of arbitrary code.
Args:
filter_expression (str): The user-provided Python expression.
msg (object): The ROS message instance to be evaluated against. The
expression can refer to this object as 'm'.
Returns:
bool: The result of the expression.
Raises:
ValueError: If the expression contains disallowed constructs.
"""
if not filter_expression:
return True
# 1. Parse the expression into an AST. mode='eval' ensures it's an expression.
try:
tree = ast.parse(filter_expression, mode='eval')
except (SyntaxError, TypeError) as e:
raise ValueError(f"Invalid filter expression: {e}")
# 2. Define a whitelist of safe AST node types.
# This allows for comparisons, attribute access, boolean logic, and literals,
# but explicitly disallows calls, imports, comprehensions, etc.
allowed_node_types = {
ast.Expression, # The top-level node
ast.Compare, # Comparisons (e.g., m.data > 10)
ast.Attribute, # Attribute access (e.g., m.data)
ast.Name, # Variable names (e.g., 'm')
ast.Load, # Context for loading variables
ast.Constant, # Literals (Python 3.8+)
ast.BoolOp, # Boolean operators (and, or)
ast.And,
ast.Or,
ast.UnaryOp, # Unary operators (not)
ast.Not,
ast.BinOp, # Binary operators (+, -, *, /)
ast.Add, ast.Sub, ast.Mult, ast.Div,
# For older Python compatibility (pre-3.8)
ast.NameConstant, # True, False, None
ast.Num, # Numbers
ast.Str, # Strings
}
# 3. Walk the entire AST and validate every node against the whitelist.
for node in ast.walk(tree):
if type(node) not in allowed_node_types:
raise ValueError(
f"Disallowed expression construct '{type(node).__name__}' found. "
"Execution of arbitrary code is blocked."
)
# 4. Additionally, prevent access to "private" or "magic" attributes.
if isinstance(node, ast.Attribute) and node.attr.startswith('_'):
raise ValueError(
"Access to private or magic attributes (starting with '_') is not permitted."
)
# 5. Ensure the only allowed variable name is 'm'.
if isinstance(node, ast.Name) and node.id != 'm':
raise ValueError(f"Unknown variable '{node.id}' found. Only 'm' is allowed.")
# 6. If validation passes, compile and evaluate the expression in a
# restricted environment.
# The 'globals' dictionary is restricted to prevent access to built-ins.
# The 'locals' dictionary only exposes the message object as 'm'.
code = compile(tree, '<string>', 'eval')
return eval(code, {"__builtins__": {}}, {'m': msg})Payload
`__import__('os').system('id')`
Cite this entry
@misc{vaitp:cve202441921,
title = {{ROS rostopic echo --filter allows code injection via unsanitized eval().}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2024-41921},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2024-41921/}}
}
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 ::
