Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f84035a

Browse files
committed
Ruby: add rb/sensitive-get-query query
1 parent c7e3051 commit f84035a

13 files changed

Lines changed: 295 additions & 0 deletions

File tree

ruby/ql/lib/codeql/ruby/Concepts.qll

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,11 @@ module HTTP {
250250

251251
/** Gets a string that identifies the framework used for this route setup. */
252252
string getFramework() { result = super.getFramework() }
253+
254+
/**
255+
* Gets the HTTP method name, in lowercase, that this handler will respond to.
256+
*/
257+
string getHttpMethod() { result = super.getHttpMethod() }
253258
}
254259

255260
/** Provides a class for modeling new HTTP routing APIs. */
@@ -287,6 +292,11 @@ module HTTP {
287292

288293
/** Gets a string that identifies the framework used for this route setup. */
289294
abstract string getFramework();
295+
296+
/**
297+
* Gets the HTTP method name, in all caps, that this handler will respond to.
298+
*/
299+
abstract string getHttpMethod();
290300
}
291301
}
292302

@@ -343,6 +353,12 @@ module HTTP {
343353

344354
/** Gets a string that identifies the framework used for this route setup. */
345355
string getFramework() { result = super.getFramework() }
356+
357+
/**
358+
* Gets an HTTP method name, in all caps, that this handler will respond to.
359+
* Handlers can potentially respond to multiple HTTP methods.
360+
*/
361+
string getAnHttpMethod() { result = super.getAnHttpMethod() }
346362
}
347363

348364
/** Provides a class for modeling new HTTP request handlers. */
@@ -364,6 +380,12 @@ module HTTP {
364380

365381
/** Gets a string that identifies the framework used for this request handler. */
366382
abstract string getFramework();
383+
384+
/**
385+
* Gets an HTTP method name, in all caps, that this handler will respond to.
386+
* Handlers can potentially respond to multiple HTTP methods.
387+
*/
388+
abstract string getAnHttpMethod();
367389
}
368390
}
369391

@@ -378,6 +400,8 @@ module HTTP {
378400
}
379401

380402
override string getFramework() { result = rs.getFramework() }
403+
404+
override string getAnHttpMethod() { result = rs.getHttpMethod() }
381405
}
382406

383407
/** A parameter that will receive parts of the url when handling an incoming request. */

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ class ActionControllerActionMethod extends Method, HTTP::Server::RequestHandler:
6767

6868
override string getFramework() { result = "ActionController" }
6969

70+
override string getAnHttpMethod() { result = this.getARoute().getHttpMethod() }
71+
7072
/** Gets a call to render from within this method. */
7173
RenderCall getARenderCall() { result.getParent+() = this }
7274

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ private class GraphqlSchemaResolverClass extends ClassDeclaration {
8585
}
8686
}
8787

88+
/** Gets an HTTP method that is supported for querying a GraphQL server. */
89+
private string getASupportedHTTPMethod() { result = ["get", "post"] }
90+
8891
/**
8992
* A `ClassDeclaration` for a class that extends `GraphQL::Schema::Object`.
9093
* For example,
@@ -173,6 +176,8 @@ class GraphqlResolveMethod extends Method, HTTP::Server::RequestHandler::Range {
173176

174177
override string getFramework() { result = "GraphQL" }
175178

179+
override string getAnHttpMethod() { result = getASupportedHTTPMethod() }
180+
176181
/** Gets the mutation class containing this method. */
177182
GraphqlResolvableClass getMutationClass() { result = resolvableClass }
178183
}
@@ -220,6 +225,8 @@ class GraphqlLoadMethod extends Method, HTTP::Server::RequestHandler::Range {
220225

221226
override string getFramework() { result = "GraphQL" }
222227

228+
override string getAnHttpMethod() { result = getASupportedHTTPMethod() }
229+
223230
/** Gets the mutation class containing this method. */
224231
GraphqlResolvableClass getMutationClass() { result = resolvableClass }
225232
}
@@ -389,6 +396,8 @@ class GraphqlFieldResolutionMethod extends Method, HTTP::Server::RequestHandler:
389396

390397
override string getFramework() { result = "GraphQL" }
391398

399+
override string getAnHttpMethod() { result = getASupportedHTTPMethod() }
400+
392401
/** Gets the class containing this method. */
393402
GraphqlSchemaObjectClass getGraphqlClass() { result = schemaObjectClass }
394403
}

ruby/ql/lib/codeql/ruby/security/SensitiveActions.qll

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,130 @@
1111

