-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Go: Promote non-httponly cookie query, and add insecure cookie query #20762
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
d00f670 to
2805a60
Compare
|
QHelp previews: go/ql/src/Security/CWE-1004/CookieWithoutHttpOnly.qhelpCookie 'HttpOnly' attribute is not set to trueCookies without the RecommendationSet the ExampleIn the following example, in the case marked BAD, the package main
import (
"net/http"
)
func handlerBad(w http.ResponseWriter, r *http.Request) {
c := http.Cookie{
Name: "session",
Value: "secret",
}
http.SetCookie(w, &c) // BAD: The HttpOnly flag is set to false by default.
}
func handlerGood(w http.ResponseWriter, r *http.Request) {
c := http.Cookie{
Name: "session",
Value: "secret",
HttpOnly: true,
}
http.SetCookie(w, &c) // GOOD: The HttpOnly flag is set to true.
}References
go/ql/src/Security/CWE-614/CookieWithoutSecure.qhelpCookie 'Secure' attribute is not set to trueCookies without the RecommendationSet the ExampleIn the following example, in the case marked BAD, the package main
import (
"net/http"
)
func handlerBad(w http.ResponseWriter, r *http.Request) {
c := http.Cookie{
Name: "session",
Value: "secret",
}
http.SetCookie(w, &c) // BAD: The Secure flag is set to false by default.
}
func handlerGood(w http.ResponseWriter, r *http.Request) {
c := http.Cookie{
Name: "session",
Value: "secret",
Secure: true,
}
http.SetCookie(w, &c) // GOOD: The Secure flag is set to true.
}References
|
6f26b83 to
999eff1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR promotes an experimental query for detecting cookies without the HttpOnly flag and introduces a new query for detecting cookies without the Secure flag. The changes include:
- Migration of the
CookieWithoutHttpOnlyquery from experimental to the main security query pack - Introduction of a new
CookieWithoutSecurequery - New shared library code (
SecureCookies.qll) for modeling HTTP cookie security attributes - Updates to framework models for
net/httpandgin-gonic/gin
Reviewed Changes
Copilot reviewed 31 out of 42 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| go/ql/src/Security/CWE-1004/CookieWithoutHttpOnly.ql | New query for detecting cookies without HttpOnly flag |
| go/ql/src/Security/CWE-614/CookieWithoutSecure.ql | New query for detecting cookies without Secure flag |
| go/ql/lib/semmle/go/security/SecureCookies.qll | Shared library for cookie security analysis |
| go/ql/lib/semmle/go/concepts/HTTP.qll | Added CookieWrite and CookieOptions concepts |
| go/ql/lib/semmle/go/frameworks/NetHttp.qll | Added cookie write models for net/http |
| go/ql/lib/semmle/go/frameworks/Gin.qll | New framework models for gin-gonic/gin |
| go/ql/src/change-notes/2025-11-10-inseucre-cookie.md | Change notes documenting the updates |
| go/ql/src/experimental/CWE-1004/* | Removed experimental query files |
| go/ql/test/query-tests/Security/CWE-614/* | Test files for CookieWithoutSecure |
| go/ql/test/query-tests/Security/CWE-1004/* | Test files for CookieWithoutHttpOnly |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
owen-mc
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love the new concepts. I think they make the structure quite clean, and allow us to add other libraries easily in future.
I've only done a partial review so far. It will be easier to continue once these points have been addressed.
I note that you've removed modelling and tests for github.com/gorilla/sessions. Was that deliberate? It seems like a popular enough library, and it's only a bit of extra modelling and some tests that have already been written.
| predicate guardedBy(DataFlow::Node check) { super.guardedBy(check) } | ||
| } | ||
|
|
||
| /** Provides a class for modeling HTTP response cookie writes. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /** Provides a class for modeling HTTP response cookie writes. */ | |
| /** Provides a class for modeling new HTTP response cookie write APIs. */ |
| DataFlow::Node getHttpOnly() { result = super.getHttpOnly() } | ||
| } | ||
|
|
||
| /** Provides a class for modeling the options of an HTTP cookie. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /** Provides a class for modeling the options of an HTTP cookie. */ | |
| /** Provides a class for modeling new APIs for the options of an HTTP cookie. */ |
| Write w; | ||
| Field f; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than storing these as fields, they should be introduced in the characteristic predicate using exists, since they aren't referenced by any of the member predicates.
| /** Gets the name of the cookie represented. */ | ||
| DataFlow::Node getName() { result = super.getName() } | ||
|
|
||
| /** Gets the value of the cookie represented. */ | ||
| DataFlow::Node getValue() { result = super.getValue() } | ||
|
|
||
| /** Gets the `Secure` attribute of the cookie represented. */ | ||
| DataFlow::Node getSecure() { result = super.getSecure() } | ||
|
|
||
| /** Gets the `HttpOnly` attribute of the cookie represented. */ | ||
| DataFlow::Node getHttpOnly() { result = super.getHttpOnly() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have I understood correctly that these won't always be defined? If so, add ", if any" to the end of the QLDocs for them. (Same in the Range class too.)
| module CookieOptions { | ||
| /** | ||
| * An HTTP Cookie object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class name doesn't really match the QLDoc, and I think they're both wrong. It isn't a cookie object, or the options of a cookie. The only class extending it is NetHttp::CookieFieldWrite. I think it is a node which sets an option of a cookie, so maybe CookieOptionWrite?
| import semmle.go.dataflow.DataFlow | ||
|
|
||
| /** | ||
| * Holds if the expression or its value has a sensitive name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * Holds if the expression or its value has a sensitive name | |
| * Holds if the expression or its value has a sensitive name. |
| /** | ||
| * Holds if the expression or its value has a sensitive name | ||
| */ | ||
| private predicate isSensitiveExpr(Expr expr, string val) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The predicate name is confusing because in go/ql/lib/semmle/go/security/SensitiveActions.qll there is a class SensitiveExpr. A clearer name would be something like isSensitiveCookieName? But actually I think there is a better approach, which avoids having to come up with a clear name for it: make it an additional predicate in SensitiveCookieNameConfig with the signature isSource(Expr expr, string val) and then isSource(DataFlow::Node source) would just be isSource(source.asExpr(), _).
| private module SensitiveCookieNameConfig implements DataFlow::ConfigSig { | ||
| predicate isSource(DataFlow::Node source) { isSensitiveExpr(source.asExpr(), _) } | ||
|
|
||
| predicate isSink(DataFlow::Node sink) { exists(Http::CookieWrite cw | sink = cw.getName()) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would be cleaner with an additional predicate isSink(DataFlow::Node sink, Http::CookieWrite cw) { sink = cw.getName() } and then isSink(DataFlow::Node sink) { isSink(sink, _) }. Then you can use the additional predicate in isSensitiveCookie
| * @kind problem | ||
| * @problem.severity warning | ||
| * @precision high | ||
| * @security-severity 5.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this be lowered to 4.0, as was recently done for java.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's confusing having predicates for two different queries in this one file (which is then only named after one of the predicates). Unless there is a good reason to keep them together, please put them in two separate files, named CookieWithoutSecure.qll and CookieWithoutHttpOnly.qll, so it's clear which query they relate to. I think there isn't any shared code - there probably was before you put things into concepts/HTTP.qll.
Promotes
go/cookie-httponly-not-setfrom experimental, and addsgo/cookie-secure-not-setquery.