mirror of
https://github.com/github/codeql.git
synced 2026-05-02 20:25:13 +02:00
Merge branch 'main' into jorgectf/python/insecure-cookie
This commit is contained in:
@@ -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
|
||||
@@ -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."
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
56
python/ql/src/experimental/Security/CWE-022/ZipSlip.qhelp
Normal file
56
python/ql/src/experimental/Security/CWE-022/ZipSlip.qhelp
Normal 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>
|
||||
22
python/ql/src/experimental/Security/CWE-022/ZipSlip.ql
Normal file
22
python/ql/src/experimental/Security/CWE-022/ZipSlip.ql
Normal 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"
|
||||
16
python/ql/src/experimental/Security/CWE-022/zipslip_bad.py
Normal file
16
python/ql/src/experimental/Security/CWE-022/zipslip_bad.py
Normal 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)
|
||||
|
||||
10
python/ql/src/experimental/Security/CWE-022/zipslip_good.py
Normal file
10
python/ql/src/experimental/Security/CWE-022/zipslip_good.py
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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>
|
||||
@@ -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."
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -0,0 +1,6 @@
|
||||
import random
|
||||
|
||||
|
||||
def generatePassword():
|
||||
# BAD: the random is not cryptographically secure
|
||||
return random.random()
|
||||
@@ -0,0 +1,7 @@
|
||||
import secrets
|
||||
|
||||
|
||||
def generatePassword():
|
||||
# GOOD: the random is cryptographically secure
|
||||
secret_generator = secrets.SystemRandom()
|
||||
return secret_generator.random()
|
||||
@@ -0,0 +1,7 @@
|
||||
import jwt
|
||||
|
||||
# algorithm set to None
|
||||
jwt.encode(payload, "somekey", None)
|
||||
|
||||
# empty key
|
||||
jwt.encode(payload, key="", algorithm="HS256")
|
||||
@@ -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>
|
||||
@@ -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 + "."
|
||||
@@ -0,0 +1,4 @@
|
||||
import jwt
|
||||
|
||||
# unverified decoding
|
||||
jwt.decode(payload, key="somekey", verify=False)
|
||||
@@ -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>
|
||||
@@ -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."
|
||||
@@ -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" }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
@@ -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 can’t 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 user’s input has been added to the string. This is a better route because you don’t 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>
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
]
|
||||
@@ -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)
|
||||
]
|
||||
@@ -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()
|
||||
@@ -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+|(?<!\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>
|
||||
@@ -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"
|
||||
@@ -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>
|
||||
@@ -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 + "'."
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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 */
|
||||
|
||||
23
python/ql/src/experimental/semmle/python/frameworks/JWT.qll
Normal file
23
python/ql/src/experimental/semmle/python/frameworks/JWT.qll
Normal 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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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"))]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"])
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
108
python/ql/src/experimental/semmle/python/libraries/PyJWT.qll
Normal file
108
python/ql/src/experimental/semmle/python/libraries/PyJWT.qll
Normal 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()) }
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 { }
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user