mirror of
https://github.com/github/codeql.git
synced 2025-12-21 19:26:31 +01:00
Add cookie injection query missing proper tests
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* @name Failure to use secure cookies
|
||||||
|
* @description Insecure cookies may be sent in cleartext, which makes them vulnerable to
|
||||||
|
* interception.
|
||||||
|
* @kind problem
|
||||||
|
* @problem.severity error
|
||||||
|
* @id py/insecure-cookie
|
||||||
|
* @tags security
|
||||||
|
* external/cwe/cwe-614
|
||||||
|
*/
|
||||||
|
|
||||||
|
// determine precision above
|
||||||
|
import python
|
||||||
|
import semmle.python.dataflow.new.DataFlow
|
||||||
|
import experimental.semmle.python.Concepts
|
||||||
|
import experimental.semmle.python.CookieHeader
|
||||||
|
import experimental.semmle.python.security.injection.CookieInjection
|
||||||
|
|
||||||
|
from
|
||||||
|
CookieInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink,
|
||||||
|
string insecure
|
||||||
|
where
|
||||||
|
config.hasFlowPath(source, sink) and
|
||||||
|
if exists(sink.getNode().(CookieSink))
|
||||||
|
then insecure = "and it's " + sink.getNode().(CookieSink).getFlag() + " flag is not properly set"
|
||||||
|
else insecure = ""
|
||||||
|
select sink.getNode(), "Cookie is constructed from a", source.getNode(), "user-supplied input",
|
||||||
|
insecure
|
||||||
@@ -322,6 +322,16 @@ class Cookie extends DataFlow::Node {
|
|||||||
* Holds if the cookie is SameSite
|
* Holds if the cookie is SameSite
|
||||||
*/
|
*/
|
||||||
predicate isSameSite() { range.isSameSite() }
|
predicate isSameSite() { range.isSameSite() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the argument containing the header name.
|
||||||
|
*/
|
||||||
|
DataFlow::Node getName() { result = range.getName() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the argument containing the header value.
|
||||||
|
*/
|
||||||
|
DataFlow::Node getValue() { result = range.getValue() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Provides a class for modeling new cookie writes on HTTP responses. */
|
/** Provides a class for modeling new cookie writes on HTTP responses. */
|
||||||
@@ -347,5 +357,15 @@ module Cookie {
|
|||||||
* Holds if the cookie is SameSite.
|
* Holds if the cookie is SameSite.
|
||||||
*/
|
*/
|
||||||
abstract predicate isSameSite();
|
abstract predicate isSameSite();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the argument containing the header name.
|
||||||
|
*/
|
||||||
|
abstract DataFlow::Node getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the argument containing the header value.
|
||||||
|
*/
|
||||||
|
abstract DataFlow::Node getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,18 +9,28 @@ import experimental.semmle.python.Concepts
|
|||||||
|
|
||||||
class CookieHeader extends HeaderDeclaration, Cookie::Range {
|
class CookieHeader extends HeaderDeclaration, Cookie::Range {
|
||||||
CookieHeader() {
|
CookieHeader() {
|
||||||
this instanceof HeaderDeclaration and this.getNameArg().asExpr().(Str_).getS() = "Set-Cookie"
|
this instanceof HeaderDeclaration and
|
||||||
|
this.(HeaderDeclaration).getNameArg().asExpr().(Str_).getS() = "Set-Cookie"
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSecure() {
|
override predicate isSecure() {
|
||||||
this.getValueArg().asExpr().(Str_).getS().regexpMatch(".*; *Secure;.*")
|
this.(HeaderDeclaration).getValueArg().asExpr().(Str_).getS().regexpMatch(".*; *Secure;.*")
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isHttpOnly() {
|
override predicate isHttpOnly() {
|
||||||
this.getValueArg().asExpr().(Str_).getS().regexpMatch(".*; *HttpOnly;.*")
|
this.(HeaderDeclaration).getValueArg().asExpr().(Str_).getS().regexpMatch(".*; *HttpOnly;.*")
|
||||||
}
|
}
|
||||||
|
|
||||||
override predicate isSameSite() {
|
override predicate isSameSite() {
|
||||||
this.getValueArg().asExpr().(Str_).getS().regexpMatch(".*; *SameSite=(Strict|Lax);.*")
|
this.(HeaderDeclaration)
|
||||||
|
.getValueArg()
|
||||||
|
.asExpr()
|
||||||
|
.(Str_)
|
||||||
|
.getS()
|
||||||
|
.regexpMatch(".*; *SameSite=(Strict|Lax);.*")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getName() { result = this.(HeaderDeclaration).getValueArg() }
|
||||||
|
|
||||||
|
override DataFlow::Node getValue() { result = this.(HeaderDeclaration).getValueArg() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ private module PrivateDjango {
|
|||||||
class DjangoSetCookieCall extends DataFlow::CallCfgNode, Cookie::Range {
|
class DjangoSetCookieCall extends DataFlow::CallCfgNode, Cookie::Range {
|
||||||
DjangoSetCookieCall() { this = baseClassRef().getMember("set_cookie").getACall() }
|
DjangoSetCookieCall() { this = baseClassRef().getMember("set_cookie").getACall() }
|
||||||
|
|
||||||
|
override DataFlow::Node getName() { result = this.getArg(0) }
|
||||||
|
|
||||||
|
override DataFlow::Node getValue() { result = this.getArgByName("value") }
|
||||||
|
|
||||||
override predicate isSecure() {
|
override predicate isSecure() {
|
||||||
DataFlow::exprNode(any(True t))
|
DataFlow::exprNode(any(True t))
|
||||||
.(DataFlow::LocalSourceNode)
|
.(DataFlow::LocalSourceNode)
|
||||||
|
|||||||
@@ -91,6 +91,10 @@ module ExperimentalFlask {
|
|||||||
.getACall()
|
.getACall()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override DataFlow::Node getName() { result = this.getArg(0) }
|
||||||
|
|
||||||
|
override DataFlow::Node getValue() { result = this.getArgByName("value") }
|
||||||
|
|
||||||
override predicate isSecure() {
|
override predicate isSecure() {
|
||||||
DataFlow::exprNode(any(True t))
|
DataFlow::exprNode(any(True t))
|
||||||
.(DataFlow::LocalSourceNode)
|
.(DataFlow::LocalSourceNode)
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import python
|
||||||
|
import experimental.semmle.python.Concepts
|
||||||
|
import semmle.python.dataflow.new.DataFlow
|
||||||
|
import semmle.python.dataflow.new.TaintTracking
|
||||||
|
import semmle.python.dataflow.new.RemoteFlowSources
|
||||||
|
|
||||||
|
class CookieSink extends DataFlow::Node {
|
||||||
|
string flag;
|
||||||
|
|
||||||
|
CookieSink() {
|
||||||
|
exists(Cookie cookie |
|
||||||
|
this in [cookie.getName(), cookie.getValue()] and
|
||||||
|
(
|
||||||
|
not cookie.isSecure() and
|
||||||
|
flag = "secure"
|
||||||
|
or
|
||||||
|
not cookie.isHttpOnly() and
|
||||||
|
flag = "httponly"
|
||||||
|
or
|
||||||
|
not cookie.isSameSite() and
|
||||||
|
flag = "samesite"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
string getFlag() { result = flag }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A taint-tracking configuration for detecting Cookie injections.
|
||||||
|
*/
|
||||||
|
class CookieInjectionFlowConfig extends TaintTracking::Configuration {
|
||||||
|
CookieInjectionFlowConfig() { this = "CookieInjectionFlowConfig" }
|
||||||
|
|
||||||
|
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||||
|
|
||||||
|
override predicate isSink(DataFlow::Node sink) {
|
||||||
|
exists(Cookie c | sink in [c.getName(), c.getValue()])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ app = Flask(__name__)
|
|||||||
@app.route("/false")
|
@app.route("/false")
|
||||||
def false():
|
def false():
|
||||||
resp = make_response()
|
resp = make_response()
|
||||||
resp.set_cookie("name", value="value", secure=False,
|
resp.set_cookie(request.args["name"], value=request.args["value"], secure=False,
|
||||||
httponly=False, samesite='None')
|
httponly=False, samesite='None')
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user