-
Notifications
You must be signed in to change notification settings - Fork 1.8k
RB: Experimental query to manually check request verb #9605
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
hmac
merged 34 commits into
github:main
from
thiggy1342:experimental-manually-check-request-verb
Jul 25, 2022
Merged
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
c012c23
rough draft of check request verb query
thiggy1342 7bdec98
draft tests
thiggy1342 6bef71e
tweaks to tests
thiggy1342 8aa2602
trying to hone in on eq comparison and include?
thiggy1342 059c4d3
refine query to use appropriate types
thiggy1342 8b36191
drop precision to low for now
thiggy1342 ecb2114
replace duplicate post with put
thiggy1342 0456870
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 990747c
Limit findings to just those called in Controllers
thiggy1342 f6c4b5c
Merge branch 'experimental-manually-check-request-verb' of https://gi…
thiggy1342 c767f24
narrow query scope
thiggy1342 995f365
just check string literal
thiggy1342 6aab970
refactor query to use cfg and dataflow
thiggy1342 ad7c3e7
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 74d6061
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 b3f1a51
Update tests
thiggy1342 7129002
tweak tests more
thiggy1342 7df7b92
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 2cc7033
use taint config for data flow
thiggy1342 ae63436
add qhelp file
thiggy1342 ee79834
formatting in qhelp
thiggy1342 3dd61ca
formatting query
thiggy1342 62a10e2
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 fc00e56
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 304203a
fix path problem output
thiggy1342 43a9b89
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 8c55a15
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 a10370f
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 cc958dc
Update ruby/ql/src/experimental/manually-check-http-verb/ManuallyChec…
thiggy1342 8fabc06
fix test assertion
thiggy1342 c1a6ca5
add change note
thiggy1342 871b651
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 c2710fb
Update ruby/ql/src/change-notes/2022-07-21-check-http-verb.md
thiggy1342 b4d762f
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
--- | ||
category: newQuery | ||
--- | ||
* Added a new experimental query, `rb/manually-checking-http-verb`, to detect cases when the HTTP verb for an incoming request is checked and then used as part of control flow. |
23 changes: 23 additions & 0 deletions
23
ruby/ql/src/experimental/manually-check-http-verb/ManuallyCheckHttpVerb.qhelp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<!DOCTYPE qhelp PUBLIC | ||
"-//Semmle//qhelp//EN" | ||
"qhelp.dtd"> | ||
<qhelp> | ||
<overview> | ||
<p> | ||
Manually checking the HTTP request verb inside of a controller method can lead to | ||
CSRF bypass if GET or HEAD requests are handled improperly. | ||
</p> | ||
</overview> | ||
<recommendation> | ||
<p> | ||
It is better to use different controller methods for each resource/http verb combination | ||
and configure the Rails routes in your application to call them accordingly. | ||
</p> | ||
</recommendation> | ||
|
||
<references> | ||
<li> | ||
See https://guides.rubyonrails.org/routing.html for more information. | ||
</li> | ||
</references> | ||
</qhelp> |
96 changes: 96 additions & 0 deletions
96
ruby/ql/src/experimental/manually-check-http-verb/ManuallyCheckHttpVerb.ql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/** | ||
* @name Manually checking http verb instead of using built in rails routes and protections | ||
* @description Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods. | ||
* @kind path-problem | ||
* @problem.severity error | ||
* @security-severity 5.0 | ||
* @precision low | ||
* @id rb/manually-checking-http-verb | ||
* @tags security | ||
*/ | ||
|
||
import ruby | ||
import codeql.ruby.DataFlow | ||
import codeql.ruby.controlflow.CfgNodes | ||
import codeql.ruby.frameworks.ActionController | ||
import codeql.ruby.TaintTracking | ||
import DataFlow::PathGraph | ||
|
||
// any `request` calls in an action method | ||
class Request extends DataFlow::CallNode { | ||
Request() { | ||
this.getMethodName() = "request" and | ||
this.asExpr().getExpr().getEnclosingMethod() instanceof ActionControllerActionMethod | ||
} | ||
} | ||
|
||
// `request.env` | ||
class RequestEnvMethod extends DataFlow::CallNode { | ||
RequestEnvMethod() { | ||
this.getMethodName() = "env" and | ||
any(Request r).flowsTo(this.getReceiver()) | ||
} | ||
} | ||
|
||
// `request.request_method` | ||
class RequestRequestMethod extends DataFlow::CallNode { | ||
RequestRequestMethod() { | ||
this.getMethodName() = "request_method" and | ||
any(Request r).flowsTo(this.getReceiver()) | ||
} | ||
} | ||
|
||
// `request.method` | ||
class RequestMethod extends DataFlow::CallNode { | ||
RequestMethod() { | ||
this.getMethodName() = "method" and | ||
any(Request r).flowsTo(this.getReceiver()) | ||
} | ||
} | ||
|
||
// `request.raw_request_method` | ||
class RequestRawRequestMethod extends DataFlow::CallNode { | ||
RequestRawRequestMethod() { | ||
this.getMethodName() = "raw_request_method" and | ||
any(Request r).flowsTo(this.getReceiver()) | ||
} | ||
} | ||
|
||
// `request.request_method_symbol` | ||
class RequestRequestMethodSymbol extends DataFlow::CallNode { | ||
RequestRequestMethodSymbol() { | ||
this.getMethodName() = "request_method_symbol" and | ||
any(Request r).flowsTo(this.getReceiver()) | ||
} | ||
} | ||
|
||
// `request.get?` | ||
class RequestGet extends DataFlow::CallNode { | ||
RequestGet() { | ||
this.getMethodName() = "get?" and | ||
any(Request r).flowsTo(this.getReceiver()) | ||
} | ||
} | ||
|
||
class HttpVerbConfig extends TaintTracking::Configuration { | ||
HttpVerbConfig() { this = "HttpVerbConfig" } | ||
|
||
override predicate isSource(DataFlow::Node source) { | ||
source instanceof RequestMethod or | ||
source instanceof RequestRequestMethod or | ||
source instanceof RequestEnvMethod or | ||
source instanceof RequestRawRequestMethod or | ||
source instanceof RequestRequestMethodSymbol or | ||
source instanceof RequestGet | ||
} | ||
|
||
override predicate isSink(DataFlow::Node sink) { | ||
exists(ExprNodes::ConditionalExprCfgNode c | c.getCondition() = sink.asExpr()) or | ||
exists(ExprNodes::CaseExprCfgNode c | c.getValue() = sink.asExpr()) | ||
} | ||
} | ||
|
||
from HttpVerbConfig config, DataFlow::PathNode source, DataFlow::PathNode sink | ||
where config.hasFlowPath(source, sink) | ||
select sink.getNode(), source, sink, | ||
"Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods." |
32 changes: 32 additions & 0 deletions
32
.../ql/test/query-tests/experimental/manually-check-http-verb/ManuallyCheckHttpVerb.expected
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
edges | ||
| ManuallyCheckHttpVerb.rb:11:14:11:24 | call to env : | ManuallyCheckHttpVerb.rb:11:14:11:42 | ...[...] : | | ||
| ManuallyCheckHttpVerb.rb:11:14:11:42 | ...[...] : | ManuallyCheckHttpVerb.rb:12:8:12:22 | ... == ... | | ||
| ManuallyCheckHttpVerb.rb:19:14:19:35 | call to request_method : | ManuallyCheckHttpVerb.rb:20:8:20:22 | ... == ... | | ||
| ManuallyCheckHttpVerb.rb:27:14:27:27 | call to method : | ManuallyCheckHttpVerb.rb:28:8:28:22 | ... == ... | | ||
| ManuallyCheckHttpVerb.rb:35:14:35:39 | call to raw_request_method : | ManuallyCheckHttpVerb.rb:36:8:36:22 | ... == ... | | ||
| ManuallyCheckHttpVerb.rb:51:16:51:44 | call to request_method_symbol : | ManuallyCheckHttpVerb.rb:52:10:52:23 | ... == ... | | ||
| ManuallyCheckHttpVerb.rb:59:10:59:20 | call to env : | ManuallyCheckHttpVerb.rb:59:10:59:38 | ...[...] | | ||
nodes | ||
| ManuallyCheckHttpVerb.rb:4:8:4:19 | call to get? | semmle.label | call to get? | | ||
| ManuallyCheckHttpVerb.rb:11:14:11:24 | call to env : | semmle.label | call to env : | | ||
| ManuallyCheckHttpVerb.rb:11:14:11:42 | ...[...] : | semmle.label | ...[...] : | | ||
| ManuallyCheckHttpVerb.rb:12:8:12:22 | ... == ... | semmle.label | ... == ... | | ||
| ManuallyCheckHttpVerb.rb:19:14:19:35 | call to request_method : | semmle.label | call to request_method : | | ||
| ManuallyCheckHttpVerb.rb:20:8:20:22 | ... == ... | semmle.label | ... == ... | | ||
| ManuallyCheckHttpVerb.rb:27:14:27:27 | call to method : | semmle.label | call to method : | | ||
| ManuallyCheckHttpVerb.rb:28:8:28:22 | ... == ... | semmle.label | ... == ... | | ||
| ManuallyCheckHttpVerb.rb:35:14:35:39 | call to raw_request_method : | semmle.label | call to raw_request_method : | | ||
| ManuallyCheckHttpVerb.rb:36:8:36:22 | ... == ... | semmle.label | ... == ... | | ||
| ManuallyCheckHttpVerb.rb:51:16:51:44 | call to request_method_symbol : | semmle.label | call to request_method_symbol : | | ||
| ManuallyCheckHttpVerb.rb:52:10:52:23 | ... == ... | semmle.label | ... == ... | | ||
| ManuallyCheckHttpVerb.rb:59:10:59:20 | call to env : | semmle.label | call to env : | | ||
| ManuallyCheckHttpVerb.rb:59:10:59:38 | ...[...] | semmle.label | ...[...] | | ||
subpaths | ||
#select | ||
| ManuallyCheckHttpVerb.rb:4:8:4:19 | call to get? | ManuallyCheckHttpVerb.rb:4:8:4:19 | call to get? | ManuallyCheckHttpVerb.rb:4:8:4:19 | call to get? | Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods. | | ||
| ManuallyCheckHttpVerb.rb:12:8:12:22 | ... == ... | ManuallyCheckHttpVerb.rb:11:14:11:24 | call to env : | ManuallyCheckHttpVerb.rb:12:8:12:22 | ... == ... | Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods. | | ||
| ManuallyCheckHttpVerb.rb:20:8:20:22 | ... == ... | ManuallyCheckHttpVerb.rb:19:14:19:35 | call to request_method : | ManuallyCheckHttpVerb.rb:20:8:20:22 | ... == ... | Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods. | | ||
| ManuallyCheckHttpVerb.rb:28:8:28:22 | ... == ... | ManuallyCheckHttpVerb.rb:27:14:27:27 | call to method : | ManuallyCheckHttpVerb.rb:28:8:28:22 | ... == ... | Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods. | | ||
| ManuallyCheckHttpVerb.rb:36:8:36:22 | ... == ... | ManuallyCheckHttpVerb.rb:35:14:35:39 | call to raw_request_method : | ManuallyCheckHttpVerb.rb:36:8:36:22 | ... == ... | Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods. | | ||
| ManuallyCheckHttpVerb.rb:52:10:52:23 | ... == ... | ManuallyCheckHttpVerb.rb:51:16:51:44 | call to request_method_symbol : | ManuallyCheckHttpVerb.rb:52:10:52:23 | ... == ... | Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods. | | ||
| ManuallyCheckHttpVerb.rb:59:10:59:38 | ...[...] | ManuallyCheckHttpVerb.rb:59:10:59:20 | call to env : | ManuallyCheckHttpVerb.rb:59:10:59:38 | ...[...] | Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods. | |
1 change: 1 addition & 0 deletions
1
ruby/ql/test/query-tests/experimental/manually-check-http-verb/ManuallyCheckHttpVerb.qlref
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
experimental/manually-check-http-verb/ManuallyCheckHttpVerb.ql |
117 changes: 117 additions & 0 deletions
117
ruby/ql/test/query-tests/experimental/manually-check-http-verb/ManuallyCheckHttpVerb.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
class ExampleController < ActionController::Base | ||
# Should find | ||
def example_action | ||
if request.get? | ||
Resource.find(id: params[:example_id]) | ||
end | ||
end | ||
|
||
# Should find | ||
def other_action | ||
method = request.env['REQUEST_METHOD'] | ||
if method == "GET" | ||
Resource.find(id: params[:id]) | ||
end | ||
end | ||
|
||
# Should find | ||
def foo | ||
method = request.request_method | ||
if method == "GET" | ||
Resource.find(id: params[:id]) | ||
end | ||
end | ||
|
||
# Should find | ||
def bar | ||
method = request.method | ||
if method == "GET" | ||
Resource.find(id: params[:id]) | ||
end | ||
end | ||
|
||
# Should find | ||
def baz | ||
method = request.raw_request_method | ||
if method == "GET" | ||
Resource.find(id: params[:id]) | ||
end | ||
end | ||
|
||
# Should not find | ||
def baz2 | ||
method = request.raw_request_method | ||
if some_other_function == "GET" | ||
Resource.find(id: params[:id]) | ||
end | ||
end | ||
|
||
# Should find | ||
def foobarbaz | ||
method = request.request_method_symbol | ||
if method == :GET | ||
Resource.find(id: params[:id]) | ||
end | ||
end | ||
|
||
# Should find | ||
def resource_action | ||
case request.env['REQUEST_METHOD'] | ||
when "GET" | ||
Resource.find(id: params[:id]) | ||
when "POST" | ||
Resource.new(id: params[:id], details: params[:details]) | ||
end | ||
end | ||
|
||
# Should not find | ||
def resource_action | ||
case request.random_method | ||
when "GET" | ||
Resource.find(id: params[:id]) | ||
when "POST" | ||
Resource.new(id: params[:id], details: params[:details]) | ||
end | ||
end | ||
end | ||
|
||
class SafeController < ActionController::Base | ||
# this class should have no hits because controllers rely on conventional Rails routes | ||
def index | ||
Resource.find(id: params[:id]) | ||
end | ||
|
||
def create | ||
Resource.new(id: params[:id], details: params[:details]) | ||
end | ||
|
||
def update | ||
Resource.update(id: params[:id], details: params[:details]) | ||
end | ||
|
||
def delete | ||
s = Resource.find(id: params[:id]) | ||
s.delete | ||
end | ||
end | ||
|
||
# There should be no hits from this class because it does not inherit from ActionController | ||
class NotAController | ||
def example_action | ||
if request.get? | ||
Resource.find(params[:example_id]) | ||
end | ||
end | ||
|
||
def resource_action | ||
case env['REQUEST_METHOD'] | ||
when "GET" | ||
Resource.find(params[:id]) | ||
when "POST" | ||
Resource.new(params[:id], params[:details]) | ||
end | ||
end | ||
end | ||
|
||
class Resource < ActiveRecord::Base | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check notice
Code scanning
Dead code