Skip to content
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

Python: Promote the insecure cookie query from experimental #16933

Merged

Conversation

joefarebrother
Copy link
Contributor

Part of https://github.com/github/codeql-python-team/issues/792 promoting #6360;
Depends on #16696

Promotes the insecure cookie query from experimental, finding instances of cookies set without secure values for the Secure, HttpOnly, or SameSite attributes.

Copy link
Contributor

github-actions bot commented Jul 11, 2024

QHelp previews:

python/ql/src/Security/CWE-614/InsecureCookie.qhelp

Failure to use secure cookies

Cookies without the Secure flag set may be transmittd using HTTP instead of HTTPS, which leaves it vulnerable to being read by a third party.

Cookies without the HttpOnly flag set are accessible to JavaScript running in the same origin. In case of a Cross-Site Scripting (XSS) vulnerability, the cookie can be stolen by a malicious script.

Cookies with the SameSite attribute set to 'None' will be sent with cross-origin requests, which can be controlled by third-party JavaScript code and allow for Cross-Site Request Forgery (CSRF) attacks.

Recommendation

Always set secure to True or add "; Secure;" to the cookie's raw value.

Always set httponly to True or add "; HttpOnly;" to the cookie's raw value.

Always set samesite to Lax or Strict, or add "; SameSite=Lax;", or "; Samesite=Strict;" to the cookie's raw header value.

Example

In the following examples, the cases marked GOOD show secure cookie attributes being set; whereas in the cases marked BAD they are not set.

from flask import Flask, request, make_response, Response


@app.route("/good1")
def good1():
    resp = make_response()
    resp.set_cookie("name", value="value", secure=True, httponly=True, samesite='Strict') # GOOD: Attributes are securely set
    return resp


@app.route("/good2")
def good2():
    resp = make_response()
    resp.headers['Set-Cookie'] = "name=value; Secure; HttpOnly; SameSite=Strict" # GOOD: Attributes are securely set 
    return resp

@app.route("/bad1")
    resp = make_response()
    resp.set_cookie("name", value="value", samesite='None') # BAD: the SameSite attribute is set to 'None' and the 'Secure' and 'HttpOnly' attributes are set to False by default.
    return resp

References

@joefarebrother joefarebrother changed the title [Draft] Python: Promote the insecure cookie query from experimental Python: Promote the insecure cookie query from experimental Jul 23, 2024
@joefarebrother joefarebrother marked this pull request as ready for review July 23, 2024 12:38
@joefarebrother joefarebrother requested a review from a team as a code owner July 23, 2024 12:38
Copy link
Contributor

@yoff yoff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIce work, I like the more careful semantics regarding known values.
A few suggestions around coding style.

python/ql/src/Security/CWE-614/examples/InsecureCookie.py Outdated Show resolved Hide resolved
python/ql/lib/semmle/python/Concepts.qll Outdated Show resolved Hide resolved
python/ql/lib/semmle/python/Concepts.qll Outdated Show resolved Hide resolved
Comment on lines 668 to 711
override predicate hasSecureFlag(boolean b) {
super.hasSecureFlag(b)
or
exists(DataFlow::Node arg, BooleanLiteral bool | arg = this.getArgByName("secure") |
DataFlow::localFlow(DataFlow::exprNode(bool), arg) and
b = bool.booleanValue()
)
or
not exists(this.getArgByName("secure")) and
b = false
}

override predicate hasHttpOnlyFlag(boolean b) {
super.hasHttpOnlyFlag(b)
or
exists(DataFlow::Node arg, BooleanLiteral bool | arg = this.getArgByName("httponly") |
DataFlow::localFlow(DataFlow::exprNode(bool), arg) and
b = bool.booleanValue()
)
or
not exists(this.getArgByName("httponly")) and
b = false
}

override predicate hasSameSiteAttribute(Http::Server::CookieWrite::SameSiteValue v) {
super.hasSameSiteAttribute(v)
or
exists(DataFlow::Node arg, StringLiteral str | arg = this.getArgByName("samesite") |
DataFlow::localFlow(DataFlow::exprNode(str), arg) and
(
str.getText().toLowerCase() = "strict" and
v instanceof Http::Server::CookieWrite::SameSiteStrict
or
str.getText().toLowerCase() = "lax" and
v instanceof Http::Server::CookieWrite::SameSiteLax
or
str.getText().toLowerCase() = "none" and
v instanceof Http::Server::CookieWrite::SameSiteNone
)
)
or
not exists(this.getArgByName("samesite")) and
v instanceof Http::Server::CookieWrite::SameSiteLax // Lax is the default
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code seems to be copied across a bunch of frameworks. Would it make sense to create a SetCookieCall abstraction so the frameworks just have to point out where the calls are? It seems all calls to set_cookie (in any framework) are likely to follow this convention (and if not, it can be overridden by the framework).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it would make sense. Should it be defined in the Concepts library?

Copy link
Contributor

@yoff yoff Jul 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, since it comes from the protocol (something like here) it should probably not be in a specific framework, so if it could be in Concepts, that would be nice.

@yoff
Copy link
Contributor

yoff commented Jul 29, 2024

For the failing tests, it looks reasonable to just update the inline expectations. The content type will be set by the renderer in the unspecified cases, so is not statically known (at least not by our analysis).

yoff
yoff previously approved these changes Aug 6, 2024
Copy link
Contributor

@yoff yoff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! I have one suggestion for a doc link, but otherwise I think I cannot ask for more here :-)
We should kick off a DCA run, but I do not really expect any of this to blow up...

python/ql/lib/semmle/python/Concepts.qll Outdated Show resolved Hide resolved
Co-authored-by: yoff <lerchedahl@gmail.com>
@yoff
Copy link
Contributor

yoff commented Aug 7, 2024

DCA run looks good. The new results look fine to me, but please confirm that you would expect them too :-) (The ones that are not unclassified; the unclassified ones are just our tests, it seems.)

@yoff yoff self-requested a review August 7, 2024 07:34
Copy link
Contributor

@yoff yoff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@joefarebrother joefarebrother merged commit 62c2fe6 into github:main Aug 7, 2024
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants