Skip to content

Rails 8 Changes From Rails 5

Ken Johnson edited this page Dec 5, 2025 · 2 revisions

What Changed From Rails 5 to Rails 8? Security Comparison

This guide provides a comprehensive comparison of security features and vulnerabilities between Rails 5 (what RailsGoat was originally built for) and Rails 8 (the current version).

TL;DR - Quick Summary

93% of RailsGoat vulnerabilities still apply to Rails 8 because most web security issues are caused by developer mistakes, not framework flaws. Rails 8 makes it easier to be secure with better defaults and tools, but the same vulnerabilities exist when developers:

  • Disable built-in protections
  • Use unsafe methods
  • Write custom authentication logic
  • Don't validate user input
  • Expose sensitive data through APIs

What's New in Rails 8

πŸ†• 1. Automatic ReDoS Protection

The Big One: Rails 8 sets Regexp.timeout = 1.0 by default.

# Rails 5 - This can hang forever
username =~ /^(a+)+$/

# Rails 8 - Raises Regexp::TimeoutError after 1 second
username =~ /^(a+)+$/

Impact: Prevents Regular Expression Denial of Service attacks automatically.

Learn More: A1 - ReDoS Tutorial


πŸ”§ 2. Improved Authentication Generator

What's New: bin/rails generate authentication creates secure auth out-of-the-box.

# Rails 8
rails generate authentication

# Generates:
# - User model with has_secure_password
# - Sessions controller with authenticate_by
# - Password reset functionality
# - BCrypt password hashing

Rails 5 Equivalent: Manual implementation required (prone to mistakes).

RailsGoat Context: The credential enumeration vulnerability (A2) only exists because developers write custom authentication like RailsGoat does. Rails 8 makes it easier to avoid these mistakes.

Learn More: R8 - Authentication Improvements


πŸ”§ 3. Enhanced Parameter Handling: params.expect()

What's New: Stricter parameter filtering than params.permit().

# Rails 5 - params.permit()
def user_params
  params.require(:user).permit(:email, :first_name, :last_name)
end

# Rails 8 - params.expect() (more explicit)
def user_params
  params.expect(user: [:email, :first_name, :last_name])
end

Benefits:

  • More explicit about what parameters are expected
  • Better error messages when structure doesn't match
  • Can help prevent mass assignment vulnerabilities

RailsGoat Context: Doesn't fix the fundamental mass assignment issue if developers permit admin attributes, but makes intent clearer.

Learn More: R8 - Parameter Handling


πŸ”§ 4. Enhanced Content Security Policy (CSP)

What's New: Better CSP configuration and defaults.

# config/initializers/content_security_policy.rb (Rails 8)
Rails.application.config.content_security_policy do |policy|
  policy.default_src :self, :https
  policy.script_src :self, :https
  policy.style_src :self, :https
  # ... more defaults
end

Impact on XSS: Properly configured CSP provides defense-in-depth against XSS attacks.

RailsGoat Context: XSS vulnerabilities from .html_safe still exist, but CSP can limit damage.


What Stayed The Same

A1 - SQL Injection ❌ No Change

Rails 5 Vulnerable Pattern:

User.where("id = '#{params[:user][:id]}'")  # String interpolation

Rails 8: Same vulnerability if developers use string interpolation.

Current Status in RailsGoat: Still vulnerable at app/controllers/users_controller.rb:29

Why It Persists: Framework can't prevent string interpolation in queries.

Mitigation (Same in Both):

User.where("id = ?", params[:user][:id])  # Parameterized query
User.find(params[:user][:id])              # Idiomatic Rails

A1 - Command Injection ❌ No Change

Rails 5 Vulnerable Pattern:

system("cp #{file.original_filename} #{backup_path}")

Rails 8: Same vulnerability if developers use system() with user input.

Why It Persists: Framework can't prevent unsafe system calls.

Mitigation (Same in Both):

FileUtils.cp(file.original_filename, backup_path)  # Use Ruby methods

A2 - Credential Enumeration ❌ No Change

Rails 5 Vulnerable Pattern:

user = find_by_email(email)
raise "#{email} doesn't exist!" if !user        # Reveals email exists
raise "Incorrect Password!" if wrong_password   # Different error

