Skip to content

Python: Add cookie security-related queries #6360

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

Merged
merged 33 commits into from
May 11, 2022

Conversation

jorgectf
Copy link
Contributor

@jorgectf jorgectf commented Jul 25, 2021

This PR adds:

  • Modeling of:

    • Adding cookies in flask.
    • Adding cookies in django.
  • Queries:

    • Cookie without (secure|httponly|samesite) flag properly set.
    • Cookie constructed from user-supplied input.

@jorgectf jorgectf marked this pull request as draft August 30, 2021 13:46
@jorgectf jorgectf changed the title Python: Add missing 'secure' flag in response cookies query Python: Add cookie-related queries Oct 27, 2021
@jorgectf jorgectf changed the title Python: Add cookie-related queries Python: Add cookie security-related queries Oct 28, 2021
@jorgectf jorgectf marked this pull request as ready for review November 5, 2021 19:17
Copy link
Member

@RasmusWL RasmusWL left a comment

Choose a reason for hiding this comment

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

I have not looked at this PR in depth, but just noted that this deals with setting cookies in HTTP responses. Would be great if you could integrate this with the existing HTTP::Server::CookieWrite concept implemented here:

/**
* A data-flow node that sets a cookie in an HTTP response.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `HTTP::CookieWrite::Range` instead.
*/
class CookieWrite extends DataFlow::Node {
CookieWrite::Range range;
CookieWrite() { this = range }
/**
* Gets the argument, if any, specifying the raw cookie header.
*/
DataFlow::Node getHeaderArg() { result = range.getHeaderArg() }
/**
* Gets the argument, if any, specifying the cookie name.
*/
DataFlow::Node getNameArg() { result = range.getNameArg() }
/**
* Gets the argument, if any, specifying the cookie value.
*/
DataFlow::Node getValueArg() { result = range.getValueArg() }
}

You can do this by creating your own experimental concepts that extend the existing concept, for an exmaple, see

/**
* A data flow node that writes data to the file system access.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `FileSystemWriteAccess::Range` instead.
*/
class FileSystemWriteAccess extends FileSystemAccess {
override FileSystemWriteAccess::Range range;
/**
* Gets a node that represents data to be written to the file system (possibly with
* some transformation happening before it is written, like JSON encoding).
*/
DataFlow::Node getADataNode() { result = range.getADataNode() }
}
/** Provides a class for modeling new file system writes. */
module FileSystemWriteAccess {
/**
* A data flow node that writes data to the file system access.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `FileSystemWriteAccess` instead.
*/
abstract class Range extends FileSystemAccess::Range {
/**
* Gets a node that represents data to be written to the file system (possibly with
* some transformation happening before it is written, like JSON encoding).
*/
abstract DataFlow::Node getADataNode();
}
}

I would base my modeling in Django/Flask on the existing modeling (that is, copy-paste it), such that it is easy to just override the existing modeling when promoting this query 👍

@jorgectf jorgectf requested a review from RasmusWL November 16, 2021 14:00
@jorgectf
Copy link
Contributor Author

I haven't been able to find any CVE for this specific security issue, so the bounty submission has been closed.

@RasmusWL
Copy link
Member

Thanks for the update @jorgectf. Will take a look on this PR soon 👍

Copy link

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

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

Found 5 vulnerabilities.

Comment on lines +74 to +79
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}

Check warning

Code scanning / CodeQL

Dead code

Code is dead
}

/** Gets a reference to an instance of `django.http.response.HttpResponse`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }

Check warning

Code scanning / CodeQL

Dead code

Code is dead
Comment on lines +10 to +28
/**
* Gets a header setting a cookie.
*
* Given the following example:
*
* ```py
* @app.route("/")
* def flask_make_response():
* resp = make_response("")
* resp.headers['Set-Cookie'] = "name=value; Secure;"
* return resp
* ```
*
* * `this` would be `resp.headers['Set-Cookie'] = "name=value; Secure;"`.
* * `isSecure()` predicate would succeed.
* * `isHttpOnly()` predicate would fail.
* * `isSameSite()` predicate would fail.
* * `getName()` and `getValue()` results would be `"name=value; Secure;"`.
*/

Check warning

Code scanning / CodeQL

Class QLDoc style.

The QLDoc for a class should start with 'A', 'An', or 'The'.
Comment on lines +77 to +96
/**
* Gets a call to `set_cookie()`.
*
* Given the following example:
*
* ```py
* @app.route("/")
* def false():
* resp = make_response()
* resp.set_cookie("name", value="value", secure=True, httponly=True, samesite='Lax')
* return resp
* ```
*
* * `this` would be `resp.set_cookie("name", value="value", secure=False, httponly=False, samesite='None')`.
* * `getName()`'s result would be `"name"`.
* * `getValue()`'s result would be `"value"`.
* * `isSecure()` predicate would succeed.
* * `isHttpOnly()` predicate would succeed.
* * `isSameSite()` predicate would succeed.
*/

Check warning

Code scanning / CodeQL

Class QLDoc style.

The QLDoc for a class should start with 'A', 'An', or 'The'.
Comment on lines +133 to +151
/**
* Gets a call to `set_cookie()`.
*
* Given the following example:
*
* ```py
* def django_response(request):
* resp = django.http.HttpResponse()
* resp.set_cookie("name", "value", secure=True, httponly=True, samesite='Lax')
* return resp
* ```
*
* * `this` would be `resp.set_cookie("name", "value", secure=False, httponly=False, samesite='None')`.
* * `getName()`'s result would be `"name"`.
* * `getValue()`'s result would be `"value"`.
* * `isSecure()` predicate would succeed.
* * `isHttpOnly()` predicate would succeed.
* * `isSameSite()` predicate would succeed.
*/

Check warning

Code scanning / CodeQL

Class QLDoc style.

The QLDoc for a class should start with 'A', 'An', or 'The'.
RasmusWL
RasmusWL previously approved these changes May 11, 2022
Copy link
Member

@RasmusWL RasmusWL left a comment

Choose a reason for hiding this comment

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

I took forever to review this, sorry 😬

For reference when promoting this, JS adopted an approach with multiple queries for each of these flags, so we should consider doing the same

  • js/samesite-none-cookie (precision medium)
  • js/client-exposed-cookie (precision high) -- for httpOnly flag
  • js/clear-text-cookie (precision high) -- for 'secure' flag

See #6855 and #7721

I also think we need to review how bad CookieInjection really is. My thoughts without reading up on this at all is that it should be a @precision medium query -- a user can always set cookies on their own machine, so this is only a problem when a third-party attacker can set a cookie on a victim machine, meaning some cookie writing request without CSRF protection... but my intuition might be wrong 🤷

@RasmusWL RasmusWL force-pushed the jorgectf/python/insecure-cookie branch from 62b3ec1 to fc8633c Compare May 11, 2022 11:18
RasmusWL
RasmusWL previously approved these changes May 11, 2022
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.

2 participants