mirror of
https://github.com/github/codeql.git
synced 2026-04-28 02:05:14 +02:00
Python: Add library support for cookies. Update and extend sensitive data library.
This commit is contained in:
66
python/ql/src/semmle/python/security/ClearText.qll
Normal file
66
python/ql/src/semmle/python/security/ClearText.qll
Normal file
@@ -0,0 +1,66 @@
|
||||
import python
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.security.SensitiveData
|
||||
import semmle.python.dataflow.Files
|
||||
import semmle.python.web.Http
|
||||
|
||||
module ClearTextStorage {
|
||||
|
||||
abstract class Sink extends TaintSink {
|
||||
override predicate sinks(TaintKind kind) {
|
||||
kind instanceof SensitiveData
|
||||
}
|
||||
}
|
||||
|
||||
class CookieStorageSink extends Sink {
|
||||
CookieStorageSink() {
|
||||
any(CookieSet cookie).getValue() = this
|
||||
}
|
||||
}
|
||||
|
||||
class FileStorageSink extends Sink {
|
||||
FileStorageSink() {
|
||||
exists(CallNode call, AttrNode meth, string name |
|
||||
any(OpenFile fd).taints(meth.getObject(name)) and
|
||||
call.getFunction() = meth and
|
||||
call.getAnArg() = this |
|
||||
name = "write"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module ClearTextLogging {
|
||||
|
||||
abstract class Sink extends TaintSink {
|
||||
override predicate sinks(TaintKind kind) {
|
||||
kind instanceof SensitiveData
|
||||
}
|
||||
}
|
||||
|
||||
class PrintSink extends Sink {
|
||||
PrintSink() {
|
||||
exists(CallNode call |
|
||||
call.getAnArg() = this and
|
||||
thePrintFunction().(FunctionObject).getACall() = call
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class LoggingSink extends Sink {
|
||||
LoggingSink() {
|
||||
exists(CallNode call, AttrNode meth, string name |
|
||||
call.getFunction() = meth and
|
||||
meth.getObject(name).(NameNode).getId().matches("logg%") and
|
||||
call.getAnArg() = this |
|
||||
name = "error" or
|
||||
name = "warn" or
|
||||
name = "warning" or
|
||||
name = "debug" or
|
||||
name = "info"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,91 +13,160 @@ import python
|
||||
import semmle.python.security.TaintTracking
|
||||
|
||||
|
||||
/** A regular expression that identifies strings that look like they represent secret data that are not passwords. */
|
||||
private string suspiciousNonPassword() {
|
||||
result = "(?is).*(account|accnt|(?<!un)trusted).*"
|
||||
}
|
||||
/** A regular expression that identifies strings that look like they represent secret data that are passwords. */
|
||||
private string suspiciousPassword() {
|
||||
result = "(?is).*(password|passwd).*"
|
||||
}
|
||||
|
||||
/** A regular expression that identifies strings that look like they represent secret data. */
|
||||
private string suspicious() {
|
||||
result = suspiciousPassword() or result = suspiciousNonPassword()
|
||||
}
|
||||
|
||||
/**
|
||||
* A string for `match` that identifies strings that look like they represent secret data that is
|
||||
* hashed or encrypted.
|
||||
* Provides heuristics for identifying names related to sensitive information.
|
||||
*
|
||||
* INTERNAL: Do not use directly.
|
||||
* This is copied from the javascript library, but should be language independent.
|
||||
*/
|
||||
private string nonSuspicious() {
|
||||
result = "(?is).*(hash|(?<!un)encrypted|\\bcrypt\\b).*"
|
||||
private module HeuristicNames {
|
||||
|
||||
/**
|
||||
* Gets a regular expression that identifies strings that may indicate the presence of secret
|
||||
* or trusted data.
|
||||
*/
|
||||
string maybeSecret() { result = "(?is).*((?<!is)secret|(?<!un|is)trusted).*" }
|
||||
|
||||
/**
|
||||
* Gets a regular expression that identifies strings that may indicate the presence of
|
||||
* user names or other account information.
|
||||
*/
|
||||
string maybeAccountInfo() {
|
||||
result = "(?is).*acc(ou)?nt.*" or
|
||||
result = "(?is).*(puid|username|userid).*"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a regular expression that identifies strings that may indicate the presence of
|
||||
* a password or an authorization key.
|
||||
*/
|
||||
string maybePassword() {
|
||||
result = "(?is).*pass(wd|word|code|phrase)(?!.*question).*" or
|
||||
result = "(?is).*(auth(entication|ori[sz]ation)?)key.*"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a regular expression that identifies strings that may indicate the presence of
|
||||
* a certificate.
|
||||
*/
|
||||
string maybeCertificate() { result = "(?is).*(cert)(?!.*(format|name)).*" }
|
||||
|
||||
/**
|
||||
* Gets a regular expression that identifies strings that may indicate the presence
|
||||
* of sensitive data, with `classification` describing the kind of sensitive data involved.
|
||||
*/
|
||||
string maybeSensitive(SensitiveData data) {
|
||||
result = maybeSecret() and data instanceof SensitiveData::Secret
|
||||
or
|
||||
result = maybeAccountInfo() and data instanceof SensitiveData::Id
|
||||
or
|
||||
result = maybePassword() and data instanceof SensitiveData::Password
|
||||
or
|
||||
result = maybeCertificate() and data instanceof SensitiveData::Certificate
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a regular expression that identifies strings that may indicate the presence of data
|
||||
* that is hashed or encrypted, and hence rendered non-sensitive.
|
||||
*/
|
||||
string notSensitive() {
|
||||
result = "(?is).*(redact|censor|obfuscate|hash|md5|sha|((?<!un)(en))?(crypt|code)).*"
|
||||
}
|
||||
|
||||
bindingset[name]
|
||||
SensitiveData getSensitiveDataForName(string name) {
|
||||
name.regexpMatch(HeuristicNames::maybeSensitive(result)) and
|
||||
not name.regexpMatch(HeuristicNames::notSensitive())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** An expression that might contain sensitive data. */
|
||||
abstract class SensitiveExpr extends Expr { }
|
||||
abstract class SensitiveData extends TaintKind {
|
||||
|
||||
/** A method access that might produce sensitive data. */
|
||||
class SensitiveCall extends SensitiveExpr, Call {
|
||||
SensitiveCall() {
|
||||
exists(string name |
|
||||
name = this.getFunc().(Name).getId() or
|
||||
name = this.getFunc().(Attribute).getName() or
|
||||
exists(StringObject s |
|
||||
this.getAnArg().refersTo(s) |
|
||||
name = s.getText()
|
||||
)
|
||||
|
|
||||
name.regexpMatch(suspicious()) and
|
||||
not name.regexpMatch(nonSuspicious())
|
||||
bindingset[this]
|
||||
SensitiveData() { this = this }
|
||||
|
||||
}
|
||||
|
||||
module SensitiveData {
|
||||
|
||||
class Secret extends SensitiveData {
|
||||
Secret() { this = "sensitive.data.secret" }
|
||||
override string repr() { result = "a secret" }
|
||||
}
|
||||
|
||||
class Id extends SensitiveData {
|
||||
Id() { this = "sensitive.data.id" }
|
||||
override string repr() { result = "an ID" }
|
||||
}
|
||||
|
||||
class Password extends SensitiveData {
|
||||
Password() { this = "sensitive.data.password" }
|
||||
override string repr() { result = "a password" }
|
||||
}
|
||||
|
||||
class Certificate extends SensitiveData {
|
||||
Certificate() { this = "sensitive.data.certificate" }
|
||||
override string repr() { result = "a certificate or key" }
|
||||
}
|
||||
|
||||
private SensitiveData fromFunction(FunctionObject f) {
|
||||
result = HeuristicNames::getSensitiveDataForName(f.getName())
|
||||
or
|
||||
// This is particularly to pick up methods with an argument like "password", which
|
||||
// may indicate a lookup.
|
||||
exists(string name | name = f.getFunction().getAnArg().asName().getId() |
|
||||
result = HeuristicNames::getSensitiveDataForName(name)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An access to a variable or property that might contain sensitive data. */
|
||||
abstract class SensitiveVariableAccess extends SensitiveExpr {
|
||||
abstract class Source extends TaintSource {
|
||||
|
||||
string name;
|
||||
abstract string repr();
|
||||
|
||||
SensitiveVariableAccess() {
|
||||
this.(Name).getId() = name or
|
||||
this.(Attribute).getName() = name
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private class SensitiveCallSource extends Source {
|
||||
|
||||
/** An access to a variable or property that might contain sensitive data. */
|
||||
private class BasicSensitiveVariableAccess extends SensitiveVariableAccess {
|
||||
SensitiveData data;
|
||||
|
||||
BasicSensitiveVariableAccess() {
|
||||
name.regexpMatch(suspicious()) and not name.regexpMatch(nonSuspicious())
|
||||
}
|
||||
SensitiveCallSource() {
|
||||
exists(FunctionObject callee |
|
||||
callee.getACall() = this |
|
||||
data = fromFunction(callee)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
override predicate isSourceOf(TaintKind kind) {
|
||||
kind = data
|
||||
}
|
||||
|
||||
class SensitiveData extends TaintKind {
|
||||
override string repr() {
|
||||
result = "Call returning " + data.repr()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** An access to a variable or property that might contain sensitive data. */
|
||||
private class SensitiveVariableAccess extends SensitiveData::Source {
|
||||
|
||||
SensitiveData data;
|
||||
|
||||
SensitiveVariableAccess() {
|
||||
data = HeuristicNames::getSensitiveDataForName(this.(AttrNode).getName())
|
||||
}
|
||||
|
||||
override predicate isSourceOf(TaintKind kind) {
|
||||
kind = data
|
||||
}
|
||||
|
||||
override string repr() {
|
||||
result = "an attribute or property containing " + data.repr()
|
||||
}
|
||||
|
||||
SensitiveData() {
|
||||
this = "sensitive.data"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class SensitiveDataSource extends TaintSource {
|
||||
|
||||
SensitiveDataSource() {
|
||||
this.(ControlFlowNode).getNode() instanceof SensitiveExpr
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
result = "sensitive.data.source"
|
||||
}
|
||||
|
||||
override predicate isSourceOf(TaintKind kind) {
|
||||
kind instanceof SensitiveData
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Backwards compatibility
|
||||
class SensitiveDataSource = SensitiveData::Source;
|
||||
|
||||
@@ -72,6 +72,19 @@ class UntrustedCookie extends TaintKind {
|
||||
|
||||
}
|
||||
|
||||
abstract class CookieOperation extends @py_flow_node {
|
||||
|
||||
abstract string toString();
|
||||
|
||||
abstract ControlFlowNode getKey();
|
||||
|
||||
abstract ControlFlowNode getValue();
|
||||
|
||||
}
|
||||
|
||||
abstract class CookieGet extends CookieOperation {}
|
||||
|
||||
abstract class CookieSet extends CookieOperation {}
|
||||
|
||||
/** Generic taint sink in a http response */
|
||||
abstract class HttpResponseTaintSink extends TaintSink {
|
||||
|
||||
@@ -56,3 +56,17 @@ class BottleHandlerFunctionResult extends HttpResponseTaintSink {
|
||||
|
||||
}
|
||||
|
||||
class BottleCookieSet extends CookieSet, CallNode {
|
||||
|
||||
BottleCookieSet() {
|
||||
any(BottleResponse r).taints(this.getFunction().(AttrNode).getObject("set_cookie"))
|
||||
}
|
||||
|
||||
override string toString() { result = this.(CallNode).toString() }
|
||||
|
||||
override ControlFlowNode getKey() { result = this.getArg(0) }
|
||||
|
||||
override ControlFlowNode getValue() { result = this.getArg(1) }
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -83,5 +83,16 @@ class DjangoResponseContent extends HttpResponseTaintSink {
|
||||
|
||||
}
|
||||
|
||||
class DjangoCookieSet extends CookieSet, CallNode {
|
||||
|
||||
DjangoCookieSet() {
|
||||
any(DjangoResponse r).taints(this.getFunction().(AttrNode).getObject("set_cookie"))
|
||||
}
|
||||
|
||||
override string toString() { result = this.(CallNode).toString() }
|
||||
|
||||
override ControlFlowNode getKey() { result = this.getArg(0) }
|
||||
|
||||
override ControlFlowNode getValue() { result = this.getArg(1) }
|
||||
|
||||
}
|
||||
|
||||
@@ -115,3 +115,18 @@ private class AsView extends TaintSource {
|
||||
|
||||
}
|
||||
|
||||
|
||||
class FlaskCookieSet extends CookieSet, CallNode {
|
||||
|
||||
FlaskCookieSet() {
|
||||
this.getFunction().(AttrNode).getObject("set_cookie").refersTo(_, theFlaskReponseClass(), _)
|
||||
}
|
||||
|
||||
override string toString() { result = this.(CallNode).toString() }
|
||||
|
||||
override ControlFlowNode getKey() { result = this.getArg(0) }
|
||||
|
||||
override ControlFlowNode getValue() { result = this.getArg(1) }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import python
|
||||
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.security.strings.Basic
|
||||
import semmle.python.web.Http
|
||||
|
||||
private import semmle.python.web.pyramid.View
|
||||
private import semmle.python.web.Http
|
||||
@@ -27,3 +28,21 @@ class PyramidRoutedResponse extends HttpResponseTaintSink {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class PyramidCookieSet extends CookieSet, CallNode {
|
||||
|
||||
PyramidCookieSet() {
|
||||
exists(ControlFlowNode f |
|
||||
f = this.getFunction().(AttrNode).getObject("set_cookie") and
|
||||
f.refersTo(_, ModuleObject::named("pyramid").attr("Response"), _)
|
||||
)
|
||||
}
|
||||
|
||||
override string toString() { result = this.(CallNode).toString() }
|
||||
|
||||
override ControlFlowNode getKey() { result = this.getArg(0) }
|
||||
|
||||
override ControlFlowNode getValue() { result = this.getArg(1) }
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import python
|
||||
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.web.Http
|
||||
|
||||
private ClassObject theTornadoRequestHandlerClass() {
|
||||
result = ModuleObject::named("tornado.web").attr("RequestHandler")
|
||||
@@ -32,4 +33,22 @@ predicate isTornadoRequestHandlerInstance(ControlFlowNode node) {
|
||||
|
||||
CallNode callToNamedTornadoRequestHandlerMethod(string name) {
|
||||
isTornadoRequestHandlerInstance(result.getFunction().(AttrNode).getObject(name))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TornadoCookieSet extends CookieSet, CallNode {
|
||||
|
||||
TornadoCookieSet() {
|
||||
exists(ControlFlowNode f |
|
||||
f = this.getFunction().(AttrNode).getObject("set_cookie") and
|
||||
isTornadoRequestHandlerInstance(f)
|
||||
)
|
||||
}
|
||||
|
||||
override string toString() { result = this.(CallNode).toString() }
|
||||
|
||||
override ControlFlowNode getKey() { result = this.getArg(0) }
|
||||
|
||||
override ControlFlowNode getValue() { result = this.getArg(1) }
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user