Skip to content

Commit 85cb3e0

Browse files
Merge pull request #20762 from joefarebrother/go-insecure-cookie
Go: Promote non-httponly cookie query, and add insecure cookie query
2 parents 26e5320 + cece73b commit 85cb3e0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1548
-1156
lines changed

go/ql/integration-tests/query-suite/go-code-scanning.qls.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ ql/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXss.ql
1212
ql/go/ql/src/Security/CWE-079/ReflectedXss.ql
1313
ql/go/ql/src/Security/CWE-089/SqlInjection.ql
1414
ql/go/ql/src/Security/CWE-089/StringBreak.ql
15+
ql/go/ql/src/Security/CWE-1004/CookieWithoutHttpOnly.ql
1516
ql/go/ql/src/Security/CWE-190/AllocationSizeOverflow.ql
1617
ql/go/ql/src/Security/CWE-209/StackTraceExposure.ql
1718
ql/go/ql/src/Security/CWE-295/DisabledCertificateCheck.ql
@@ -26,6 +27,7 @@ ql/go/ql/src/Security/CWE-347/MissingJwtSignatureCheck.ql
2627
ql/go/ql/src/Security/CWE-352/ConstantOauth2State.ql
2728
ql/go/ql/src/Security/CWE-601/BadRedirectCheck.ql
2829
ql/go/ql/src/Security/CWE-601/OpenUrlRedirect.ql
30+
ql/go/ql/src/Security/CWE-614/CookieWithoutSecure.ql
2931
ql/go/ql/src/Security/CWE-640/EmailInjection.ql
3032
ql/go/ql/src/Security/CWE-643/XPathInjection.ql
3133
ql/go/ql/src/Security/CWE-681/IncorrectIntegerConversionQuery.ql

go/ql/integration-tests/query-suite/go-security-and-quality.qls.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ ql/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXss.ql
3434
ql/go/ql/src/Security/CWE-079/ReflectedXss.ql
3535
ql/go/ql/src/Security/CWE-089/SqlInjection.ql
3636
ql/go/ql/src/Security/CWE-089/StringBreak.ql
37+
ql/go/ql/src/Security/CWE-1004/CookieWithoutHttpOnly.ql
3738
ql/go/ql/src/Security/CWE-117/LogInjection.ql
3839
ql/go/ql/src/Security/CWE-190/AllocationSizeOverflow.ql
3940
ql/go/ql/src/Security/CWE-209/StackTraceExposure.ql
@@ -49,6 +50,7 @@ ql/go/ql/src/Security/CWE-347/MissingJwtSignatureCheck.ql
4950
ql/go/ql/src/Security/CWE-352/ConstantOauth2State.ql
5051
ql/go/ql/src/Security/CWE-601/BadRedirectCheck.ql
5152
ql/go/ql/src/Security/CWE-601/OpenUrlRedirect.ql
53+
ql/go/ql/src/Security/CWE-614/CookieWithoutSecure.ql
5254
ql/go/ql/src/Security/CWE-640/EmailInjection.ql
5355
ql/go/ql/src/Security/CWE-643/XPathInjection.ql
5456
ql/go/ql/src/Security/CWE-681/IncorrectIntegerConversionQuery.ql

go/ql/integration-tests/query-suite/go-security-extended.qls.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ ql/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXss.ql
1212
ql/go/ql/src/Security/CWE-079/ReflectedXss.ql
1313
ql/go/ql/src/Security/CWE-089/SqlInjection.ql
1414
ql/go/ql/src/Security/CWE-089/StringBreak.ql
15+
ql/go/ql/src/Security/CWE-1004/CookieWithoutHttpOnly.ql
1516
ql/go/ql/src/Security/CWE-117/LogInjection.ql
1617
ql/go/ql/src/Security/CWE-190/AllocationSizeOverflow.ql
1718
ql/go/ql/src/Security/CWE-209/StackTraceExposure.ql
@@ -27,6 +28,7 @@ ql/go/ql/src/Security/CWE-347/MissingJwtSignatureCheck.ql
2728
ql/go/ql/src/Security/CWE-352/ConstantOauth2State.ql
2829
ql/go/ql/src/Security/CWE-601/BadRedirectCheck.ql
2930
ql/go/ql/src/Security/CWE-601/OpenUrlRedirect.ql
31+
ql/go/ql/src/Security/CWE-614/CookieWithoutSecure.ql
3032
ql/go/ql/src/Security/CWE-640/EmailInjection.ql
3133
ql/go/ql/src/Security/CWE-643/XPathInjection.ql
3234
ql/go/ql/src/Security/CWE-681/IncorrectIntegerConversionQuery.ql

