Merge branch 'main' into jorgectf/python/insecure-cookie

This commit is contained in:
Rasmus Wriedt Larsen
2022-05-11 11:13:47 +02:00
7423 changed files with 705987 additions and 227271 deletions

View File

@@ -1,13 +0,0 @@
#!/bin/bash
set -Eeuo pipefail # see https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
# Promotes new dataflow queries to be the real ones
SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
cd $SCRIPTDIR
for file in $(find . -mindepth 2); do
echo "Promoting $file"
mkdir -p "../../Security/$(dirname $file)"
mv "$file" "../../Security/${file}"
done

View File

@@ -1,35 +0,0 @@
/**
* @name Binding a socket to all network interfaces
* @description Binding a socket to all interfaces opens it up to traffic from any IPv4 address
* and is therefore associated with security risks.
* @kind problem
* @id py/old/bind-socket-all-network-interfaces
* @problem.severity error
*/
import python
Value aSocket() { result.getClass() = Value::named("socket.socket") }
CallNode socketBindCall() {
result = aSocket().attr("bind").(CallableValue).getACall() and major_version() = 3
or
result.getFunction().(AttrNode).getObject("bind").pointsTo(aSocket()) and
major_version() = 2
}
string allInterfaces() { result = "0.0.0.0" or result = "" }
Value getTextValue(string address) {
result = Value::forUnicode(address) and major_version() = 3
or
result = Value::forString(address) and major_version() = 2
}
from CallNode call, TupleValue args, string address
where
call = socketBindCall() and
call.getArg(0).pointsTo(args) and
args.getItem(0) = getTextValue(address) and
address = allInterfaces()
select call.getNode(), "'" + address + "' binds a socket to all interfaces."

View File

@@ -1,38 +0,0 @@
/**
* @name OLD QUERY: Uncontrolled data used in path expression
* @description Accessing paths influenced by users can allow an attacker to access unexpected resources.
* @kind path-problem
* @problem.severity error
* @id py/old/path-injection
*/
import python
import semmle.python.security.Paths
/* Sources */
import semmle.python.web.HttpRequest
/* Sinks */
import semmle.python.security.injection.Path
class PathInjectionConfiguration extends TaintTracking::Configuration {
PathInjectionConfiguration() { this = "Path injection configuration" }
override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof OpenNode }
override predicate isSanitizer(Sanitizer sanitizer) {
sanitizer instanceof PathSanitizer or
sanitizer instanceof NormalizedPathSanitizer
}
override predicate isExtension(TaintTracking::Extension extension) {
extension instanceof AbsPath
}
}
from PathInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "This path depends on $@.", src.getSource(),
"a user-provided value"

View File

@@ -1,36 +0,0 @@
/**
* @name OLD QUERY: Uncontrolled command line
* @description Using externally controlled strings in a command line may allow a malicious
* user to change the meaning of the command.
* @kind path-problem
* @problem.severity error
* @id py/old/command-line-injection
*/
import python
import semmle.python.security.Paths
/* Sources */
import semmle.python.web.HttpRequest
/* Sinks */
import semmle.python.security.injection.Command
class CommandInjectionConfiguration extends TaintTracking::Configuration {
CommandInjectionConfiguration() { this = "Command injection configuration" }
override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof CommandSink }
override predicate isExtension(TaintTracking::Extension extension) {
extension instanceof FirstElementFlow
or
extension instanceof FabricExecuteExtension
}
}
from CommandInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "This command depends on $@.", src.getSource(),
"a user-provided value"

View File

@@ -1,37 +0,0 @@
/**
* @name OLD QUERY: Reflected server-side cross-site scripting
* @description Writing user input directly to a web page
* allows for a cross-site scripting vulnerability.
* @kind path-problem
* @problem.severity error
* @id py/old/reflective-xss
*/
import python
import semmle.python.security.Paths
/* Sources */
import semmle.python.web.HttpRequest
/* Sinks */
import semmle.python.web.HttpResponse
/* Flow */
import semmle.python.security.strings.Untrusted
class ReflectedXssConfiguration extends TaintTracking::Configuration {
ReflectedXssConfiguration() { this = "Reflected XSS configuration" }
override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) {
sink instanceof HttpResponseTaintSink and
not sink instanceof DjangoResponseContent
or
sink instanceof DjangoResponseContentXSSVulnerable
}
}
from ReflectedXssConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "Cross-site scripting vulnerability due to $@.", src.getSource(),
"a user-provided value"

View File

@@ -1,47 +0,0 @@
/**
* @name OLD QUERY: SQL query built from user-controlled sources
* @description Building a SQL query from user-controlled sources is vulnerable to insertion of
* malicious SQL code by the user.
* @kind path-problem
* @problem.severity error
* @id py/old/sql-injection
*/
import python
import semmle.python.security.Paths
/* Sources */
import semmle.python.web.HttpRequest
/* Sinks */
import semmle.python.security.injection.Sql
import semmle.python.web.django.Db
import semmle.python.web.django.Model
class SQLInjectionConfiguration extends TaintTracking::Configuration {
SQLInjectionConfiguration() { this = "SQL injection configuration" }
override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof SqlInjectionSink }
}
/*
* Additional configuration to support tracking of DB objects. Connections, cursors, etc.
* Without this configuration (or the LegacyConfiguration), the pattern of
* `any(MyTaintKind k).taints(control_flow_node)` used in DbConnectionExecuteArgument would not work.
*/
class DbConfiguration extends TaintTracking::Configuration {
DbConfiguration() { this = "DB configuration" }
override predicate isSource(TaintTracking::Source source) {
source instanceof DjangoModelObjects or
source instanceof DbConnectionSource
}
}
from SQLInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "This SQL query depends on $@.", src.getSource(),
"a user-provided value"

View File

@@ -1,30 +0,0 @@
/**
* @name Code injection
* @description OLD QUERY: Interpreting unsanitized user input as code allows a malicious user arbitrary
* code execution.
* @kind path-problem
* @problem.severity error
* @id py/old/code-injection
*/
import python
import semmle.python.security.Paths
/* Sources */
import semmle.python.web.HttpRequest
/* Sinks */
import semmle.python.security.injection.Exec
class CodeInjectionConfiguration extends TaintTracking::Configuration {
CodeInjectionConfiguration() { this = "Code injection configuration" }
override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof StringEvaluationNode }
}
from CodeInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "$@ flows to here and is interpreted as code.", src.getSource(),
"A user-provided value"

View File

@@ -1,33 +0,0 @@
/**
* @name Clear-text logging of sensitive information
* @description OLD QUERY: Logging sensitive information without encryption or hashing can
* expose it to an attacker.
* @kind path-problem
* @problem.severity error
* @id py/old/clear-text-logging-sensitive-data
* @deprecated
*/
import python
import semmle.python.security.Paths
import semmle.python.dataflow.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()

View File

@@ -1,33 +0,0 @@
/**
* @name Clear-text storage of sensitive information
* @description OLD QUERY: Sensitive information stored without encryption or hashing can expose it to an
* attacker.
* @kind path-problem
* @problem.severity error
* @id py/old/clear-text-storage-sensitive-data
* @deprecated
*/
import python
import semmle.python.security.Paths
import semmle.python.dataflow.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()

View File

@@ -1,78 +0,0 @@
/**
* @name OLD QUERY: Use of weak cryptographic key
* @description Use of a cryptographic key that is too small may allow the encryption to be broken.
* @kind problem
* @problem.severity error
* @id py/old/weak-crypto-key
*/
import python
int minimumSecureKeySize(string algo) {
algo = "DSA" and result = 2048
or
algo = "RSA" and result = 2048
or
algo = "ECC" and result = 224
}
predicate dsaRsaKeySizeArg(FunctionValue func, string algorithm, string arg) {
exists(ModuleValue mod | func = mod.attr(_) |
algorithm = "DSA" and
(
mod = Module::named("cryptography.hazmat.primitives.asymmetric.dsa") and arg = "key_size"
or
mod = Module::named("Crypto.PublicKey.DSA") and arg = "bits"
or
mod = Module::named("Cryptodome.PublicKey.DSA") and arg = "bits"
)
or
algorithm = "RSA" and
(
mod = Module::named("cryptography.hazmat.primitives.asymmetric.rsa") and arg = "key_size"
or
mod = Module::named("Crypto.PublicKey.RSA") and arg = "bits"
or
mod = Module::named("Cryptodome.PublicKey.RSA") and arg = "bits"
)
)
}
predicate ecKeySizeArg(FunctionValue func, string arg) {
exists(ModuleValue mod | func = mod.attr(_) |
mod = Module::named("cryptography.hazmat.primitives.asymmetric.ec") and arg = "curve"
)
}
int keySizeFromCurve(ClassValue curveClass) {
result = curveClass.declaredAttribute("key_size").(NumericValue).getIntValue()
}
predicate algorithmAndKeysizeForCall(
CallNode call, string algorithm, int keySize, ControlFlowNode keyOrigin
) {
exists(FunctionValue func, string argname, ControlFlowNode arg |
arg = func.getNamedArgumentForCall(call, argname)
|
exists(NumericValue key |
arg.pointsTo(key, keyOrigin) and
dsaRsaKeySizeArg(func, algorithm, argname) and
keySize = key.getIntValue()
)
or
exists(Value curveClassInstance |
algorithm = "ECC" and
ecKeySizeArg(func, argname) and
arg.pointsTo(_, curveClassInstance, keyOrigin) and
keySize = keySizeFromCurve(curveClassInstance.getClass())
)
)
}
from CallNode call, string algo, int keySize, ControlFlowNode origin
where
algorithmAndKeysizeForCall(call, algo, keySize, origin) and
keySize < minimumSecureKeySize(algo)
select call,
"Creation of an " + algo + " key uses $@ bits, which is below " + minimumSecureKeySize(algo) +
" and considered breakable.", origin, keySize.toString()

View File

@@ -1,28 +0,0 @@
/**
* @name OLD QUERY: Use of a broken or weak cryptographic algorithm
* @description Using broken or weak cryptographic algorithms can compromise security.
* @kind path-problem
* @problem.severity warning
* @id py/old/weak-cryptographic-algorithm
* @deprecated
*/
import python
import semmle.python.security.Paths
import semmle.python.security.SensitiveData
import semmle.python.security.Crypto
class BrokenCryptoConfiguration extends TaintTracking::Configuration {
BrokenCryptoConfiguration() { this = "Broken crypto configuration" }
override predicate isSource(TaintTracking::Source source) {
source instanceof SensitiveDataSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof WeakCryptoSink }
}
from BrokenCryptoConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "$@ is used in a broken or weak cryptographic algorithm.",
src.getSource(), "Sensitive data"

View File

@@ -1,32 +0,0 @@
/**
* @name OLD QUERY: Deserializing untrusted input
* @description Deserializing user-controlled data may allow attackers to execute arbitrary code.
* @kind path-problem
* @id py/old/unsafe-deserialization
* @problem.severity error
*/
import python
import semmle.python.security.Paths
// Sources -- Any untrusted input
import semmle.python.web.HttpRequest
// Flow -- untrusted string
import semmle.python.security.strings.Untrusted
// Sink -- Unpickling and other deserialization formats.
import semmle.python.security.injection.Pickle
import semmle.python.security.injection.Marshal
import semmle.python.security.injection.Yaml
class UnsafeDeserializationConfiguration extends TaintTracking::Configuration {
UnsafeDeserializationConfiguration() { this = "Unsafe deserialization configuration" }
override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof DeserializationSink }
}
from UnsafeDeserializationConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "Deserializing of $@.", src.getSource(), "untrusted input"

View File

@@ -1,37 +0,0 @@
/**
* @name OLD QUERY: URL redirection from remote source
* @description URL redirection based on unvalidated user input
* may cause redirection to malicious web sites.
* @kind path-problem
* @problem.severity error
* @id py/old/url-redirection
*/
import python
import semmle.python.security.Paths
import semmle.python.web.HttpRedirect
import semmle.python.web.HttpRequest
import semmle.python.security.strings.Untrusted
/** Url redirection is a problem only if the user controls the prefix of the URL */
class UntrustedPrefixStringKind extends UntrustedStringKind {
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
result = UntrustedStringKind.super.getTaintForFlowStep(fromnode, tonode) and
not tonode.(BinaryExprNode).getRight() = fromnode
}
}
class UrlRedirectConfiguration extends TaintTracking::Configuration {
UrlRedirectConfiguration() { this = "URL redirect configuration" }
override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof HttpRedirectTaintSink }
}
from UrlRedirectConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "Untrusted URL redirection due to $@.", src.getSource(),
"a user-provided value"

View File

@@ -0,0 +1,56 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Extracting files from a malicious zip archive without validating that the destination file path
is within the destination directory can cause files outside the destination directory to be
overwritten, due to the possible presence of directory traversal elements (<code>..</code>) in
archive paths.</p>
<p>Zip archives contain archive entries representing each file in the archive. These entries
include a file path for the entry, but these file paths are not restricted and may contain
unexpected special elements such as the directory traversal element (<code>..</code>). If these
file paths are used to determine an output file to write the contents of the archive item to, then
the file may be written to an unexpected location. This can result in sensitive information being
revealed or deleted, or an attacker being able to influence behavior by modifying unexpected
files.</p>
<p>For example, if a Zip archive contains a file entry <code>..\sneaky-file</code>, and the Zip archive
is extracted to the directory <code>c:\output</code>, then naively combining the paths would result
in an output file path of <code>c:\output\..\sneaky-file</code>, which would cause the file to be
written to <code>c:\sneaky-file</code>.</p>
</overview>
<recommendation>
<p>Ensure that output paths constructed from Zip archive entries are validated
to prevent writing files to unexpected locations.</p>
<p>The recommended way of writing an output file from a Zip archive entry is to call <code>extract()</code> or <code>extractall()</code>.
</p>
</recommendation>
<example>
<p>
In this example an archive is extracted without validating file paths.
</p>
<sample src="zipslip_bad.py" />
<p>To fix this vulnerability, we need to call the function <code>extractall()</code>.
</p>
<sample src="zipslip_good.py" />
</example>
<references>
<li>
Snyk:
<a href="https://snyk.io/research/zip-slip-vulnerability">Zip Slip Vulnerability</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,22 @@
/**
* @name Arbitrary file write during archive extraction ("Zip Slip")
* @description Extracting files from a malicious archive without validating that the
* destination file path is within the destination directory can cause files outside
* the destination directory to be overwritten.
* @kind path-problem
* @id py/zipslip
* @problem.severity error
* @security-severity 7.5
* @precision high
* @tags security
* external/cwe/cwe-022
*/
import python
import experimental.semmle.python.security.ZipSlip
import DataFlow::PathGraph
from ZipSlipConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Extraction of zipfile from $@", source.getNode(),
"a potentially untrusted source"

View File

