Merge branch 'main' into amammad-python-bombs

This commit is contained in:
amammad
2023-12-07 13:50:20 +01:00
9569 changed files with 683674 additions and 332770 deletions

View File

@@ -1,3 +1,69 @@
## 0.9.3
### Minor Analysis Improvements
* Added modeling of more `FileSystemAccess` in packages `cherrypy`, `aiofile`, `aiofiles`, `anyio`, `sanic`, `starlette`, `baize`, and `io`. This will mainly affect the _Uncontrolled data used in path expression_ (`py/path-injection`) query.
## 0.9.2
No user-facing changes.
## 0.9.1
No user-facing changes.
## 0.9.0
### New Queries
* The query `py/nosql-injection` for finding NoSQL injection vulnerabilities is now available in the default security suite.
### Minor Analysis Improvements
* Improved _URL redirection from remote source_ (`py/url-redirection`) query to not alert when URL has been checked with `django.utils.http. url_has_allowed_host_and_scheme`.
* Extended the `py/command-line-injection` query with sinks from Python's `asyncio` module.
## 0.8.5
No user-facing changes.
## 0.8.4
### Minor Analysis Improvements
* Improved _Reflected server-side cross-site scripting_ (`py/reflective-xss`) query to not alert on data passed to `flask.jsonify`. Since these HTTP responses are returned with mime-type `application/json`, they do not pose a security risk for XSS.
* Updated path explanations for `@kind path-problem` queries to always include left hand side of assignments, making paths easier to understand.
## 0.8.3
No user-facing changes.
## 0.8.2
No user-facing changes.
## 0.8.1
### Minor Analysis Improvements
* Fixed modeling of `aiohttp.ClientSession` so we properly handle `async with` uses. This can impact results of server-side request forgery queries (`py/full-ssrf`, `py/partial-ssrf`).
## 0.8.0
### Bug Fixes
* The query "Arbitrary file write during archive extraction ("Zip Slip")" (`py/zipslip`) has been renamed to "Arbitrary file access during archive extraction ("Zip Slip")."
## 0.7.4
No user-facing changes.
## 0.7.3
### Bug Fixes
* The display name (`@name`) of the `py/unsafe-deserialization` query has been updated in favor of consistency with other languages.
## 0.7.2
No user-facing changes.

View File

@@ -13,11 +13,11 @@
import python
import semmle.python.functions.ModificationOfParameterWithDefault
import DataFlow::PathGraph
import ModificationOfParameterWithDefault::Flow::PathGraph
from
ModificationOfParameterWithDefault::Configuration config, DataFlow::PathNode source,
DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
ModificationOfParameterWithDefault::Flow::PathNode source,
ModificationOfParameterWithDefault::Flow::PathNode sink
where ModificationOfParameterWithDefault::Flow::flowPath(source, sink)
select sink.getNode(), source, sink, "This expression mutates a $@.", source.getNode(),
"default value"

View File

