Skip to content
Merged
Show file tree
Hide file tree
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 Jun 14, 2022
7bdec98
draft tests
thiggy1342 Jun 14, 2022
6bef71e
tweaks to tests
thiggy1342 Jun 14, 2022
8aa2602
trying to hone in on eq comparison and include?
thiggy1342 Jun 18, 2022
059c4d3
refine query to use appropriate types
thiggy1342 Jun 18, 2022
8b36191
drop precision to low for now
thiggy1342 Jun 18, 2022
ecb2114
replace duplicate post with put
thiggy1342 Jun 18, 2022
0456870
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 Jun 18, 2022
990747c
Limit findings to just those called in Controllers
thiggy1342 Jun 21, 2022
f6c4b5c
Merge branch 'experimental-manually-check-request-verb' of https://gi…
thiggy1342 Jun 21, 2022
c767f24
narrow query scope
thiggy1342 Jun 22, 2022
995f365
just check string literal
thiggy1342 Jun 22, 2022
6aab970
refactor query to use cfg and dataflow
thiggy1342 Jul 8, 2022
ad7c3e7
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 Jul 11, 2022
74d6061
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 Jul 12, 2022
b3f1a51
Update tests
thiggy1342 Jul 13, 2022
7129002
tweak tests more
thiggy1342 Jul 13, 2022
7df7b92
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 Jul 13, 2022
2cc7033
use taint config for data flow
thiggy1342 Jul 14, 2022
ae63436
add qhelp file
thiggy1342 Jul 14, 2022
ee79834
formatting in qhelp
thiggy1342 Jul 14, 2022
3dd61ca
formatting query
thiggy1342 Jul 14, 2022
62a10e2
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 Jul 14, 2022
fc00e56
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 Jul 18, 2022
304203a
fix path problem output
thiggy1342 Jul 19, 2022
43a9b89
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 Jul 19, 2022
8c55a15
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 Jul 20, 2022
a10370f
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 Jul 20, 2022
cc958dc
Update ruby/ql/src/experimental/manually-check-http-verb/ManuallyChec…
thiggy1342 Jul 21, 2022
8fabc06
fix test assertion
thiggy1342 Jul 21, 2022
c1a6ca5
add change note
thiggy1342 Jul 21, 2022
871b651
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 Jul 21, 2022
c2710fb
Update ruby/ql/src/change-notes/2022-07-21-check-http-verb.md
thiggy1342 Jul 22, 2022
b4d762f
Merge branch 'main' into experimental-manually-check-request-verb
thiggy1342 Jul 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ruby/ql/src/change-notes/2022-07-21-check-http-verb.md
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.
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>
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 {

Check notice

Code scanning

Dead code

Code is dead
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."
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. |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
experimental/manually-check-http-verb/ManuallyCheckHttpVerb.ql
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