1212
private import codeql.ruby.AST
1313
private import codeql.ruby.DataFlow
14+
import codeql.ruby.security.internal.SensitiveDataHeuristics
15+
private import HeuristicNames
16+
17+
/** An expression that might contain sensitive data. */
18+
cached
19+
abstract class SensitiveExpr extends Expr {
20+
/** Gets a human-readable description of this expression for use in alert messages. */
21+
cached
22+
abstract string describe();
23+
24+
/** Gets a classification of the kind of sensitive data this expression might contain. */
25+
cached
26+
abstract SensitiveDataClassification getClassification();
27+
}
28+
29+
/** A method call that might produce sensitive data. */
30+
class SensitiveCall extends SensitiveExpr, MethodCall {
31+
SensitiveDataClassification classification;
32+
33+
SensitiveCall() {
34+
classification = this.getMethodName().(SensitiveDataMethodName).getClassification()
35+
or
36+
// This is particularly to pick up methods with an argument like "password", which
37+
// may indicate a lookup.
38+
exists(string s | this.getAnArgument().getConstantValue().isStringlikeValue(s) |
39+
nameIndicatesSensitiveData(s, classification)
40+
)
41+
}
42+
43+
override string describe() { result = "a call to " + this.getMethodName() }
44+
45+
override SensitiveDataClassification getClassification() { result = classification }
46+
}
47+
48+
/** An access to a variable or hash value that might contain sensitive data. */
49+
abstract class SensitiveVariableAccess extends SensitiveExpr {
50+
string name;
51+
52+
SensitiveVariableAccess() {
53+
this.(VariableAccess).getVariable().hasName(name)
54+
or
55+
this.(ElementReference).getAnArgument().getConstantValue().isStringlikeValue(name)
56+
}
57+
58+
override string describe() { result = "an access to " + name }
59+
}
60+
61+
/** A write to a location that might contain sensitive data. */
62+
abstract class SensitiveWrite extends DataFlow::Node { }
63+
64+
/**
65+
* Holds if `node` is a write to a variable or hash value named `name`.
66+
*
67+
* Helper predicate factored out for performance,
68+
* to filter `name` as much as possible before using it in
69+
* regex matching.
70+
*/
71+
pragma[nomagic]
72+
private predicate writesProperty(DataFlow::Node node, string name) {
73+
exists(VariableWriteAccess vwa | vwa.getVariable().getName() = name |
74+
node.asExpr().getExpr() = vwa
75+
)
76+
or
77+
// hash value assignment
78+
node.(DataFlow::CallNode).getMethodName() = "[]=" and
79+
node.(DataFlow::CallNode).getArgument(0).asExpr().getConstantValue().isStringlikeValue(name)
80+
}
81+
82+
/** A write to a variable or property that might contain sensitive data. */
83+
private class BasicSensitiveWrite extends SensitiveWrite {
84+
SensitiveDataClassification classification;
85+
86+
BasicSensitiveWrite() {
87+
exists(string name |
88+
/*
89+
* PERFORMANCE OPTIMISATION:
90+
* `nameIndicatesSensitiveData` performs a `regexpMatch` on `name`.
91+
* To carry out a regex match, we must first compute the Cartesian product
92+
* of all possible `name`s and regexes, then match.
93+
* To keep this product as small as possible,
94+
* we want to filter `name` as much as possible before the product.
95+
*
96+
* Do this by factoring out a helper predicate containing the filtering
97+
* logic that restricts `name`. This helper predicate will get picked first
98+
* in the join order, since it is the only call here that binds `name`.
99+
*/
100+
101+
writesProperty(this, name) and
102+
nameIndicatesSensitiveData(name, classification)
103+
)
104+
}
105+
106+
/** Gets a classification of the kind of sensitive data the write might handle. */
107+
SensitiveDataClassification getClassification() { result = classification }
108+
}
109+
110+
/** An access to a variable or hash value that might contain sensitive data. */
111+
private class BasicSensitiveVariableAccess extends SensitiveVariableAccess {
112+
SensitiveDataClassification classification;
113+
114+
BasicSensitiveVariableAccess() { nameIndicatesSensitiveData(name, classification) }
115+
116+
override SensitiveDataClassification getClassification() { result = classification }
117+
}
118+
119+
/** A method name that suggests it may be sensitive. */
120+
abstract class SensitiveMethodName extends string {
121+
SensitiveMethodName() { this = any(MethodBase m).getName() }
122+
}
123+
124+
/** A method name that suggests it may produce sensitive data. */
125+
abstract class SensitiveDataMethodName extends SensitiveMethodName {
126+
/** Gets a classification of the kind of sensitive data this method may produce. */
127+
abstract SensitiveDataClassification getClassification();
128+
}
129+
130+
/** A method name that might return sensitive credential data. */
131+
class CredentialsMethodName extends SensitiveDataMethodName {
132+
SensitiveDataClassification classification;
133+
134+
CredentialsMethodName() { nameIndicatesSensitiveData(this, classification) }
135+
136+
override SensitiveDataClassification getClassification() { result = classification }
137+
}
14138