@@ -167,8 +167,12 @@ class ExternalApiDataNode extends DataFlow::Node {
}
}
/** A configuration for tracking flow from `RemoteFlowSource`s to `ExternalApiDataNode`s. */
class UntrustedDataToExternalApiConfig extends TaintTracking::Configuration {
/**
* DEPRECATED: Use `XmlBombFlow` module instead.
*
* A configuration for tracking flow from `RemoteFlowSource`s to `ExternalApiDataNode`s.
*/
deprecated class UntrustedDataToExternalApiConfig extends TaintTracking::Configuration {
UntrustedDataToExternalApiConfig() { this = "UntrustedDataToExternalAPIConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
@@ -176,14 +180,21 @@ class UntrustedDataToExternalApiConfig extends TaintTracking::Configuration {
override predicate isSink(DataFlow::Node sink) { sink instanceof ExternalApiDataNode }
}
private module UntrustedDataToExternalApiConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
predicate isSink(DataFlow::Node sink) { sink instanceof ExternalApiDataNode }
}
/** Global taint-tracking from `RemoteFlowSource`s to `ExternalApiDataNode`s. */
module UntrustedDataToExternalApiFlow = TaintTracking::Global<UntrustedDataToExternalApiConfig>;
/** A node representing untrusted data being passed to an external API. */
class UntrustedExternalApiDataNode extends ExternalApiDataNode {
UntrustedExternalApiDataNode() { any(UntrustedDataToExternalApiConfig c).hasFlow(_, this) }
UntrustedExternalApiDataNode() { UntrustedDataToExternalApiFlow::flow(_, this) }
/** Gets a source of untrusted data which is passed to this external API data node. */
DataFlow::Node getAnUntrustedSource() {
any(UntrustedDataToExternalApiConfig c).hasFlow(result, this)
}
DataFlow::Node getAnUntrustedSource() { UntrustedDataToExternalApiFlow::flow(result, this) }
}
/** An external API which is used with untrusted data. */

View File

@@ -11,14 +11,14 @@
import python
import ExternalAPIs
import DataFlow::PathGraph
import UntrustedDataToExternalApiFlow::PathGraph
from
UntrustedDataToExternalApiConfig config, DataFlow::PathNode source, DataFlow::PathNode sink,
UntrustedDataToExternalApiFlow::PathNode source, UntrustedDataToExternalApiFlow::PathNode sink,
ExternalApiUsedWithUntrustedData externalApi
where
sink.getNode() = externalApi.getUntrustedDataNode() and
config.hasFlowPath(source, sink)
UntrustedDataToExternalApiFlow::flowPath(source, sink)
select sink.getNode(), source, sink,
"Call to " + externalApi.toString() + " with untrusted data from $@.", source.getNode(),
source.toString()

View File

@@ -18,9 +18,9 @@
import python
import semmle.python.security.dataflow.PathInjectionQuery
import DataFlow::PathGraph
import PathInjectionFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from PathInjectionFlow::PathNode source, PathInjectionFlow::PathNode sink
where PathInjectionFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This path depends on a $@.", source.getNode(),
"user-provided value"

View File

@@ -14,9 +14,9 @@
import python
import semmle.python.security.dataflow.TarSlipQuery
import DataFlow::PathGraph
import TarSlipFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from TarSlipFlow::PathNode source, TarSlipFlow::PathNode sink
where TarSlipFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This file extraction depends on a $@.", source.getNode(),
"potentially untrusted source"

View File

@@ -16,9 +16,9 @@
import python
import semmle.python.security.dataflow.CommandInjectionQuery
import DataFlow::PathGraph
import CommandInjectionFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from CommandInjectionFlow::PathNode source, CommandInjectionFlow::PathNode sink
where CommandInjectionFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This command line depends on a $@.", source.getNode(),
"user-provided value"

View File

@@ -16,11 +16,13 @@
import python
import semmle.python.security.dataflow.UnsafeShellCommandConstructionQuery
import DataFlow::PathGraph
import UnsafeShellCommandConstructionFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode
from
UnsafeShellCommandConstructionFlow::PathNode source,
UnsafeShellCommandConstructionFlow::PathNode sink, Sink sinkNode
where
config.hasFlowPath(source, sink) and
UnsafeShellCommandConstructionFlow::flowPath(source, sink) and
sinkNode = sink.getNode()
select sinkNode.getStringConstruction(), source, sink,
"This " + sinkNode.describe() + " which depends on $@ is later used in a $@.", source.getNode(),

View File

@@ -15,9 +15,9 @@
import python
import semmle.python.security.dataflow.ReflectedXssQuery
import DataFlow::PathGraph
import ReflectedXssFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from ReflectedXssFlow::PathNode source, ReflectedXssFlow::PathNode sink
where ReflectedXssFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to a $@.",
source.getNode(), "user-provided value"

View File

@@ -13,9 +13,9 @@
import python
import semmle.python.security.dataflow.SqlInjectionQuery
import DataFlow::PathGraph
import SqlInjectionFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from SqlInjectionFlow::PathNode source, SqlInjectionFlow::PathNode sink
where SqlInjectionFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This SQL query depends on a $@.", source.getNode(),
"user-provided value"

View File

@@ -14,14 +14,14 @@
// Determine precision above
import python
import semmle.python.security.dataflow.LdapInjectionQuery
import DataFlow::PathGraph
import LdapInjectionFlow::PathGraph
from DataFlow::PathNode source, DataFlow::PathNode sink, string parameterName
from LdapInjectionFlow::PathNode source, LdapInjectionFlow::PathNode sink, string parameterName
where
any(DnConfiguration dnConfig).hasFlowPath(source, sink) and
LdapInjectionDnFlow::flowPath(source.asPathNode1(), sink.asPathNode1()) and
parameterName = "DN"
or
any(FilterConfiguration filterConfig).hasFlowPath(source, sink) and
LdapInjectionFilterFlow::flowPath(source.asPathNode2(), sink.asPathNode2()) and
parameterName = "filter"
select sink.getNode(), source, sink,
"LDAP query parameter (" + parameterName + ") depends on a $@.", source.getNode(),

View File

@@ -16,9 +16,9 @@
import python
import semmle.python.security.dataflow.CodeInjectionQuery
import DataFlow::PathGraph
import CodeInjectionFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from CodeInjectionFlow::PathNode source, CodeInjectionFlow::PathNode sink
where CodeInjectionFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This code execution depends on a $@.", source.getNode(),
"user-provided value"

View File

@@ -13,9 +13,9 @@
import python
import semmle.python.security.dataflow.LogInjectionQuery
import DataFlow::PathGraph
import LogInjectionFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from LogInjectionFlow::PathNode source, LogInjectionFlow::PathNode sink
where LogInjectionFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This log entry depends on a $@.", source.getNode(),
"user-provided value"

View File

@@ -15,10 +15,10 @@
import python
import semmle.python.security.dataflow.StackTraceExposureQuery
import DataFlow::PathGraph
import StackTraceExposureFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from StackTraceExposureFlow::PathNode source, StackTraceExposureFlow::PathNode sink
where StackTraceExposureFlow::flowPath(source, sink)
select sink.getNode(), source, sink,
"$@ flows to this location and may be exposed to an external user.", source.getNode(),
"Stack trace information"

View File

@@ -11,12 +11,12 @@
*/
import python
import DataFlow::PathGraph
import PamAuthorizationFlow::PathGraph
import semmle.python.ApiGraphs
import semmle.python.security.dataflow.PamAuthorizationQuery
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from PamAuthorizationFlow::PathNode source, PamAuthorizationFlow::PathNode sink
where PamAuthorizationFlow::flowPath(source, sink)
select sink.getNode(), source, sink,
"This PAM authentication depends on a $@, and 'pam_acct_mgmt' is not called afterwards.",
source.getNode(), "user-provided value"

View File

@@ -2,4 +2,33 @@
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<include src="CleartextStorage.qhelp" /></qhelp>
<overview>
<p>If sensitive data is written to a log entry it could be exposed to an attacker
who gains access to the logs.</p>
<p>Potential attackers can obtain sensitive user data when the log output is displayed. Additionally that data may
expose system information such as full path names, system information, and sometimes usernames and passwords.</p>
</overview>
<recommendation>
<p>
Sensitive data should not be logged.
</p>
</recommendation>
<example>
<p>In the example the entire process environment is logged using `print`. Regular users of the production deployed application
should not have access to this much information about the environment configuration.
</p>
<sample src="examples/CleartextLogging.py" />
<p> In the second example the data that is logged is not sensitive.</p>
<sample src="examples/CleartextLoggingGood.py" />
</example>
<references>
<li>OWASP: <a href="https://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures/">Insertion of Sensitive Information into Log File</a>.</li>
</references>
</qhelp>

View File

@@ -15,12 +15,13 @@
import python
private import semmle.python.dataflow.new.DataFlow
import DataFlow::PathGraph
import CleartextLoggingFlow::PathGraph
import semmle.python.security.dataflow.CleartextLoggingQuery
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, string classification
from
CleartextLoggingFlow::PathNode source, CleartextLoggingFlow::PathNode sink, string classification
where
config.hasFlowPath(source, sink) and
CleartextLoggingFlow::flowPath(source, sink) and
classification = source.getNode().(Source).getClassification()
select sink.getNode(), source, sink, "This expression logs $@ as clear text.", source.getNode(),
"sensitive data (" + classification + ")"

View File

@@ -15,12 +15,13 @@
import python
private import semmle.python.dataflow.new.DataFlow
import DataFlow::PathGraph
import CleartextStorageFlow::PathGraph
import semmle.python.security.dataflow.CleartextStorageQuery
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, string classification
from
CleartextStorageFlow::PathNode source, CleartextStorageFlow::PathNode sink, string classification
where
config.hasFlowPath(source, sink) and
CleartextStorageFlow::flowPath(source, sink) and
classification = source.getNode().(Source).getClassification()
select sink.getNode(), source, sink, "This expression stores $@ as clear text.", source.getNode(),
"sensitive data (" + classification + ")"

View File

@@ -0,0 +1,3 @@
# BAD: Logging cleartext sensitive data
import os
print(f"[INFO] Environment: {os.environ}")

View File

@@ -0,0 +1,3 @@
not_sensitive_data = {'a': 1, 'b': 2}
# GOOD: it is fine to log data that is not sensitive
print(f"[INFO] Some object contains: {not_sensitive_data}")

View File

@@ -13,18 +13,15 @@
import python
import semmle.python.Concepts
from
Cryptography::CryptographicOperation operation, Cryptography::CryptographicAlgorithm algorithm,
string msgPrefix
from Cryptography::CryptographicOperation operation, string msgPrefix
where
algorithm = operation.getAlgorithm() and
// `Cryptography::HashingAlgorithm` and `Cryptography::PasswordHashingAlgorithm` are
// handled by `py/weak-sensitive-data-hashing`
algorithm instanceof Cryptography::EncryptionAlgorithm and
(
exists(Cryptography::EncryptionAlgorithm algorithm | algorithm = operation.getAlgorithm() |
algorithm.isWeak() and
msgPrefix = "The cryptographic algorithm " + operation.getAlgorithm().getName()
msgPrefix = "The cryptographic algorithm " + algorithm.getName()
)
or
operation.getBlockMode().isWeak() and msgPrefix = "The block mode " + operation.getBlockMode()
select operation, msgPrefix + " is broken or weak, and should not be used."
select operation, "$@ is broken or weak, and should not be used.", operation.getInitialization(),
msgPrefix

View File

@@ -16,33 +16,29 @@ import python
import semmle.python.security.dataflow.WeakSensitiveDataHashingQuery
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import DataFlow::PathGraph
import WeakSensitiveDataHashingFlow::PathGraph
from
DataFlow::PathNode source, DataFlow::PathNode sink, string ending, string algorithmName,
string classification
WeakSensitiveDataHashingFlow::PathNode source, WeakSensitiveDataHashingFlow::PathNode sink,
string ending, string algorithmName, string classification
where
exists(NormalHashFunction::Configuration config |
config.hasFlowPath(source, sink) and
algorithmName = sink.getNode().(NormalHashFunction::Sink).getAlgorithmName() and
classification = source.getNode().(NormalHashFunction::Source).getClassification() and
ending = "."
)
normalHashFunctionFlowPath(source, sink) and
algorithmName = sink.getNode().(NormalHashFunction::Sink).getAlgorithmName() and
classification = source.getNode().(NormalHashFunction::Source).getClassification() and
ending = "."
or
exists(ComputationallyExpensiveHashFunction::Configuration config |
config.hasFlowPath(source, sink) and
algorithmName = sink.getNode().(ComputationallyExpensiveHashFunction::Sink).getAlgorithmName() and
classification =
source.getNode().(ComputationallyExpensiveHashFunction::Source).getClassification() and
(
sink.getNode().(ComputationallyExpensiveHashFunction::Sink).isComputationallyExpensive() and
ending = "."
or
not sink.getNode().(ComputationallyExpensiveHashFunction::Sink).isComputationallyExpensive() and
ending =
" for " + classification +
" hashing, since it is not a computationally expensive hash function."
)
computationallyExpensiveHashFunctionFlowPath(source, sink) and
algorithmName = sink.getNode().(ComputationallyExpensiveHashFunction::Sink).getAlgorithmName() and
classification =
source.getNode().(ComputationallyExpensiveHashFunction::Source).getClassification() and
(
sink.getNode().(ComputationallyExpensiveHashFunction::Sink).isComputationallyExpensive() and
ending = "."
or
not sink.getNode().(ComputationallyExpensiveHashFunction::Sink).isComputationallyExpensive() and
ending =
" for " + classification +
" hashing, since it is not a computationally expensive hash function."
)
select sink.getNode(), source, sink,
"$@ is used in a hashing algorithm (" + algorithmName + ") that is insecure" + ending,

View File

@@ -14,9 +14,9 @@
import python
import semmle.python.security.dataflow.UnsafeDeserializationQuery
import DataFlow::PathGraph
import UnsafeDeserializationFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from UnsafeDeserializationFlow::PathNode source, UnsafeDeserializationFlow::PathNode sink
where UnsafeDeserializationFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Unsafe deserialization depends on a $@.", source.getNode(),
"user-provided value"

View File

@@ -14,9 +14,9 @@
import python
import semmle.python.security.dataflow.UrlRedirectQuery
import DataFlow::PathGraph
import UrlRedirectFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from UrlRedirectFlow::PathNode source, UrlRedirectFlow::PathNode sink
where UrlRedirectFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Untrusted URL redirection depends on a $@.", source.getNode(),
"user-provided value"

View File

@@ -14,10 +14,10 @@
import python
import semmle.python.security.dataflow.XxeQuery
import DataFlow::PathGraph
import XxeFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
from XxeFlow::PathNode source, XxeFlow::PathNode sink
where XxeFlow::flowPath(source, sink)
select sink.getNode(), source, sink,
"XML parsing depends on a $@ without guarding against external entity expansion.",
source.getNode(), "user-provided value"

View File

@@ -13,9 +13,9 @@
import python
import semmle.python.security.dataflow.XpathInjectionQuery
import DataFlow::PathGraph
import XpathInjectionFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from XpathInjectionFlow::PathNode source, XpathInjectionFlow::PathNode sink
where XpathInjectionFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "XPath expression depends on a $@.", source.getNode(),
"user-provided value"

View File

@@ -15,13 +15,13 @@
import python
import semmle.python.security.dataflow.PolynomialReDoSQuery
import DataFlow::PathGraph
import PolynomialReDoSFlow::PathGraph
from
Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode,
PolynomialReDoSFlow::PathNode source, PolynomialReDoSFlow::PathNode sink, Sink sinkNode,
PolynomialBackTrackingTerm regexp
where
config.hasFlowPath(source, sink) and
PolynomialReDoSFlow::flowPath(source, sink) and
sinkNode = sink.getNode() and
regexp.getRootTerm() = sinkNode.getRegExp()
// not (

View File

@@ -10,7 +10,7 @@
<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>.
<a href="https://arxiv.org/abs/1301.0849">Static Analysis for Regular Expression Denial-of-Service Attack</a>.
</li>
</references>
</qhelp>

View File

@@ -16,13 +16,13 @@
import python
private import semmle.python.Concepts
import semmle.python.security.dataflow.RegexInjectionQuery
import DataFlow::PathGraph
import RegexInjectionFlow::PathGraph
from
Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
RegexInjectionFlow::PathNode source, RegexInjectionFlow::PathNode sink,
RegexExecution regexExecution
where
config.hasFlowPath(source, sink) and
RegexInjectionFlow::flowPath(source, sink) and
regexExecution = sink.getNode().(Sink).getRegexExecution()
select sink.getNode(), source, sink,
"This regular expression depends on a $@ and is executed by $@.", source.getNode(),

View File

@@ -14,10 +14,10 @@
import python
import semmle.python.security.dataflow.XmlBombQuery
import DataFlow::PathGraph
import XmlBombFlow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
from XmlBombFlow::PathNode source, XmlBombFlow::PathNode sink
where XmlBombFlow::flowPath(source, sink)
select sink.getNode(), source, sink,
"XML parsing depends on a $@ without guarding against uncontrolled entity expansion.",
source.getNode(), "user-provided value"

View File

@@ -13,14 +13,10 @@
*/
import python
import semmle.python.security.Paths
import semmle.python.dataflow.TaintTracking
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import semmle.python.filters.Tests
class HardcodedValue extends TaintKind {
HardcodedValue() { this = "hard coded value" }
}
bindingset[char, fraction]
predicate fewer_characters_than(StrConst str, string char, float fraction) {
exists(string text, int chars |
@@ -78,31 +74,27 @@ predicate maybeCredential(ControlFlowNode f) {
)
}
class HardcodedValueSource extends TaintSource {
HardcodedValueSource() { maybeCredential(this) }
override predicate isSourceOf(TaintKind kind) { kind instanceof HardcodedValue }
class HardcodedValueSource extends DataFlow::Node {
HardcodedValueSource() { maybeCredential(this.asCfgNode()) }
}
class CredentialSink extends TaintSink {
class CredentialSink extends DataFlow::Node {
CredentialSink() {
exists(string name |
name.regexpMatch(getACredentialRegex()) and
not name.matches("%file")
|
any(FunctionValue func).getNamedArgumentForCall(_, name) = this
any(FunctionValue func).getNamedArgumentForCall(_, name) = this.asCfgNode()
or
exists(Keyword k | k.getArg() = name and k.getValue().getAFlowNode() = this)
exists(Keyword k | k.getArg() = name and k.getValue().getAFlowNode() = this.asCfgNode())
or
exists(CompareNode cmp, NameNode n | n.getId() = name |
cmp.operands(this, any(Eq eq), n)
cmp.operands(this.asCfgNode(), any(Eq eq), n)
or
cmp.operands(n, any(Eq eq), this)
cmp.operands(n, any(Eq eq), this.asCfgNode())
)
)
}
override predicate sinks(TaintKind kind) { kind instanceof HardcodedValue }
}
/**
@@ -115,19 +107,19 @@ private string getACredentialRegex() {
result = "(?i).*(cert)(?!.*(format|name)).*"
}
class HardcodedCredentialsConfiguration extends TaintTracking::Configuration {
HardcodedCredentialsConfiguration() { this = "Hardcoded credentials configuration" }
private module HardcodedCredentialsConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof HardcodedValueSource }
override predicate isSource(TaintTracking::Source source) {
source instanceof HardcodedValueSource
}
override predicate isSink(TaintTracking::Sink sink) { sink instanceof CredentialSink }
predicate isSink(DataFlow::Node sink) { sink instanceof CredentialSink }
}
from HardcodedCredentialsConfiguration config, TaintedPathSource src, TaintedPathSink sink
module HardcodedCredentialsFlow = TaintTracking::Global<HardcodedCredentialsConfig>;
import HardcodedCredentialsFlow::PathGraph
from HardcodedCredentialsFlow::PathNode src, HardcodedCredentialsFlow::PathNode sink
where
config.hasFlowPath(src, sink) and
not any(TestScope test).contains(src.getAstNode())
select src.getSource(), src, sink, "This hardcoded value is $@.", sink.getNode(),
HardcodedCredentialsFlow::flowPath(src, sink) and
not any(TestScope test).contains(src.getNode().asCfgNode().getNode())
select src.getNode(), src, sink, "This hardcoded value is $@.", sink.getNode(),
"used as credentials"

View File

@@ -12,14 +12,14 @@
import python
import semmle.python.security.dataflow.ServerSideRequestForgeryQuery
import DataFlow::PathGraph
import FullServerSideRequestForgeryFlow::PathGraph
from
FullServerSideRequestForgeryConfiguration fullConfig, DataFlow::PathNode source,
DataFlow::PathNode sink, Http::Client::Request request
FullServerSideRequestForgeryFlow::PathNode source,
FullServerSideRequestForgeryFlow::PathNode sink, Http::Client::Request request
where
request = sink.getNode().(Sink).getRequest() and
fullConfig.hasFlowPath(source, sink) and
FullServerSideRequestForgeryFlow::flowPath(source, sink) and
fullyControlledRequest(request)
select request, source, sink, "The full URL of this request depends on a $@.", source.getNode(),
"user-provided value"

View File

@@ -12,14 +12,14 @@
import python
import semmle.python.security.dataflow.ServerSideRequestForgeryQuery
import DataFlow::PathGraph
import PartialServerSideRequestForgeryFlow::PathGraph
from
PartialServerSideRequestForgeryConfiguration partialConfig, DataFlow::PathNode source,
DataFlow::PathNode sink, Http::Client::Request request
PartialServerSideRequestForgeryFlow::PathNode source,
PartialServerSideRequestForgeryFlow::PathNode sink, Http::Client::Request request
where
request = sink.getNode().(Sink).getRequest() and
partialConfig.hasFlowPath(source, sink) and
PartialServerSideRequestForgeryFlow::flowPath(source, sink) and
not fullyControlledRequest(request)
select request, source, sink, "Part of the URL of this request depends on a $@.", source.getNode(),
"user-provided value"

View File

@@ -8,8 +8,8 @@ def full_ssrf():
target = request.args["target"]
# BAD: user has full control of URL
resp = request.get("https://" + target + ".example.com/data/")
resp = requests.get("https://" + target + ".example.com/data/")
# GOOD: `subdomain` is controlled by the server.
subdomain = "europe" if target == "EU" else "world"
resp = request.get("https://" + subdomain + ".example.com/data/")
resp = requests.get("https://" + subdomain + ".example.com/data/")

View File

@@ -4,17 +4,17 @@
* malicious NoSQL code by the user.
* @kind path-problem
* @problem.severity error
* @security-severity 8.8
* @id py/nosql-injection
* @tags security
* experimental
* external/cwe/cwe-943
*/
import python
import experimental.semmle.python.security.injection.NoSQLInjection
import DataFlow::PathGraph
import semmle.python.security.dataflow.NoSqlInjectionQuery
import NoSqlInjectionFlow::PathGraph
from NoSqlInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink, source, sink, "This NoSQL query contains an unsanitized $@.", source,
from NoSqlInjectionFlow::PathNode source, NoSqlInjectionFlow::PathNode sink
where NoSqlInjectionFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This NoSQL query contains an unsanitized $@.", source,
"user-provided value"

View File

@@ -5,6 +5,7 @@
* database. This query counts the lines of code, excluding whitespace or comments.
* @kind metric
* @tags summary
* telemetry
* @id py/summary/lines-of-code
*/

View File

@@ -0,0 +1 @@
semmle-extractor-options: --lang=3

View File

@@ -1,4 +1,5 @@
---
category: fix
---
* The display name (`@name`) of the `py/unsafe-deserialization` query has been updated in favor of consistency with other languages.
## 0.7.3
### Bug Fixes
* The display name (`@name`) of the `py/unsafe-deserialization` query has been updated in favor of consistency with other languages.

View File

@@ -0,0 +1,3 @@
## 0.7.4
No user-facing changes.

View File

@@ -0,0 +1,5 @@
## 0.8.0
### Bug Fixes
* The query "Arbitrary file write during archive extraction ("Zip Slip")" (`py/zipslip`) has been renamed to "Arbitrary file access during archive extraction ("Zip Slip")."

View File

@@ -0,0 +1,5 @@
## 0.8.1
### Minor Analysis Improvements
* Fixed modeling of `aiohttp.ClientSession` so we properly handle `async with` uses. This can impact results of server-side request forgery queries (`py/full-ssrf`, `py/partial-ssrf`).

View File

@@ -0,0 +1,3 @@
## 0.8.2
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.8.3
No user-facing changes.

View File

@@ -0,0 +1,6 @@
## 0.8.4
### Minor Analysis Improvements
* Improved _Reflected server-side cross-site scripting_ (`py/reflective-xss`) query to not alert on data passed to `flask.jsonify`. Since these HTTP responses are returned with mime-type `application/json`, they do not pose a security risk for XSS.
* Updated path explanations for `@kind path-problem` queries to always include left hand side of assignments, making paths easier to understand.

View File

@@ -0,0 +1,3 @@
## 0.8.5
No user-facing changes.

View File

@@ -0,0 +1,10 @@
## 0.9.0
### New Queries
* The query `py/nosql-injection` for finding NoSQL injection vulnerabilities is now available in the default security suite.
### Minor Analysis Improvements
* Improved _URL redirection from remote source_ (`py/url-redirection`) query to not alert when URL has been checked with `django.utils.http. url_has_allowed_host_and_scheme`.
* Extended the `py/command-line-injection` query with sinks from Python's `asyncio` module.

View File

@@ -0,0 +1,3 @@
## 0.9.1
No user-facing changes.

View File

@@ -0,0 +1,3 @@
## 0.9.2
No user-facing changes.

View File

@@ -0,0 +1,5 @@
## 0.9.3
### Minor Analysis Improvements
* Added modeling of more `FileSystemAccess` in packages `cherrypy`, `aiofile`, `aiofiles`, `anyio`, `sanic`, `starlette`, `baize`, and `io`. This will mainly affect the _Uncontrolled data used in path expression_ (`py/path-injection`) query.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.7.2
lastReleaseVersion: 0.9.3

View File

@@ -4,16 +4,15 @@
<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>Extracting files from a malicious zip file, or similar type of archive,
is at risk of directory traversal attacks if filenames from the archive are
not properly validated.</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
file paths are used to create a filesystem path, then a file operation may happen in 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>

View File

@@ -1,8 +1,8 @@
/**
* @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.
* @name Arbitrary file access during archive extraction ("Zip Slip")
* @description Extracting files from a malicious ZIP file, or similar type of archive, without
* validating that the destination file path is within the destination directory
* can allow an attacker to unexpectedly gain access to resources.
* @kind path-problem
* @id py/zipslip
* @problem.severity error
@@ -15,10 +15,10 @@
import python
import experimental.semmle.python.security.ZipSlip
import DataFlow::PathGraph
import ZipSlipFlow::PathGraph
from ZipSlipConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from ZipSlipFlow::PathNode source, ZipSlipFlow::PathNode sink
where ZipSlipFlow::flowPath(source, sink)
select source.getNode(), source, sink,
"This unsanitized archive entry, which may contain '..', is used in a $@.", sink.getNode(),
"file system operation"

View File

@@ -16,7 +16,7 @@
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import DataFlow::PathGraph
import TarSlipImprovFlow::PathGraph
import semmle.python.ApiGraphs
import semmle.python.dataflow.new.internal.Attributes
import semmle.python.dataflow.new.BarrierGuards
@@ -54,12 +54,10 @@ class AllTarfileOpens extends API::CallNode {
/**
* A taint-tracking configuration for detecting more "TarSlip" vulnerabilities.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "TarSlip" }
private module TarSlipImprovConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source = tarfileOpen().getACall() }
override predicate isSource(DataFlow::Node source) { source = tarfileOpen().getACall() }
override predicate isSink(DataFlow::Node sink) {
predicate isSink(DataFlow::Node sink) {
(
// A sink capturing method calls to `extractall` without `members` argument.
// For a call to `file.extractall` without `members` argument, `file` is considered a sink.
@@ -100,7 +98,7 @@ class Configuration extends TaintTracking::Configuration {
not sink.getScope().getLocation().getFile().inStdlib()
}
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
nodeTo.(MethodCallNode).calls(nodeFrom, "getmembers") and
nodeFrom instanceof AllTarfileOpens
or
@@ -113,7 +111,10 @@ class Configuration extends TaintTracking::Configuration {
}
}
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
/** Global taint-tracking for detecting more "TarSlip" vulnerabilities. */
module TarSlipImprovFlow = TaintTracking::Global<TarSlipImprovConfig>;
from TarSlipImprovFlow::PathNode source, TarSlipImprovFlow::PathNode sink
where TarSlipImprovFlow::flowPath(source, sink)
select sink, source, sink, "Extraction of tarfile from $@ to a potentially untrusted source $@.",
source.getNode(), source.getNode().toString(), sink.getNode(), sink.getNode().toString()

View File

@@ -16,9 +16,9 @@
import python
import experimental.Security.UnsafeUnpackQuery
import DataFlow::PathGraph
import UnsafeUnpackFlow::PathGraph
from UnsafeUnpackingConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from UnsafeUnpackFlow::PathNode source, UnsafeUnpackFlow::PathNode sink
where UnsafeUnpackFlow::flowPath(source, sink)
select sink.getNode(), source, sink,
"Unsafe extraction from a malicious tarball retrieved from a remote location."

View File

@@ -0,0 +1,159 @@
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.ApiGraphs
/**
* A data-flow node that constructs a template.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `TemplateConstruction::Range` instead.
*/
class TemplateConstruction extends DataFlow::Node instanceof TemplateConstruction::Range {
/** Gets the argument that specifies the template source. */
DataFlow::Node getSourceArg() { result = super.getSourceArg() }
}
/** Provides a class for modeling new system-command execution APIs. */
module TemplateConstruction {
/**
* A data-flow node that constructs a template.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `TemplateConstruction` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument that specifies the template source. */
abstract DataFlow::Node getSourceArg();
}
}
// -----------------------------------------------------------------------------
/** A call to `airspeed.Template`. */
class AirspeedTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
AirspeedTemplateConstruction() {
this = API::moduleImport("airspeed").getMember("Template").getACall()
}
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
}
/** A call to `bottle.SimpleTemplate`. */
class BottleSimpleTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
BottleSimpleTemplateConstruction() {
this = API::moduleImport("bottle").getMember("SimpleTemplate").getACall()
}
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
}
/** A call to `bottle.template`. */
class BottleTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
BottleTemplateConstruction() {
this = API::moduleImport("bottle").getMember("template").getACall()
}
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
}
/** A call to `chameleon.PageTemplate`. */
class ChameleonTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
ChameleonTemplateConstruction() {
this = API::moduleImport("chameleon").getMember("PageTemplate").getACall()
}
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
}
/** A call to `Cheetah.Template.Template`. */
class CheetahTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
CheetahTemplateConstruction() {
this =
API::moduleImport("Cheetah")
.getMember("Template")
.getMember("Template")
.getASubclass*()
.getACall()
}
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
}
/** A call to `chevron.render`. */
class ChevronRenderConstruction extends TemplateConstruction::Range, API::CallNode {
ChevronRenderConstruction() { this = API::moduleImport("chevron").getMember("render").getACall() }
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
}
/** A call to `django.template.Template` */
class DjangoTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
DjangoTemplateConstruction() {
this = API::moduleImport("django").getMember("template").getMember("Template").getACall()
}
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
}
// TODO: support django.template.engines["django"]].from_string
/** A call to `flask.render_template_string`. */
class FlaskTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
FlaskTemplateConstruction() {
this = API::moduleImport("flask").getMember("render_template_string").getACall()
}
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
}
/** A call to `genshi.template.TextTemplate`. */
class GenshiTextTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
GenshiTextTemplateConstruction() {
this = API::moduleImport("genshi").getMember("template").getMember("TextTemplate").getACall()
}
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
}
/** A call to `genshi.template.MarkupTemplate` */
class GenshiMarkupTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
GenshiMarkupTemplateConstruction() {
this = API::moduleImport("genshi").getMember("template").getMember("MarkupTemplate").getACall()
}
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
}
/** A call to `jinja2.Template`. */
class Jinja2TemplateConstruction extends TemplateConstruction::Range, API::CallNode {
Jinja2TemplateConstruction() {
this = API::moduleImport("jinja2").getMember("Template").getACall()
}
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
}
/** A call to `jinja2.from_string`. */
class Jinja2FromStringConstruction extends TemplateConstruction::Range, API::CallNode {
Jinja2FromStringConstruction() {
this = API::moduleImport("jinja2").getMember("from_string").getACall()
}
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
}
/** A call to `mako.template.Template`. */
class MakoTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
MakoTemplateConstruction() {
this = API::moduleImport("mako").getMember("template").getMember("Template").getACall()
}
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
}
/** A call to `trender.TRender`. */
class TRenderTemplateConstruction extends TemplateConstruction::Range, API::CallNode {
TRenderTemplateConstruction() {
this = API::moduleImport("trender").getMember("TRender").getACall()
}
override DataFlow::Node getSourceArg() { result = this.getArg(0) }
}

