Skip to content

Commit dc46487

Browse files
committed
Add a query for server-side request forgery
1 parent cd33e4d commit dc46487

File tree

6 files changed

+134
-0
lines changed

6 files changed

+134
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Provides default sources, sinks and sanitizers for reasoning about
3+
* server side request forgery, as well as extension points for adding your own.
4+
*/
5+
6+
private import ruby
7+
private import codeql.ruby.ApiGraphs
8+
private import codeql.ruby.CFG
9+
private import codeql.ruby.DataFlow
10+
private import codeql.ruby.dataflow.RemoteFlowSources
11+
private import codeql.ruby.Concepts
12+
private import codeql.ruby.dataflow.Sanitizers
13+
14+
module ServerSideRequestForgery {
15+
/**
16+
* A data flow source for server side request forgery vulnerabilities.
17+
*/
18+
abstract class Source extends DataFlow::Node { }
19+
20+
/**
21+
* A data flow sink for server side request forgery vulnerabilities.
22+
*/
23+
abstract class Sink extends DataFlow::Node { }
24+
25+
/**
26+
* A sanitizer for server side request forgery vulnerabilities.
27+
*/
28+
abstract class Sanitizer extends DataFlow::Node { }
29+
30+
/**
31+
* A sanitizer guard for "URL redirection" vulnerabilities.
32+
*/
33+
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
34+
35+
/** A source of remote user input, considered as a flow source for server side request forgery. */
36+
class RemoteFlowSourceAsSource extends Source {
37+
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
38+
}
39+
40+
/** The URL of an HTTP request, considered as a sink. */
41+
class HttpRequestAsSink extends Sink {
42+
HttpRequestAsSink() { exists(HTTP::Client::Request req | req.getURL() = this) }
43+
}
44+
45+
/** String interpolation with a fixed prefix, considered as a flow sanitizer. */
46+
class StringInterpolationAsSanitizer extends PrefixedStringInterpolation, Sanitizer { }
47+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Provides a taint-tracking configuration for detecting
3+
* "Server side request forgery" vulnerabilities.
4+
*
5+
* Note, for performance reasons: only import this file if `Configuration` is needed,
6+
* otherwise `ServerSideRequestForgeryCustomizations` should be imported instead.
7+
*/
8+
9+
import codeql.ruby.DataFlow::DataFlow::PathGraph
10+
import codeql.ruby.DataFlow
11+
import codeql.ruby.TaintTracking
12+
import ServerSideRequestForgeryCustomizations::ServerSideRequestForgery
13+
import codeql.ruby.dataflow.BarrierGuards
14+
15+
/**
16+
* A taint-tracking configuration for detecting
17+
* "Server side request forgery" vulnerabilities.
18+
*/
19+
class Configuration extends TaintTracking::Configuration {
20+
Configuration() { this = "ServerSideRequestForgery" }
21+
22+
override predicate isSource(DataFlow::Node source) { source instanceof Source }
23+
24+
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
25+
26+
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
27+
28+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
29+
guard instanceof SanitizerGuard or
30+
guard instanceof StringConstCompare or
31+
guard instanceof StringConstArrayInclusionCall
32+
}
33+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @name Server Side Request Forgery
3+
* @description Making a request to a URL that is controlled by user input
4+
* can allow an attacker to forge requests to internal services.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @security-severity TODO
8+
* @precision medium
9+
* @id rb/server-side-request-forgery
10+
* @tags security
11+
* external/cwe/cwe-918
12+
*/
13+
14+
import ruby
15+
import codeql.ruby.Concepts
16+
import codeql.ruby.DataFlow
17+
import codeql.ruby.TaintTracking
18+
import codeql.ruby.dataflow.RemoteFlowSources
19+
import codeql.ruby.security.ServerSideRequestForgeryQuery
20+
21+
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
22+
where config.hasFlowPath(source, sink)
23+
select sink.getNode(), source, sink, "Untrusted HTTP request due to $@.", source.getNode(),
24+
"a user-provided value"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
edges
2+
| ServerSideRequestForgery.rb:9:32:9:37 | call to params : | ServerSideRequestForgery.rb:10:31:10:62 | "#{...}/logins" |
3+
nodes
4+
| ServerSideRequestForgery.rb:9:32:9:37 | call to params : | semmle.label | call to params : |
5+
| ServerSideRequestForgery.rb:10:31:10:62 | "#{...}/logins" | semmle.label | "#{...}/logins" |
6+
subpaths
7+
#select
8+
| ServerSideRequestForgery.rb:10:31:10:62 | "#{...}/logins" | ServerSideRequestForgery.rb:9:32:9:37 | call to params : | ServerSideRequestForgery.rb:10:31:10:62 | "#{...}/logins" | Untrusted HTTP request due to $@. | ServerSideRequestForgery.rb:9:32:9:37 | call to params | a user-provided value |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
queries/security/cwe-918/ServerSideRequestForgery.ql
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
require "Excon"
2+
require "json"
3+
4+
class PostsController < ActionController::Base
5+
def create
6+
user = params[:user_id]
7+
8+
# BAD - user can control the entire URL of the request
9+
users_service_domain = params[:users_service_domain]
10+
response = Excon.post("#{users_service_domain}/logins", body: {user_id: user}).body
11+
token = JSON.parse(response)["token"]
12+
13+
# GOOD - user can only control the suffix of the URL
14+
users_service_path = params[:users_service_path]
15+
response = Excon.post("users-service/#{users_service_path}", body: {user_id: user}).body
16+
token = JSON.parse(response)["token"]
17+
18+
@post = Post.create(params[:post].merge(user_token: token))
19+
render @post
20+
end
21+
end

0 commit comments

Comments
 (0)