mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge pull request #1763 from markshannon/python-cwe-312
Python: Two new queries for CWE-312.
This commit is contained in:
14
change-notes/1.23/analysis-python.md
Normal file
14
change-notes/1.23/analysis-python.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Improvements to Python analysis
|
||||
|
||||
|
||||
## General improvements
|
||||
|
||||
|
||||
|
||||
## New queries
|
||||
|
||||
| **Query** | **Tags** | **Purpose** |
|
||||
|-----------|----------|-------------|
|
||||
| Clear-text logging of sensitive information (`py/clear-text-logging-sensitive-data`) | security, external/cwe/cwe-312 | Finds instances where sensitive information is logged without encryption or hashing. Results are shown on LGTM by default. |
|
||||
| Clear-text storage of sensitive information (`py/clear-text-storage-sensitive-data`) | security, external/cwe/cwe-312 | Finds instances where sensitive information is stored without encryption or hashing. Results are shown on LGTM by default. |
|
||||
|
||||
5
python/ql/src/Security/CWE-312/CleartextLogging.qhelp
Normal file
5
python/ql/src/Security/CWE-312/CleartextLogging.qhelp
Normal file
@@ -0,0 +1,5 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<include src="CleartextStorage.qhelp" /></qhelp>
|
||||
42
python/ql/src/Security/CWE-312/CleartextLogging.ql
Normal file
42
python/ql/src/Security/CWE-312/CleartextLogging.ql
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @name Clear-text logging of sensitive information
|
||||
* @description Logging sensitive information without encryption or hashing can
|
||||
* expose it to an attacker.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id py/clear-text-logging-sensitive-data
|
||||
* @tags security
|
||||
* external/cwe/cwe-312
|
||||
* external/cwe/cwe-315
|
||||
* external/cwe/cwe-359
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.security.SensitiveData
|
||||
import semmle.python.security.ClearText
|
||||
|
||||
|
||||
class CleartextLoggingConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
CleartextLoggingConfiguration() { this = "ClearTextLogging" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src, TaintKind kind) {
|
||||
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, TaintKind kind) {
|
||||
sink.asCfgNode() instanceof ClearTextLogging::Sink and
|
||||
kind instanceof SensitiveData
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
from CleartextLoggingConfiguration config, TaintedPathSource source, TaintedPathSink sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getSink(), source, sink, "Sensitive data returned by $@ is logged here.",
|
||||
source.getSource(), source.getCfgNode().(SensitiveData::Source).repr()
|
||||
52
python/ql/src/Security/CWE-312/CleartextStorage.qhelp
Normal file
52
python/ql/src/Security/CWE-312/CleartextStorage.qhelp
Normal file
@@ -0,0 +1,52 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Sensitive information that is stored unencrypted is accessible to an attacker
|
||||
who gains access to the storage. This is particularly important for cookies,
|
||||
which are stored on the machine of the end-user.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Ensure that sensitive information is always encrypted before being stored.
|
||||
If possible, avoid placing sensitive information in cookies altogether.
|
||||
Instead, prefer storing, in the cookie, a key that can be used to look up the
|
||||
sensitive information.
|
||||
</p>
|
||||
<p>
|
||||
In general, decrypt sensitive information only at the point where it is
|
||||
necessary for it to be used in cleartext.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
Be aware that external processes often store the <code>standard
|
||||
out</code> and <code>standard error</code> streams of the application,
|
||||
causing logged sensitive information to be stored as well.
|
||||
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example code stores user credentials (in this case, their password) in a cookie in plain text:
|
||||
</p>
|
||||
<sample src="examples/password_in_cookie.py"/>
|
||||
<p>
|
||||
Instead, the credentials should be encrypted, for instance by using the <code>cryptography</code> module, or not stored at all.
|
||||
</p>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
|
||||
<li>M. Dowd, J. McDonald and J. Schuhm, <i>The Art of Software Security Assessment</i>, 1st Edition, Chapter 2 - 'Common Vulnerabilities of Encryption', p. 43. Addison Wesley, 2006.</li>
|
||||
<li>M. Howard and D. LeBlanc, <i>Writing Secure Code</i>, 2nd Edition, Chapter 9 - 'Protecting Secret Data', p. 299. Microsoft, 2002.</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
41
python/ql/src/Security/CWE-312/CleartextStorage.ql
Normal file
41
python/ql/src/Security/CWE-312/CleartextStorage.ql
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @name Clear-text storage of sensitive information
|
||||
* @description Sensitive information stored without encryption or hashing can expose it to an
|
||||
* attacker.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id py/clear-text-storage-sensitive-data
|
||||
* @tags security
|
||||
* external/cwe/cwe-312
|
||||
* external/cwe/cwe-315
|
||||
* external/cwe/cwe-359
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.security.SensitiveData
|
||||
import semmle.python.security.ClearText
|
||||
|
||||
class CleartextStorageConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
CleartextStorageConfiguration() { this = "ClearTextStorage" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src, TaintKind kind) {
|
||||
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, TaintKind kind) {
|
||||
sink.asCfgNode() instanceof ClearTextStorage::Sink and
|
||||
kind instanceof SensitiveData
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
from CleartextStorageConfiguration config, TaintedPathSource source, TaintedPathSink sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getSink(), source, sink, "Sensitive data from $@ is stored here.",
|
||||
source.getSource(), source.getCfgNode().(SensitiveData::Source).repr()
|
||||
@@ -0,0 +1,10 @@
|
||||
from flask import Flask, make_response, request
|
||||
|
||||
app = Flask("Leak password")
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
password = request.args.get("password")
|
||||
resp = make_response(render_template(...))
|
||||
resp.set_cookie("password", password)
|
||||
return resp
|
||||
26
python/ql/src/semmle/python/dataflow/Files.qll
Normal file
26
python/ql/src/semmle/python/dataflow/Files.qll
Normal file
@@ -0,0 +1,26 @@
|
||||
import python
|
||||
import semmle.python.security.TaintTracking
|
||||
|
||||
class OpenFile extends TaintKind {
|
||||
|
||||
OpenFile() { this = "file.open" }
|
||||
|
||||
override string repr() { result = "an open file" }
|
||||
|
||||
|
||||
}
|
||||
|
||||
class OpenFileConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
OpenFileConfiguration() { this = "Open file configuration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src, TaintKind kind) {
|
||||
theOpenFunction().(FunctionObject).getACall() = src.asCfgNode() and
|
||||
kind instanceof OpenFile
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink, TaintKind kind) {
|
||||
none()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -435,8 +435,11 @@ class AbsentModuleAttributeObjectInternal extends ObjectInternal, TAbsentModuleA
|
||||
|
||||
override predicate subscriptUnknown() { any() }
|
||||
|
||||
/* We know what this is called, but not its innate name */
|
||||
override string getName() { none() }
|
||||
/* We know what this is called, but not its innate name.
|
||||
* However, if we are looking for things by name, this is a reasonable approximation */
|
||||
override string getName() {
|
||||
this = TAbsentModuleAttribute(_, result)
|
||||
}
|
||||
|
||||
override predicate contextSensitiveCallee() { none() }
|
||||
|
||||
|
||||
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"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,93 +11,184 @@
|
||||
|
||||
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()
|
||||
}
|
||||
import semmle.python.web.HttpRequest
|
||||
|
||||
/**
|
||||
* 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(Value func) {
|
||||
result = HeuristicNames::getSensitiveDataForName(func.getName())
|
||||
or
|
||||
// This is particularly to pick up methods with an argument like "password", which
|
||||
// may indicate a lookup.
|
||||
exists(string name | name = func.(PythonFunctionValue).getScope().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(Value callee |
|
||||
callee.getACall() = this |
|
||||
data = fromFunction(callee)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
override predicate isSourceOf(TaintKind kind) {
|
||||
kind = data
|
||||
}
|
||||
|
||||
class SensitiveData extends TaintKind {
|
||||
override string repr() {
|
||||
result = "a 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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class SensitiveRequestParameter extends SensitiveData::Source {
|
||||
|
||||
SensitiveData data;
|
||||
|
||||
SensitiveRequestParameter() {
|
||||
this.(CallNode).getFunction().(AttrNode).getName() = "get" and
|
||||
exists(string sensitive |
|
||||
this.(CallNode).getAnArg().refersTo(any(StringObject s | s.getText() = sensitive)) and
|
||||
data = HeuristicNames::getSensitiveDataForName(sensitive)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSourceOf(TaintKind kind) {
|
||||
kind = data
|
||||
}
|
||||
|
||||
override string repr() {
|
||||
result = "a request parameter 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) }
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
| test.py:16:1:16:14 | test.py:16 | a call returning a password |
|
||||
| test.py:17:1:17:12 | test.py:17 | a call returning a password |
|
||||
| test.py:18:1:18:12 | test.py:18 | a call returning a secret |
|
||||
| test.py:19:1:19:19 | test.py:19 | a call returning a certificate or key |
|
||||
| test.py:20:1:20:12 | test.py:20 | a call returning an ID |
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
import python
|
||||
|
||||
import semmle.python.security.SensitiveData
|
||||
|
||||
from SensitiveData::Source src
|
||||
select src.getLocation(), src.repr()
|
||||
21
python/ql/test/library-tests/security/sensitive/test.py
Normal file
21
python/ql/test/library-tests/security/sensitive/test.py
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
from not_found import get_passwd, account_id
|
||||
|
||||
def get_password():
|
||||
pass
|
||||
|
||||
def get_secret():
|
||||
pass
|
||||
|
||||
def fetch_certificate():
|
||||
pass
|
||||
|
||||
def encrypt_password(pwd):
|
||||
pass
|
||||
|
||||
get_password()
|
||||
get_passwd()
|
||||
get_secret()
|
||||
fetch_certificate()
|
||||
account_id()
|
||||
safe_to_store = encrypt_password(pwd)
|
||||
@@ -0,0 +1,6 @@
|
||||
edges
|
||||
| test.py:7:16:7:29 | a password | test.py:8:35:8:42 | a password |
|
||||
#select
|
||||
| test.py:8:35:8:42 | password | test.py:7:16:7:29 | a password | test.py:8:35:8:42 | a password | Sensitive data returned by $@ is logged here. | test.py:7:16:7:29 | get_password() | a call returning a password |
|
||||
| test.py:14:30:14:39 | get_cert() | test.py:14:30:14:39 | a certificate or key | test.py:14:30:14:39 | a certificate or key | Sensitive data returned by $@ is logged here. | test.py:14:30:14:39 | get_cert() | a call returning a certificate or key |
|
||||
| test.py:17:11:17:24 | get_password() | test.py:17:11:17:24 | a password | test.py:17:11:17:24 | a password | Sensitive data returned by $@ is logged here. | test.py:17:11:17:24 | get_password() | a call returning a password |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-312/CleartextLogging.ql
|
||||
@@ -0,0 +1,6 @@
|
||||
edges
|
||||
| password_in_cookie.py:7:16:7:43 | a password | password_in_cookie.py:9:33:9:40 | a password |
|
||||
| test.py:20:12:20:21 | a certificate or key | test.py:22:20:22:23 | a certificate or key |
|
||||
#select
|
||||
| password_in_cookie.py:9:33:9:40 | password | password_in_cookie.py:7:16:7:43 | a password | password_in_cookie.py:9:33:9:40 | a password | Sensitive data from $@ is stored here. | password_in_cookie.py:7:16:7:43 | Attribute() | a request parameter containing a password |
|
||||
| test.py:22:20:22:23 | cert | test.py:20:12:20:21 | a certificate or key | test.py:22:20:22:23 | a certificate or key | Sensitive data from $@ is stored here. | test.py:20:12:20:21 | get_cert() | a call returning a certificate or key |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-312/CleartextStorage.ql
|
||||
1
python/ql/test/query-tests/Security/CWE-312/options
Normal file
1
python/ql/test/query-tests/Security/CWE-312/options
Normal file
@@ -0,0 +1 @@
|
||||
semmle-extractor-options: -p ../lib/ --max-import-depth=3
|
||||
@@ -0,0 +1,10 @@
|
||||
from flask import Flask, make_response, request
|
||||
|
||||
app = Flask("Leak password")
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
password = request.args.get("password")
|
||||
resp = make_response(render_template(...))
|
||||
resp.set_cookie("password", password)
|
||||
return resp
|
||||
22
python/ql/test/query-tests/Security/CWE-312/test.py
Normal file
22
python/ql/test/query-tests/Security/CWE-312/test.py
Normal file
@@ -0,0 +1,22 @@
|
||||
#Don't import logging; it transitively imports a lot of stuff
|
||||
|
||||
def get_password():
|
||||
pass
|
||||
|
||||
def log_password():
|
||||
password = get_password()
|
||||
logging.info("Password '%s'", password)
|
||||
|
||||
def get_cert():
|
||||
pass
|
||||
|
||||
def log_cert():
|
||||
logging.debug("Cert=%s", get_cert())
|
||||
|
||||
def print_password():
|
||||
print(get_password())
|
||||
|
||||
def write_cert(filename):
|
||||
cert = get_cert()
|
||||
with open(filename, "w") as file:
|
||||
file.write(cert)
|
||||
@@ -1,2 +1,2 @@
|
||||
| test_cryptography.py:7:29:7:37 | Use of weak crypto algorithm | Sensitive data from $@ is used in a broken or weak cryptographic algorithm. | test_cryptography.py:4:17:4:30 | sensitive.data.source | sensitive.data.source |
|
||||
| test_pycrypto.py:6:27:6:35 | Use of weak crypto algorithm ARC4 | Sensitive data from $@ is used in a broken or weak cryptographic algorithm. | test_pycrypto.py:4:17:4:30 | sensitive.data.source | sensitive.data.source |
|
||||
| test_cryptography.py:8:29:8:37 | Use of weak crypto algorithm | Sensitive data from $@ is used in a broken or weak cryptographic algorithm. | test_cryptography.py:5:17:5:30 | Taint source | Taint source |
|
||||
| test_pycrypto.py:7:27:7:35 | Use of weak crypto algorithm ARC4 | Sensitive data from $@ is used in a broken or weak cryptographic algorithm. | test_pycrypto.py:5:17:5:30 | Taint source | Taint source |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
| Taint Crypto.Cipher.ARC4 | test_pycrypto.py:5:14:5:27 | test_pycrypto.py:5 | test_pycrypto.py:5:14:5:27 | Attribute() | |
|
||||
| Taint Crypto.Cipher.ARC4 | test_pycrypto.py:6:12:6:17 | test_pycrypto.py:6 | test_pycrypto.py:6:12:6:17 | cipher | |
|
||||
| Taint cryptography.Cipher.RC4 | test_cryptography.py:5:14:5:47 | test_cryptography.py:5 | test_cryptography.py:5:14:5:47 | Cipher() | |
|
||||
| Taint cryptography.Cipher.RC4 | test_cryptography.py:6:17:6:22 | test_cryptography.py:6 | test_cryptography.py:6:17:6:22 | cipher | |
|
||||
| Taint cryptography.encryptor.RC4 | test_cryptography.py:6:17:6:34 | test_cryptography.py:6 | test_cryptography.py:6:17:6:34 | Attribute() | |
|
||||
| Taint cryptography.encryptor.RC4 | test_cryptography.py:7:12:7:20 | test_cryptography.py:7 | test_cryptography.py:7:12:7:20 | encryptor | |
|
||||
| Taint cryptography.encryptor.RC4 | test_cryptography.py:7:42:7:50 | test_cryptography.py:7 | test_cryptography.py:7:42:7:50 | encryptor | |
|
||||
| Taint Crypto.Cipher.ARC4 | test_pycrypto.py:6:14:6:27 | test_pycrypto.py:6 | test_pycrypto.py:6:14:6:27 | Attribute() | |
|
||||
| Taint Crypto.Cipher.ARC4 | test_pycrypto.py:7:12:7:17 | test_pycrypto.py:7 | test_pycrypto.py:7:12:7:17 | cipher | |
|
||||
| Taint cryptography.Cipher.RC4 | test_cryptography.py:6:14:6:47 | test_cryptography.py:6 | test_cryptography.py:6:14:6:47 | Cipher() | |
|
||||
| Taint cryptography.Cipher.RC4 | test_cryptography.py:7:17:7:22 | test_cryptography.py:7 | test_cryptography.py:7:17:7:22 | cipher | |
|
||||
| Taint cryptography.encryptor.RC4 | test_cryptography.py:7:17:7:34 | test_cryptography.py:7 | test_cryptography.py:7:17:7:34 | Attribute() | |
|
||||
| Taint cryptography.encryptor.RC4 | test_cryptography.py:8:12:8:20 | test_cryptography.py:8 | test_cryptography.py:8:12:8:20 | encryptor | |
|
||||
| Taint cryptography.encryptor.RC4 | test_cryptography.py:8:42:8:50 | test_cryptography.py:8 | test_cryptography.py:8:42:8:50 | encryptor | |
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
|
||||
from secrets_store import get_password
|
||||
|
||||
def get_badly_encrypted_password():
|
||||
dangerous = get_password()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from Crypto.Cipher import ARC4
|
||||
from secrets_store import get_password
|
||||
|
||||
def get_badly_encrypted_password():
|
||||
dangerous = get_password()
|
||||
|
||||
Reference in New Issue
Block a user