Rails 8: Same vulnerability if developers write custom authentication with specific error messages.

Why It Persists: Framework can't enforce generic error messages in custom code.

Rails 8 Improvement: New auth generator uses generic messages by default, but RailsGoat uses custom auth.


A2 - HttpOnly Flag Disabled ❌ No Change

Rails 5 Vulnerable Pattern:

config.session_store :cookie_store, key: "_railsgoat_session", httponly: false

Rails 8: Same vulnerability when explicitly disabled.

Current Status in RailsGoat: Still set to false in config/initializers/session_store.rb:4

Why It Persists: Developers can still disable security features.

Default Behavior: Both Rails 5 and 8 enable HttpOnly by default - RailsGoat explicitly disables it.


A3 - Cross-Site Scripting (XSS) ❌ No Change

Rails 5 Vulnerable Pattern:

<%= current_user.first_name.html_safe %>  # Bypasses escaping

Rails 8: Same vulnerability when .html_safe or raw is used.

Why It Persists: Developers need a way to render HTML; misuse creates XSS.

Rails 8 Improvement: Better CSP support provides defense-in-depth, but doesn't fix the root cause.


A4 - Insecure Direct Object Reference (IDOR) ❌ No Change

Rails 5 Vulnerable Pattern:

@user = User.find_by(id: params[:user_id])  # Uses URL param

Rails 8: Same vulnerability when using URL parameters instead of session.

Why It Persists: Framework can't enforce session-based access control.

Rails 8 Note: params.expect() makes parameter access more explicit but doesn't prevent IDOR.

Mitigation (Same in Both):

@user = current_user  # Use session-based user object

A6 - Sensitive Data Exposure (API) ❌ No Change

Rails 5 Vulnerable Pattern:

respond_with @user  # Returns all attributes including password

Rails 8: Same vulnerability when not overriding serialization.

Why It Persists: Default JSON serialization includes all attributes.

Mitigation (Same in Both):

# Override as_json
def as_json(options = {})
  super(only: [:id, :email, :first_name, :last_name])
end

A6 - Weak Password Hashing ❌ No Change

Rails 5 Vulnerable Pattern:

self.password = Digest::MD5.hexdigest(password)  # MD5, no salt

Rails 8: Same vulnerability if developers use MD5 or weak hashing.

Why It Persists: Framework can't prevent developers from using weak crypto.

Rails 8 Improvement: Auth generator uses has_secure_password (BCrypt) by default.

Mitigation (Same in Both):

class User < ApplicationRecord
  has_secure_password  # Built-in BCrypt support
end

A6 - Cleartext Sensitive Data ❌ No Change

Rails 5 Vulnerable Pattern:

# SSN stored in plain text in database
create_table :work_infos do |t|
  t.string :SSN  # Plain text
end

Rails 8: Same vulnerability without explicit encryption.

Why It Persists: ActiveRecord doesn't automatically encrypt attributes.

Rails 8 Option: Can use encrypts :ssn (available since Rails 7), but must be explicitly added.


A7 - Missing Function Level Access Control ❌ No Change

Rails 5 Vulnerable Pattern:

before_filter :administrative, if: :admin_param

def admin_param
  params[:id] != '1'  # Bypass if ID is 1
end

Rails 8: Same vulnerability when using attacker-controlled conditions.

Why It Persists: Framework can't prevent logic errors in authorization.

Note: Rails 5 used before_filter; Rails 8 uses before_action (deprecated in Rails 5, removed in Rails 8).


A8 - CSRF Protection Disabled ❌ No Change

Rails 5 Vulnerable Pattern:

#protect_from_forgery with: :exception  # COMMENTED OUT

Rails 8: Same vulnerability when protection is disabled.

Current Status in RailsGoat: Still commented out in app/controllers/application_controller.rb:9

Why It Persists: Developers can still disable CSRF protection.

Default Behavior: Both Rails 5 and 8 enable CSRF protection by default - RailsGoat explicitly disables it.


A9 - Using Components with Known Vulnerabilities ❌ No Change

Rails 5 Vulnerable Pattern:

