Skip to content

Commit 83705c5

Browse files
authored
Merge pull request #306 from github/hmac-outgoing-http
Model outgoing HTTP requests as remote flow sources
2 parents 21e31a4 + 5826f2c commit 83705c5

File tree

7 files changed

+142
-0
lines changed

7 files changed

+142
-0
lines changed

ql/lib/codeql/ruby/Concepts.qll

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,49 @@ module HTTP {
404404
}
405405
}
406406
}
407+
408+
/** Provides classes for modeling HTTP clients. */
409+
module Client {
410+
/**
411+
* A method call that makes an outgoing HTTP request.
412+
*
413+
* Extend this class to refine existing API models. If you want to model new APIs,
414+
* extend `Request::Range` instead.
415+
*/
416+
class Request extends MethodCall instanceof Request::Range {
417+
/** Gets a node which returns the body of the response */
418+
DataFlow::Node getResponseBody() { result = super.getResponseBody() }
419+
420+
/** Gets a string that identifies the framework used for this request. */
421+
string getFramework() { result = super.getFramework() }
422+
}
423+
424+
/** Provides a class for modeling new HTTP requests. */
425+
module Request {
426+
/**
427+
* A method call that makes an outgoing HTTP request.
428+
*
429+
* Extend this class to model new APIs. If you want to refine existing API models,
430+
* extend `Request` instead.
431+
*/
432+
abstract class Range extends MethodCall {
433+
/** Gets a node which returns the body of the response */
434+
abstract DataFlow::Node getResponseBody();
435+
436+
/** Gets a string that identifies the framework used for this request. */
437+
abstract string getFramework();
438+
}
439+
}
440+
441+
/** The response body from an outgoing HTTP request, considered as a remote flow source */
442+
private class RequestResponseBody extends RemoteFlowSource::Range, DataFlow::Node {
443+
Request request;
444+
445+
RequestResponseBody() { this = request.getResponseBody() }
446+
447+
override string getSourceType() { result = request.getFramework() }
448+
}
449+
}
407450
}
408451

409452
/**

ql/lib/codeql/ruby/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ private import codeql.ruby.frameworks.ActiveRecord
77
private import codeql.ruby.frameworks.ActionView
88
private import codeql.ruby.frameworks.StandardLibrary
99
private import codeql.ruby.frameworks.Files
10+
private import codeql.ruby.frameworks.HTTPClients
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/**
2+
* Helper file that imports all HTTP clients.
3+
*/
4+
5+
private import codeql.ruby.frameworks.http_clients.NetHTTP
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
private import codeql.ruby.AST
2+
private import codeql.ruby.Concepts
3+
private import codeql.ruby.dataflow.RemoteFlowSources
4+
private import codeql.ruby.ApiGraphs
5+
private import codeql.ruby.dataflow.internal.DataFlowPublic
6+
7+
/**
8+
* A `Net::HTTP` call which initiates an HTTP request.
9+
* ```ruby
10+
* Net::HTTP.get("http://example.com/")
11+
* Net::HTTP.post("http://example.com/", "some_data")
12+
* req = Net::HTTP.new("example.com")
13+
* response = req.get("/")
14+
* ```
15+
*/
16+
class NetHTTPRequest extends HTTP::Client::Request::Range {
17+
private DataFlow::CallNode request;
18+
private DataFlow::Node responseBody;
19+
20+
NetHTTPRequest() {
21+
exists(API::Node requestNode, string method |
22+
request = requestNode.getAnImmediateUse() and
23+
this = request.asExpr().getExpr()
24+
|
25+
// Net::HTTP.get(...)
26+
method = "get" and
27+
requestNode = API::getTopLevelMember("Net").getMember("HTTP").getReturn(method) and
28+
responseBody = request
29+
or
30+
// Net::HTTP.post(...).body
31+
method in ["post", "post_form"] and
32+
requestNode = API::getTopLevelMember("Net").getMember("HTTP").getReturn(method) and
33+
responseBody = requestNode.getAMethodCall(["body", "read_body", "entity"])
34+
or
35+
// Net::HTTP.new(..).get(..).body
36+
method in [
37+
"get", "get2", "request_get", "head", "head2", "request_head", "delete", "put", "patch",
38+
"post", "post2", "request_post", "request"
39+
] and
40+
requestNode = API::getTopLevelMember("Net").getMember("HTTP").getInstance().getReturn(method) and
41+
responseBody = requestNode.getAMethodCall(["body", "read_body", "entity"])
42+
)
43+
}
44+
45+
/**
46+
* Gets the node representing the URL of the request.
47+
* Currently unused, but may be useful in future, e.g. to filter out certain requests.
48+
*/
49+
DataFlow::Node getURLArgument() { result = request.getArgument(0) }
50+
51+
override DataFlow::Node getResponseBody() { result = responseBody }
52+
53+
override string getFramework() { result = "Net::HTTP" }
54+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
| NetHTTP.rb:4:1:4:18 | call to get | NetHTTP.rb:4:1:4:18 | call to get |
2+
| NetHTTP.rb:6:8:6:50 | call to post | NetHTTP.rb:7:1:7:9 | call to body |
3+
| NetHTTP.rb:6:8:6:50 | call to post | NetHTTP.rb:8:1:8:14 | call to read_body |
4+
| NetHTTP.rb:6:8:6:50 | call to post | NetHTTP.rb:9:1:9:11 | call to entity |
5+
| NetHTTP.rb:13:6:13:17 | call to get | NetHTTP.rb:18:1:18:7 | call to body |
6+
| NetHTTP.rb:14:6:14:18 | call to post | NetHTTP.rb:19:1:19:12 | call to read_body |
7+
| NetHTTP.rb:15:6:15:17 | call to put | NetHTTP.rb:20:1:20:9 | call to entity |
8+
| NetHTTP.rb:24:3:24:33 | call to get | NetHTTP.rb:27:1:27:28 | call to body |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import codeql.ruby.frameworks.http_clients.NetHTTP
2+
import codeql.ruby.DataFlow
3+
4+
query DataFlow::Node netHTTPRequests(NetHTTPRequest e) { result = e.getResponseBody() }
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
require "net/http"
2+
3+
uri = URI.parse("https://example.com")
4+
Net::HTTP.get(uri)
5+
6+
resp = Net::HTTP.post(URI.parse(uri), "some_body")
7+
resp.body
8+
resp.read_body
9+
resp.entity
10+
11+
req = Net::HTTP.new("https://example.com")
12+
13+
r1 = req.get("/")
14+
r2 = req.post("/")
15+
r3 = req.put("/")
16+
r4 = req.patch("/")
17+
18+
r1.body
19+
r2.read_body
20+
r3.entity
21+
r4.foo
22+
23+
def get(domain, path)
24+
Net::HTTP.new(domain).get(path)
25+
end
26+
27+
get("example.com", "/").body

0 commit comments

Comments
 (0)