@@ -0,0 +1,16 @@
import zipfile
import shutil
def unzip(filename):
with tarfile.open(filename) as zipf:
#BAD : This could write any file on the filesystem.
for entry in zipf:
shutil.copyfile(entry, "/tmp/unpack/")
def unzip4(filename):
zf = zipfile.ZipFile(filename)
filelist = zf.namelist()
for x in filelist:
with zf.open(x) as srcf:
shutil.copyfileobj(srcf, dstfile)

View File

@@ -0,0 +1,10 @@
import zipfile
def unzip(filename, dir):
zf = zipfile.ZipFile(filename)
zf.extractall(dir)
def unzip1(filename, dir):
zf = zipfile.ZipFile(filename)
zf.extract(dir)

View File

@@ -21,11 +21,11 @@ import semmle.python.security.strings.Untrusted
class TemplateInjectionConfiguration extends TaintTracking::Configuration {
TemplateInjectionConfiguration() { this = "Template injection configuration" }
override predicate isSource(TaintTracking::Source source) {
deprecated override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof SSTISink }
deprecated override predicate isSink(TaintTracking::Sink sink) { sink instanceof SSTISink }
}
from TemplateInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink

View File

@@ -1,50 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>If an LDAP query or DN is built using string concatenation or string formatting, and the
components of the concatenation include user input without any proper sanitization, a user
is likely to be able to run malicious LDAP queries.</p>
</overview>
<recommendation>
<p>If user input must be included in an LDAP query or DN, it should be escaped to
avoid a malicious user providing special characters that change the meaning
of the query. In Python2, user input should be escaped with <code>ldap.dn.escape_dn_chars</code>
or <code>ldap.filter.escape_filter_chars</code>, while in Python3, user input should be escaped with
<code>ldap3.utils.dn.escape_rdn</code> or <code>ldap3.utils.conv.escape_filter_chars</code>
depending on the component tainted by the user. A good practice is to escape filter characters
that could change the meaning of the query (https://tools.ietf.org/search/rfc4515#section-3).</p>
</recommendation>
<example>
<p>In the following examples, the code accepts both <code>username</code> and <code>dc</code> from the user,
which it then uses to build a LDAP query and DN.</p>
<p>The first and the second example uses the unsanitized user input directly
in the search filter and DN for the LDAP query.
A malicious user could provide special characters to change the meaning of these
components, and search for a completely different set of values.</p>
<sample src="examples/example_bad1.py" />
<sample src="examples/example_bad2.py" />
<p>In the third and four example, the input provided by the user is sanitized before it is included in the search filter or DN.
This ensures the meaning of the query cannot be changed by a malicious user.</p>
<sample src="examples/example_good1.py" />
<sample src="examples/example_good2.py" />
</example>
<references>
<li>OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.html">LDAP Injection Prevention Cheat Sheet</a>.</li>
<li>OWASP: <a href="https://owasp.org/www-community/attacks/LDAP_Injection">LDAP Injection</a>.</li>
<li>SonarSource: <a href="https://rules.sonarsource.com/python/RSPEC-2078">RSPEC-2078</a>.</li>
<li>Python2: <a href="https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap.html">LDAP Documentation</a>.</li>
<li>Python3: <a href="https://ldap3.readthedocs.io/en/latest/">LDAP Documentation</a>.</li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/LDAP_injection">LDAP injection</a>.</li>
<li>BlackHat: <a href="https://www.blackhat.com/presentations/bh-europe-08/Alonso-Parada/Whitepaper/bh-eu-08-alonso-parada-WP.pdf">LDAP Injection and Blind LDAP Injection</a>.</li>
<li>LDAP: <a href="https://ldap.com/2018/05/04/understanding-and-defending-against-ldap-injection-attacks/">Understanding and Defending Against LDAP Injection Attacks</a>.</li>
</references>
</qhelp>

View File

@@ -1,21 +0,0 @@
/**
* @name LDAP query built from user-controlled sources
* @description Building an LDAP query from user-controlled sources is vulnerable to insertion of
* malicious LDAP code by the user.
* @kind path-problem
* @problem.severity error
* @id py/ldap-injection
* @tags experimental
* security
* external/cwe/cwe-090
*/
// Determine precision above
import python
import experimental.semmle.python.security.injection.LDAP
import DataFlow::PathGraph
from LDAPInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ LDAP query parameter comes from $@.", sink.getNode(),
"This", source.getNode(), "a user-provided value"

View File

@@ -1,15 +0,0 @@
from flask import request, Flask
import ldap
@app.route("/normal")
def normal():
unsafe_dc = request.args['dc']
unsafe_filter = request.args['username']
dn = "dc={}".format(unsafe_dc)
search_filter = "(user={})".format(unsafe_filter)
ldap_connection = ldap.initialize("ldap://127.0.0.1")
user = ldap_connection.search_s(
dn, ldap.SCOPE_SUBTREE, search_filter)

View File

@@ -1,15 +0,0 @@
from flask import request, Flask
import ldap3
@app.route("/normal")
def normal():
unsafe_dc = request.args['dc']
unsafe_filter = request.args['username']
dn = "dc={}".format(unsafe_dc)
search_filter = "(user={})".format(unsafe_filter)
srv = ldap3.Server('ldap://127.0.0.1')
conn = ldap3.Connection(srv, user=dn, auto_bind=True)
conn.search(dn, search_filter)

View File

@@ -1,20 +0,0 @@
from flask import request, Flask
import ldap
import ldap.filter
import ldap.dn
@app.route("/normal")
def normal():
unsafe_dc = request.args['dc']
unsafe_filter = request.args['username']
safe_dc = ldap.dn.escape_dn_chars(unsafe_dc)
safe_filter = ldap.filter.escape_filter_chars(unsafe_filter)
dn = "dc={}".format(safe_dc)
search_filter = "(user={})".format(safe_filter)
ldap_connection = ldap.initialize("ldap://127.0.0.1")
user = ldap_connection.search_s(
dn, ldap.SCOPE_SUBTREE, search_filter)

View File

@@ -1,20 +0,0 @@
from flask import request, Flask
import ldap3
from ldap3.utils.dn import escape_rdn
from ldap3.utils.conv import escape_filter_chars
@app.route("/normal")
def normal():
unsafe_dc = request.args['dc']
unsafe_filter = request.args['username']
safe_dc = escape_rdn(unsafe_dc)
safe_filter = escape_filter_chars(unsafe_filter)
dn = "dc={}".format(safe_dc)
search_filter = "(user={})".format(safe_filter)
srv = ldap3.Server('ldap://127.0.0.1')
conn = ldap3.Connection(srv, user=dn, auto_bind=True)
conn.search(dn, search_filter)

View File

@@ -20,11 +20,11 @@ import experimental.semmle.python.security.injection.XSLT
class XSLTInjectionConfiguration extends TaintTracking::Configuration {
XSLTInjectionConfiguration() { this = "XSLT injection configuration" }
override predicate isSource(TaintTracking::Source source) {
deprecated override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) {
deprecated override predicate isSink(TaintTracking::Sink sink) {
sink instanceof XSLTInjection::XSLTInjectionSink
}
}

View File

@@ -1,49 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>If unsanitized user input is written to a log entry, a malicious user may be able to forge new log entries.</p>
<p>Forgery can occur if a user provides some input creating the appearance of multiple
log entries. This can include unescaped new-line characters, or HTML or other markup.</p>
</overview>
<recommendation>
<p>
User input should be suitably sanitized before it is logged.
</p>
<p>
If the log entries are plain text then line breaks should be removed from user input, using for example
<code>replace(old, new)</code> or similar. Care should also be taken that user input is clearly marked
in log entries, and that a malicious user cannot cause confusion in other ways.
</p>
<p>
For log entries that will be displayed in HTML, user input should be HTML encoded before being logged, to prevent forgery and
other forms of HTML injection.
</p>
</recommendation>
<example>
<p>
In the example, the name provided by the user is recorded using the log output function (<code>logging.info</code> or <code>app.logger.info</code>, etc.).
In these four cases, the name provided by the user is not provided The processing is recorded. If a malicious user provides <code>Guest%0D%0AUser name: Admin</code>
as a parameter, the log entry will be divided into two lines, the first line is <code>User name: Guest</code> code>, the second line is <code>User name: Admin</code>.
</p>
<sample src="LogInjectionBad.py" />
<p>
In a good example, the program uses the <code>replace</code> function to provide parameter processing to the user, and replace <code>\r\n</code> and <code>\n</code>
with empty characters. To a certain extent, the occurrence of log injection vulnerabilities is reduced.
</p>
<sample src="LogInjectionGood.py" />
</example>
<references>
<li>OWASP: <a href="https://owasp.org/www-community/attacks/Log_Injection">Log Injection</a>.</li>
</references>
</qhelp>

View File

@@ -1,20 +0,0 @@
/**
* @name Log Injection
* @description Building log entries from user-controlled data is vulnerable to
* insertion of forged log entries by a malicious user.
* @kind path-problem
* @problem.severity error
* @precision high
* @id py/log-injection
* @tags security
* external/cwe/cwe-117
*/
import python
import experimental.semmle.python.security.injection.LogInjection
import DataFlow::PathGraph
from LogInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ flows to log entry.", source.getNode(),
"User-provided value"

View File

@@ -1,44 +0,0 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Desc Log Injection
"""
from flask import Flask
from flask import request
from django.utils.log import request_logger
import logging
logging.basicConfig(level=logging.DEBUG)
app = Flask(__name__)
@app.route('/bad1')
def bad1():
name = request.args.get('name')
app.logger.info('User name: ' + name) # Bad
return 'bad1'
@app.route('/bad2')
def bad2():
name = request.args.get('name')
logging.info('User name: ' + name) # Bad
return 'bad2'
@app.route('/bad3')
def bad3():
name = request.args.get('name')
request_logger.warn('User name: ' + name) # Bad
return 'bad3'
@app.route('/bad4')
def bad4():
name = request.args.get('name')
logtest = logging.getLogger('test')
logtest.debug('User name: ' + name) # Bad
return 'bad4'
if __name__ == '__main__':
app.debug = True
handler = logging.FileHandler('log')
app.logger.addHandler(handler)
app.run()

View File

@@ -1,25 +0,0 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Desc Log Injection
"""
from flask import Flask
from flask import request
import logging
logging.basicConfig(level=logging.DEBUG)
app = Flask(__name__)
@app.route('/good1')
def good1():
name = request.args.get('name')
name = name.replace('\r\n','').replace('\n','')
logging.info('User name: ' + name) # Good
return 'good1'
if __name__ == '__main__':
app.debug = True
handler = logging.FileHandler('log')
app.logger.addHandler(handler)
app.run()

View File

@@ -0,0 +1,49 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Using only a call to
<code>pam_authenticate</code>
to check the validity of a login can lead to authorization bypass vulnerabilities.
</p>
<p>
A
<code>pam_authenticate</code>
only verifies the credentials of a user. It does not check if a user has an appropriate authorization to actually login. This means a user with a expired login or a password can still access the system.
</p>
</overview>
<recommendation>
<p>
A call to
<code>pam_authenticate</code>
should be followed by a call to
<code>pam_acct_mgmt</code>
to check if a user is allowed to login.
</p>
</recommendation>
<example>
<p>
In the following example, the code only checks the credentials of a user. Hence, in this case, a user expired with expired creds can still login. This can be verified by creating a new user account, expiring it with
<code>chage -E0 `username` </code>
and then trying to log in.
</p>
<sample src="PamAuthorizationBad.py" />
<p>
This can be avoided by calling
<code>pam_acct_mgmt</code>
call to verify access as has been done in the snippet shown below.
</p>
<sample src="PamAuthorizationGood.py" />
</example>
<references>
<li>
Man-Page:
<a href="https://man7.org/linux/man-pages/man3/pam_acct_mgmt.3.html">pam_acct_mgmt</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,36 @@
/**
* @name Authorization bypass due to incorrect usage of PAM
* @description Using only the `pam_authenticate` call to check the validity of a login can lead to a authorization bypass.
* @kind problem
* @problem.severity warning
* @precision high
* @id py/pam-auth-bypass
* @tags security
* external/cwe/cwe-285
*/
import python
import semmle.python.ApiGraphs
import experimental.semmle.python.Concepts
import semmle.python.dataflow.new.TaintTracking
API::Node libPam() {
exists(API::CallNode findLibCall, API::CallNode cdllCall |
findLibCall = API::moduleImport("ctypes.util").getMember("find_library").getACall() and
findLibCall.getParameter(0).getAValueReachingRhs().asExpr().(StrConst).getText() = "pam" and
cdllCall = API::moduleImport("ctypes").getMember("CDLL").getACall() and
cdllCall.getParameter(0).getAValueReachingRhs() = findLibCall
|
result = cdllCall.getReturn()
)
}
from API::CallNode authenticateCall, DataFlow::Node handle
where
authenticateCall = libPam().getMember("pam_authenticate").getACall() and
handle = authenticateCall.getArg(0) and
not exists(API::CallNode acctMgmtCall |
acctMgmtCall = libPam().getMember("pam_acct_mgmt").getACall() and
DataFlow::localFlow(handle, acctMgmtCall.getArg(0))
)
select authenticateCall, "This PAM authentication call may be lead to an authorization bypass."

View File

@@ -0,0 +1,13 @@
def authenticate(self, username, password, service='login', encoding='utf-8', resetcreds=True):
libpam = CDLL(find_library("pam"))
pam_authenticate = libpam.pam_authenticate
pam_authenticate.restype = c_int
pam_authenticate.argtypes = [PamHandle, c_int]
handle = PamHandle()
conv = PamConv(my_conv, 0)
retval = pam_start(service, username, byref(conv), byref(handle))
retval = pam_authenticate(handle, 0)
return retval == 0

View File

@@ -0,0 +1,17 @@
def authenticate(self, username, password, service='login', encoding='utf-8', resetcreds=True):
libpam = CDLL(find_library("pam"))
pam_authenticate = libpam.pam_authenticate
pam_acct_mgmt = libpam.pam_acct_mgmt
pam_authenticate.restype = c_int
pam_authenticate.argtypes = [PamHandle, c_int]
pam_acct_mgmt.restype = c_int
pam_acct_mgmt.argtypes = [PamHandle, c_int]
handle = PamHandle()
conv = PamConv(my_conv, 0)
retval = pam_start(service, username, byref(conv), byref(handle))
retval = pam_authenticate(handle, 0)
if retval == 0:
retval = pam_acct_mgmt(handle, 0)
return retval == 0

View File

@@ -4,8 +4,7 @@
* @kind problem
* @problem.severity warning
* @id py/improper-ldap-auth
* @tags experimental
* security
* @tags security
* external/cwe/cwe-287
*/
@@ -14,7 +13,7 @@ import python
import experimental.semmle.python.Concepts
import semmle.python.dataflow.new.DataFlow
predicate authenticatesImproperly(LDAPBind ldapBind) {
predicate authenticatesImproperly(LdapBind ldapBind) {
(
DataFlow::localFlow(DataFlow::exprNode(any(None noneName)), ldapBind.getPassword()) or
not exists(ldapBind.getPassword())
@@ -26,6 +25,6 @@ predicate authenticatesImproperly(LDAPBind ldapBind) {
)
}
from LDAPBind ldapBind
from LdapBind ldapBind
where authenticatesImproperly(ldapBind)
select ldapBind, "The following LDAP bind operation is executed without authentication"

View File

@@ -0,0 +1,48 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Using a cryptographically weak pseudo-random number generator to generate a security-sensitive value,
such as a password, makes it easier for an attacker to predict the value.
</p>
<p>
Pseudo-random number generators generate a sequence of numbers that only approximates the properties
of random numbers. The sequence is not truly random because it is completely determined by a
relatively small set of initial values, the seed. If the random number generator is
cryptographically weak, then this sequence may be easily predictable through outside observations.
</p>
</overview>
<recommendation>
<p>
Use a cryptographically secure pseudo-random number generator if the output is to be used in a
security sensitive context. As a rule of thumb, a value should be considered "security sensitive"
if predicting it would allow the attacker to perform an action that they would otherwise be unable
to perform. For example, if an attacker could predict the random password generated for a new user,
they would be able to log in as that new user.
</p>
<p>
For Python, <code>secrets</code> provides a cryptographically secure pseudo-random
number generator. <code>random</code> is not cryptographically secure, and should be avoided in
security contexts.
</p>
</recommendation>
<example>
<p>
The example below uses the <code>random</code> package instead of <code>secrets</code> to generate a password:
</p>
<sample src="examples/InsecureRandomness.py" />
<p>
Instead, use <code>secrets</code>:
</p>
<sample src="examples/InsecureRandomnessGood.py" />
</example>
<references>
<li>Wikipedia. <a href="http://en.wikipedia.org/wiki/Pseudorandom_number_generator">Pseudo-random number generator</a>.</li>
<li>OWASP: <a href="https://owasp.org/www-community/vulnerabilities/Insecure_Randomness">Insecure Randomness</a>.</li>
<li>OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#secure-random-number-generation">Secure Random Number Generation</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,23 @@
/**
* @name Insecure randomness
* @description Using a cryptographically weak pseudo-random number generator to generate a
* security-sensitive value may allow an attacker to predict what value will
* be generated.
* @kind path-problem
* @problem.severity warning
* @security-severity 7.8
* @precision high
* @id py/insecure-randomness
* @tags security
* external/cwe/cwe-338
*/
import python
import experimental.semmle.python.security.InsecureRandomness::InsecureRandomness
import semmle.python.dataflow.new.DataFlow
import DataFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Cryptographically insecure $@ in a security context.",
source.getNode(), "random value"

View File

@@ -0,0 +1,6 @@
import random
def generatePassword():
# BAD: the random is not cryptographically secure
return random.random()

View File

@@ -0,0 +1,7 @@
import secrets
def generatePassword():
# GOOD: the random is cryptographically secure
secret_generator = secrets.SystemRandom()
return secret_generator.random()

View File

@@ -0,0 +1,7 @@
import jwt
# algorithm set to None
jwt.encode(payload, "somekey", None)
# empty key
jwt.encode(payload, key="", algorithm="HS256")

View File

@@ -0,0 +1,30 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Applications encoding a JSON Web Token (JWT) may be vulnerable when the applied key or algorithm
is empty or <code>None</code>.</p>
</overview>
<recommendation>
<p>Use non-empty nor <code>None</code> values while encoding JWT payloads.</p>
</recommendation>
<example>
<p>This example shows two PyJWT encoding calls.
In the first place, the encoding process use a None algorithm whereas the second example uses an
empty key. Both examples leave the payload insecurely encoded.
</p>
<sample src="JWTEmptyKeyOrAlgorithm.py" />
</example>
<references>
<li>PyJWT: <a href="https://pyjwt.readthedocs.io/en/stable/">Documentation</a>.</li>
<li>Authlib JWT: <a href="https://docs.authlib.org/en/latest/specs/rfc7519.html">Documentation</a>.</li>
<li>Python-Jose: <a href="https://github.com/mpdavis/python-jose">Documentation</a>.</li>
<li>Auth0 Blog: <a href="https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/#Meet-the--None--Algorithm">Meet the "None" Algorithm</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,22 @@
/**
* @name JWT encoding using empty key or algorithm
* @description The application uses an empty secret or algorithm while encoding a JWT Token.
* @kind problem
* @problem.severity warning
* @id py/jwt-empty-secret-or-algorithm
* @tags security
*/
// determine precision above
import python
import experimental.semmle.python.Concepts
import experimental.semmle.python.frameworks.JWT
from JwtEncoding jwtEncoding, string affectedComponent
where
affectedComponent = "algorithm" and
isEmptyOrNone(jwtEncoding.getAlgorithm())
or
affectedComponent = "key" and
isEmptyOrNone(jwtEncoding.getKey())
select jwtEncoding, "This JWT encoding has an empty " + affectedComponent + "."

View File

@@ -0,0 +1,4 @@
import jwt
# unverified decoding
jwt.decode(payload, key="somekey", verify=False)

View File

@@ -0,0 +1,30 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Applications decoding a JSON Web Token (JWT) may be vulnerable when the
key isn't verified in the process.
</p>
</overview>
<recommendation>
<p>Set the <code>verify</code> argument to <code>True</code> or use
a framework that does it by default.
</p>
</recommendation>
<example>
<p>This example shows a PyJWT encoding call with the <code>verify</code>
argument set to <code>False</code>.
</p>
<sample src="JWTMissingSecretOrPublicKeyVerification.py" />
</example>
<references>
<li>PyJWT: <a href="https://pyjwt.readthedocs.io/en/stable/">Documentation</a>.</li>
<li>Authlib JWT: <a href="https://docs.authlib.org/en/latest/specs/rfc7519.html">Documentation</a>.</li>
<li>Python-Jose: <a href="https://github.com/mpdavis/python-jose">Documentation</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,17 @@
/**
* @name JWT missing secret or public key verification
* @description The application does not verify the JWT payload with a cryptographic secret or public key.
* @kind problem
* @problem.severity warning
* @id py/jwt-missing-verification
* @tags security
* external/cwe/cwe-347
*/
// determine precision above
import python
import experimental.semmle.python.Concepts
from JwtDecoding jwtDecoding
where not jwtDecoding.verifiesSignature()
select jwtDecoding.getPayload(), "is not verified with a cryptographic secret or public key."

View File

@@ -18,7 +18,7 @@ import ClientSuppliedIpUsedInSecurityCheckLib
import DataFlow::PathGraph
/**
* Taint-tracking configuration tracing flow from obtaining a client ip from an HTTP header to a sensitive use.
* A taint-tracking configuration tracing flow from obtaining a client ip from an HTTP header to a sensitive use.
*/
class ClientSuppliedIpUsedInSecurityCheckConfig extends TaintTracking::Configuration {
ClientSuppliedIpUsedInSecurityCheckConfig() { this = "ClientSuppliedIpUsedInSecurityCheckConfig" }

View File

@@ -1,8 +1,8 @@
private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.DataFlow
/**
* A data flow source of the client ip obtained according to the remote endpoint identifier specified

View File

@@ -4,8 +4,7 @@
* @kind path-problem
* @problem.severity error
* @id py/insecure-ldap-auth
* @tags experimental
* security
* @tags security
* external/cwe/cwe-522
* external/cwe/cwe-523
*/
@@ -15,7 +14,7 @@ import python
import DataFlow::PathGraph
import experimental.semmle.python.security.LDAPInsecureAuth
from LDAPInsecureAuthConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
from LdapInsecureAuthConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ is authenticated insecurely.", sink.getNode(),
"This LDAP host"

View File

@@ -0,0 +1,19 @@
/**
* @name SimpleXMLRPCServer DoS vulnerability
* @description SimpleXMLRPCServer is vulnerable to DoS attacks from untrusted user input
* @kind problem
* @problem.severity warning
* @precision high
* @id py/simple-xml-rpc-server-dos
* @tags security
* external/cwe/cwe-776
*/
private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
from DataFlow::CallCfgNode call
where
call = API::moduleImport("xmlrpc").getMember("server").getMember("SimpleXMLRPCServer").getACall()
select call, "SimpleXMLRPCServer is vulnerable to XML bombs"

View File

@@ -1,30 +0,0 @@
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
<qhelp>
<overview>
<p>
Using user-supplied information to construct an XPath query for XML data can
result in an XPath injection flaw. By sending intentionally malformed information,
an attacker can access data that he may not normally have access to.
He/She may even be able to elevate his privileges on the web site if the XML data
is being used for authentication (such as an XML based user file).
</p>
</overview>
<recommendation>
<p>
XPath injection can be prevented using parameterized XPath interface or escaping the user input to make it safe to include in a dynamically constructed query.
If you are using quotes to terminate untrusted input in a dynamically constructed XPath query, then you need to escape that quote in the untrusted input to ensure the untrusted data cant try to break out of that quoted context.
</p>
<p>
Another better mitigation option is to use a precompiled XPath query. Precompiled XPath queries are already preset before the program executes, rather than created on the fly after the users input has been added to the string. This is a better route because you dont have to worry about missing a character that should have been escaped.
</p>
</recommendation>
<example>
<p>In the example below, the xpath query is controlled by the user and hence leads to a vulnerability.</p>
<sample src="xpathBad.py" />
<p> This can be fixed by using a parameterized query as shown below.</p>
<sample src="xpathGood.py" />
</example>
<references>
<li>OWASP XPath injection : <a href="https://owasp.org/www-community/attacks/XPATH_Injection"></a>/>> </li>
</references>
</qhelp>

View File

@@ -1,33 +0,0 @@
/**
* @name XPath query built from user-controlled sources
* @description Building a XPath query from user-controlled sources is vulnerable to insertion of
* malicious Xpath code by the user.
* @kind path-problem
* @problem.severity error
* @precision high
* @id py/xpath-injection
* @tags security
* external/cwe/cwe-643
*/
private import python
private import semmle.python.Concepts
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.BarrierGuards
import XpathInjection::XpathInjection
import DataFlow::PathGraph
class XpathInjectionConfiguration extends TaintTracking::Configuration {
XpathInjectionConfiguration() { this = "PathNotNormalizedConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
}
from XpathInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink, source, sink, "This Xpath query depends on $@.", source, "a user-provided value"

View File

@@ -1,105 +0,0 @@
/**
* Provides class and predicates to track external data that
* may represent malicious xpath query objects.
*
* This module is intended to be imported into a taint-tracking query.
*/
private import python
private import semmle.python.Concepts
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.BarrierGuards
/** Models Xpath Injection related classes and functions */
module XpathInjection {
/**
* A data flow source for "XPath injection" vulnerabilities.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for "XPath injection" vulnerabilities.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for "XPath injection" vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }
/**
* A sanitizer guard for "XPath injection" vulnerabilities.
*/
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
/**
* A source of remote user input, considered as a flow source.
*/
class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
/** Returns an API node referring to `lxml.etree` */
API::Node etree() { result = API::moduleImport("lxml").getMember("etree") }
/** Returns an API node referring to `lxml.etree` */
API::Node etreeFromString() { result = etree().getMember("fromstring") }
/** Returns an API node referring to `lxml.etree.parse` */
API::Node etreeParse() { result = etree().getMember("parse") }
/** Returns an API node referring to `lxml.etree.parse` */
API::Node libxml2parseFile() { result = API::moduleImport("libxml2").getMember("parseFile") }
/**
* A Sink representing an argument to `etree.XPath` or `etree.ETXPath` call.
*
* from lxml import etree
* root = etree.XML("<xmlContent>")
* find_text = etree.XPath("`sink`")
* find_text = etree.ETXPath("`sink`")
*/
private class EtreeXpathArgument extends Sink {
EtreeXpathArgument() { this = etree().getMember(["XPath", "ETXPath"]).getACall().getArg(0) }
}
/**
* A Sink representing an argument to the `etree.XPath` call.
*
* from lxml import etree
* root = etree.fromstring(file(XML_DB).read(), XMLParser())
* find_text = root.xpath("`sink`")
*/
private class EtreeFromstringXpathArgument extends Sink {
EtreeFromstringXpathArgument() {
this = etreeFromString().getReturn().getMember("xpath").getACall().getArg(0)
}
}
/**
* A Sink representing an argument to the `xpath` call to a parsed xml document.
*
* from lxml import etree
* from io import StringIO
* f = StringIO('<foo><bar></bar></foo>')
* tree = etree.parse(f)
* r = tree.xpath('`sink`')
*/
private class ParseXpathArgument extends Sink {
ParseXpathArgument() { this = etreeParse().getReturn().getMember("xpath").getACall().getArg(0) }
}
/**
* A Sink representing an argument to the `xpathEval` call to a parsed libxml2 document.
*
* import libxml2
* tree = libxml2.parseFile("file.xml")
* r = tree.xpathEval('`sink`')
*/
private class ParseFileXpathEvalArgument extends Sink {
ParseFileXpathEvalArgument() {
this = libxml2parseFile().getReturn().getMember("xpathEval").getACall().getArg(0)
}
}
}

View File

@@ -1,18 +0,0 @@
from lxml import etree
from io import StringIO
from django.urls import path
from django.http import HttpResponse
from django.template import Template, Context, Engine, engines
def a(request):
value = request.GET['xpath']
f = StringIO('<foo><bar></bar></foo>')
tree = etree.parse(f)
r = tree.xpath("/tag[@id='%s']" % value)
urlpatterns = [
path('a', a)
]

View File

@@ -1,18 +0,0 @@
from lxml import etree
from io import StringIO
from django.urls import path
from django.http import HttpResponse
from django.template import Template, Context, Engine, engines
def a(request):
value = request.GET['xpath']
f = StringIO('<foo><bar></bar></foo>')
tree = etree.parse(f)
r = tree.xpath("/tag[@id=$tagid]", tagid=value)
urlpatterns = [
path('a', a)
]

View File

@@ -1,6 +0,0 @@
import python
import semmle.python.security.performance.SuperlinearBackTracking
from PolynomialBackTrackingTerm t
where t.getLocation().getFile().getBaseName() = "KnownCVEs.py"
select t.getRegex(), t, t.getReason()

View File

@@ -1,108 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<include src="ReDoSIntroduction.inc.qhelp" />
<example>
<p>
Consider this use of a regular expression, which removes
all leading and trailing whitespace in a string:
</p>
<sample language="python">
re.sub(r"^\s+|\s+$", "", text) # BAD
</sample>
<p>
The sub-expression <code>"\s+$"</code> will match the
whitespace characters in <code>text</code> from left to right, but it
can start matching anywhere within a whitespace sequence. This is
problematic for strings that do <strong>not</strong> end with a whitespace
character. Such a string will force the regular expression engine to
process each whitespace sequence once per whitespace character in the
sequence.
</p>
<p>
This ultimately means that the time cost of trimming a
string is quadratic in the length of the string. So a string like
<code>"a b"</code> will take milliseconds to process, but a similar
string with a million spaces instead of just one will take several
minutes.
</p>
<p>
Avoid this problem by rewriting the regular expression to
not contain the ambiguity about when to start matching whitespace
sequences. For instance, by using a negative look-behind
(<code>^\s+|(?&lt;!\s)\s+$</code>), or just by using the built-in strip
method (<code>text.strip()</code>).
</p>
<p>
Note that the sub-expression <code>"^\s+"</code> is
<strong>not</strong> problematic as the <code>^</code> anchor restricts
when that sub-expression can start matching, and as the regular
expression engine matches from left to right.
</p>
</example>
<example>
<p>
As a similar, but slightly subtler problem, consider the
regular expression that matches lines with numbers, possibly written
using scientific notation:
</p>
<sample language="python">
^0\.\d+E?\d+$ # BAD
</sample>
<p>
The problem with this regular expression is in the
sub-expression <code>\d+E?\d+</code> because the second
<code>\d+</code> can start matching digits anywhere after the first
match of the first <code>\d+</code> if there is no <code>E</code> in
the input string.
</p>
<p>
This is problematic for strings that do <strong>not</strong>
end with a digit. Such a string will force the regular expression
engine to process each digit sequence once per digit in the sequence,
again leading to a quadratic time complexity.
</p>
<p>
To make the processing faster, the regular expression
should be rewritten such that the two <code>\d+</code> sub-expressions
do not have overlapping matches: <code>^0\.\d+(E\d+)?$</code>.
</p>
</example>
<include src="ReDoSReferences.inc.qhelp"/>
</qhelp>

View File

@@ -1,33 +0,0 @@
/**
* @name Polynomial regular expression used on uncontrolled data
* @description A regular expression that can require polynomial time
* to match may be vulnerable to denial-of-service attacks.
* @kind path-problem
* @problem.severity warning
* @precision high
* @id py/polynomial-redos
* @tags security
* external/cwe/cwe-730
* external/cwe/cwe-400
*/
import python
import semmle.python.security.performance.SuperlinearBackTracking
import semmle.python.security.dataflow.PolynomialReDoS
import DataFlow::PathGraph
from
PolynomialReDoS::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
PolynomialReDoS::Sink sinkNode, PolynomialBackTrackingTerm regexp
where
config.hasFlowPath(source, sink) and
sinkNode = sink.getNode() and
regexp.getRootTerm() = sinkNode.getRegExp()
// not (
// source.getNode().(Source).getKind() = "url" and
// regexp.isAtEndLine()
// )
select sinkNode.getHighlight(), source, sink,
"This $@ that depends on $@ may run slow on strings " + regexp.getPrefixMessage() +
"with many repetitions of '" + regexp.getPumpString() + "'.", regexp, "regular expression",
source.getNode(), "a user-provided value"

View File

@@ -1,34 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<include src="ReDoSIntroduction.inc.qhelp" />
<example>
<p>
Consider this regular expression:
</p>
<sample language="python">
^_(__|.)+_$
</sample>
<p>
Its sub-expression <code>"(__|.)+?"</code> can match the string <code>"__"</code> either by the
first alternative <code>"__"</code> to the left of the <code>"|"</code> operator, or by two
repetitions of the second alternative <code>"."</code> to the right. Thus, a string consisting
of an odd number of underscores followed by some other character will cause the regular
expression engine to run for an exponential amount of time before rejecting the input.
</p>
<p>
This problem can be avoided by rewriting the regular expression to remove the ambiguity between
the two branches of the alternative inside the repetition:
</p>
<sample language="python">
^_(__|[^_])+_$
</sample>
</example>
<include src="ReDoSReferences.inc.qhelp"/>
</qhelp>

View File

@@ -1,25 +0,0 @@
/**
* @name Inefficient regular expression
* @description A regular expression that requires exponential time to match certain inputs
* can be a performance bottleneck, and may be vulnerable to denial-of-service
* attacks.
* @kind problem
* @problem.severity error
* @precision high
* @id py/redos
* @tags security
* external/cwe/cwe-730
* external/cwe/cwe-400
*/
import python
import semmle.python.security.performance.ExponentialBackTracking
from RegExpTerm t, string pump, State s, string prefixMsg
where
hasReDoSResult(t, pump, s, prefixMsg) and
// exclude verbose mode regexes for now
not t.getRegex().getAMode() = "VERBOSE"
select t,
"This part of the regular expression may cause exponential backtracking on strings " + prefixMsg +
"containing many repetitions of '" + pump + "'."

View File

@@ -1,54 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Some regular expressions take a long time to match certain
input strings to the point where the time it takes to match a string
of length <i>n</i> is proportional to <i>n<sup>k</sup></i> or even
<i>2<sup>n</sup></i>. Such regular expressions can negatively affect
performance, or even allow a malicious user to perform a Denial of
Service ("DoS") attack by crafting an expensive input string for the
regular expression to match.
</p>
<p>
The regular expression engine provided by Python uses a backtracking non-deterministic finite
automata to implement regular expression matching. While this approach
is space-efficient and allows supporting advanced features like
capture groups, it is not time-efficient in general. The worst-case
time complexity of such an automaton can be polynomial or even
exponential, meaning that for strings of a certain shape, increasing
the input length by ten characters may make the automaton about 1000
times slower.
</p>
<p>
Typically, a regular expression is affected by this
problem if it contains a repetition of the form <code>r*</code> or
<code>r+</code> where the sub-expression <code>r</code> is ambiguous
in the sense that it can match some string in multiple ways. More
information about the precise circumstances can be found in the
references.
</p>
</overview>
<recommendation>
<p>
Modify the regular expression to remove the ambiguity, or
ensure that the strings matched with the regular expression are short
enough that the time-complexity does not matter.
</p>
</recommendation>
</qhelp>

View File

@@ -1,16 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS">Regular expression Denial of Service - ReDoS</a>.
</li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/ReDoS">ReDoS</a>.</li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Time_complexity">Time complexity</a>.</li>
<li>James Kirrage, Asiri Rathnayake, Hayo Thielecke:
<a href="http://www.cs.bham.ac.uk/~hxt/research/reg-exp-sec.pdf">Static Analysis for Regular Expression Denial-of-Service Attack</a>.
</li>
</references>
</qhelp>

View File

@@ -5,15 +5,15 @@
* @kind path-problem
* @problem.severity error
* @id py/nosql-injection
* @tags experimental
* security
* @tags security
* external/cwe/cwe-943
*/
import python
import experimental.semmle.python.security.injection.NoSQLInjection
import DataFlow::PathGraph
from CustomPathNode source, CustomPathNode sink
where noSQLInjectionFlow(source, sink)
from NoSqlInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink, source, sink, "$@ NoSQL query contains an unsanitized $@", sink, "This", source,
"user-provided value"

View File

@@ -15,6 +15,43 @@ private import semmle.python.dataflow.new.TaintTracking
private import experimental.semmle.python.Frameworks
private import semmle.python.Concepts
/** Provides classes for modeling copying file related APIs. */
module CopyFile {
/**
* A data flow node for copying file.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `CopyFile` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the argument containing the path.
*/
abstract DataFlow::Node getAPathArgument();
/**
* Gets fsrc argument.
*/
abstract DataFlow::Node getfsrcArgument();
}
}
/**
* A data flow node for copying file.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `CopyFile::Range` instead.
*/
class CopyFile extends DataFlow::Node {
CopyFile::Range range;
CopyFile() { this = range }
DataFlow::Node getAPathArgument() { result = range.getAPathArgument() }
DataFlow::Node getfsrcArgument() { result = range.getfsrcArgument() }
}
/** Provides classes for modeling log related APIs. */
module LogOutput {
/**
@@ -46,7 +83,7 @@ class LogOutput extends DataFlow::Node {
}
/** Provides classes for modeling LDAP query execution-related APIs. */
module LDAPQuery {
module LdapQuery {
/**
* A data-flow node that collects methods executing a LDAP query.
*
@@ -61,16 +98,19 @@ module LDAPQuery {
}
}
/** DEPRECATED: Alias for LdapQuery */
deprecated module LDAPQuery = LdapQuery;
/**
* A data-flow node that collect methods executing a LDAP query.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `LDAPQuery::Range` instead.
*/
class LDAPQuery extends DataFlow::Node {
LDAPQuery::Range range;
class LdapQuery extends DataFlow::Node {
LdapQuery::Range range;
LDAPQuery() { this = range }
LdapQuery() { this = range }
/**
* Gets the argument containing the executed expression.
@@ -78,8 +118,11 @@ class LDAPQuery extends DataFlow::Node {
DataFlow::Node getQuery() { result = range.getQuery() }
}
/** DEPRECATED: Alias for LdapQuery */
deprecated class LDAPQuery = LdapQuery;
/** Provides classes for modeling LDAP components escape-related APIs. */
module LDAPEscape {
module LdapEscape {
/**
* A data-flow node that collects functions escaping LDAP components.
*
@@ -94,16 +137,19 @@ module LDAPEscape {
}
}
/** DEPRECATED: Alias for LdapEscape */
deprecated module LDAPEscape = LdapEscape;
/**
* A data-flow node that collects functions escaping LDAP components.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `LDAPEscape::Range` instead.
*/
class LDAPEscape extends DataFlow::Node {
LDAPEscape::Range range;
class LdapEscape extends DataFlow::Node {
LdapEscape::Range range;
LDAPEscape() { this = range }
LdapEscape() { this = range }
/**
* Gets the argument containing the escaped expression.
@@ -111,8 +157,11 @@ class LDAPEscape extends DataFlow::Node {
DataFlow::Node getAnInput() { result = range.getAnInput() }
}
/** DEPRECATED: Alias for LdapEscape */
deprecated class LDAPEscape = LdapEscape;
/** Provides classes for modeling LDAP bind-related APIs. */
module LDAPBind {
module LdapBind {
/**
* A data-flow node that collects methods binding a LDAP connection.
*
@@ -137,16 +186,19 @@ module LDAPBind {
}
}
/** DEPRECATED: Alias for LdapBind */
deprecated module LDAPBind = LdapBind;
/**
* A data-flow node that collects methods binding a LDAP connection.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `LDAPBind::Range` instead.
*/
class LDAPBind extends DataFlow::Node {
LDAPBind::Range range;
class LdapBind extends DataFlow::Node {
LdapBind::Range range;
LDAPBind() { this = range }
LdapBind() { this = range }
/**
* Gets the argument containing the binding host.
@@ -164,8 +216,11 @@ class LDAPBind extends DataFlow::Node {
predicate useSSL() { range.useSSL() }
}
/** DEPRECATED: Alias for LdapBind */
deprecated class LDAPBind = LdapBind;
/** Provides classes for modeling SQL sanitization libraries. */
module SQLEscape {
module SqlEscape {
/**
* A data-flow node that collects functions that escape SQL statements.
*
@@ -180,16 +235,19 @@ module SQLEscape {
}
}
/** DEPRECATED: Alias for SqlEscape */
deprecated module SQLEscape = SqlEscape;
/**
* A data-flow node that collects functions escaping SQL statements.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `SQLEscape::Range` instead.
*/
class SQLEscape extends DataFlow::Node {
SQLEscape::Range range;
class SqlEscape extends DataFlow::Node {
SqlEscape::Range range;
SQLEscape() { this = range }
SqlEscape() { this = range }
/**
* Gets the argument containing the raw SQL statement.
@@ -197,8 +255,11 @@ class SQLEscape extends DataFlow::Node {
DataFlow::Node getAnInput() { result = range.getAnInput() }
}
/** Provides a class for modeling NoSQL execution APIs. */
module NoSQLQuery {
/** DEPRECATED: Alias for SqlEscape */
deprecated class SQLEscape = SqlEscape;
/** Provides a class for modeling NoSql execution APIs. */
module NoSqlQuery {
/**
* A data-flow node that executes NoSQL queries.
*
@@ -206,28 +267,34 @@ module NoSQLQuery {
* extend `NoSQLQuery` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument that specifies the NoSQL query to be executed. */
/** Gets the argument that specifies the NoSql query to be executed. */
abstract DataFlow::Node getQuery();
}
}
/** DEPRECATED: Alias for NoSqlQuery */
deprecated module NoSQLQuery = NoSqlQuery;
/**
* A data-flow node that executes NoSQL queries.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `NoSQLQuery::Range` instead.
*/
class NoSQLQuery extends DataFlow::Node {
NoSQLQuery::Range range;
class NoSqlQuery extends DataFlow::Node {
NoSqlQuery::Range range;
NoSQLQuery() { this = range }
NoSqlQuery() { this = range }
/** Gets the argument that specifies the NoSQL query to be executed. */
/** Gets the argument that specifies the NoSql query to be executed. */
DataFlow::Node getQuery() { result = range.getQuery() }
}
/** Provides classes for modeling NoSQL sanitization-related APIs. */
module NoSQLSanitizer {
/** DEPRECATED: Alias for NoSqlQuery */
deprecated class NoSQLQuery = NoSqlQuery;
/** Provides classes for modeling NoSql sanitization-related APIs. */
module NoSqlSanitizer {
/**
* A data-flow node that collects functions sanitizing NoSQL queries.
*
@@ -235,26 +302,32 @@ module NoSQLSanitizer {
* extend `NoSQLSanitizer` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument that specifies the NoSQL query to be sanitized. */
/** Gets the argument that specifies the NoSql query to be sanitized. */
abstract DataFlow::Node getAnInput();
}
}
/** DEPRECATED: Alias for NoSqlSanitizer */
deprecated module NoSQLSanitizer = NoSqlSanitizer;
/**
* A data-flow node that collects functions sanitizing NoSQL queries.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `NoSQLSanitizer::Range` instead.
*/
class NoSQLSanitizer extends DataFlow::Node {
NoSQLSanitizer::Range range;
class NoSqlSanitizer extends DataFlow::Node {
NoSqlSanitizer::Range range;
NoSQLSanitizer() { this = range }
NoSqlSanitizer() { this = range }
/** Gets the argument that specifies the NoSQL query to be sanitized. */
/** Gets the argument that specifies the NoSql query to be sanitized. */
DataFlow::Node getAnInput() { result = range.getAnInput() }
}
/** DEPRECATED: Alias for NoSqlSanitizer */
deprecated class NoSQLSanitizer = NoSqlSanitizer;
/** Provides classes for modeling HTTP Header APIs. */
module HeaderDeclaration {
/**
@@ -346,3 +419,153 @@ module Cookie {
abstract predicate isSameSite();
}
}
/** Provides classes for modeling JWT encoding-related APIs. */
module JwtEncoding {
/**
* A data-flow node that collects methods encoding a JWT token.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `JWTEncoding` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the argument containing the encoding payload.
*/
abstract DataFlow::Node getPayload();
/**
* Gets the argument containing the encoding key.
*/
abstract DataFlow::Node getKey();
/**
* Gets the argument for the algorithm used in the encoding.
*/
abstract DataFlow::Node getAlgorithm();
/**
* Gets a string representation of the algorithm used in the encoding.
*/
abstract string getAlgorithmString();
}
}
/** DEPRECATED: Alias for JwtEncoding */
deprecated module JWTEncoding = JwtEncoding;
/**
* A data-flow node that collects methods encoding a JWT token.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `JWTEncoding::Range` instead.
*/
class JwtEncoding extends DataFlow::Node instanceof JwtEncoding::Range {
/**
* Gets the argument containing the payload.
*/
DataFlow::Node getPayload() { result = super.getPayload() }
/**
* Gets the argument containing the encoding key.
*/
DataFlow::Node getKey() { result = super.getKey() }
/**
* Gets the argument for the algorithm used in the encoding.
*/
DataFlow::Node getAlgorithm() { result = super.getAlgorithm() }
/**
* Gets a string representation of the algorithm used in the encoding.
*/
string getAlgorithmString() { result = super.getAlgorithmString() }
}
/** DEPRECATED: Alias for JwtEncoding */
deprecated class JWTEncoding = JwtEncoding;
/** Provides classes for modeling JWT decoding-related APIs. */
module JwtDecoding {
/**
* A data-flow node that collects methods decoding a JWT token.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `JWTDecoding` instead.
*/
abstract class Range extends DataFlow::Node {
/**
* Gets the argument containing the encoding payload.
*/
abstract DataFlow::Node getPayload();
/**
* Gets the argument containing the encoding key.
*/
abstract DataFlow::Node getKey();
/**
* Gets the argument for the algorithm used in the encoding.
*/
abstract DataFlow::Node getAlgorithm();
/**
* Gets a string representation of the algorithm used in the encoding.
*/
abstract string getAlgorithmString();
/**
* Gets the options Node used in the encoding.
*/
abstract DataFlow::Node getOptions();
/**
* Checks if the signature gets verified while decoding.
*/
abstract predicate verifiesSignature();
}
}
/** DEPRECATED: Alias for JwtDecoding */
deprecated module JWTDecoding = JwtDecoding;
/**
* A data-flow node that collects methods encoding a JWT token.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `JWTDecoding::Range` instead.
*/
class JwtDecoding extends DataFlow::Node instanceof JwtDecoding::Range {
/**
* Gets the argument containing the payload.
*/
DataFlow::Node getPayload() { result = super.getPayload() }
/**
* Gets the argument containing the encoding key.
*/
DataFlow::Node getKey() { result = super.getKey() }
/**
* Gets the argument for the algorithm used in the encoding.
*/
DataFlow::Node getAlgorithm() { result = super.getAlgorithm() }
/**
* Gets a string representation of the algorithm used in the encoding.
*/
string getAlgorithmString() { result = super.getAlgorithmString() }
/**
* Gets the options Node used in the encoding.
*/
DataFlow::Node getOptions() { result = super.getOptions() }
/**
* Checks if the signature gets verified while decoding.
*/
predicate verifiesSignature() { super.verifiesSignature() }
}
/** DEPRECATED: Alias for JwtDecoding */
deprecated class JWTDecoding = JwtDecoding;

View File

@@ -8,4 +8,9 @@ private import experimental.semmle.python.frameworks.Django
private import experimental.semmle.python.frameworks.Werkzeug
private import experimental.semmle.python.frameworks.LDAP
private import experimental.semmle.python.frameworks.NoSQL
private import experimental.semmle.python.frameworks.Log
private import experimental.semmle.python.frameworks.JWT
private import experimental.semmle.python.libraries.PyJWT
private import experimental.semmle.python.libraries.Python_JWT
private import experimental.semmle.python.libraries.Authlib
private import experimental.semmle.python.libraries.PythonJose
private import experimental.semmle.python.frameworks.CopyFile

View File

@@ -0,0 +1,42 @@
private import python
private import experimental.semmle.python.Concepts
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.ApiGraphs
private module CopyFileImpl {
/**
* The `shutil` module provides methods to copy or move files.
* See:
* - https://docs.python.org/3/library/shutil.html#shutil.copyfile
* - https://docs.python.org/3/library/shutil.html#shutil.copy
* - https://docs.python.org/3/library/shutil.html#shutil.copy2
* - https://docs.python.org/3/library/shutil.html#shutil.copytree
* - https://docs.python.org/3/library/shutil.html#shutil.move
*/
private class CopyFiles extends DataFlow::CallCfgNode, CopyFile::Range {
CopyFiles() {
this =
API::moduleImport("shutil")
.getMember(["copyfile", "copy", "copy2", "copytree", "move"])
.getACall()
}
override DataFlow::Node getAPathArgument() {
result in [this.getArg(0), this.getArgByName("src")]
}
override DataFlow::Node getfsrcArgument() { none() }
}
// TODO: once we have flow summaries, model `shutil.copyfileobj` which copies the content between its' file-like arguments.
// See https://docs.python.org/3/library/shutil.html#shutil.copyfileobj
private class CopyFileobj extends DataFlow::CallCfgNode, CopyFile::Range {
CopyFileobj() { this = API::moduleImport("shutil").getMember("copyfileobj").getACall() }
override DataFlow::Node getfsrcArgument() {
result in [this.getArg(0), this.getArgByName("fsrc")]
}
override DataFlow::Node getAPathArgument() { none() }
}
}

View File

@@ -11,16 +11,16 @@ private import semmle.python.ApiGraphs
import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.Concepts
private module PrivateDjango {
private module django {
private module ExperimentalPrivateDjango {
private module DjangoMod {
API::Node http() { result = API::moduleImport("django").getMember("http") }
module http {
module Http {
API::Node response() { result = http().getMember("response") }
API::Node request() { result = http().getMember("request") }
module request {
module Request {
module HttpRequest {
class DjangoGETParameter extends DataFlow::Node, RemoteFlowSource::Range {
DjangoGETParameter() { this = request().getMember("GET").getMember("get").getACall() }
@@ -30,7 +30,7 @@ private module PrivateDjango {
}
}
module response {
module Response {
module HttpResponse {
API::Node baseClassRef() {
result = response().getMember("HttpResponse")

View File

@@ -28,17 +28,8 @@ module ExperimentalFlask {
}
/** Gets a reference to a header instance. */
private DataFlow::LocalSourceNode headerInstance(DataFlow::TypeTracker t) {
t.start() and
result.(DataFlow::AttrRead).getObject().getALocalSource() =
[Flask::Response::classRef(), flaskMakeResponse()].getReturn().getAUse()
or
exists(DataFlow::TypeTracker t2 | result = headerInstance(t2).track(t2, t))
}
/** Gets a reference to a header instance use. */
private DataFlow::Node headerInstance() {
headerInstance(DataFlow::TypeTracker::end()).flowsTo(result)
private DataFlow::LocalSourceNode headerInstance() {
result = [Flask::Response::classRef(), flaskMakeResponse()].getReturn().getAMember().getAUse()
}
/** Gets a reference to a header instance call/subscript */

View File

@@ -0,0 +1,23 @@
private import python
private import semmle.python.ApiGraphs
/** Checks if the argument is empty or none. */
predicate isEmptyOrNone(DataFlow::Node arg) { isEmpty(arg) or isNone(arg) }
/** Checks if an empty string `""` flows to `arg` */
predicate isEmpty(DataFlow::Node arg) {
exists(StrConst emptyString |
emptyString.getText() = "" and
DataFlow::exprNode(emptyString).(DataFlow::LocalSourceNode).flowsTo(arg)
)
}
/** Checks if `None` flows to `arg` */
predicate isNone(DataFlow::Node arg) {
DataFlow::exprNode(any(None no)).(DataFlow::LocalSourceNode).flowsTo(arg)
}
/** Checks if `False` flows to `arg` */
predicate isFalse(DataFlow::Node arg) {
DataFlow::exprNode(any(False falseExpr)).(DataFlow::LocalSourceNode).flowsTo(arg)
}

View File

@@ -60,7 +60,7 @@ private module LDAP {
*
* See `LDAP2QueryMethods`
*/
private class LDAP2Query extends DataFlow::CallCfgNode, LDAPQuery::Range {
private class LDAP2Query extends DataFlow::CallCfgNode, LdapQuery::Range {
LDAP2Query() { this.getFunction() = ldapQuery() }
override DataFlow::Node getQuery() {
@@ -98,7 +98,7 @@ private module LDAP {
*
* See `LDAP2BindMethods`
*/
private class LDAP2Bind extends DataFlow::CallCfgNode, LDAPBind::Range {
private class LDAP2Bind extends DataFlow::CallCfgNode, LdapBind::Range {
LDAP2Bind() { this.getFunction() = ldapBind() }
override DataFlow::Node getPassword() {
@@ -149,7 +149,7 @@ private module LDAP {
*
* See https://github.com/python-ldap/python-ldap/blob/7ce471e238cdd9a4dd8d17baccd1c9e05e6f894a/Lib/ldap/dn.py#L17
*/
private class LDAP2EscapeDNCall extends DataFlow::CallCfgNode, LDAPEscape::Range {
private class LDAP2EscapeDNCall extends DataFlow::CallCfgNode, LdapEscape::Range {
LDAP2EscapeDNCall() { this = ldap().getMember("dn").getMember("escape_dn_chars").getACall() }
override DataFlow::Node getAnInput() { result = this.getArg(0) }
@@ -160,7 +160,7 @@ private module LDAP {
*
* See https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap-filter.html#ldap.filter.escape_filter_chars
*/
private class LDAP2EscapeFilterCall extends DataFlow::CallCfgNode, LDAPEscape::Range {
private class LDAP2EscapeFilterCall extends DataFlow::CallCfgNode, LdapEscape::Range {
LDAP2EscapeFilterCall() {
this = ldap().getMember("filter").getMember("escape_filter_chars").getACall()
}
@@ -190,7 +190,7 @@ private module LDAP {
/**
* A class to find `ldap3` methods executing a query.
*/
private class LDAP3Query extends DataFlow::CallCfgNode, LDAPQuery::Range {
private class LDAP3Query extends DataFlow::CallCfgNode, LdapQuery::Range {
LDAP3Query() {
this.getFunction().(DataFlow::AttrRead).getObject().getALocalSource() =
ldap3Connection().getACall() and
@@ -203,7 +203,7 @@ private module LDAP {
/**
* A class to find `ldap3` methods binding a connection.
*/
class LDAP3Bind extends DataFlow::CallCfgNode, LDAPBind::Range {
class LDAP3Bind extends DataFlow::CallCfgNode, LdapBind::Range {
LDAP3Bind() { this = ldap3Connection().getACall() }
override DataFlow::Node getPassword() {
@@ -241,7 +241,7 @@ private module LDAP {
*
* See https://github.com/cannatag/ldap3/blob/4d33166f0869b929f59c6e6825a1b9505eb99967/ldap3/utils/dn.py#L390
*/
private class LDAP3EscapeDNCall extends DataFlow::CallCfgNode, LDAPEscape::Range {
private class LDAP3EscapeDNCall extends DataFlow::CallCfgNode, LdapEscape::Range {
LDAP3EscapeDNCall() { this = ldap3Utils().getMember("dn").getMember("escape_rdn").getACall() }
override DataFlow::Node getAnInput() { result = this.getArg(0) }
@@ -252,7 +252,7 @@ private module LDAP {
*
* See https://github.com/cannatag/ldap3/blob/4d33166f0869b929f59c6e6825a1b9505eb99967/ldap3/utils/conv.py#L91
*/
private class LDAP3EscapeFilterCall extends DataFlow::CallCfgNode, LDAPEscape::Range {
private class LDAP3EscapeFilterCall extends DataFlow::CallCfgNode, LdapEscape::Range {
LDAP3EscapeFilterCall() {
this = ldap3Utils().getMember("conv").getMember("escape_filter_chars").getACall()
}

View File

@@ -1,118 +0,0 @@
/**
* Provides classes modeling security-relevant aspects of the log libraries.
*/
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.dataflow.new.RemoteFlowSources
private import experimental.semmle.python.Concepts
private import semmle.python.frameworks.Flask
private import semmle.python.ApiGraphs
/**
* Provides models for Python's log-related libraries.
*/
private module log {
/**
* Log output method list.
*
* See https://docs.python.org/3/library/logging.html#logger-objects
*/
private class LogOutputMethods extends string {
LogOutputMethods() {
this in ["info", "error", "warn", "warning", "debug", "critical", "exception", "log"]
}
}
/**
* The class used to find the log output method of the `logging` module.
*
* See `LogOutputMethods`
*/
private class LoggingCall extends DataFlow::CallCfgNode, LogOutput::Range {
LoggingCall() {
this = API::moduleImport("logging").getMember(any(LogOutputMethods m)).getACall()
}
override DataFlow::Node getAnInput() {
this.getFunction().(DataFlow::AttrRead).getAttributeName() != "log" and
result in [this.getArg(_), this.getArgByName(_)] // this includes the arg named "msg"
or
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "log" and
result in [this.getArg(any(int i | i > 0)), this.getArgByName(any(string s | s != "level"))]
}
}
/**
* The class used to find log output methods related to the `logging.getLogger` instance.
*
* See `LogOutputMethods`
*/
private class LoggerCall extends DataFlow::CallCfgNode, LogOutput::Range {
LoggerCall() {
this =
API::moduleImport("logging")
.getMember("getLogger")
.getReturn()
.getMember(any(LogOutputMethods m))
.getACall()
}
override DataFlow::Node getAnInput() {
this.getFunction().(DataFlow::AttrRead).getAttributeName() != "log" and
result in [this.getArg(_), this.getArgByName(_)] // this includes the arg named "msg"
or
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "log" and
result in [this.getArg(any(int i | i > 0)), this.getArgByName(any(string s | s != "level"))]
}
}
/**
* The class used to find the relevant log output method of the `flask.Flask.logger` instance (flask application).
*
* See `LogOutputMethods`
*/
private class FlaskLoggingCall extends DataFlow::CallCfgNode, LogOutput::Range {
FlaskLoggingCall() {
this =
Flask::FlaskApp::instance()
.getMember("logger")
.getMember(any(LogOutputMethods m))
.getACall()
}
override DataFlow::Node getAnInput() {
this.getFunction().(DataFlow::AttrRead).getAttributeName() != "log" and
result in [this.getArg(_), this.getArgByName(_)] // this includes the arg named "msg"
or
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "log" and
result in [this.getArg(any(int i | i > 0)), this.getArgByName(any(string s | s != "level"))]
}
}
/**
* The class used to find the relevant log output method of the `django.utils.log.request_logger` instance (django application).
*
* See `LogOutputMethods`
*/
private class DjangoLoggingCall extends DataFlow::CallCfgNode, LogOutput::Range {
DjangoLoggingCall() {
this =
API::moduleImport("django")
.getMember("utils")
.getMember("log")
.getMember("request_logger")
.getMember(any(LogOutputMethods m))
.getACall()
}
override DataFlow::Node getAnInput() {
this.getFunction().(DataFlow::AttrRead).getAttributeName() != "log" and
result in [this.getArg(_), this.getArgByName(_)] // this includes the arg named "msg"
or
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "log" and
result in [this.getArg(any(int i | i > 0)), this.getArgByName(any(string s | s != "level"))]
}
}
}

View File

@@ -10,11 +10,15 @@ private import semmle.python.dataflow.new.RemoteFlowSources
private import experimental.semmle.python.Concepts
private import semmle.python.ApiGraphs
private module NoSQL {
private module NoSql {
// API Nodes returning `Mongo` instances.
/** Gets a reference to `pymongo.MongoClient` */
private API::Node pyMongo() {
result = API::moduleImport("pymongo").getMember("MongoClient").getReturn()
or
// see https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient
result =
API::moduleImport("pymongo").getMember("mongo_client").getMember("MongoClient").getReturn()
}
/** Gets a reference to `flask_pymongo.PyMongo` */
@@ -34,40 +38,36 @@ private module NoSQL {
* Gets a reference to an initialized `Mongo` instance.
* See `pyMongo()`, `flask_PyMongo()`
*/
private API::Node mongoInstance() {
private API::Node mongoClientInstance() {
result = pyMongo() or
result = flask_PyMongo()
}
/**
* Gets a reference to an initialized `Mongo` DB instance.
* See `mongoEngine()`, `flask_MongoEngine()`
* Gets a reference to a `Mongo` DB instance.
*/
private API::Node mongoDBInstance() {
result = mongoEngine().getMember(["get_db", "connect"]).getReturn() or
result = mongoEngine().getMember("connection").getMember(["get_db", "connect"]).getReturn() or
result = flask_MongoEngine().getMember("get_db").getReturn()
}
/**
* Gets a reference to a `Mongo` DB use.
*
* See `mongoInstance()`, `mongoDBInstance()`.
*/
private DataFlow::LocalSourceNode mongoDB(DataFlow::TypeTracker t) {
private DataFlow::LocalSourceNode mongoDBInstance(DataFlow::TypeTracker t) {
t.start() and
(
exists(SubscriptNode subscript |
subscript.getObject() = mongoInstance().getAUse().asCfgNode() and
subscript.getObject() = mongoClientInstance().getAUse().asCfgNode() and
result.asCfgNode() = subscript
)
or
result.(DataFlow::AttrRead).getObject() = mongoInstance().getAUse()
result.(DataFlow::AttrRead).getObject() = mongoClientInstance().getAUse()
or
result = mongoDBInstance().getAUse()
result = mongoEngine().getMember(["get_db", "connect"]).getACall()
or
result = mongoEngine().getMember("connection").getMember(["get_db", "connect"]).getACall()
or
result = flask_MongoEngine().getMember("get_db").getACall()
or
// see https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient.get_default_database
// see https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient.get_database
result = mongoClientInstance().getMember(["get_default_database", "get_database"]).getACall()
)
or
exists(DataFlow::TypeTracker t2 | result = mongoDB(t2).track(t2, t))
exists(DataFlow::TypeTracker t2 | result = mongoDBInstance(t2).track(t2, t))
}
/**
@@ -81,21 +81,27 @@ private module NoSQL {
*
* `mongo.db` would be a use of a `Mongo` instance, and so the result.
*/
private DataFlow::Node mongoDB() { mongoDB(DataFlow::TypeTracker::end()).flowsTo(result) }
private DataFlow::Node mongoDBInstance() {
mongoDBInstance(DataFlow::TypeTracker::end()).flowsTo(result)
}
/**
* Gets a reference to a `Mongo` collection use.
*
* See `mongoDB()`.
*/
private DataFlow::LocalSourceNode mongoCollection(DataFlow::TypeTracker t) {
t.start() and
(
exists(SubscriptNode subscript | result.asCfgNode() = subscript |
subscript.getObject() = mongoDB().asCfgNode()
subscript.getObject() = mongoDBInstance().asCfgNode()
)
or
result.(DataFlow::AttrRead).getObject() = mongoDB()
result.(DataFlow::AttrRead).getObject() = mongoDBInstance()
or
// see https://pymongo.readthedocs.io/en/stable/api/pymongo/database.html#pymongo.database.Database.get_collection
// see https://pymongo.readthedocs.io/en/stable/api/pymongo/database.html#pymongo.database.Database.create_collection
result
.(DataFlow::MethodCallNode)
.calls(mongoDBInstance(), ["get_collection", "create_collection"])
)
or
exists(DataFlow::TypeTracker t2 | result = mongoCollection(t2).track(t2, t))
@@ -153,7 +159,7 @@ private module NoSQL {
*
* `mongo.db.user.find({'name': safe_search})` would be a collection method call, and so the result.
*/
private class MongoCollectionCall extends DataFlow::CallCfgNode, NoSQLQuery::Range {
private class MongoCollectionCall extends DataFlow::CallCfgNode, NoSqlQuery::Range {
MongoCollectionCall() { this.getFunction() = mongoCollectionMethod() }
override DataFlow::Node getQuery() { result = this.getArg(0) }
@@ -174,7 +180,7 @@ private module NoSQL {
*
* `Movie.objects(__raw__=json_search)` would be the result.
*/
private class MongoEngineObjectsCall extends DataFlow::CallCfgNode, NoSQLQuery::Range {
private class MongoEngineObjectsCall extends DataFlow::CallCfgNode, NoSqlQuery::Range {
MongoEngineObjectsCall() {
this =
[mongoEngine(), flask_MongoEngine()]
@@ -188,7 +194,7 @@ private module NoSQL {
}
/** Gets a reference to `mongosanitizer.sanitizer.sanitize` */
private class MongoSanitizerCall extends DataFlow::CallCfgNode, NoSQLSanitizer::Range {
private class MongoSanitizerCall extends DataFlow::CallCfgNode, NoSqlSanitizer::Range {
MongoSanitizerCall() {
this =
API::moduleImport("mongosanitizer").getMember("sanitizer").getMember("sanitize").getACall()
@@ -202,7 +208,7 @@ private module NoSQL {
* If at any time ObjectId can't parse it's input (like when a tainted dict in passed in),
* then ObjectId will throw an error preventing the query from running.
*/
private class BsonObjectIdCall extends DataFlow::CallCfgNode, NoSQLSanitizer::Range {
private class BsonObjectIdCall extends DataFlow::CallCfgNode, NoSqlSanitizer::Range {
BsonObjectIdCall() {
this =
API::moduleImport(["bson", "bson.objectid", "bson.json_util"])

View File

@@ -12,7 +12,7 @@ private import experimental.semmle.python.Concepts
private import semmle.python.ApiGraphs
private module Werkzeug {
module datastructures {
module Datastructures {
module Headers {
class WerkzeugHeaderAddCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
WerkzeugHeaderAddCall() {

View File

@@ -0,0 +1,87 @@
private import python
private import experimental.semmle.python.Concepts
private import semmle.python.ApiGraphs
private import experimental.semmle.python.frameworks.JWT
private module Authlib {
/** Gets a reference to `authlib.jose.(jwt|JsonWebToken)` */
private API::Node authlibJwt() {
result in [
API::moduleImport("authlib").getMember("jose").getMember("jwt"),
API::moduleImport("authlib").getMember("jose").getMember("JsonWebToken").getReturn()
]
}
/** Gets a reference to `jwt.encode` */
private API::Node authlibJwtEncode() { result = authlibJwt().getMember("encode") }
/** Gets a reference to `jwt.decode` */
private API::Node authlibJwtDecode() { result = authlibJwt().getMember("decode") }
/**
* Gets a call to `authlib.jose.(jwt|JsonWebToken).encode`.
*
* Given the following example:
*
* ```py
* jwt.encode({"alg": "HS256"}, token, "key")
* ```
*
* * `this` would be `jwt.encode({"alg": "HS256"}, token, "key")`.
* * `getPayload()`'s result would be `token`.
* * `getKey()`'s result would be `"key"`.
* * `getAlgorithm()`'s result would be `"HS256"`.
* * `getAlgorithmstring()`'s result would be `HS256`.
*/
private class AuthlibJwtEncodeCall extends DataFlow::CallCfgNode, JwtEncoding::Range {
AuthlibJwtEncodeCall() { this = authlibJwtEncode().getACall() }
override DataFlow::Node getPayload() { result = this.getArg(1) }
override DataFlow::Node getKey() { result = this.getArg(2) }
override DataFlow::Node getAlgorithm() {
exists(KeyValuePair headerDict |
headerDict = this.getArg(0).asExpr().(Dict).getItem(_) and
headerDict.getKey().(Str_).getS().matches("alg") and
result.asExpr() = headerDict.getValue()
)
}
override string getAlgorithmString() {
exists(StrConst str |
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(this.getAlgorithm()) and
result = str.getText()
)
}
}
/**
* Gets a call to `authlib.jose.(jwt|JsonWebToken).decode`
*
* Given the following example:
*
* ```py
* jwt.decode(token, key)
* ```
*
* * `this` would be `jwt.decode(token, key)`.
* * `getPayload()`'s result would be `token`.
* * `getKey()`'s result would be `key`.
*/
private class AuthlibJwtDecodeCall extends DataFlow::CallCfgNode, JwtDecoding::Range {
AuthlibJwtDecodeCall() { this = authlibJwtDecode().getACall() }
override DataFlow::Node getPayload() { result = this.getArg(0) }
override DataFlow::Node getKey() { result = this.getArg(1) }
override DataFlow::Node getAlgorithm() { none() }
override string getAlgorithmString() { none() }
override DataFlow::Node getOptions() { none() }
override predicate verifiesSignature() { any() }
}
}

View File

@@ -0,0 +1,108 @@
private import python
private import experimental.semmle.python.Concepts
private import semmle.python.ApiGraphs
private import experimental.semmle.python.frameworks.JWT
private module PyJwt {
/** Gets a reference to `jwt.encode` */
private API::Node pyjwtEncode() { result = API::moduleImport("jwt").getMember("encode") }
/** Gets a reference to `jwt.decode` */
private API::Node pyjwtDecode() { result = API::moduleImport("jwt").getMember("decode") }
/**
* Gets a call to `jwt.encode`.
*
* Given the following example:
*
* ```py
* jwt.encode(token, "key", "HS256")
* ```
*
* * `this` would be `jwt.encode(token, "key", "HS256")`.
* * `getPayload()`'s result would be `token`.
* * `getKey()`'s result would be `"key"`.
* * `getAlgorithm()`'s result would be `"HS256"`.
* * `getAlgorithmstring()`'s result would be `HS256`.
*/
private class PyJwtEncodeCall extends DataFlow::CallCfgNode, JwtEncoding::Range {
PyJwtEncodeCall() { this = pyjwtEncode().getACall() }
override DataFlow::Node getPayload() {
result in [this.getArg(0), this.getArgByName("payload")]
}
override DataFlow::Node getKey() { result in [this.getArg(1), this.getArgByName("key")] }
override DataFlow::Node getAlgorithm() {
result in [this.getArg(2), this.getArgByName("algorithm")]
}
override string getAlgorithmString() {
exists(StrConst str |
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(this.getAlgorithm()) and
result = str.getText()
)
}
}
/**
* Gets a call to `jwt.decode`.
*
* Given the following example:
*
* ```py
* jwt.decode(token, key, "HS256", options={"verify_signature": True})
* ```
*
* * `this` would be `jwt.decode(token, key, options={"verify_signature": True})`.
* * `getPayload()`'s result would be `token`.
* * `getKey()`'s result would be `key`.
* * `getAlgorithm()`'s result would be `"HS256"`.
* * `getAlgorithmstring()`'s result would be `HS256`.
* * `getOptions()`'s result would be `{"verify_signature": True}`.
* * `verifiesSignature()` predicate would succeed.
*/
private class PyJwtDecodeCall extends DataFlow::CallCfgNode, JwtDecoding::Range {
PyJwtDecodeCall() { this = pyjwtDecode().getACall() }
override DataFlow::Node getPayload() { result in [this.getArg(0), this.getArgByName("jwt")] }
override DataFlow::Node getKey() { result in [this.getArg(1), this.getArgByName("key")] }
override DataFlow::Node getAlgorithm() {
result in [this.getArg(2), this.getArgByName("algorithms")]
}
override string getAlgorithmString() {
exists(StrConst str |
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(this.getAlgorithm()) and
result = str.getText()
)
}
override DataFlow::Node getOptions() {
result in [this.getArg(3), this.getArgByName("options")]
}
override predicate verifiesSignature() {
not this.hasVerifySetToFalse() and
not this.hasVerifySignatureSetToFalse()
}
predicate hasNoVerifyArgumentOrOptions() {
not exists(this.getArgByName("verify")) and not exists(this.getOptions())
}
predicate hasVerifySetToFalse() { isFalse(this.getArgByName("verify")) }
predicate hasVerifySignatureSetToFalse() {
exists(KeyValuePair optionsDict, NameConstant falseName |
falseName.getId() = "False" and
optionsDict = this.getOptions().asExpr().(Dict).getItem(_) and
optionsDict.getKey().(Str_).getS().matches("%verify%") and
falseName = optionsDict.getValue()
)
}
}
}

View File

@@ -0,0 +1,105 @@
private import python
private import experimental.semmle.python.Concepts
private import semmle.python.ApiGraphs
private import experimental.semmle.python.frameworks.JWT
private module PythonJose {
/** Gets a reference to `jwt` */
private API::Node joseJwt() { result = API::moduleImport("jose").getMember("jwt") }
/** Gets a reference to `jwt.encode` */
private API::Node joseJwtEncode() { result = joseJwt().getMember("encode") }
/** Gets a reference to `jwt.decode` */
private API::Node joseJwtDecode() { result = joseJwt().getMember("decode") }
/**
* Gets a call to `jwt.encode`.
*
* Given the following example:
*
* ```py
* jwt.encode(token, key="key", algorithm="HS256")
* ```
*
* * `this` would be `jwt.encode(token, key="key", algorithm="HS256")`.
* * `getPayload()`'s result would be `token`.
* * `getKey()`'s result would be `"key"`.
* * `getAlgorithm()`'s result would be `"HS256"`.
* * `getAlgorithmstring()`'s result would be `HS256`.
*/
private class JoseJwtEncodeCall extends DataFlow::CallCfgNode, JwtEncoding::Range {
JoseJwtEncodeCall() { this = joseJwtEncode().getACall() }
override DataFlow::Node getPayload() { result = this.getArg(0) }
override DataFlow::Node getKey() { result in [this.getArg(1), this.getArgByName("key")] }
override DataFlow::Node getAlgorithm() {
result in [this.getArg(2), this.getArgByName("algorithm")]
}
override string getAlgorithmString() {
exists(StrConst str |
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(this.getAlgorithm()) and
result = str.getText()
)
}
}
/**
* Gets a call to `jwt.decode`.
*
* Given the following example:
*
* ```py
* jwt.decode(token, "key", "HS256")
* ```
*
* * `this` would be `jwt.decode(token, "key", "HS256")`.
* * `getPayload()`'s result would be `token`.
* * `getKey()`'s result would be `"key"`.
* * `getAlgorithm()`'s result would be `"HS256"`.
* * `getAlgorithmstring()`'s result would be `HS256`.
* * `getOptions()`'s result would be none.
* * `verifiesSignature()` predicate would succeed.
*/
private class JoseJwtDecodeCall extends DataFlow::CallCfgNode, JwtDecoding::Range {
JoseJwtDecodeCall() { this = joseJwtDecode().getACall() }
override DataFlow::Node getPayload() { result = this.getArg(0) }
override DataFlow::Node getKey() { result in [this.getArg(1), this.getArgByName("key")] }
override DataFlow::Node getAlgorithm() {
result in [this.getArg(2), this.getArgByName("algorithms")]
}
override string getAlgorithmString() {
exists(StrConst str |
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(this.getAlgorithm()) and
result = str.getText()
)
}
override DataFlow::Node getOptions() {
result in [this.getArg(3), this.getArgByName("options")]
}
override predicate verifiesSignature() {
// jwt.decode(token, key, options={"verify_signature": False})
not this.hasVerifySignatureSetToFalse()
}
predicate hasNoOptions() { not exists(this.getOptions()) }
predicate hasVerifySignatureSetToFalse() {
exists(KeyValuePair optionsDict, NameConstant falseName |
falseName.getId() = "False" and
optionsDict = this.getOptions().asExpr().(Dict).getItem(_) and
optionsDict.getKey().(Str_).getS().matches("%verify%") and
falseName = optionsDict.getValue()
)
}
}
}

View File

@@ -0,0 +1,51 @@
private import python
private import experimental.semmle.python.Concepts
private import semmle.python.ApiGraphs
private module Python_Jwt {
/**
* Gets a call to `python_jwt.process_jwt`.
*
* Given the following example:
*
* ```py
* python_jwt.process_jwt(token)
* python_jwt.verify_jwt(token, "key", "HS256")
* ```
*
* * `this` would be `jwt.process_jwt(token)`.
* * `getPayload()`'s result would be `token`.
* * `getKey()`'s result would be `"key"`.
* * `getAlgorithm()`'s result would be `"HS256"`.
* * `getAlgorithmstring()`'s result would be `HS256`.
* * `getOptions()`'s result would be `none()`.
* * `verifiesSignature()` predicate would succeed.
*/
private class PythonJwtProcessCall extends DataFlow::CallCfgNode, JwtDecoding::Range {
PythonJwtProcessCall() {
this = API::moduleImport("python_jwt").getMember("process_jwt").getACall()
}
private DataFlow::CallCfgNode verifyCall() {
result = API::moduleImport("python_jwt").getMember("verify_jwt").getACall() and
this.getPayload().getALocalSource() = result.getArg(0).getALocalSource()
}
override DataFlow::Node getPayload() { result = this.getArg(0) }
override DataFlow::Node getKey() { result = this.verifyCall().getArg(1) }
override DataFlow::Node getAlgorithm() { result = this.verifyCall().getArg(2) }
override string getAlgorithmString() {
exists(StrConst str |
DataFlow::exprNode(str).(DataFlow::LocalSourceNode).flowsTo(this.getAlgorithm()) and
result = str.getText()
)
}
override DataFlow::Node getOptions() { none() }
override predicate verifiesSignature() { exists(this.verifyCall()) }
}
}

View File

@@ -1,9 +1,10 @@
/**
* Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
* Provides a taint tracking configuration for reasoning about random
* values that are not cryptographically secure.
*
* Note, for performance reasons: only import this file if
* `XpathInjection::Configuration` is needed, otherwise
* `XpathInjectionCustomizations` should be imported instead.
* `InsecureRandomness::Configuration` is needed, otherwise
* `InsecureRandomnessCustomizations` should be imported instead.
*/
private import python
@@ -11,16 +12,17 @@ import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
/**
* Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
* A taint tracking configuration for random values that are not cryptographically secure.
*/
module XpathInjection {
import XpathInjectionCustomizations::XpathInjection
module InsecureRandomness {
import InsecureRandomnessCustomizations::InsecureRandomness
/**
* A taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
* A taint-tracking configuration for reasoning about random values that are
* not cryptographically secure.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "Xpath Injection" }
Configuration() { this = "InsecureRandomness" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }

View File

@@ -0,0 +1,79 @@
/**
* Provides default sources, sinks and sanitizers for reasoning about random values that are
* not cryptographically secure, as well as extension points for adding your own.
*/
private import python
private import semmle.python.ApiGraphs
private import semmle.python.Concepts
private import semmle.python.dataflow.new.BarrierGuards
private import semmle.python.dataflow.new.internal.DataFlowPrivate
/**
* Provides default sources, sinks and sanitizers for reasoning about random values that are
* not cryptographically secure, as well as extension points for adding your own.
*/
module InsecureRandomness {
/**
* A data flow source for random values that are not cryptographically secure.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for random values that are not cryptographically secure.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for random values that are not cryptographically secure.
*/
abstract class Sanitizer extends DataFlow::Node { }
/**
* A sanitizer guard for random values that are not cryptographically secure.
*/
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
/**
* A random source that is not sufficient for security use. So far this is only made up
* of the math package's rand function, more insufficient random sources can be added here.
*/
class InsecureRandomSource extends Source {
InsecureRandomSource() {
this =
API::moduleImport("random")
.getMember([
"betavariate", "choice", "choices", "expovariate", "gammavariate", "gauss",
"getrandbits", "getstate", "lognormvariate", "normalvariate", "paretovariate",
"randbytes", "randint", "random", "randrange", "sample", "seed", "setstate",
"shuffle", "triangular", "uniform", "vonmisesvariate", "weibullvariate"
])
.getACall()
}
}
/**
* A use in a function that heuristically deals with unsafe random numbers or random strings.
*/
class RandomFnSink extends Sink {
RandomFnSink() {
exists(DataFlowCallable randomFn |
randomFn
.getName()
.regexpMatch("(?i).*(gen(erate)?|make|mk|create).*(nonce|salt|pepper|Password).*")
|
this.getEnclosingCallable() = randomFn
)
}
}
/**
* A cryptographic key, considered as a sink for random values that are not cryptographically
* secure.
*/
class CryptoKeySink extends Sink {
CryptoKeySink() {
exists(Cryptography::CryptographicOperation operation | this = operation.getAnInput())
}
}
}

View File

@@ -18,8 +18,8 @@ string getPrivateHostRegex() {
}
// "ldap://somethingon.theinternet.com"
class LDAPFullHost extends StrConst {
LDAPFullHost() {
class LdapFullHost extends StrConst {
LdapFullHost() {
exists(string s |
s = this.getText() and
s.regexpMatch(getFullHostRegex()) and
@@ -29,27 +29,39 @@ class LDAPFullHost extends StrConst {
}
}
class LDAPSchema extends StrConst {
LDAPSchema() { this.getText().regexpMatch(getSchemaRegex()) }
/** DEPRECATED: Alias for LdapFullHost */
deprecated class LDAPFullHost = LdapFullHost;
class LdapSchema extends StrConst {
LdapSchema() { this.getText().regexpMatch(getSchemaRegex()) }
}
class LDAPPrivateHost extends StrConst {
LDAPPrivateHost() { this.getText().regexpMatch(getPrivateHostRegex()) }
/** DEPRECATED: Alias for LdapSchema */
deprecated class LDAPSchema = LdapSchema;
class LdapPrivateHost extends StrConst {
LdapPrivateHost() { this.getText().regexpMatch(getPrivateHostRegex()) }
}
predicate concatAndCompareAgainstFullHostRegex(LDAPSchema schema, StrConst host) {
not host instanceof LDAPPrivateHost and
/** DEPRECATED: Alias for LdapPrivateHost */
deprecated class LDAPPrivateHost = LdapPrivateHost;
predicate concatAndCompareAgainstFullHostRegex(LdapSchema schema, StrConst host) {
not host instanceof LdapPrivateHost and
(schema.getText() + host.getText()).regexpMatch(getFullHostRegex())
}
// "ldap://" + "somethingon.theinternet.com"
class LDAPBothStrings extends BinaryExpr {
LDAPBothStrings() { concatAndCompareAgainstFullHostRegex(this.getLeft(), this.getRight()) }
class LdapBothStrings extends BinaryExpr {
LdapBothStrings() { concatAndCompareAgainstFullHostRegex(this.getLeft(), this.getRight()) }
}
/** DEPRECATED: Alias for LdapBothStrings */
deprecated class LDAPBothStrings = LdapBothStrings;
// schema + host
class LDAPBothVar extends BinaryExpr {
LDAPBothVar() {
class LdapBothVar extends BinaryExpr {
LdapBothVar() {
exists(SsaVariable schemaVar, SsaVariable hostVar |
this.getLeft() = schemaVar.getVariable().getALoad() and // getAUse is incompatible with Expr
this.getRight() = hostVar.getVariable().getALoad() and
@@ -61,9 +73,12 @@ class LDAPBothVar extends BinaryExpr {
}
}
/** DEPRECATED: Alias for LdapBothVar */
deprecated class LDAPBothVar = LdapBothVar;
// schema + "somethingon.theinternet.com"
class LDAPVarString extends BinaryExpr {
LDAPVarString() {
class LdapVarString extends BinaryExpr {
LdapVarString() {
exists(SsaVariable schemaVar |
this.getLeft() = schemaVar.getVariable().getALoad() and
concatAndCompareAgainstFullHostRegex(schemaVar
@@ -74,9 +89,12 @@ class LDAPVarString extends BinaryExpr {
}
}
/** DEPRECATED: Alias for LdapVarString */
deprecated class LDAPVarString = LdapVarString;
// "ldap://" + host
class LDAPStringVar extends BinaryExpr {
LDAPStringVar() {
class LdapStringVar extends BinaryExpr {
LdapStringVar() {
exists(SsaVariable hostVar |
this.getRight() = hostVar.getVariable().getALoad() and
concatAndCompareAgainstFullHostRegex(this.getLeft(),
@@ -85,22 +103,28 @@ class LDAPStringVar extends BinaryExpr {
}
}
/** DEPRECATED: Alias for LdapStringVar */
deprecated class LDAPStringVar = LdapStringVar;
/**
* A taint-tracking configuration for detecting LDAP insecure authentications.
*/
class LDAPInsecureAuthConfig extends TaintTracking::Configuration {
LDAPInsecureAuthConfig() { this = "LDAPInsecureAuthConfig" }
class LdapInsecureAuthConfig extends TaintTracking::Configuration {
LdapInsecureAuthConfig() { this = "LDAPInsecureAuthConfig" }
override predicate isSource(DataFlow::Node source) {
source instanceof RemoteFlowSource or
source.asExpr() instanceof LDAPFullHost or
source.asExpr() instanceof LDAPBothStrings or
source.asExpr() instanceof LDAPBothVar or
source.asExpr() instanceof LDAPVarString or
source.asExpr() instanceof LDAPStringVar
source.asExpr() instanceof LdapFullHost or
source.asExpr() instanceof LdapBothStrings or
source.asExpr() instanceof LdapBothVar or
source.asExpr() instanceof LdapVarString or
source.asExpr() instanceof LdapStringVar
}
override predicate isSink(DataFlow::Node sink) {
exists(LDAPBind ldapBind | not ldapBind.useSSL() and sink = ldapBind.getHost())
exists(LdapBind ldapBind | not ldapBind.useSSL() and sink = ldapBind.getHost())
}
}
/** DEPRECATED: Alias for LdapInsecureAuthConfig */
deprecated class LDAPInsecureAuthConfig = LdapInsecureAuthConfig;

View File

@@ -0,0 +1,39 @@
import python
import experimental.semmle.python.Concepts
import semmle.python.dataflow.new.DataFlow
import semmle.python.ApiGraphs
import semmle.python.dataflow.new.TaintTracking
class ZipSlipConfig extends TaintTracking::Configuration {
ZipSlipConfig() { this = "ZipSlipConfig" }
override predicate isSource(DataFlow::Node source) {
(
source =
API::moduleImport("zipfile").getMember("ZipFile").getReturn().getMember("open").getACall() or
source =
API::moduleImport("zipfile")
.getMember("ZipFile")
.getReturn()
.getMember("namelist")
.getACall() or
source = API::moduleImport("tarfile").getMember("open").getACall() or
source = API::moduleImport("tarfile").getMember("TarFile").getACall() or
source = API::moduleImport("bz2").getMember("open").getACall() or
source = API::moduleImport("bz2").getMember("BZ2File").getACall() or
source = API::moduleImport("gzip").getMember("GzipFile").getACall() or
source = API::moduleImport("gzip").getMember("open").getACall() or
source = API::moduleImport("lzma").getMember("open").getACall() or
source = API::moduleImport("lzma").getMember("LZMAFile").getACall()
) and
not source.getScope().getLocation().getFile().inStdlib()
}
override predicate isSink(DataFlow::Node sink) {
(
sink = any(CopyFile copyfile).getAPathArgument() or
sink = any(CopyFile copyfile).getfsrcArgument()
) and
not sink.getScope().getLocation().getFile().inStdlib()
}
}

View File

@@ -1,24 +0,0 @@
/**
* Provides a taint-tracking configuration for detecting LDAP injection vulnerabilities
*/
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
/**
* A taint-tracking configuration for detecting LDAP injections.
*/
class LDAPInjectionFlowConfig extends TaintTracking::Configuration {
LDAPInjectionFlowConfig() { this = "LDAPInjectionFlowConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink = any(LDAPQuery ldapQuery).getQuery() }
override predicate isSanitizer(DataFlow::Node sanitizer) {
sanitizer = any(LDAPEscape ldapEsc).getAnInput()
}
}

View File

@@ -1,24 +0,0 @@
import python
import semmle.python.Concepts
import experimental.semmle.python.Concepts
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import semmle.python.dataflow.new.RemoteFlowSources
/**
* A taint-tracking configuration for tracking untrusted user input used in log entries.
*/
class LogInjectionFlowConfig extends TaintTracking::Configuration {
LogInjectionFlowConfig() { this = "LogInjectionFlowConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink = any(LogOutput logoutput).getAnInput() }
override predicate isSanitizer(DataFlow::Node node) {
exists(CallNode call |
node.asCfgNode() = call.getFunction().(AttrNode).getObject("replace") and
call.getArg(0).getNode().(StrConst).getText() in ["\r\n", "\n"]
)
}
}

View File

@@ -1,57 +1,57 @@
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.DataFlow2
import semmle.python.dataflow.new.TaintTracking
import semmle.python.dataflow.new.TaintTracking2
import semmle.python.dataflow.new.RemoteFlowSources
import semmle.python.security.dataflow.ChainedConfigs12
import experimental.semmle.python.Concepts
import semmle.python.Concepts
/**
* A taint-tracking configuration for detecting string-to-dict conversions.
*/
class RFSToDictConfig extends TaintTracking::Configuration {
RFSToDictConfig() { this = "RFSToDictConfig" }
module NoSqlInjection {
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "NoSQLInjection" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
source instanceof RemoteFlowSource and
state instanceof RemoteInput
}
override predicate isSink(DataFlow::Node sink) {
exists(Decoding decoding | decoding.getFormat() = "JSON" and sink = decoding.getOutput())
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
sink = any(NoSqlQuery noSqlQuery).getQuery() and
state instanceof ConvertedToDict
}
override predicate isSanitizer(DataFlow::Node node, DataFlow::FlowState state) {
// Block `RemoteInput` paths here, since they change state to `ConvertedToDict`
exists(Decoding decoding | decoding.getFormat() = "JSON" and node = decoding.getOutput()) and
state instanceof RemoteInput
}
override predicate isAdditionalTaintStep(
DataFlow::Node nodeFrom, DataFlow::FlowState stateFrom, DataFlow::Node nodeTo,
DataFlow::FlowState stateTo
) {
exists(Decoding decoding | decoding.getFormat() = "JSON" |
nodeFrom = decoding.getAnInput() and
nodeTo = decoding.getOutput()
) and
stateFrom instanceof RemoteInput and
stateTo instanceof ConvertedToDict
}
override predicate isSanitizer(DataFlow::Node sanitizer) {
sanitizer = any(NoSqlSanitizer noSqlSanitizer).getAnInput()
}
}
override predicate isSanitizer(DataFlow::Node sanitizer) {
sanitizer = any(NoSQLSanitizer noSQLSanitizer).getAnInput()
/** A flow state signifying remote input. */
class RemoteInput extends DataFlow::FlowState {
RemoteInput() { this = "RemoteInput" }
}
/** A flow state signifying remote input converted to a dictionary. */
class ConvertedToDict extends DataFlow::FlowState {
ConvertedToDict() { this = "ConvertedToDict" }
}
}
/**
* A taint-tracking configuration for detecting NoSQL injections (previously converted to a dict).
*/
class FromDataDictToSink extends TaintTracking2::Configuration {
FromDataDictToSink() { this = "FromDataDictToSink" }
override predicate isSource(DataFlow::Node source) {
exists(Decoding decoding | decoding.getFormat() = "JSON" and source = decoding.getOutput())
}
override predicate isSink(DataFlow::Node sink) { sink = any(NoSQLQuery noSQLQuery).getQuery() }
override predicate isSanitizer(DataFlow::Node sanitizer) {
sanitizer = any(NoSQLSanitizer noSQLSanitizer).getAnInput()
}
}
/**
* A predicate checking string-to-dict conversion and its arrival to a NoSQL injection sink.
*/
predicate noSQLInjectionFlow(CustomPathNode source, CustomPathNode sink) {
exists(
RFSToDictConfig config, DataFlow::PathNode mid1, DataFlow2::PathNode mid2,
FromDataDictToSink config2
|
config.hasFlowPath(source.asNode1(), mid1) and
config2.hasFlowPath(mid2, sink.asNode2()) and
mid1.getNode().asCfgNode() = mid2.getNode().asCfgNode()
)
}
/** DEPRECATED: Alias for NoSqlInjection */
deprecated module NoSQLInjection = NoSqlInjection;

View File

@@ -21,11 +21,11 @@ module XSLTInjection {
/**
* A kind of "taint", representing an untrusted XML string
*/
private class ExternalXmlStringKind extends ExternalStringKind {
deprecated private class ExternalXmlStringKind extends ExternalStringKind {
ExternalXmlStringKind() { this = "etree.XML string" }
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
etreeXML(fromnode, tonode) and result instanceof ExternalXmlKind
etreeXml(fromnode, tonode) and result instanceof ExternalXmlKind
or
etreeFromStringList(fromnode, tonode) and result instanceof ExternalXmlKind
or
@@ -40,7 +40,7 @@ module XSLTInjection {
ExternalXmlKind() { this = "lxml etree xml" }
}
private predicate etreeXML(ControlFlowNode fromnode, CallNode tonode) {
private predicate etreeXml(ControlFlowNode fromnode, CallNode tonode) {
// etree.XML("<xmlContent>")
exists(CallNode call | call.getFunction().(AttrNode).getObject("XML").pointsTo(etree()) |
call.getArg(0) = fromnode and

View File

@@ -5,15 +5,15 @@ import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink
/** returns the ClassValue representing `airspeed.Template` */
ClassValue theAirspeedTemplateClass() { result = Value::named("airspeed.Template") }
deprecated ClassValue theAirspeedTemplateClass() { result = Value::named("airspeed.Template") }
/**
* Sink representing the `airspeed.Template` class instantiation argument.
* A sink representing the `airspeed.Template` class instantiation argument.
*
* import airspeed
* temp = airspeed.Template(`"sink"`)
*/
class AirspeedTemplateSink extends SSTISink {
deprecated class AirspeedTemplateSink extends SSTISink {
override string toString() { result = "argument to airspeed.Template()" }
AirspeedTemplateSink() {

View File

@@ -5,15 +5,17 @@ import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink
/** returns the ClassValue representing `bottle.SimpleTemplate` */
ClassValue theBottleSimpleTemplateClass() { result = Value::named("bottle.SimpleTemplate") }
deprecated ClassValue theBottleSimpleTemplateClass() {
result = Value::named("bottle.SimpleTemplate")
}
/**
* Sink representing the `bottle.SimpleTemplate` class instantiation argument.
* A sink representing the `bottle.SimpleTemplate` class instantiation argument.
*
* from bottle import SimpleTemplate
* template = SimpleTemplate(`sink`)
*/
class BottleSimpleTemplateSink extends SSTISink {
deprecated class BottleSimpleTemplateSink extends SSTISink {
override string toString() { result = "argument to bottle.SimpleTemplate()" }
BottleSimpleTemplateSink() {
@@ -27,12 +29,12 @@ class BottleSimpleTemplateSink extends SSTISink {
}
/**
* Sink representing the `bottle.template` function call argument.
* A sink representing the `bottle.template` function call argument.
*
* from bottle import template
* tmp = template(`sink`)
*/
class BottleTemplateSink extends SSTISink {
deprecated class BottleTemplateSink extends SSTISink {
override string toString() { result = "argument to bottle.template()" }
BottleTemplateSink() {

View File

@@ -5,15 +5,17 @@ import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink
/** returns the ClassValue representing `chameleon.PageTemplate` */
ClassValue theChameleonPageTemplateClass() { result = Value::named("chameleon.PageTemplate") }
deprecated ClassValue theChameleonPageTemplateClass() {
result = Value::named("chameleon.PageTemplate")
}
/**
* Sink representing the `chameleon.PageTemplate` class instantiation argument.
* A sink representing the `chameleon.PageTemplate` class instantiation argument.
*
* from chameleon import PageTemplate
* template = PageTemplate(`sink`)
*/
class ChameleonTemplateSink extends SSTISink {
deprecated class ChameleonTemplateSink extends SSTISink {
override string toString() { result = "argument to Chameleon.PageTemplate()" }
ChameleonTemplateSink() {

View File

@@ -5,10 +5,12 @@ import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink
/** returns the ClassValue representing `Cheetah.Template.Template` */
ClassValue theCheetahTemplateClass() { result = Value::named("Cheetah.Template.Template") }
deprecated ClassValue theCheetahTemplateClass() {
result = Value::named("Cheetah.Template.Template")
}
/**
* Sink representing the instantiation argument of any class which derives from
* A sink representing the instantiation argument of any class which derives from
* the `Cheetah.Template.Template` class .
*
* from Cheetah.Template import Template
@@ -22,7 +24,7 @@ ClassValue theCheetahTemplateClass() { result = Value::named("Cheetah.Template.T
* from Cheetah.Template import Template
* t3 = Template("sink")
*/
class CheetahTemplateInstantiationSink extends SSTISink {
deprecated class CheetahTemplateInstantiationSink extends SSTISink {
override string toString() { result = "argument to Cheetah.Template.Template()" }
CheetahTemplateInstantiationSink() {

View File

@@ -5,15 +5,15 @@ import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink
/** returns the Value representing `chevron.render` function */
Value theChevronRenderFunc() { result = Value::named("chevron.render") }
deprecated Value theChevronRenderFunc() { result = Value::named("chevron.render") }
/**
* Sink representing the `chevron.render` function call argument.
* A sink representing the `chevron.render` function call argument.
*
* import chevron
* tmp = chevron.render(`sink`,{ 'key' : 'value' })
*/
class ChevronRenderSink extends SSTISink {
deprecated class ChevronRenderSink extends SSTISink {
override string toString() { result = "argument to chevron.render()" }
ChevronRenderSink() {

View File

@@ -4,15 +4,15 @@ import python
import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink
ClassValue theDjangoTemplateClass() { result = Value::named("django.template.Template") }
deprecated ClassValue theDjangoTemplateClass() { result = Value::named("django.template.Template") }
/**
* Sink representng `django.template.Template` class instantiation argument.
* A sink representng `django.template.Template` class instantiation argument.
*
* from django.template import Template
* template = Template(`sink`)
*/
class DjangoTemplateTemplateSink extends SSTISink {
deprecated class DjangoTemplateTemplateSink extends SSTISink {
override string toString() { result = "argument to Django.template()" }
DjangoTemplateTemplateSink() {

View File

@@ -4,15 +4,17 @@ import python
import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink
Value theFlaskRenderTemplateClass() { result = Value::named("flask.render_template_string") }
deprecated Value theFlaskRenderTemplateClass() {
result = Value::named("flask.render_template_string")
}
/**
* Sink representng `flask.render_template_string` function call argument.
* A sink representng `flask.render_template_string` function call argument.
*
* from flask import render_template_string
* render_template_string(`sink`)
*/
class FlaskTemplateSink extends SSTISink {
deprecated class FlaskTemplateSink extends SSTISink {
override string toString() { result = "argument to flask.render_template_string()" }
FlaskTemplateSink() {

View File

@@ -5,20 +5,22 @@ import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink
/** returns the ClassValue representing `Genshi.template.TextTemplate` */
ClassValue theGenshiTextTemplateClass() { result = Value::named("genshi.template.TextTemplate") }
deprecated ClassValue theGenshiTextTemplateClass() {
result = Value::named("genshi.template.TextTemplate")
}
/** returns the ClassValue representing `Genshi.template.MarkupTemplate` */
ClassValue theGenshiMarkupTemplateClass() {
deprecated ClassValue theGenshiMarkupTemplateClass() {
result = Value::named("genshi.template.MarkupTemplate")
}
/**
* Sink representing the `genshi.template.TextTemplate` class instantiation argument.
* A sink representing the `genshi.template.TextTemplate` class instantiation argument.
*
* from genshi.template import TextTemplate
* tmpl = TextTemplate('sink')
*/
class GenshiTextTemplateSink extends SSTISink {
deprecated class GenshiTextTemplateSink extends SSTISink {
override string toString() { result = "argument to genshi.template.TextTemplate()" }
GenshiTextTemplateSink() {
@@ -32,12 +34,12 @@ class GenshiTextTemplateSink extends SSTISink {
}
/**
* Sink representing the `genshi.template.MarkupTemplate` class instantiation argument.
* A sink representing the `genshi.template.MarkupTemplate` class instantiation argument.
*
* from genshi.template import MarkupTemplate
* tmpl = MarkupTemplate('sink')
*/
class GenshiMarkupTemplateSink extends SSTISink {
deprecated class GenshiMarkupTemplateSink extends SSTISink {
override string toString() { result = "argument to genshi.template.MarkupTemplate()" }
GenshiMarkupTemplateSink() {

View File

@@ -5,18 +5,18 @@ import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink
/** returns the ClassValue representing `jinja2.Template` */
ClassValue theJinja2TemplateClass() { result = Value::named("jinja2.Template") }
deprecated ClassValue theJinja2TemplateClass() { result = Value::named("jinja2.Template") }
/** returns the ClassValue representing `jinja2.Template` */
Value theJinja2FromStringValue() { result = Value::named("jinja2.from_string") }
deprecated Value theJinja2FromStringValue() { result = Value::named("jinja2.from_string") }
/**
* Sink representing the `jinja2.Template` class instantiation argument.
* A sink representing the `jinja2.Template` class instantiation argument.
*
* from jinja2 import Template
* template = Template(`sink`)
*/
class Jinja2TemplateSink extends SSTISink {
deprecated class Jinja2TemplateSink extends SSTISink {
override string toString() { result = "argument to jinja2.Template()" }
Jinja2TemplateSink() {
@@ -30,12 +30,12 @@ class Jinja2TemplateSink extends SSTISink {
}
/**
* Sink representing the `jinja2.from_string` function call argument.
* A sink representing the `jinja2.from_string` function call argument.
*
* from jinja2 import from_string
* template = from_string(`sink`)
*/
class Jinja2FromStringSink extends SSTISink {
deprecated class Jinja2FromStringSink extends SSTISink {
override string toString() { result = "argument to jinja2.from_string()" }
Jinja2FromStringSink() {

View File

@@ -5,15 +5,15 @@ import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink
/** returns the ClassValue representing `mako.template.Template` */
ClassValue theMakoTemplateClass() { result = Value::named("mako.template.Template") }
deprecated ClassValue theMakoTemplateClass() { result = Value::named("mako.template.Template") }
/**
* Sink representing the `mako.template.Template` class instantiation argument.
* A sink representing the `mako.template.Template` class instantiation argument.
*
* from mako.template import Template
* mytemplate = Template("hello world!")
*/
class MakoTemplateSink extends SSTISink {
deprecated class MakoTemplateSink extends SSTISink {
override string toString() { result = "argument to mako.template.Template()" }
MakoTemplateSink() {

View File

@@ -4,4 +4,4 @@ import semmle.python.dataflow.TaintTracking
* A generic taint sink that is vulnerable to template inclusions.
* The `temp` in `jinja2.Template(temp)` and similar.
*/
abstract class SSTISink extends TaintSink { }
abstract deprecated class SSTISink extends TaintSink { }

View File

@@ -5,15 +5,15 @@ import semmle.python.web.HttpRequest
import experimental.semmle.python.templates.SSTISink
/** returns the ClassValue representing `trender.TRender` */
ClassValue theTRenderTemplateClass() { result = Value::named("trender.TRender") }
deprecated ClassValue theTRenderTemplateClass() { result = Value::named("trender.TRender") }
/**
* Sink representing the `trender.TRender` class instantiation argument.
* A sink representing the `trender.TRender` class instantiation argument.
*
* from trender import TRender
* template = TRender(`sink`)
*/
class TRenderTemplateSink extends SSTISink {
deprecated class TRenderTemplateSink extends SSTISink {
override string toString() { result = "argument to trender.TRender()" }
TRenderTemplateSink() {