15139
/**
16140
* A sensitive action, such as transfer of sensitive data.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: newQuery
3+
---
4+
* Added a new query, `rb/sensitive-get-query`, to detect cases where sensitive data is read from the query parameters of an HTTP `GET` request.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
<overview>
4+
<p>
5+
Sensitive information such as user passwords should not be transmitted within the query string of the requested URL.
6+
Sensitive information within URLs may be logged in various locations, including the user's browser, the web server,
7+
and any forward or reverse proxy servers between the two endpoints. URLs may also be displayed on-screen, bookmarked
8+
or emailed around by users. They may be disclosed to third parties via the Referer header when any off-site links are
9+
followed. Placing sensitive information into the URL therefore increases the risk that it will be captured by an attacker.
10+
</p>
11+
</overview>
12+
13+
<recommendation>
14+
<p>
15+
Use HTTP POST to send sensitive information as part of the request body; for example, as form data.
16+
</p>
17+
</recommendation>
18+
19+
<example>
20+
<p>
21+
The following example shows two route handlers that both receive a username and a password.
22+
The first receives this sensitive information from the query parameters of a GET request, which is
23+
transmitted in the URL. The second receives this sensitive information from the request body of a POST request.
24+
</p>
25+
<sample src="examples/routes.rb" />
26+
<sample src="examples/users_controller.rb" />
27+
</example>
28+
29+
<references>
30+
<li>
31+
CWE:
32+
<a href="https://cwe.mitre.org/data/definitions/598.html">CWE-598: Use of GET Request Method with Sensitive Query Strings</a>
33+
</li>
34+
<li>
35+
PortSwigger (Burp):
36+
<a href="https://portswigger.net/kb/issues/00400300_password-submitted-using-get-method">Password Submitted using GET Method</a>
37+
</li>
38+
<li>
39+
OWASP:
40+
<a href="https://owasp.org/www-community/vulnerabilities/Information_exposure_through_query_strings_in_url">Information Exposure through Query Strings in URL</a>
41+
</li>
42+
</references>
43+
</qhelp>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* @name Sensitive data read from GET request
3+
* @description Placing sensitive data in a GET request increases the risk of
4+
* the data being exposed to an attacker.
5+
* @kind problem
6+
* @problem.severity warning
7+
* @security-severity 6.5
8+
* @precision high
9+
* @id rb/sensitive-get-query
10+
* @tags security
11+
* external/cwe/cwe-598
12+
*/
13+
14+
import ruby
15+
private import codeql.ruby.DataFlow
16+
private import codeql.ruby.security.SensitiveActions
17+
private import codeql.ruby.Concepts
18+
private import codeql.ruby.frameworks.ActionDispatch
19+
private import codeql.ruby.frameworks.ActionController
20+
private import codeql.ruby.frameworks.core.Array
21+
22+
// Local flow augmented with flow through element references
23+
private predicate localFlowWithElementReference(DataFlow::LocalSourceNode src, DataFlow::Node to) {
24+
src.flowsTo(to)
25+
or
26+
exists(DataFlow::Node midRecv, DataFlow::LocalSourceNode mid, ElementReference ref |
27+
src.flowsTo(midRecv) and
28+
midRecv.asExpr().getExpr() = ref.getReceiver() and
29+
mid.asExpr().getExpr() = ref
30+
|
31+
localFlowWithElementReference(mid, to)
32+
)
33+
}
34+
35+
from
36+
HTTP::Server::RequestHandler handler, HTTP::Server::RequestInputAccess input,
37+
DataFlow::Node sensitive
38+
where
39+
handler.getAnHttpMethod() = "get" and
40+
input.asExpr().getExpr().getEnclosingMethod() = handler and
41+
sensitive.asExpr().getExpr() instanceof SensitiveExpr and
42+
localFlowWithElementReference(input, sensitive)
43+
select input, "$@ for GET requests uses query parameter as sensitive data.", handler,
44+
"Request handler"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Rails.application.routes.draw do
2+
get "users/login", to: "#login_get" # BAD: sensitive data transmitted through query parameters
3+
post "users/login", to: "users#login_post" # GOOD: sensitive data transmitted in the request body
4+
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class UsersController < ActionController::Base
2+
def login_get
3+
password = params[:password]
4+
authenticate_user(params[:username], password)
5+
end
6+
7+
def login_post
8+
password = params[:password]
9+
authenticate_user(params[:username], password)
10+
end
11+
12+
private
13+
def authenticate_user(username, password)
14+
# ... authenticate the user here
15+
end
16+
end
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| app/controllers/users_controller.rb:4:16:4:21 | call to params | $@ for GET requests uses query parameter as sensitive data. | app/controllers/users_controller.rb:3:3:6:5 | login_get | Request handler |
2+
| app/controllers/users_controller.rb:5:23:5:28 | call to params | $@ for GET requests uses query parameter as sensitive data. | app/controllers/users_controller.rb:3:3:6:5 | login_get | Request handler |

0 commit comments

Comments
 (0)