Rails audit patterns
TL;DR: Rails defaults handle CSRF, XSS escaping, and parameterised AR queries — bugs cluster around
find_by_sql/stringwhereinterpolation,permit!(wildcard mass-assign),render file:/inline:with params,Marshal/YAML.loadon session/cache,send/constantizewith attacker input, and exposed admin endpoints. Brakeman is the canonical tool.
Common bug patterns
1. SQL injection via string conditions
1
2
3
4
5
User.where("name = '#{params[:name]}'") # SQLi
User.find_by_sql("SELECT * FROM users WHERE name='#{params[:name]}'") # SQLi
User.order(params[:sort]) # SQLi if value used raw as column expression
User.where("name = ?", params[:name]) # SAFE
User.where(name: params[:name]) # SAFE
Rails 7+ sanitize_sql_array helps but doesn’t auto-apply to user code; audit every find_by_sql, every string-form where, every order(params[...]).
2. Mass assignment / strong params
User.create(params[:user])withoutpermit→ blocked byActionController::Parametersdefault (raises ForbiddenAttributesError).params.require(:user).permit!— wildcard permit; mass-assign all attributes (includingadmin,password_digest).params.require(:user).permit(:name, attributes: {})empty-hash trick allows everything insideattributes.- Audit every
permitcall; flagpermit!and barepermit(:nested => {})patterns.
3. Render abuse
render file: params[:f]→ LFI (“../” allowed).render inline: params[:t]→ ERB on attacker input → RCE.redirect_to params[:url]→ open redirect (url_for+ allowlist required).
4. send/public_send with params
obj.send(params[:m])— arbitrary method onobj. Even constrained models reach:eval,:system,:execvia Object base methods.params[:type].constantize.new(params[:user])— arbitrary class instantiation + mass-assign. Common in admin panels.
5. Marshal / YAML deserialization
Marshal.load(cookies[:session])(old Rails or custom session) → RCE on attacker cookie.YAML.load(params[:config])instantiates Ruby objects → RCE via Psych gadgets (Gem::Specification, etc.). On Ruby 3.1+ Psych 4 defaultloadcallssafe_load; flag explicitYAML.unsafe_load.secret_key_baseleak → session forgery (cookie signed). Rotate immediately on suspected leak.
6. Rails console/web-console in prod
web-consolegem indevelopmentgroup only — but checkGemfile.lockresolution and Rails environment.RAILS_ENV=productionwith web-console mounted = unauthenticated console./rails/info/routesexposes all routes if not gated.
7. CSRF gaps
protect_from_forgery with: :exceptionis default. API controllers oftenskip_before_action :verify_authenticity_token— fine only if token auth is used (no cookies), risky if mixed.protect_from_forgery with: :null_sessionreturns empty session on failure; the route still executes.
8. Active Storage / file upload
params[:image]direct upload — no MIME validation unless added.serveaction serves file at user-provided path → traversal.- Content-Disposition default
inline→ stored XSS via SVG/HTML upload.
9. JSON / API
JSON.load(body)instantiates objects → useJSON.parse.- Active Model Serializers / Jbuilder typically safe; flag any
as_json(only: params[:fields])where user picks fields they shouldn’t see.
10. Devise / Doorkeeper / authentication
devise_for :usersships login/reset/registration routes; auditpassword_reset_tokenlifetime,confirmablecallback chain.Doorkeeper.configure— check PKCE required,force_pkce, allowed scopes; missing PKCE on public clients = OAuth code injection (oauth-authorization-code-injection).
11. ActiveRecord callback side-effects
after_save :send_emailruns synchronously; if mailer hits external URL with user-controlled fields → SSRF chain.before_destroywithdependent: :destroycascade — audit for orphan removal of resources outside ownership scope.
12. CVE history to look for
- CVE-2019-5418 (file disclosure via render with Accept header) — Rails ≤5.2.
- CVE-2020-8163 (RCE in dev with
web-console). - CVE-2022-23633 (race in Action Pack session).
- Rails Doorkeeper CVEs (OAuth flow bugs).
Grep starter
1
2
3
4
5
6
rg -n 'find_by_sql|where\("[^"]*#\{|order\(\s*params|select\("[^"]*#\{' .
rg -n 'permit!|attributes: \{\}|merge\(params' app/controllers
rg -n 'render\s+(file|inline):\s*params|redirect_to\s+params' .
rg -n 'send\(\s*params|public_send\(\s*params|constantize' .
rg -n 'Marshal\.load|YAML\.(load|unsafe_load)' .
rg -n 'mount\s+\w+::Engine.*=>\s*[\x27"]/' config/routes.rb # mounted apps
Tooling
- Brakeman — Rails-specific SAST. Run in CI; treat warnings as P2 minimum.
bundle audit(orbundler-audit) — CVE deps.rubocop-rails+rubocop-rails-omakase— style + some security lints.- Semgrep
ruby.rails.securityruleset.