go/ql/integration-tests/query-suite/not_included_in_qls.expected

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ ql/go/ql/src/Security/CWE-079/StoredXss.ql
99
ql/go/ql/src/Security/CWE-798/HardcodedCredentials.ql
1010
ql/go/ql/src/definitions.ql
1111
ql/go/ql/src/experimental/CWE-090/LDAPInjection.ql
12-
ql/go/ql/src/experimental/CWE-1004/CookieWithoutHttpOnly.ql
1312
ql/go/ql/src/experimental/CWE-203/Timing.ql
1413
ql/go/ql/src/experimental/CWE-285/PamAuthBypass.ql
1514
ql/go/ql/src/experimental/CWE-287/ImproperLdapAuth.ql

go/ql/lib/go.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import semmle.go.frameworks.ElazarlGoproxy
4141
import semmle.go.frameworks.Email
4242
import semmle.go.frameworks.Encoding
4343
import semmle.go.frameworks.Fasthttp
44+
import semmle.go.frameworks.Gin
4445
import semmle.go.frameworks.GinCors
4546
import semmle.go.frameworks.Glog
4647
import semmle.go.frameworks.GoJose

go/ql/lib/semmle/go/concepts/HTTP.qll

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,4 +380,96 @@ module Http {
380380
/** Gets a node that is used in a check that is tested before this handler is run. */
381381
predicate guardedBy(DataFlow::Node check) { super.guardedBy(check) }
382382
}
383+
384+
/** Provides a class for modeling new HTTP response cookie write APIs. */
385+
module CookieWrite {
386+
/**
387+
* A write of an HTTP Cookie to an HTTP response.
388+
*
389+
* Extend this class to model new APIs. If you want to refine existing API models,
390+
* extend `HTTP::CookieWrite` instead.
391+
*/
392+
abstract class Range extends DataFlow::Node {
393+
/** Gets the name of the cookie written. */
394+
abstract DataFlow::Node getName();
395+
396+
/** Gets the value of the cookie written. */
397+
abstract DataFlow::Node getValue();
398+
399+
/** Gets the `Secure` attribute of the cookie written. */
400+
abstract DataFlow::Node getSecure();
401+
402+
/** Gets the `HttpOnly` attribute of the cookie written. */
403+
abstract DataFlow::Node getHttpOnly();
404+
}
405+
}
406+
407+
/**
408+
* A write of an HTTP Cookie to an HTTP response.
409+
*
410+
* Extend this class to refine existing API models. If you want to model new APIs,
411+
* extend `HTTP::CookieWrite::Range` instead.
412+
*/
413+
class CookieWrite extends DataFlow::Node instanceof CookieWrite::Range {
414+
/** Gets the name of the cookie written. */
415+
DataFlow::Node getName() { result = super.getName() }
416+
417+
/** Gets the value of the cookie written. */
418+
DataFlow::Node getValue() { result = super.getValue() }
419+
420+
/** Gets the `Secure` attribute of the cookie written. */
421+
DataFlow::Node getSecure() { result = super.getSecure() }
422+
423+
/** Gets the `HttpOnly` attribute of the cookie written. */
424+
DataFlow::Node getHttpOnly() { result = super.getHttpOnly() }
425+
}
426+
427+
/** Provides a class for modeling the new APIs for writes to options of an HTTP cookie. */
428+
module CookieOptionWrite {
429+
/**
430+
* A write to an option of an HTTP cookie object.
431+
*
432+
* Extend this class to model new APIs. If you want to refine existing API models,
433+
* extend `HTTP::CookieOptionWrite` instead.
434+
*/
435+
abstract class Range extends DataFlow::Node {
436+
/** Gets the node representing the cookie object for the options being set. */
437+
abstract DataFlow::Node getCookieOutput();
438+
439+
/** Gets the name of the cookie represented, if any. */
440+
abstract DataFlow::Node getName();
441+
442+
/** Gets the value of the cookie represented, if any. */
443+
abstract DataFlow::Node getValue();
444+
445+
/** Gets the `Secure` attribute of the cookie represented, if any. */
446+
abstract DataFlow::Node getSecure();
447+
448+
/** Gets the `HttpOnly` attribute of the cookie represented, if any. */
449+
abstract DataFlow::Node getHttpOnly();
450+
}
451+
}
452+
453+
/**
454+
* A write to an option of an HTTP cookie object.
455+
*
456+
* Extend this class to refine existing API models. If you want to model new APIs,
457+
* extend `HTTP::CookieOptionWrite::Range` instead.
458+
*/
459+
class CookieOptionWrite extends DataFlow::Node instanceof CookieOptionWrite::Range {
460+
/** Gets the node representing the cookie object for the options being set. */
461+
DataFlow::Node getCookieOutput() { result = super.getCookieOutput() }
462+
463+
/** Gets the name of the cookie represented, if any. */
464+
DataFlow::Node getName() { result = super.getName() }
465+
466+
/** Gets the value of the cookie represented, if any. */
467+
DataFlow::Node getValue() { result = super.getValue() }
468+
469+
/** Gets the `Secure` attribute of the cookie represented, if any. */
470+
DataFlow::Node getSecure() { result = super.getSecure() }
471+
472+
/** Gets the `HttpOnly` attribute of the cookie represented, if any. */
473+
DataFlow::Node getHttpOnly() { result = super.getHttpOnly() }
474+
}
383475
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Provides classes for modeling the `github.com/gin-gonic/gin` package.
3+
*/
4+
5+
import go
6+
import semmle.go.concepts.HTTP
7+
8+
/** Provides models for the `gin-gonic/gin` package. */
9+
module Gin {
10+
/** Gets the package name `github.com/gin-gonic/gin`. */
11+
string packagePath() { result = package("github.com/gin-gonic/gin", "") }
12+
13+
private class GinCookieWrite extends Http::CookieWrite::Range, DataFlow::MethodCallNode {
14+
GinCookieWrite() { this.getTarget().hasQualifiedName(packagePath(), "Context", "SetCookie") }
15+
16+
override DataFlow::Node getName() { result = this.getArgument(0) }
17+
18+
override DataFlow::Node getValue() { result = this.getArgument(1) }
19+
20+
override DataFlow::Node getSecure() { result = this.getArgument(5) }
21+
22+
override DataFlow::Node getHttpOnly() { result = this.getArgument(6) }
23+
}
24+
}

