Ruby Rails Open Redirect Vulnerability (Brakeman Check)

Medium Risk Open Redirect
RubyRailsOpen RedirectBrakemanURL ValidationPhishing

What it is

Application uses redirect_to with user-controlled URLs in Rails, enabling open redirect attacks that can be exploited for phishing and malicious redirections.

class SessionsController < ApplicationController def create user = User.authenticate(params[:email], params[:password]) if user session[:user_id] = user.id # Vulnerable: User controls redirect destination redirect_url = params[:redirect_url] || root_path redirect_to redirect_url # Open redirect vulnerability else flash[:error] = 'Invalid credentials' render :new end end def logout session.clear # Vulnerable: External redirect after logout return_url = params[:return_url] redirect_to return_url || login_path end end class ApplicationController < ActionController::Base def redirect_after_action # Vulnerable: Dynamic redirect without validation next_page = request.referer || params[:next] redirect_to next_page end end
class SessionsController < ApplicationController ALLOWED_REDIRECT_HOSTS = [ 'app.example.com', 'secure.example.com', 'api.example.com' ].freeze def validate_redirect_url(url) return root_path if url.blank? begin uri = URI.parse(url) # Allow relative URLs if uri.relative? # Ensure it's a path, not a protocol-relative URL return url if url.start_with?('/') return "/#{url}" unless url.start_with?('//') raise ArgumentError, 'Protocol-relative URLs not allowed' end # For absolute URLs, check host allowlist if uri.absolute? unless uri.scheme.in?(%w[http https]) raise ArgumentError, 'Invalid URL scheme' end unless ALLOWED_REDIRECT_HOSTS.include?(uri.host) raise ArgumentError, 'Host not in allowlist' end return url end raise ArgumentError, 'Invalid URL format' rescue URI::InvalidURIError raise ArgumentError, 'Malformed URL' end end def safe_redirect_to(url, fallback = root_path) begin safe_url = validate_redirect_url(url) redirect_to safe_url rescue ArgumentError => e Rails.logger.warn "Redirect validation failed: #{e.message} for URL: #{url}" redirect_to fallback end end def create user = User.authenticate(params[:email], params[:password]) if user session[:user_id] = user.id # Secure: Validate redirect URL redirect_url = params[:redirect_url] safe_redirect_to(redirect_url, dashboard_path) else flash[:error] = 'Invalid credentials' render :new end end def logout session.clear # Secure: Validate return URL return_url = params[:return_url] safe_redirect_to(return_url, login_path) end end class ApplicationController < ActionController::Base def redirect_after_action # Secure: Use internal routes only begin # Check if next parameter is a valid internal route next_page = params[:next] if next_page.present? # Try to recognize as internal route Rails.application.routes.recognize_path(next_page) redirect_to next_page else redirect_to root_path end rescue ActionController::RoutingError # Invalid route, redirect to safe default redirect_to root_path rescue => e Rails.logger.error "Redirect error: #{e.message}" redirect_to root_path end end # Alternative: Use permitted redirect paths SAFE_REDIRECT_PATHS = [ '/dashboard', '/profile', '/settings', '/help' ].freeze def safe_internal_redirect requested_path = params[:path] if SAFE_REDIRECT_PATHS.include?(requested_path) redirect_to requested_path else redirect_to root_path end end end

💡 Why This Fix Works

See fix suggestions for detailed explanation.

Why it happens

Rails controller redirects with user input: redirect_to params[:url]. params[:url] user-controlled. Open redirect vulnerability. Attackers craft phishing URLs. redirect_to trusts input without validation. Users redirected to malicious sites appearing legitimate through trusted domain.

Root causes

Using redirect_to with Unvalidated params

Rails controller redirects with user input: redirect_to params[:url]. params[:url] user-controlled. Open redirect vulnerability. Attackers craft phishing URLs. redirect_to trusts input without validation. Users redirected to malicious sites appearing legitimate through trusted domain.

Using redirect_to with URL from Request

Redirect based on request data: redirect_to request.referrer or redirect_to params[:return_to]. Referrer and params untrusted. Attackers control redirect destination. OAuth flows vulnerable to redirect manipulation. Post-login redirect exploitation common attack vector.

Not Validating Redirect URLs Against Allowlist

Missing URL validation: url = params[:destination]; redirect_to url. No check if URL within application or trusted domains. External redirects without validation. Allowlist approach not implemented. Any URL accepted for redirection.

Using only_path Without Additional Validation

Partial validation with only_path: redirect_to params[:path], only_path: true. only_path: true prevents full URLs but still allows manipulation. Path can be any route. External redirect still possible with //evil.com syntax. only_path insufficient protection alone.

Building Redirect URLs with String Interpolation

Constructing URLs: redirect_to "/dashboard?next=#{params[:page]}". String interpolation with params. Crafted values like /dashboard?next=//evil.com. URL parsing ambiguities. String building creates injection points for open redirects.

Fixes

1

Validate Redirect URLs Against Allowlist of Paths

Allowlist validation: ALLOWED_PATHS = ['/dashboard', '/profile', '/settings']; redirect_path = params[:path]; raise unless ALLOWED_PATHS.include?(redirect_path); redirect_to redirect_path. Explicit allowed paths. No arbitrary redirects. Users choose from predetermined safe destinations.

2

Use Named Routes Instead of Dynamic URLs

Use route helpers: redirect_to dashboard_path or redirect_to user_profile_path(current_user). Named routes guaranteed internal. No external redirect risk. Controller and route names instead of URLs. Rails routing ensures validity.

3

Validate URL Scheme and Host

Check URL components: uri = URI.parse(params[:url]); raise unless uri.scheme == 'https' && uri.host == request.host; redirect_to uri.to_s. Parse URL. Validate scheme and host. Ensure same origin. Prevent redirects to external sites.

4

Use only_path: true with Path Validation

Combine only_path with validation: path = params[:path]; raise unless path.starts_with?('/') && !path.include?('//'); redirect_to path, only_path: true. only_path prevents full URLs. Additional checks for // preventing protocol-relative URLs. Layered approach.

5

Implement Signed Redirect Tokens

Sign return URLs: redirect_url = verifier.generate(params[:return_to]); later: safe_url = verifier.verify(params[:token]); redirect_to safe_url. Rails message verifier. Cryptographically signed tokens. Tampering detected. Only application-generated URLs accepted.

6

Run Brakeman to Detect Unsafe Redirects

Use Brakeman scanner: brakeman -A detects redirect vulnerabilities. Review Redirect warnings. Fix flagged redirect_to calls. Automated detection in CI/CD. Regular scans prevent unsafe redirects. Brakeman specifically checks redirect_to with user input.

Detect This Vulnerability in Your Code

Sourcery automatically identifies ruby rails open redirect vulnerability (brakeman check) and many other security issues in your codebase.