// Third-party library with XSS vulnerability
top.consoleRef.document.writeln('<title>'+location.href+'</title>');

Rails 8: Same vulnerability when using vulnerable dependencies.

Why It Persists: Third-party code can have vulnerabilities.

Rails 8 Note: Importmaps may reduce JavaScript dependency surface, but vulnerabilities still possible.


A10 - Unvalidated Redirects and Forwards ❌ No Change

Rails 5 Vulnerable Pattern:

path = params[:url]  # No validation
redirect_to path      # Redirects anywhere

Rails 8: Same vulnerability without URL validation.

Why It Persists: Framework can't automatically determine which redirects are safe.

Mitigation (Same in Both):

def safe_redirect_path(path, default: root_path)
  Rails.application.routes.recognize_path(path)
  path
rescue ActionController::RoutingError
  default
end

Vulnerability Comparison Table

Category Rails 5 Status Rails 8 Status Framework Fix? Notes
A1 - SQL Injection ❌ Vulnerable ❌ Vulnerable No String interpolation still possible
A1 - Command Injection ❌ Vulnerable ❌ Vulnerable No system() still accepts user input
A1 - ReDoS ❌ Vulnerable βœ… Protected Yes Automatic 1s timeout
A2 - Credential Enum ❌ Vulnerable ❌ Vulnerable No Custom auth still possible
A2 - HttpOnly Disabled ❌ Vulnerable ❌ Vulnerable No Can still be disabled
A2 - Weak Passwords ❌ Vulnerable πŸ”§ Easier Partial Auth generator uses BCrypt
A3 - XSS (.html_safe) ❌ Vulnerable ❌ Vulnerable No Method still available
A3 - DOM XSS ❌ Vulnerable ❌ Vulnerable No Client-side issue
A4 - IDOR ❌ Vulnerable ❌ Vulnerable No Params vs session choice
A6 - API Data Exposure ❌ Vulnerable ❌ Vulnerable No Default serialization
A6 - MD5 Hashing ❌ Vulnerable πŸ”§ Easier Partial has_secure_password easier
A6 - Cleartext Storage ❌ Vulnerable πŸ”§ Easier Partial encrypts available (Rails 7+)
A7 - Missing AuthZ ❌ Vulnerable ❌ Vulnerable No Logic errors possible
A8 - CSRF Disabled ❌ Vulnerable ❌ Vulnerable No Can still be disabled
A9 - Vuln Dependencies ❌ Vulnerable ❌ Vulnerable No Third-party code
A10 - Open Redirects ❌ Vulnerable ❌ Vulnerable No No auto URL validation

Legend:

  • ❌ Vulnerable: Same vulnerability exists
  • βœ… Protected: Framework prevents vulnerability
  • πŸ”§ Easier: Framework makes secure implementation easier, but not required

Key Insights

1. Most Vulnerabilities Are Developer Mistakes

Rails can't prevent:

  • String interpolation in SQL queries
  • Using system() with user input
  • Calling .html_safe on user data
  • Writing authorization logic with security holes
  • Disabling built-in protections

2. Rails 8 Has Better Defaults

But developers can still:

  • Comment out protect_from_forgery
  • Set httponly: false
  • Use MD5 instead of BCrypt
  • Write custom authentication with flaws

3. RailsGoat Remains Relevant

Because it demonstrates how developers introduce vulnerabilities by:

  • Misusing framework features
  • Disabling security protections
  • Writing unsafe custom code
  • Not following best practices

4. The One True Framework Fix

ReDoS protection is the only vulnerability where Rails 8 provides automatic protection that developers can't easily bypass (without explicitly setting Regexp.timeout = nil).


Conclusion

Rails 8 is a security improvement, but not a silver bullet. The framework provides better defaults and tools, but the vast majority of web application vulnerabilities stem from developer mistakes rather than framework issues. This means RailsGoat remains highly relevant for Rails 8 security training, with the added benefit of demonstrating new Rails 8-specific protections like ReDoS prevention.


Additional Resources

Sections are divided by their OWASP Top Ten label (A1-A10) and marked as R4 and R5 for Rails 4 and 5.

Clone this wiki locally