go/ql/lib/semmle/go/frameworks/stdlib/NetHttp.qll

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,4 +293,38 @@ module NetHttp {
293293

294294
override DataFlow::Node getAPathArgument() { result = this.getArgument(2) }
295295
}
296+
297+
private class CookieWrite extends Http::CookieWrite::Range, DataFlow::CallNode {
298+
CookieWrite() { this.getTarget().hasQualifiedName(package("net/http", ""), "SetCookie") }
299+
300+
override DataFlow::Node getName() { result = this.getArgument(1) }
301+
302+
override DataFlow::Node getValue() { result = this.getArgument(1) }
303+
304+
override DataFlow::Node getSecure() { result = this.getArgument(1) }
305+
306+
override DataFlow::Node getHttpOnly() { result = this.getArgument(1) }
307+
}
308+
309+
private class CookieFieldWrite extends Http::CookieOptionWrite::Range {
310+
DataFlow::Node written;
311+
string fieldName;
312+
313+
CookieFieldWrite() {
314+
exists(Write w, Field f |
315+
f.hasQualifiedName(package("net/http", ""), "Cookie", fieldName) and
316+
w.writesField(this, f, written)
317+
)
318+
}
319+
320+
override DataFlow::Node getCookieOutput() { result = this }
321+
322+
override DataFlow::Node getName() { fieldName = "Name" and result = written }
323+
324+
override DataFlow::Node getValue() { fieldName = "Value" and result = written }
325+
326+
override DataFlow::Node getSecure() { fieldName = "Secure" and result = written }
327+
328+
override DataFlow::Node getHttpOnly() { fieldName = "HttpOnly" and result = written }
329+
}
296330
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/** Provides classes and predicates for identifying HTTP cookies without the `HttpOnly` attribute. */
2+
3+
import go
4+
import semmle.go.concepts.HTTP
5+
import semmle.go.dataflow.DataFlow
6+
7+
private module SensitiveCookieNameConfig implements DataFlow::ConfigSig {
8+
/**
9+
* Holds if `source` is an expression with a name or literal value `val` indicating a sensitive cookie.
10+
*/
11+
additional predicate isSource(DataFlow::Node source, string val) {
12+
(
13+
val = source.asExpr().getStringValue() or
14+
val = source.asExpr().(Name).getTarget().getName()
15+
) and
16+
val.regexpMatch("(?i).*(session|login|token|user|auth|credential).*") and
17+
not val.regexpMatch("(?i).*(xsrf|csrf|forgery).*")
18+
}
19+
20+
predicate isSource(DataFlow::Node source) { isSource(source, _) }
21+
22+
additional predicate isSink(DataFlow::Node sink, Http::CookieWrite cw) { sink = cw.getName() }
23+
24+
predicate isSink(DataFlow::Node sink) { isSink(sink, _) }
25+
26+
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
27+
exists(Http::CookieOptionWrite co | co.getName() = pred and co.getCookieOutput() = succ)
28+
}
29+
}
30+
31+
/** Tracks flow from sensitive names to HTTP cookie writes. */
32+
module SensitiveCookieNameFlow = TaintTracking::Global<SensitiveCookieNameConfig>;
33+
34+
private module BooleanCookieHttpOnlyConfig implements DataFlow::ConfigSig {
35+
predicate isSource(DataFlow::Node source) {
36+
source.getType().getUnderlyingType() instanceof BoolType
37+
}
38+
39+
predicate isSink(DataFlow::Node sink) { exists(Http::CookieWrite cw | sink = cw.getHttpOnly()) }
40+
41+
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
42+
exists(Http::CookieOptionWrite co | co.getHttpOnly() = pred and co.getCookieOutput() = succ)
43+
}
44+
}
45+
46+
/** Tracks flow from boolean expressions to the `HttpOnly` attribute of HTTP cookie writes. */
47+
module BooleanCookieHttpOnlyFlow = TaintTracking::Global<BooleanCookieHttpOnlyConfig>;
48+
49+
/** Holds if `cw` has the `HttpOnly` attribute left at its default value of `false`. */
50+
predicate isNonHttpOnlyDefault(Http::CookieWrite cw) {
51+
not BooleanCookieHttpOnlyFlow::flowTo(cw.getHttpOnly())
52+
}
53+
54+
/** Holds if `cw` has the `HttpOnly` attribute explicitly set to `false`, from the expression `boolFalse`. */
55+
predicate isNonHttpOnlyDirect(Http::CookieWrite cw, Expr boolFalse) {
56+
BooleanCookieHttpOnlyFlow::flow(DataFlow::exprNode(boolFalse), cw.getHttpOnly()) and
57+
boolFalse.getBoolValue() = false
58+
}
59+
60+
/** Holds if `cw` has the `HttpOnly` attribute set to `false`, either explicitly or by default. */
61+
predicate isNonHttpOnlyCookie(Http::CookieWrite cw) {
62+
isNonHttpOnlyDefault(cw) or
63+
isNonHttpOnlyDirect(cw, _)
64+
}
65+
66+
/**
67+
* Holds if `cw` has the sensitive name `name`, from the expression `nameExpr`.
68+
* `source` and `sink` represent the data flow path from the sensitive name expression to the cookie write.
69+
*/
70+
predicate isSensitiveCookie(
71+
Http::CookieWrite cw, string name, SensitiveCookieNameFlow::PathNode source,
72+
SensitiveCookieNameFlow::PathNode sink
73+
) {
74+
SensitiveCookieNameFlow::flowPath(source, sink) and
75+
SensitiveCookieNameConfig::isSource(source.getNode(), name) and
76+
SensitiveCookieNameConfig::isSink(sink.getNode(), cw)
77+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/** Provides classes and predicates for identifying HTTP cookies without the `Secure` attribute. */
2+
3+
import go
4+
import semmle.go.concepts.HTTP
5+
import semmle.go.dataflow.DataFlow
6+
7+
private module BooleanCookieSecureConfig implements DataFlow::ConfigSig {
8+
predicate isSource(DataFlow::Node source) {
9+
source.getType().getUnderlyingType() instanceof BoolType
10+
}
11+
12+
predicate isSink(DataFlow::Node sink) { exists(Http::CookieWrite cw | sink = cw.getSecure()) }
13+
14+
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
15+
exists(Http::CookieOptionWrite co | co.getSecure() = pred and co.getCookieOutput() = succ)
16+
}
17+
}
18+
19+
/** Tracks flow from boolean expressions to the `Secure` attribute of HTTP cookie writes. */
20+
module BooleanCookieSecureFlow = TaintTracking::Global<BooleanCookieSecureConfig>;
21+
22+
/** Holds if `cw` has the `Secure` attribute left at its default value of `false`. */
23+
predicate isInsecureDefault(Http::CookieWrite cw) {
24+
not BooleanCookieSecureFlow::flowTo(cw.getSecure())
25+
}
26+
27+
/** Holds if `cw` has the `Secure` attribute explicitly set to `false`, from the expression `boolFalse`. */
28+
predicate isInsecureDirect(Http::CookieWrite cw, Expr boolFalse) {
29+
BooleanCookieSecureFlow::flow(DataFlow::exprNode(boolFalse), cw.getSecure()) and
30+
boolFalse.getBoolValue() = false
31+
}
32+
33+
/** Holds if `cw` has the `Secure` attribute set to `false`, either explicitly or by default. */
34+
predicate isInsecureCookie(Http::CookieWrite cw) {
35+
isInsecureDefault(cw) or
36+
isInsecureDirect(cw, _)
37+
}

0 commit comments

Comments
 (0)