Python: Add library support for cookies. Update and extend sensitive data library.

This commit is contained in:
Mark Shannon
2019-04-05 15:11:07 +01:00
parent ae2a68b988
commit 79ebd5652a
8 changed files with 295 additions and 69 deletions

View 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"
)
}
}
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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) }
}

View File

@@ -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) }
}

View File

@@ -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) }
}

View File

@@ -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) }
}

View File

@@ -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) }
}