View File

@@ -11,25 +11,10 @@
*/
import python
import semmle.python.security.Paths
/* Sources */
import semmle.python.web.HttpRequest
/* Sinks */
import experimental.semmle.python.templates.Ssti
/* Flow */
import semmle.python.security.strings.Untrusted
import TemplateInjectionQuery
import TemplateInjectionFlow::PathGraph
class TemplateInjectionConfiguration extends TaintTracking::Configuration {
TemplateInjectionConfiguration() { this = "Template injection configuration" }
deprecated override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
deprecated override predicate isSink(TaintTracking::Sink sink) { sink instanceof SSTISink }
}
from TemplateInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "This Template depends on $@.", src.getSource(),
"a user-provided value"
from TemplateInjectionFlow::PathNode source, TemplateInjectionFlow::PathNode sink
where TemplateInjectionFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This Template depends on $@.", source.getNode(),
"user-provided value"

View File

@@ -0,0 +1,51 @@
/**
* Provides default sources, sinks and sanitizers for detecting
* "template injection"
* vulnerabilities, as well as extension points for adding your own.
*/
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.Concepts
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.BarrierGuards
private import TemplateConstructionConcept
/**
* Provides default sources, sinks and sanitizers for detecting
* "template injection"
* vulnerabilities, as well as extension points for adding your own.
*/
module TemplateInjection {
/**
* A data flow source for "template injection" vulnerabilities.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for "template injection" vulnerabilities.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for "template injection" vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }
/**
* A source of remote user input, considered as a flow source.
*/
class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
/**
* A SQL statement of a SQL construction, considered as a flow sink.
*/
class TemplateConstructionAsSink extends Sink {
TemplateConstructionAsSink() { this = any(TemplateConstruction c).getSourceArg() }
}
/**
* A comparison with a constant string, considered as a sanitizer-guard.
*/
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
}

