Command Injection in Grape with Mutual Tls
Command Injection in Grape with Mutual Tls — how this specific combination creates or exposes the vulnerability
Command Injection occurs when an API passes untrusted input directly to a system shell or to a function that executes a command. In a Grape API, this typically manifests through endpoints that construct shell commands using user-supplied parameters. When Mutual Tls (mTLS) is enforced, the API requires client certificates for authentication, which can create a false sense of security. Developers may assume that because mTLS authenticates the client, inputs are safe. However, mTLS does not alter how the server processes requests; if input validation and sanitization are missing, an authenticated client can still inject commands.
Consider a Grape endpoint that uses a client certificate to identify a partner system and then runs a tool based on a query parameter:
class App::API < Grape::API
format :json
before { authenticate_client! } # mTLS enforced at connection level
helpers do
def authenticate_client!
# mTLS verification happens earlier in the stack (e.g., reverse proxy or Ruby code)
# Assuming client identity is available as env['SSL_CLIENT_VERIFY'] and cert info
error!('Unauthorized', 401) unless env['SSL_CLIENT_VERIFY'] == 'SUCCESS'
end
end
resource :reports do
desc 'Generate a report using an external tool'
params do
requires :type, type: String, desc: 'Report type, e.g., pdf or csv'
optional :filter, type: String, desc: 'Optional filter string'
end
get do'
report_type = params[:type]
# Dangerous: directly interpolating user input into a shell command
output = `generate-report --type #{report_type}`
{ output: output }
end
end
end
Here, mTLS ensures only clients with valid certificates can call the endpoint, but report_type is taken directly from the request and interpolated into a backtick command. An attacker who presents a valid client certificate can inject additional shell commands, such as pdf; cat /etc/passwd, leading to unauthorized file reads. This is a classic Command Injection that mTLS does not prevent because the vulnerability lies in command construction, not in transport or authentication.
The risk is compounded when the API uses system utilities that are available in the runtime environment. Even with mTLS restricting access to the endpoint, input validation remains essential. Attack patterns like OS Command Injection (CWE-78) remain applicable. MiddleBrick scans detect such patterns by correlating endpoint behavior with known unsafe command construction, regardless of mTLS presence.
Mutual Tls-Specific Remediation in Grape — concrete code fixes
To remediate Command Injection in a Grape API with mTLS, focus on strict input validation, avoiding shell interpolation, and using safe execution patterns. Do not rely on mTLS to sanitize inputs. Below are concrete fixes with code examples.
1. Validate and whitelist allowed values
Instead of passing raw user input to a shell command, validate against a strict set of allowed values. This eliminates unexpected characters and commands.
class App::API < Grape::API
format :json
before { authenticate_client! }
helpers do
def authenticate_client!
error!('Unauthorized', 401) unless env['SSL_CLIENT_VERIFY'] == 'SUCCESS'
end
end
ALLOWED_REPORT_TYPES = %w[pdf csv json]
resource :reports do
desc 'Generate a report using an external tool'
params do
requires :type, type: String, values: ALLOWED_REPORT_TYPES, desc: 'Report type, e.g., pdf or csv'
end
get do
report_type = params[:type]
# Safe: using a whitelisted value prevents injection
output = `generate-report --type #{report_type}`
{ output: output }
end
end
end
2. Use system calls with argument arrays
In Ruby, using backticks or system with a single string invokes a shell, which enables injection. Use the array form of system or Open3.capture3 to bypass the shell.
require 'open3'
class App::API < Grape::API
format :json
before { authenticate_client! }
helpers do
def authenticate_client!
error!('Unauthorized', 401) unless env['SSL_CLIENT_VERIFY'] == 'SUCCESS'
end
end
ALLOWED_REPORT_TYPES = %w[pdf csv json]
resource :reports do
desc 'Generate a report using an external tool'
params do
requires :type, type: String, values: ALLOWED_REPORT_TYPES, desc: 'Report type'
end
get do
report_type = params[:type]
# Safe: using array form avoids shell interpretation
stdout, status = Open3.capture2('generate-report', '--type', report_type)
{ output: stdout, status: status.exitstatus }
end
end
end
3. Enforce input sanitization and escaping
If shell usage is unavoidable, sanitize input by removing or escaping dangerous characters. This is a fallback and less secure than the above approaches.
class App::API < Grape::API
format :json
before { authenticate_client! }
helpers do
def authenticate_client!
error!('Unauthorized', 401) unless env['SSL_CLIENT_VERIFY'] == 'SUCCESS'
end
def safe_report_type(input)
# Allow only alphanumeric and limited safe characters
input.to_s.gsub(/[^a-zA-Z0-9_\-]/, '')
end
end
resource :reports do
desc 'Generate a report using an external tool'
params do
requires :type, type: String, desc: 'Report type'
end
get do
report_type = safe_report_type(params[:type])
# Safer: sanitized input, but prefer array-based execution
output = `generate-report --type #{report_type}`
{ output: output }
end
end
end
These practices align with OWASP API Security Top 10 and help prevent Command Injection regardless of mTLS enforcement. MiddleBrick can identify risky patterns in your OpenAPI spec and runtime tests, providing prioritized findings and remediation guidance.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |