flawopen.com/Command Injection/Ruby

Command Injection in Ruby

Critical CWE-78 Draft — pending review
ELI5

Imagine an intercom where you state your name to enter. If someone says 'Alice and open all doors', command injection is the intercom unlocking all doors because it failed to parse where the name ended.

Key terms on this page
Kernel#open pipe trap
In Ruby, open("| cmd") executes shell commands. Passing user input to open() allows command execution if input begins with a pipe.
system(*args)
Passing multiple arguments to system() bypasses the shell and invokes the kernel directly.

What's happening

Ruby provides multiple ways to execute commands: backticks (`cmd`), %x{cmd}, system(), exec(), and IO.popen(). If passed a single string containing user data, Ruby invokes /bin/sh.

Real-world impact

Ruby on Rails applications have faced critical CVEs when file upload controllers called open() on user filenames, executing commands embedded in file paths.

CVE-2019-5477 & Rails Security Advisory.

Vulnerable vs. fixed

VULNERABLE
# Backtick execution invokes /bin/sh
def fetch_git_log(branch)
  # Input: "main; rm -rf /"
  `git log #{branch}`
end
FIXED
# Multiple arguments to system/Open3 bypass the shell
require 'open3'

def fetch_git_log(branch)
  # branch is passed as an isolated argument to git
  stdout, stderr, status = Open3.capture3("git", "log", branch)
  stdout
end

Why the fix works

When Open3.capture3 or system receives multiple arguments, Ruby bypasses /bin/sh and directly calls execve.

Gotchas

Kernel#open with pipe character

In Ruby, open("| #{filename}") executes the filename as a shell command. Always use File.open() instead of Kernel#open or URI.open.

Common misconceptions

"Shellwords.escape makes backticks safe"

While Shellwords.escape helps, developers often forget it on some variables. Using multiple arguments in Open3 is immune to forgetting.

How to check if you're affected

grep -rn '`.*#\{' --include="*.rb" . grep -rn 'Kernel\.open\|URI\.open' --include="*.rb" .
Run Brakeman (static security analysis for Rails) in CI.

Prevention checklist

FAQ

Why is Kernel#open so dangerous in Ruby?

Because if a user submits a filename starting with '|', Ruby treats it as a pipe to a command. File.open() never has this behavior.

References

View in: Python JavaScript Go Java PHP C# Ruby C/C++ Rust Kotlin Swift Solidity (N/A)
Also see: SQL Injection XSS Path Traversal Insecure Deserialization