View File

@@ -0,0 +1,18 @@
/**
* Provides a taint-tracking configuration for detecting "template injection" vulnerabilities.
*/
private import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import TemplateInjectionCustomizations::TemplateInjection
module TemplateInjectionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) { node instanceof Source }
predicate isSink(DataFlow::Node node) { node instanceof Sink }
predicate isBarrierIn(DataFlow::Node node) { node instanceof Sanitizer }
}
module TemplateInjectionFlow = TaintTracking::Global<TemplateInjectionConfig>;

View File

@@ -16,16 +16,13 @@ import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import semmle.python.dataflow.new.RemoteFlowSources
import semmle.python.ApiGraphs
import DataFlow::PathGraph
private API::Node paramikoClient() {
result = API::moduleImport("paramiko").getMember("SSHClient").getReturn()
}
class ParamikoCmdInjectionConfiguration extends TaintTracking::Configuration {
ParamikoCmdInjectionConfiguration() { this = "ParamikoCMDInjectionConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
private module ParamikoConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
/**
* exec_command of `paramiko.SSHClient` class execute command on ssh target server
@@ -33,7 +30,7 @@ class ParamikoCmdInjectionConfiguration extends TaintTracking::Configuration {
* and it run CMD on current system that running the ssh command
* the Sink related to proxy command is the `connect` method of `paramiko.SSHClient` class
*/
override predicate isSink(DataFlow::Node sink) {
predicate isSink(DataFlow::Node sink) {
sink = paramikoClient().getMember("exec_command").getACall().getParameter(0, "command").asSink()
or
sink = paramikoClient().getMember("connect").getACall().getParameter(11, "sock").asSink()
@@ -42,7 +39,7 @@ class ParamikoCmdInjectionConfiguration extends TaintTracking::Configuration {
/**
* this additional taint step help taint tracking to find the vulnerable `connect` method of `paramiko.SSHClient` class
*/
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
exists(API::CallNode call |
call = API::moduleImport("paramiko").getMember("ProxyCommand").getACall() and
nodeFrom = call.getParameter(0, "command_line").asSink() and
@@ -51,7 +48,12 @@ class ParamikoCmdInjectionConfiguration extends TaintTracking::Configuration {
}
}
from ParamikoCmdInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
/** Global taint-tracking for detecting "paramiko command injection" vulnerabilities. */
module ParamikoFlow = TaintTracking::Global<ParamikoConfig>;
import ParamikoFlow::PathGraph
from ParamikoFlow::PathNode source, ParamikoFlow::PathNode sink
where ParamikoFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This code execution depends on a $@.", source.getNode(),
"a user-provided value"

View File

@@ -15,10 +15,10 @@
// determine precision above
import python
import experimental.semmle.python.security.dataflow.ReflectedXSS
import DataFlow::PathGraph
import experimental.semmle.python.security.dataflow.EmailXss
import EmailXssFlow::PathGraph
from ReflectedXssConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from EmailXssFlow::PathNode source, EmailXssFlow::PathNode sink
where EmailXssFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.",
source.getNode(), "a user-provided value"

View File

@@ -1,36 +0,0 @@
/**
* @name XSLT query built from user-controlled sources
* @description Building a XSLT query from user-controlled sources is vulnerable to insertion of
* malicious XSLT code by the user.
* @kind path-problem
* @problem.severity error
* @precision high
* @id py/xslt-injection
* @tags security
* experimental
* external/cwe/cwe-643
*/
import python
import semmle.python.security.Paths
/* Sources */
import semmle.python.web.HttpRequest
/* Sinks */
import experimental.semmle.python.security.injection.XSLT
class XsltInjectionConfiguration extends TaintTracking::Configuration {
XsltInjectionConfiguration() { this = "XSLT injection configuration" }
deprecated override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
deprecated override predicate isSink(TaintTracking::Sink sink) {
sink instanceof XSLTInjection::XSLTInjectionSink
}
}
from XsltInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "This XSLT query depends on $@.", src.getSource(),
"a user-provided value"

View File

@@ -0,0 +1,110 @@
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.ApiGraphs
/**
* A data-flow node that constructs a XSLT transformer.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `TemplateConstruction::Range` instead.
*/
class XsltConstruction extends DataFlow::Node instanceof XsltConstruction::Range {
/** Gets the argument that specifies the XSLT transformer. */
DataFlow::Node getXsltArg() { result = super.getXsltArg() }
}
/** Provides a class for modeling new system-command execution APIs. */
module XsltConstruction {
/**
* A data-flow node that constructs a XSLT transformer.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `XsltConstruction` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument that specifies the XSLT transformer. */
abstract DataFlow::Node getXsltArg();
}
}
/**
* A data-flow node that executes a XSLT transformer.
*
* Extend this class to refine existing API models. If you want to model new APIs,
* extend `TemplateConstruction::Range` instead.
*/
class XsltExecution extends DataFlow::Node instanceof XsltExecution::Range {
/** Gets the argument that specifies the XSLT transformer. */
DataFlow::Node getXsltArg() { result = super.getXsltArg() }
}
/** Provides a class for modeling new system-command execution APIs. */
module XsltExecution {
/**
* A data-flow node that executes a XSLT transformer.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `XsltExecution` instead.
*/
abstract class Range extends DataFlow::Node {
/** Gets the argument that specifies the XSLT transformer. */
abstract DataFlow::Node getXsltArg();
}
}
// -----------------------------------------------------------------------------
/**
* A call to `lxml.etree.XSLT`.
*
* ```py
* from lxml import etree
* xslt_tree = etree.parse(...)
* doc = etree.parse(...)
* transform = etree.XSLT(xslt_tree)
* result = transform(doc)
* ```
*/
class LxmlEtreeXsltCall extends XsltConstruction::Range, API::CallNode {
LxmlEtreeXsltCall() {
this = API::moduleImport("lxml").getMember("etree").getMember("XSLT").getACall()
}
override DataFlow::Node getXsltArg() { result = this.getParameter(0, "xslt_input").asSink() }
}
/**
* A call to `.xslt` on an lxml ElementTree object.
*
* ```py
* from lxml import etree
* xslt_tree = etree.parse(...)
* doc = etree.parse(...)
* result = doc.xslt(xslt_tree)
* ```
*/
class XsltAttributeCall extends XsltExecution::Range, API::CallNode {
XsltAttributeCall() { this = elementTreeConstruction(_).getReturn().getMember("xslt").getACall() }
override DataFlow::Node getXsltArg() { result = this.getParameter(0, "_xslt").asSink() }
}
// -----------------------------------------------------------------------------
API::CallNode elementTreeConstruction(DataFlow::Node inputArg) {
// TODO: If we could, would be nice to model this as flow-summaries. But I'm not sure if we actually can :thinking:
// see https://lxml.de/api/lxml.etree-module.html#fromstring
result = API::moduleImport("lxml").getMember("etree").getMember("fromstring").getACall() and
inputArg = result.getParameter(0, "text").asSink()
or
// see https://lxml.de/api/lxml.etree-module.html#fromstringlist
result = API::moduleImport("lxml").getMember("etree").getMember("fromstringlist").getACall() and
inputArg = result.getParameter(0, "strings").asSink()
or
// TODO: technically we should treat parse differently, since it takes a file as argument
// see https://lxml.de/api/lxml.etree-module.html#parse
result = API::moduleImport("lxml").getMember("etree").getMember("parse").getACall() and
inputArg = result.getParameter(0, "source").asSink()
or
// see https://lxml.de/api/lxml.etree-module.html#XML
result = API::moduleImport("lxml").getMember("etree").getMember("XML").getACall() and
inputArg = result.getParameter(0, "text").asSink()
}

View File

@@ -0,0 +1,21 @@
/**
* @name XSLT query built from user-controlled sources
* @description Building a XSLT query from user-controlled sources is vulnerable to insertion of
* malicious XSLT code by the user.
* @kind path-problem
* @problem.severity error
* @precision high
* @id py/xslt-injection
* @tags security
* experimental
* external/cwe/cwe-643
*/
import python
import XsltInjectionQuery
import XsltInjectionFlow::PathGraph
from XsltInjectionFlow::PathNode source, XsltInjectionFlow::PathNode sink
where XsltInjectionFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This XSLT query depends on $@.", source.getNode(),
"user-provided value"

View File

@@ -0,0 +1,58 @@
/**
* Provides default sources, sinks and sanitizers for detecting
* "XSLT injection"
* vulnerabilities, as well as extension points for adding your own.
*/
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.Concepts
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.BarrierGuards
private import XsltConcept
/**
* Provides default sources, sinks and sanitizers for detecting
* "XSLT injection"
* vulnerabilities, as well as extension points for adding your own.
*/
module XsltInjection {
/**
* A data flow source for "XSLT injection" vulnerabilities.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for "XSLT injection" vulnerabilities.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for "XSLT injection" vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }
/**
* A source of remote user input, considered as a flow source.
*/
class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
/**
* An XSLT construction, considered as a flow sink.
*/
class XsltConstructionAsSink extends Sink {
XsltConstructionAsSink() { this = any(XsltConstruction c).getXsltArg() }
}
/**
* An XSLT execution, considered as a flow sink.
*/
class XsltExecutionAsSink extends Sink {
XsltExecutionAsSink() { this = any(XsltExecution c).getXsltArg() }
}
/**
* A comparison with a constant string, considered as a sanitizer-guard.
*/
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
}

View File

@@ -0,0 +1,24 @@
/**
* Provides a taint-tracking configuration for detecting "XSLT injection" vulnerabilities.
*/
private import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import XsltInjectionCustomizations::XsltInjection
import XsltConcept
module XsltInjectionConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) { node instanceof Source }
predicate isSink(DataFlow::Node node) { node instanceof Sink }
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// I considered using a FlowState of (raw-string, ElementTree), but in all honesty
// valid code would never have direct flow from a string to a sink anyway... so I
// opted for the more simple approach.
nodeTo = elementTreeConstruction(nodeFrom)
}
}
module XsltInjectionFlow = TaintTracking::Global<XsltInjectionConfig>;

View File

@@ -14,9 +14,9 @@
// determine precision above
import python
import experimental.semmle.python.security.injection.HTTPHeaders
import DataFlow::PathGraph
import HeaderInjectionFlow::PathGraph
from HeaderInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from HeaderInjectionFlow::PathNode source, HeaderInjectionFlow::PathNode sink
where HeaderInjectionFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This HTTP header is constructed from a $@.", source.getNode(),
"user-provided value"

View File

@@ -11,11 +11,11 @@
*/
import python
import DataFlow::PathGraph
import CsvInjectionFlow::PathGraph
import semmle.python.dataflow.new.DataFlow
import experimental.semmle.python.security.injection.CsvInjection
from CsvInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from CsvInjectionFlow::PathNode source, CsvInjectionFlow::PathNode sink
where CsvInjectionFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Csv injection might include code from $@.", source.getNode(),
"this user input"

View File

@@ -14,10 +14,10 @@
import python
import UnicodeBypassValidationQuery
import DataFlow::PathGraph
import UnicodeBypassValidationFlow::PathGraph
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from UnicodeBypassValidationFlow::PathNode source, UnicodeBypassValidationFlow::PathNode sink
where UnicodeBypassValidationFlow::flowPath(source, sink)
select sink.getNode(), source, sink,
"This $@ processes unsafely $@ and any logical validation in-between could be bypassed using special Unicode characters.",
sink.getNode(), "Unicode transformation (Unicode normalization)", source.getNode(),

View File

@@ -3,6 +3,7 @@
*/
private import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.ApiGraphs
import semmle.python.Concepts
import semmle.python.dataflow.new.internal.DataFlowPublic
@@ -27,16 +28,15 @@ class PostValidation extends DataFlow::FlowState {
* This configuration uses two flow states, `PreValidation` and `PostValidation`,
* to track the requirement that a logical validation has been performed before the Unicode Transformation.
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "UnicodeBypassValidation" }
private module UnicodeBypassValidationConfig implements DataFlow::StateConfigSig {
class FlowState = DataFlow::FlowState;
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
predicate isSource(DataFlow::Node source, FlowState state) {
source instanceof RemoteFlowSource and state instanceof PreValidation
}
override predicate isAdditionalTaintStep(
DataFlow::Node nodeFrom, DataFlow::FlowState stateFrom, DataFlow::Node nodeTo,
DataFlow::FlowState stateTo
predicate isAdditionalFlowStep(
DataFlow::Node nodeFrom, FlowState stateFrom, DataFlow::Node nodeTo, FlowState stateTo
) {
(
exists(Escaping escaping | nodeFrom = escaping.getAnInput() and nodeTo = escaping.getOutput())
@@ -51,7 +51,7 @@ class Configuration extends TaintTracking::Configuration {
}
/* A Unicode Tranformation (Unicode tranformation) is considered a sink when the algorithm used is either NFC or NFKC. */
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
predicate isSink(DataFlow::Node sink, FlowState state) {
exists(API::CallNode cn |
cn = API::moduleImport("unicodedata").getMember("normalize").getACall() and
sink = cn.getArg(1)
@@ -71,3 +71,6 @@ class Configuration extends TaintTracking::Configuration {
state instanceof PostValidation
}
}
/** Global taint-tracking for detecting "Unicode transformation mishandling" vulnerabilities. */
module UnicodeBypassValidationFlow = TaintTracking::GlobalWithState<UnicodeBypassValidationConfig>;

View File

@@ -17,21 +17,25 @@ import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import experimental.semmle.python.security.TimingAttack
import DataFlow::PathGraph
/**
* A configuration that tracks data flow from cryptographic operations
* to equality test
*/
class PossibleTimingAttackAgainstHash extends TaintTracking::Configuration {
PossibleTimingAttackAgainstHash() { this = "PossibleTimingAttackAgainstHash" }
private module PossibleTimingAttackAgainstHashConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
override predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
override predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
}
from PossibleTimingAttackAgainstHash config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
module PossibleTimingAttackAgainstHashFlow =
TaintTracking::Global<PossibleTimingAttackAgainstHashConfig>;
import PossibleTimingAttackAgainstHashFlow::PathGraph
from
PossibleTimingAttackAgainstHashFlow::PathNode source,
PossibleTimingAttackAgainstHashFlow::PathNode sink
where PossibleTimingAttackAgainstHashFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Possible Timing attack against $@ validation.",
source.getNode().(ProduceCryptoCall).getResultType(), "message"

View File

@@ -16,23 +16,24 @@ import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import experimental.semmle.python.security.TimingAttack
import DataFlow::PathGraph
/**
* A configuration that tracks data flow from cryptographic operations
* to Equality test.
*/
class TimingAttackAgainsthash extends TaintTracking::Configuration {
TimingAttackAgainsthash() { this = "TimingAttackAgainsthash" }
private module TimingAttackAgainstHashConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
override predicate isSource(DataFlow::Node source) { source instanceof ProduceCryptoCall }
override predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
}
from TimingAttackAgainsthash config, DataFlow::PathNode source, DataFlow::PathNode sink
module TimingAttackAgainstHashFlow = TaintTracking::Global<TimingAttackAgainstHashConfig>;
import TimingAttackAgainstHashFlow::PathGraph
from TimingAttackAgainstHashFlow::PathNode source, TimingAttackAgainstHashFlow::PathNode sink
where
config.hasFlowPath(source, sink) and
TimingAttackAgainstHashFlow::flowPath(source, sink) and
sink.getNode().(NonConstantTimeComparisonSink).includesUserInput()
select sink.getNode(), source, sink, "Timing attack against $@ validation.",
source.getNode().(ProduceCryptoCall).getResultType(), "message"

View File

@@ -15,20 +15,26 @@ import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import experimental.semmle.python.security.TimingAttack
import DataFlow::PathGraph
/**
* A configuration tracing flow from a client Secret obtained by an HTTP header to a unsafe Comparison.
*/
class ClientSuppliedSecretConfig extends TaintTracking::Configuration {
ClientSuppliedSecretConfig() { this = "ClientSuppliedSecretConfig" }
private module TimingAttackAgainstHeaderValueConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof ClientSuppliedSecret }
override predicate isSource(DataFlow::Node source) { source instanceof ClientSuppliedSecret }
override predicate isSink(DataFlow::Node sink) { sink instanceof CompareSink }
predicate isSink(DataFlow::Node sink) { sink instanceof CompareSink }
}
from ClientSuppliedSecretConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink) and not sink.getNode().(CompareSink).flowtolen()
module TimingAttackAgainstHeaderValueFlow =
TaintTracking::Global<TimingAttackAgainstHeaderValueConfig>;
import TimingAttackAgainstHeaderValueFlow::PathGraph
from
TimingAttackAgainstHeaderValueFlow::PathNode source,
TimingAttackAgainstHeaderValueFlow::PathNode sink
where
TimingAttackAgainstHeaderValueFlow::flowPath(source, sink) and
not sink.getNode().(CompareSink).flowtolen()
select sink.getNode(), source, sink, "Timing attack against $@ validation.", source.getNode(),
"client-supplied token"

View File

@@ -15,20 +15,24 @@ import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import experimental.semmle.python.security.TimingAttack
import DataFlow::PathGraph
/**
* A configuration tracing flow from obtaining a client Secret to a unsafe Comparison.
*/
class ClientSuppliedSecretConfig extends TaintTracking::Configuration {
ClientSuppliedSecretConfig() { this = "ClientSuppliedSecretConfig" }
private module PossibleTimingAttackAgainstSensitiveInfoConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof SecretSource }
override predicate isSource(DataFlow::Node source) { source instanceof SecretSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
}
from ClientSuppliedSecretConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
module PossibleTimingAttackAgainstSensitiveInfoFlow =
TaintTracking::Global<PossibleTimingAttackAgainstSensitiveInfoConfig>;
import PossibleTimingAttackAgainstSensitiveInfoFlow::PathGraph
from
PossibleTimingAttackAgainstSensitiveInfoFlow::PathNode source,
PossibleTimingAttackAgainstSensitiveInfoFlow::PathNode sink
where PossibleTimingAttackAgainstSensitiveInfoFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Timing attack against $@ validation.", source.getNode(),
"client-supplied token"

View File

@@ -15,22 +15,25 @@ import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import experimental.semmle.python.security.TimingAttack
import DataFlow::PathGraph
import TimingAttackAgainstSensitiveInfoFlow::PathGraph
/**
* A configuration tracing flow from obtaining a client Secret to a unsafe Comparison.
*/
class ClientSuppliedSecretConfig extends TaintTracking::Configuration {
ClientSuppliedSecretConfig() { this = "ClientSuppliedSecretConfig" }
private module TimingAttackAgainstSensitiveInfoConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof SecretSource }
override predicate isSource(DataFlow::Node source) { source instanceof SecretSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
predicate isSink(DataFlow::Node sink) { sink instanceof NonConstantTimeComparisonSink }
}
from ClientSuppliedSecretConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
module TimingAttackAgainstSensitiveInfoFlow =
TaintTracking::Global<TimingAttackAgainstSensitiveInfoConfig>;
from
TimingAttackAgainstSensitiveInfoFlow::PathNode source,
TimingAttackAgainstSensitiveInfoFlow::PathNode sink
where
config.hasFlowPath(source, sink) and
TimingAttackAgainstSensitiveInfoFlow::flowPath(source, sink) and
(
source.getNode().(SecretSource).includesUserInput() or
sink.getNode().(NonConstantTimeComparisonSink).includesUserInput()

View File

@@ -0,0 +1,57 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Flask and Django require a Securely signed key for singing the session cookies. most of the time developers rely on load hardcoded secret keys from a config file or python code. this proves that the way of hardcoded secret can make problems when you forgot to change the constant secret keys.
</p>
</overview>
<recommendation>
<p>
In Flask Consider using a secure random generator with <a href="https://docs.python.org/3/library/secrets.html#secrets.token_hex">Python standard secrets library</a>
</p>
<p>
In Django Consider using a secure random generator with "get_random_secret_key()"" method from "django.core.management.utils".
</p>
</recommendation>
<example>
<p>Safe Django SECRET_KEY</p>
<sample src="examples/example_Django_safe.py" />
<p>Unsafe Django SECRET_KEY Example:</p>
<sample src="examples/example_Django_unsafe.py" />
<p>Safe Flask SECRET_KEY Example:</p>
<sample src="examples/example_Flask_safe.py" />
<sample src="examples/example_Flask_unsafe.py" />
<p>Unsafe Flask SECRET_KEY Example:</p>
<sample src="examples/example_Flask_unsafe2.py" />
<p>config1.py</p>
<sample src="examples/config1.py" />
<p>config2.py</p>
<sample src="examples/config2.py" />
<p>config3.py</p>
<sample src="examples/config3.py" />
<p>__init__.py</p>
<sample src="examples/settings/__init__.py" />
</example>
<references>
<li>
<a href="https://flask.palletsprojects.com/en/2.3.x/config/#SECRET_KEY">Flask Documentation</a>
</li>
<li>
<a href="https://docs.djangoproject.com/en/4.2/ref/settings/#secret-key">Django Documentation</a>
</li>
<li>
<a href="https://flask-jwt-extended.readthedocs.io/en/stable/basic_usage.html#basic-usage">Flask-JWT-Extended Documentation</a>
</li>
<li>
<a href="https://www.horizon3.ai/cve-2023-27524-insecure-default-configuration-in-apache-superset-leads-to-remote-code-execution/">CVE-2023-27524 - Apache Superset had multiple CVEs related to this kind of Vulnerability</a>
</li>
<li>
<a href="https://nvd.nist.gov/vuln/detail/CVE-2020-17526">CVE-2020-17526 - Apache Airflow had multiple CVEs related to this kind of Vulnerability</a>
</li>
<li>
<a href="https://nvd.nist.gov/vuln/detail/CVE-2021-41192">CVE-2021-41192 - Redash was assigning a environment variable with a default value which it was assigning the default secrect if the environment variable does not exists</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,64 @@
/**
* @name Initializing SECRET_KEY of Flask application with Constant value
* @description Initializing SECRET_KEY of Flask application with Constant value
* files can lead to Authentication bypass
* @kind path-problem
* @id py/flask-constant-secret-key
* @problem.severity error
* @security-severity 8.5
* @precision high
* @tags security
* experimental
* external/cwe/cwe-287
*/
import python
import experimental.semmle.python.Concepts
import semmle.python.dataflow.new.DataFlow
import semmle.python.ApiGraphs
import semmle.python.dataflow.new.TaintTracking
import WebAppConstantSecretKeyDjango
import WebAppConstantSecretKeyFlask
import semmle.python.filters.Tests
newtype TFrameWork =
Flask() or
Django()
private module WebAppConstantSecretKeyConfig implements DataFlow::StateConfigSig {
class FlowState = TFrameWork;
predicate isSource(DataFlow::Node source, FlowState state) {
state = Flask() and FlaskConstantSecretKeyConfig::isSource(source)
or
state = Django() and DjangoConstantSecretKeyConfig::isSource(source)
}
predicate isBarrier(DataFlow::Node node) {
node.getLocation().getFile().inStdlib()
or
// To reduce FP rate, the following was added
node.getLocation()
.getFile()
.getRelativePath()
.matches(["%test%", "%demo%", "%example%", "%sample%"]) and
// but that also meant all data-flow nodes in query tests were excluded... so we had
// to add this:
not node.getLocation().getFile().getRelativePath().matches("%query-tests/Security/CWE-287%")
}
predicate isSink(DataFlow::Node sink, FlowState state) {
state = Flask() and FlaskConstantSecretKeyConfig::isSink(sink)
or
state = Django() and DjangoConstantSecretKeyConfig::isSink(sink)
}
}
module WebAppConstantSecretKeyFlow = TaintTracking::GlobalWithState<WebAppConstantSecretKeyConfig>;
import WebAppConstantSecretKeyFlow::PathGraph
from WebAppConstantSecretKeyFlow::PathNode source, WebAppConstantSecretKeyFlow::PathNode sink
where WebAppConstantSecretKeyFlow::flowPath(source, sink)
select sink, source, sink, "The SECRET_KEY config variable is assigned by $@.", source,
" this constant String"

View File

@@ -0,0 +1,52 @@
import python
import experimental.semmle.python.Concepts
import semmle.python.dataflow.new.DataFlow
import semmle.python.ApiGraphs
import semmle.python.dataflow.new.TaintTracking
import WebAppConstantSecretKeySource
module DjangoConstantSecretKeyConfig {
/**
* Sources are Constants that without any Tainting reach the Sinks.
* Also Sources can be the default value of getenv or similar methods
* in a case that no value is assigned to Desired SECRET_KEY environment variable
*/
predicate isSource(DataFlow::Node source) { source instanceof WebAppConstantSecretKeySource }
/**
* Holds if There is a sink like following SECRET_KEY Assignments
* ```python
*from django.conf import settings
*settings.configure(
* SECRET_KEY="constant",
*)
* # or
*settings.SECRET_KEY = "constant"
* ```
*/
predicate isSink(DataFlow::Node sink) {
exists(API::moduleImport("django")) and
(
exists(AssignStmt e | e.getTarget(0).(Name).getId() = ["SECRET_KEY", "SECRET_KEY_FALLBACKS"] |
sink.asExpr() = e.getValue()
)
or
exists(API::Node settings |
settings =
API::moduleImport("django").getMember("conf").getMember(["global_settings", "settings"]) and
sink =
settings
.getMember("configure")
.getKeywordParameter(["SECRET_KEY_FALLBACKS", "SECRET_KEY"])
.asSink()
)
or
exists(DataFlow::AttrWrite attr |
attr.getAttributeName() = ["SECRET_KEY_FALLBACKS", "SECRET_KEY"] and
sink = attr.getValue()
)
) and
exists(sink.getScope().getLocation().getFile().getRelativePath()) and
not sink.getScope().getLocation().getFile().inStdlib()
}
}

View File

@@ -0,0 +1,139 @@
import python
import experimental.semmle.python.Concepts
import semmle.python.dataflow.new.DataFlow
import semmle.python.ApiGraphs
import semmle.python.dataflow.new.TaintTracking
import WebAppConstantSecretKeySource
/**
* with using flask-session package, there is no jwt exists in cookies in user side
* ```python
*import os
*from flask import Flask, session
*app = Flask(__name__)
* ```
*/
module FlaskConstantSecretKeyConfig {
/**
* `flask.Flask()`
*/
API::Node flaskInstance() {
result = API::moduleImport("flask").getMember("Flask").getASubclass*()
}
/**
* Sources are Constants that without any Tainting reach the Sinks.
* Also Sources can be the default value of getenv or similar methods
* in a case that no value is assigned to Desired SECRET_KEY environment variable
*/
predicate isSource(DataFlow::Node source) { source instanceof WebAppConstantSecretKeySource }
/**
* Sinks are one of the following kinds, some of them are directly connected to a flask Instance like
* ```python
* app.config['SECRET_KEY'] = 'CHANGEME1'
* app.secret_key = 'CHANGEME2'
* app.config.update(SECRET_KEY="CHANGEME3")
* app.config.from_mapping(SECRET_KEY="CHANGEME4")
* ```
* other Sinks are SECRET_KEY Constants Variables that are defined in separate files or a class in those files like:
* ```python
* app.config.from_pyfile("config.py")
* app.config.from_object('config.Config')
*```
* we find these files with `FromObjectFileName` DataFlow Configuration
* note that "JWT_SECRET_KEY" is same as "SECRET_KEY" but it is belong to popular flask-jwt-extended library
*/
predicate isSink(DataFlow::Node sink) {
(
exists(API::Node n | n = flaskInstance().getReturn() |
sink =
[
n.getMember("config").getSubscript(["SECRET_KEY", "JWT_SECRET_KEY"]).asSink(),
n.getMember("config")
.getMember(["update", "from_mapping"])
.getACall()
.getArgByName(["SECRET_KEY", "JWT_SECRET_KEY"])
]
)
or
exists(DataFlow::AttrWrite attr |
attr.getObject().getALocalSource() = flaskInstance().getACall() and
attr.getAttributeName() = ["secret_key", "jwt_secret_key"] and
sink = attr.getValue()
)
or
exists(SecretKeyAssignStmt e | sink.asExpr() = e.getValue())
) and
exists(sink.getScope().getLocation().getFile().getRelativePath()) and
not sink.getScope().getLocation().getFile().inStdlib()
}
/**
* An Assignments like `SECRET_KEY = ConstantValue`
* and `SECRET_KEY` file must be the Location that is specified in argument of `from_object` or `from_pyfile` methods
*/
class SecretKeyAssignStmt extends AssignStmt {
SecretKeyAssignStmt() {
exists(string configFileName, string fileNamehelper, DataFlow::Node n1, File file |
fileNamehelper = [flaskConfiFileName(n1), flaskConfiFileName2(n1)] and
// because of `from_object` we want first part of `Config.AClassName` which `Config` is a python file name
configFileName = fileNamehelper.splitAt(".") and
file = this.getLocation().getFile()
|
(
if fileNamehelper = "__name__"
then
file.getShortName() = flaskInstance().asSource().getLocation().getFile().getShortName()
else (
fileNamehelper.matches("%.py") and
file.getShortName().matches("%" + configFileName + "%") and
// after spliting, don't look at %py% pattern
configFileName != ".py"
or
// in case of referencing to a directory which then we must look for __init__.py file
not fileNamehelper.matches("%.py") and
file.getRelativePath()
.matches("%" + fileNamehelper.replaceAll(".", "/") + "/__init__.py")
)
) and
this.getTarget(0).(Name).getId() = ["SECRET_KEY", "JWT_SECRET_KEY"]
) and
exists(this.getScope().getLocation().getFile().getRelativePath()) and
not this.getScope().getLocation().getFile().inStdlib()
}
}
/**
* Holds if there is a helper predicate that specify where the Flask `SECRET_KEY` variable location is defined.
* In Flask we have config files that specify the location of `SECRET_KEY` variable initialization
* and the name of these files are determined by
* `app.config.from_pyfile("configFileName.py")`
* or
* `app.config.from_object("configFileName.ClassName")`
*/
string flaskConfiFileName(API::CallNode cn) {
cn =
flaskInstance()
.getReturn()
.getMember("config")
.getMember(["from_object", "from_pyfile"])
.getACall() and
result =
[
cn.getParameter(0).getAValueReachingSink().asExpr().(StrConst).getText(),
cn.getParameter(0).asSink().asExpr().(Name).getId()
]
}
string flaskConfiFileName2(API::CallNode cn) {
cn =
API::moduleImport("flask")
.getMember("Flask")
.getASubclass*()
.getASuccessor*()
.getMember("from_object")
.getACall() and
result = cn.getParameter(0).asSink().asExpr().(StrConst).getText()
}
}

View File

@@ -0,0 +1,38 @@
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.ApiGraphs
/** A bad source for secret key in web app configurations. */
class WebAppConstantSecretKeySource extends DataFlow::Node {
WebAppConstantSecretKeySource() {
(
// we should check whether there is a default value or not
exists(API::Node env |
env = API::moduleImport("environ").getMember("Env") and
// has default value
exists(API::Node param | param = env.getKeywordParameter("SECRET_KEY") |
param.asSink().asExpr().getASubExpression*() instanceof StrConst
) and
this = env.getReturn().getReturn().asSource()
)
or
this.asExpr() instanceof StrConst
or
exists(API::CallNode cn |
cn =
[
API::moduleImport("os").getMember("getenv").getACall(),
API::moduleImport("os").getMember("environ").getMember("get").getACall()
] and
cn.getNumArgument() = 2 and
DataFlow::localFlow(any(DataFlow::Node n | n.asExpr() instanceof StrConst), cn.getArg(1)) and
this.asExpr() = cn.asExpr()
)
) and
// followings will sanitize the get_random_secret_key of django.core.management.utils and similar random generators which we have their source code and some of them can be tracking by taint tracking because they are initilized by a constant!
exists(this.getScope().getLocation().getFile().getRelativePath()) and
not this.getScope().getLocation().getFile().inStdlib() and
// special sanitize case for get_random_secret_key and django-environ
not this.getScope().getLocation().getFile().getBaseName() = ["environ.py", "crypto.py"]
}
}

View File

@@ -0,0 +1,14 @@
"""Flask App configuration."""
import os
import random
FLASK_DEBUG = True
aConstant = 'CHANGEME2'
class Config:
SECRET_KEY = aConstant
SECRET_KEY = os.environ.get('envKey', random.randint)
SECRET_KEY = os.getenv('envKey', random.randint)
SECRET_KEY = os.getenv('envKey', aConstant)
SECRET_KEY = os.environ.get('envKey', aConstant)

View File

@@ -0,0 +1,23 @@
"""Flask App configuration."""
from os import environ
import os
import random
import configparser
FLASK_DEBUG = True
aConstant = 'CHANGEME2'
config = configparser.ConfigParser()
class Config:
SECRET_KEY = config["a"]["Secret"]
SECRET_KEY = config.get("key", "value")
SECRET_KEY = environ.get("envKey")
SECRET_KEY = aConstant
SECRET_KEY = os.getenv('envKey')
SECRET_KEY = os.environ.get('envKey')
SECRET_KEY = os.environ.get('envKey', random.randint)
SECRET_KEY = os.getenv('envKey', random.randint)
SECRET_KEY = os.getenv('envKey', aConstant)
SECRET_KEY = os.environ.get('envKey', aConstant)
SECRET_KEY = os.environ['envKey']

View File

@@ -0,0 +1,9 @@
"""Flask App configuration."""
aConstant = 'CHANGEME2'
SECRET_KEY = aConstant
class Config:
SECRET_KEY = aConstant

View File

@@ -0,0 +1,12 @@
"""Flask App configuration."""
import os
# General Config
FLASK_DEBUG = True
# if we are loading SECRET_KEY from config files then
# it is good to check default value always, maybe
# the user responsible for setup the application make a mistake
# and has not changed the default SECRET_KEY value
SECRET_KEY = os.getenv('envKey', "A_CONSTANT_SECRET") # A_CONSTANT_SECRET
if SECRET_KEY == "A_CONSTANT_SECRET":
raise "not possible"

View File

@@ -0,0 +1,32 @@
import sys
from django.conf import settings
from django.conf import global_settings
from django.urls import path
from django.http import HttpResponse
from django.core.management.utils import get_random_secret_key
settings.configure(
DEBUG=True,
SECRET_KEY=get_random_secret_key(),
ROOT_URLCONF=__name__,
)
global_settings.SECRET_KEY = get_random_secret_key()
settings.SECRET_KEY = get_random_secret_key()
def home(request):
return HttpResponse(settings.SECRET_KEY)
urlpatterns = [
path("", home),
]
if __name__ == "__main__":
from django.core.management import execute_from_command_line
if len(sys.argv) == 1:
sys.argv.append("runserver")
sys.argv.append("8080")
execute_from_command_line(sys.argv)

View File

@@ -0,0 +1,38 @@
import sys
import environ
from django.conf import settings
from django.conf import global_settings
from django.urls import path
from django.http import HttpResponse
env = environ.Env(
SECRET_KEY=(str, "AConstantKey")
)
env.read_env(env_file='.env')
# following is not safe if there is default value in Env(..)
settings.SECRET_KEY = env('SECRET_KEY')
settings.configure(
DEBUG=True,
SECRET_KEY="constant1",
ROOT_URLCONF=__name__,
)
global_settings.SECRET_KEY = "constant2"
settings.SECRET_KEY = "constant3"
def home(request):
return HttpResponse(settings.SECRET_KEY)
urlpatterns = [
path("", home),
]
if __name__ == "__main__":
from django.core.management import execute_from_command_line
if len(sys.argv) == 1:
sys.argv.append("runserver")
sys.argv.append("8080")
execute_from_command_line(sys.argv)

View File

@@ -0,0 +1,16 @@
from flask import Flask, session
from secrets import token_hex
app = Flask(__name__)
app.secret_key = token_hex(16)
app.config.from_pyfile("config3.py")
@app.route('/')
def CheckForSecretKeyValue():
# debugging whether secret_key is secure or not
return app.secret_key, session.get('logged_in')
if __name__ == '__main__':
app.run()

View File

@@ -0,0 +1,23 @@
from flask import Flask, session
app = Flask(__name__)
aConstant = 'CHANGEME1'
app.config['SECRET_KEY'] = aConstant
app.secret_key = aConstant
app.config.update(SECRET_KEY=aConstant)
app.config.from_mapping(SECRET_KEY=aConstant)
app.config.from_pyfile("config.py")
app.config.from_pyfile("config2.py")
app.config.from_object('config.Config')
app.config.from_object('config2.Config')
app.config.from_object('settings')
@app.route('/')
def CheckForSecretKeyValue():
# debugging whether secret_key is secure or not
return app.secret_key, session.get('logged_in')
if __name__ == '__main__':
app.run()

View File

@@ -0,0 +1,18 @@
from flask import Flask, session
app = Flask(__name__)
aConstant = 'CHANGEME1'
SECRET_KEY = aConstant
app.config.from_object(__name__)
@app.route('/')
def DEB_EX():
if 'logged_in' not in session:
session['logged_in'] = 'value'
# debugging whether secret_key is secure or not
return app.secret_key, session.__str__()
if __name__ == '__main__':
app.run()

View File

@@ -0,0 +1,3 @@
import os
SECRET_KEY = "REDASH_COOKIE_SECRET"

View File

@@ -96,7 +96,7 @@ newtype TAzureFlowState =
MkUsesV1Encryption() or
MkUsesNoEncryption()
module AzureBlobClientConfig implements DataFlow::StateConfigSig {
private module AzureBlobClientConfig implements DataFlow::StateConfigSig {
class FlowState = TAzureFlowState;
predicate isSource(DataFlow::Node node, FlowState state) {
@@ -147,10 +147,10 @@ module AzureBlobClientConfig implements DataFlow::StateConfigSig {
}
}
module AzureBlobClient = DataFlow::GlobalWithState<AzureBlobClientConfig>;
module AzureBlobClientFlow = DataFlow::GlobalWithState<AzureBlobClientConfig>;
import AzureBlobClient::PathGraph
import AzureBlobClientFlow::PathGraph
from AzureBlobClient::PathNode source, AzureBlobClient::PathNode sink
where AzureBlobClient::flowPath(source, sink)
from AzureBlobClientFlow::PathNode source, AzureBlobClientFlow::PathNode sink
where AzureBlobClientFlow::flowPath(source, sink)
select sink, source, sink, "Unsafe usage of v1 version of Azure Storage client-side encryption"

View File

@@ -14,11 +14,11 @@
*/
import python
import experimental.semmle.python.security.InsecureRandomness::InsecureRandomness
import experimental.semmle.python.security.InsecureRandomness
import semmle.python.dataflow.new.DataFlow
import DataFlow::PathGraph
import InsecureRandomness::Flow::PathGraph
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
from InsecureRandomness::Flow::PathNode source, InsecureRandomness::Flow::PathNode sink
where InsecureRandomness::Flow::flowPath(source, sink)
select sink.getNode(), source, sink, "Cryptographically insecure $@ in a security context.",
source.getNode(), "random value"

View File

@@ -16,7 +16,6 @@ import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.ApiGraphs
import semmle.python.dataflow.new.TaintTracking
import DataFlow::PathGraph
class PredictableResultSource extends DataFlow::Node {
PredictableResultSource() {
@@ -40,14 +39,12 @@ class TokenAssignmentValueSink extends DataFlow::Node {
}
}
class TokenBuiltFromUuidConfig extends TaintTracking::Configuration {
TokenBuiltFromUuidConfig() { this = "TokenBuiltFromUuidConfig" }
private module TokenBuiltFromUuidConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source instanceof PredictableResultSource }
override predicate isSource(DataFlow::Node source) { source instanceof PredictableResultSource }
predicate isSink(DataFlow::Node sink) { sink instanceof TokenAssignmentValueSink }
override predicate isSink(DataFlow::Node sink) { sink instanceof TokenAssignmentValueSink }
override predicate isAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
exists(DataFlow::CallCfgNode call |
call = API::builtin("str").getACall() and
nodeFrom = call.getArg(0) and
@@ -56,6 +53,11 @@ class TokenBuiltFromUuidConfig extends TaintTracking::Configuration {
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, TokenBuiltFromUuidConfig config
where config.hasFlowPath(source, sink)
/** Global taint-tracking for detecting "TokenBuiltFromUUID" vulnerabilities. */
module TokenBuiltFromUuidFlow = TaintTracking::Global<TokenBuiltFromUuidConfig>;
import TokenBuiltFromUuidFlow::PathGraph
from TokenBuiltFromUuidFlow::PathNode source, TokenBuiltFromUuidFlow::PathNode sink
where TokenBuiltFromUuidFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Token built from $@.", source.getNode(), "predictable value"

View File

@@ -16,21 +16,19 @@ import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import semmle.python.ApiGraphs
import ClientSuppliedIpUsedInSecurityCheckLib
import DataFlow::PathGraph
import ClientSuppliedIpUsedInSecurityCheckFlow::PathGraph
/**
* 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" }
override predicate isSource(DataFlow::Node source) {
private module ClientSuppliedIpUsedInSecurityCheckConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source instanceof ClientSuppliedIpUsedInSecurityCheck
}
override predicate isSink(DataFlow::Node sink) { sink instanceof PossibleSecurityCheck }
predicate isSink(DataFlow::Node sink) { sink instanceof PossibleSecurityCheck }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallCfgNode ccn |
ccn = API::moduleImport("netaddr").getMember("IPAddress").getACall() and
ccn.getArg(0) = pred and
@@ -38,7 +36,7 @@ class ClientSuppliedIpUsedInSecurityCheckConfig extends TaintTracking::Configura
)
}
override predicate isSanitizer(DataFlow::Node node) {
predicate isBarrier(DataFlow::Node node) {
// `client_supplied_ip.split(",")[n]` for `n` > 0
exists(Subscript ss |
not ss.getIndex().(IntegerLiteral).getText() = "0" and
@@ -49,9 +47,13 @@ class ClientSuppliedIpUsedInSecurityCheckConfig extends TaintTracking::Configura
}
}
/** Global taint-tracking for detecting "client ip used in security check" vulnerabilities. */
module ClientSuppliedIpUsedInSecurityCheckFlow =
TaintTracking::Global<ClientSuppliedIpUsedInSecurityCheckConfig>;
from
ClientSuppliedIpUsedInSecurityCheckConfig config, DataFlow::PathNode source,
DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
ClientSuppliedIpUsedInSecurityCheckFlow::PathNode source,
ClientSuppliedIpUsedInSecurityCheckFlow::PathNode sink
where ClientSuppliedIpUsedInSecurityCheckFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "IP address spoofing might include code from $@.",
source.getNode(), "this user input"

View File

@@ -12,9 +12,9 @@
// determine precision above
import python
import DataFlow::PathGraph
import experimental.semmle.python.security.LDAPInsecureAuth
import experimental.semmle.python.security.LdapInsecureAuth
import LdapInsecureAuthFlow::PathGraph
from LdapInsecureAuthConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
from LdapInsecureAuthFlow::PathNode source, LdapInsecureAuthFlow::PathNode sink
where LdapInsecureAuthFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This LDAP host is authenticated insecurely."

View File

@@ -15,13 +15,11 @@ import semmle.python.dataflow.new.DataFlow
import experimental.semmle.python.Concepts
import experimental.semmle.python.CookieHeader
import experimental.semmle.python.security.injection.CookieInjection
import DataFlow::PathGraph
import CookieInjectionFlow::PathGraph
from
CookieInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink,
string insecure
from CookieInjectionFlow::PathNode source, CookieInjectionFlow::PathNode sink, string insecure
where
config.hasFlowPath(source, sink) and
CookieInjectionFlow::flowPath(source, sink) and
if exists(sink.getNode().(CookieSink))
then insecure = ",and its " + sink.getNode().(CookieSink).getFlag() + " flag is not properly set."
else insecure = "."

Some files were not shown because too many files have changed in this diff Show More