Skip to content

Check for unsafe SQL when two arguments are passed to AR methods - #1936

Merged
presidentbeef merged 2 commits into
presidentbeef:mainfrom
patbl:patbl/correct-sql-check-magic-number
Jul 29, 2025
Merged

Check for unsafe SQL when two arguments are passed to AR methods#1936
presidentbeef merged 2 commits into
presidentbeef:mainfrom
patbl:patbl/correct-sql-check-magic-number

Conversation

@patbl

@patbl patbl commented Apr 23, 2025

Copy link
Copy Markdown
Contributor

I think this conditional is meant to

  1. Always check the first and last arguments.
  2. Avoid checking the argument twice when there's only one argument.

The structure of call appears to be:

  • 0: the symbol :call
  • 1: the receiver of the method call
  • 2: the method being called
  • 3: the first argument
  • 4: the second argument
  • 5: the third argument (and so on)

So call.length > 5 will be true only if there are at least 3 arguments.

Before this change, the behavior was

User.count("#{params[:input]}") # triggers warning
User.count("#{params[:input]}", :foo) # doesn't trigger warning
User.count("#{params[:input]}", :foo, :bar) # triggers warning

After this change, the two-argument version will trigger the warning.

Another option would be to use if call.arglist.length > 1. This is more explicit, but it involves an additional method call, and I don't know what the performance implications of that are.

@dryrunsecurity

dryrunsecurity Bot commented Apr 23, 2025

Copy link
Copy Markdown

DryRun Security

This pull request involves a minor modification to Brakeman's SQL injection detection logic, adjusting the method argument length condition to potentially capture a broader range of method call scenarios for improved vulnerability scanning.

💭 Unconfirmed Findings (1)
Vulnerability Potential Marginal SQL Injection Detection Impact
Description A patch in Brakeman's SQL injection check modifies method argument length condition from call.length > 5 to call.length >= 5, potentially expanding the scope of SQL injection detection by capturing different method call scenarios. Located in lib/brakeman/checks/check_sql.rb, this change represents a minor logic adjustment that could slightly alter injection detection capabilities.

All finding details can be found in the DryRun Security Dashboard.

I think this conditional is meant to

1. Always check the first and last arguments.
2. Avoid checking the argument twice when there's only one argument.

The structure of `call` appears to be:

* 0: the symbol `:call`
* 1: the receiver of the method call
* 2: the method being called
* 3: the first argument
* 4: the second argument
* 5: the third argument (and so on)

So `call.length > 5` will be true only if there are at least 3
arguments.

Before this change, the behavior was

```ruby
User.count("#{params[:input]}") # triggers warning
User.count("#{params[:input]}", :foo) # doesn't trigger warning
User.count("#{params[:input]}", :foo, :bar) # triggers warning
```

After this change, the two-argument version will trigger the warning.

Another option would be to use `if call.arglist.length > 1`. This is
more explicit, but it involves an additional method call, and I don't
know what the performance implications of that are.
@patbl
patbl force-pushed the patbl/correct-sql-check-magic-number branch from 4ee9000 to 38a3005 Compare April 23, 2025 23:57
@presidentbeef

presidentbeef commented Jun 13, 2025

Copy link
Copy Markdown
Owner

I'm going to have to figure out what the original intent was here... it's been a while since those methods even accepted more than one argument.

(But I think you are right)

@presidentbeef

Copy link
Copy Markdown
Owner

@patbl Can you update to use the more explicit call.arglist.length > 1?

@patbl

patbl commented Jul 10, 2025

Copy link
Copy Markdown
Contributor Author

@presidentbeef I made that change.

@dryrunsecurity

Copy link
Copy Markdown

DryRun Security

No security concerns detected in this pull request.


All finding details can be found in the DryRun Security Dashboard.

@presidentbeef
presidentbeef merged commit 2045b16 into presidentbeef:main Jul 29, 2025
@soulcutter

Copy link
Copy Markdown

This change seems to have triggered a LOT of warnings across many of our applications that use a statsd-like interface for calling $stats.count("metric.name.#{custom_thing}", 1) - something we use WAY more than ActiveRecord count-with-2-args.

https://www.rubydoc.info/gems/dogstatsd-ruby/3.0.0/Datadog/Statsd#count-instance_method

Is there a way this check might be better-targeted to avoid this deluge of false positives?

@presidentbeef

Copy link
Copy Markdown
Owner

@soulcutter can you provide an example of the exact warning from Brakeman? Does it say $stats or does it know what $status is?

@soulcutter

Copy link
Copy Markdown
Confidence: Weak
Category: SQL Injection
Check: SQL
Message: Possible SQL injection
Code: $stats.count("prefix.#{variable}", :tags => ({ :result => "error_raised" }))
File: app/models/<model>.rb
Line: 61

The $stats global is initialized in dependency, not defined anywhere in the application code (it's not defined in app/models/<model>.rb for example). I do not know how to answer the question of if it knows what $stats is

@presidentbeef

Copy link
Copy Markdown
Owner

Maybe Brakeman can just ignore calls that look like ActiveRecord methods but are on globals 🤔

@patbl

patbl commented Nov 20, 2025

Copy link
Copy Markdown
Contributor Author

Maybe Brakeman can just ignore calls that look like ActiveRecord methods but are on globals 🤔

A global variable such as $statsd is one way of referencing this sort of object, but applications could instead use a module, class, method, constant, or local variable. (StatsD, STATSD, statsd...).

It would be nice if there were a way to tell Brakeman "this [$statsd, STATSD, etc.] isn't an Active Record class." But maybe that's too much scope creep (I'm assuming something like this doesn't already exist).

@presidentbeef

Copy link
Copy Markdown
Owner

Writing down for myself... for common methods like count this check can go back to only warning if the target is known to be an ActiveRecord model.

presidentbeef added a commit that referenced this pull request Dec 1, 2025
In versions of Rails starting in 5.0, these methods only take one
argument.

Also reduce false positives reported in #1936
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants