Skip to content

Commit 9613ff7

Browse files
authored
Merge pull request github#7611 from github/ruby/protect_from_forgery-without-exception
Ruby: flag up `protect_from_forgery` calls without an exception strategy
2 parents caab1c3 + 0aab670 commit 9613ff7

File tree

8 files changed

+88
-9
lines changed

8 files changed

+88
-9
lines changed

ruby/ql/lib/codeql/ruby/frameworks/ActionController.qll

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,3 +297,23 @@ class ActionControllerSkipForgeryProtectionCall extends CSRFProtectionSetting::R
297297

298298
override boolean getVerificationSetting() { result = false }
299299
}
300+
301+
/**
302+
* A call to `protect_from_forgery`.
303+
*/
304+
private class ActionControllerProtectFromForgeryCall extends CSRFProtectionSetting::Range {
305+
private ActionControllerContextCall callExpr;
306+
307+
ActionControllerProtectFromForgeryCall() {
308+
callExpr = this.asExpr().getExpr() and
309+
callExpr.getMethodName() = "protect_from_forgery"
310+
}
311+
312+
private string getWithValueText() { result = callExpr.getKeywordArgument("with").getValueText() }
313+
314+
// Calls without `with: :exception` can allow for bypassing CSRF protection
315+
// in some scenarios.
316+
override boolean getVerificationSetting() {
317+
if this.getWithValueText() = "exception" then result = true else result = false
318+
}
319+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
lgtm,codescanning
5+
* The query `rb/csrf-protection-disabled` has been extended to find calls to the Rails method `protect_from_forgery` that may weaken CSRF protection.

ruby/ql/src/queries/security/cwe-352/CSRFProtectionDisabled.qhelp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,23 @@
5050
<code>skip_before_action</code>.
5151
</p>
5252

53+
<p>
54+
Care should be taken when using the Rails
55+
<code>protect_from_forgery</code> method to prevent CSRF. The default
56+
behaviour of this method is to null the session when an invalid CSRF token
57+
is provided. This may not be sufficient to avoid a CSRF vulnerability -
58+
for example if parts of the session are memoized. Calling
59+
<code>protect_from_forgery with: :exception</code> can help to avoid this
60+
by raising an exception on an invalid CSRF token instead.
61+
</p>
62+
5363
</example>
5464

5565
<references>
5666
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Cross-site_request_forgery">Cross-site request forgery</a></li>
5767
<li>OWASP: <a href="https://owasp.org/www-community/attacks/csrf">Cross-site request forgery</a></li>
5868
<li>Securing Rails Applications: <a href="https://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf">Cross-Site Request Forgery (CSRF)</a></li>
69+
<li>Veracode: <a href="https://www.veracode.com/blog/managing-appsec/when-rails-protectfromforgery-fails">When Rails' protect_from_forgery Fails</a>.</li>
5970
</references>
6071

6172
</qhelp>

ruby/ql/src/queries/security/cwe-352/CSRFProtectionDisabled.ql

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
2-
* @name CSRF protection disabled
3-
* @description Disabling CSRF protection makes the application vulnerable to
4-
* a Cross-Site Request Forgery (CSRF) attack.
2+
* @name CSRF protection weakened or disabled
3+
* @description Disabling or weakening CSRF protection may make the application
4+
* vulnerable to a Cross-Site Request Forgery (CSRF) attack.
55
* @kind problem
66
* @problem.severity warning
77
* @security-severity 8.8
@@ -16,4 +16,4 @@ import codeql.ruby.Concepts
1616

1717
from CSRFProtectionSetting s
1818
where s.getVerificationSetting() = false
19-
select s, "Potential CSRF vulnerability due to forgery protection being disabled."
19+
select s, "Potential CSRF vulnerability due to forgery protection being disabled or weakened."
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
| railsapp/app/controllers/users_controller.rb:4:3:4:47 | call to skip_before_action | Potential CSRF vulnerability due to forgery protection being disabled. |
2-
| railsapp/config/application.rb:15:5:15:53 | call to allow_forgery_protection= | Potential CSRF vulnerability due to forgery protection being disabled. |
3-
| railsapp/config/environments/development.rb:5:3:5:51 | call to allow_forgery_protection= | Potential CSRF vulnerability due to forgery protection being disabled. |
4-
| railsapp/config/environments/production.rb:5:3:5:51 | call to allow_forgery_protection= | Potential CSRF vulnerability due to forgery protection being disabled. |
1+
| railsapp/app/controllers/application_controller.rb:5:3:5:22 | call to protect_from_forgery | Potential CSRF vulnerability due to forgery protection being disabled or weakened. |
2+
| railsapp/app/controllers/users_controller.rb:4:3:4:47 | call to skip_before_action | Potential CSRF vulnerability due to forgery protection being disabled or weakened. |
3+
| railsapp/config/application.rb:15:5:15:53 | call to allow_forgery_protection= | Potential CSRF vulnerability due to forgery protection being disabled or weakened. |
4+
| railsapp/config/environments/development.rb:5:3:5:51 | call to allow_forgery_protection= | Potential CSRF vulnerability due to forgery protection being disabled or weakened. |
5+
| railsapp/config/environments/production.rb:5:3:5:51 | call to allow_forgery_protection= | Potential CSRF vulnerability due to forgery protection being disabled or weakened. |
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,20 @@
11
class ApplicationController < ActionController::Base
2+
3+
# BAD: `protect_from_forgery` without `with: :exception` can expose an
4+
# application to CSRF attacks in some circumstances
5+
protect_from_forgery
6+
7+
before_action authz_guard
8+
9+
def current_user
10+
@current_user ||= User.find_by_id(session[:user_id])
11+
end
12+
13+
def logged_in?
14+
!current_user.nil?
15+
end
16+
17+
def authz_guard
18+
render(plain: "not logged in") unless logged_in?
19+
end
220
end
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
class ArticlesController < ApplicationController
2+
prepend_before_action :user_authored_article?, only: [:delete_authored_article, :change_title]
3+
4+
# GOOD: `with: :exception` provides more effective CSRF protection than
5+
# `with: :null_session` or `with: :reset_session`.
6+
protect_from_forgery with: :exception, only: [:change_title]
7+
8+
def delete_authored_article
9+
article.destroy
10+
end
11+
12+
def change_title
13+
article.title = params[:article_title]
14+
article.save!
15+
end
16+
17+
def article
18+
@article ||= Article.find(params[:article_id])
19+
end
20+
21+
def user_authored_article?
22+
@article.author_id = current_user.id
23+
end
24+
end

ruby/ql/test/query-tests/security/cwe-352/railsapp/app/controllers/users_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ class UsersController < ApplicationController
44
skip_before_action :verify_authenticity_token
55

66
def change_email
7-
user = User.find_by(name: params[:user_name])
7+
user = current_user
88
user.email = params[:new_email]
99
user.save!
1010
end

0 commit comments

Comments
 (0)