mirror of
https://github.com/github/codeql.git
synced 2026-04-24 00:05:14 +02:00
Merge branch 'main' into amammad-python-bombs
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 + ")"
|
||||
|
||||
@@ -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 + ")"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# BAD: Logging cleartext sensitive data
|
||||
import os
|
||||
print(f"[INFO] Environment: {os.environ}")
|
||||
@@ -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}")
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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/")
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
|
||||
1
python/ql/src/Summary/options
Normal file
1
python/ql/src/Summary/options
Normal file
@@ -0,0 +1 @@
|
||||
semmle-extractor-options: --lang=3
|
||||
@@ -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.
|
||||
3
python/ql/src/change-notes/released/0.7.4.md
Normal file
3
python/ql/src/change-notes/released/0.7.4.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.7.4
|
||||
|
||||
No user-facing changes.
|
||||
5
python/ql/src/change-notes/released/0.8.0.md
Normal file
5
python/ql/src/change-notes/released/0.8.0.md
Normal 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")."
|
||||
5
python/ql/src/change-notes/released/0.8.1.md
Normal file
5
python/ql/src/change-notes/released/0.8.1.md
Normal 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`).
|
||||
3
python/ql/src/change-notes/released/0.8.2.md
Normal file
3
python/ql/src/change-notes/released/0.8.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.2
|
||||
|
||||
No user-facing changes.
|
||||
3
python/ql/src/change-notes/released/0.8.3.md
Normal file
3
python/ql/src/change-notes/released/0.8.3.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.3
|
||||
|
||||
No user-facing changes.
|
||||
6
python/ql/src/change-notes/released/0.8.4.md
Normal file
6
python/ql/src/change-notes/released/0.8.4.md
Normal 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.
|
||||
3
python/ql/src/change-notes/released/0.8.5.md
Normal file
3
python/ql/src/change-notes/released/0.8.5.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.5
|
||||
|
||||
No user-facing changes.
|
||||
10
python/ql/src/change-notes/released/0.9.0.md
Normal file
10
python/ql/src/change-notes/released/0.9.0.md
Normal 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.
|
||||
3
python/ql/src/change-notes/released/0.9.1.md
Normal file
3
python/ql/src/change-notes/released/0.9.1.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.9.1
|
||||
|
||||
No user-facing changes.
|
||||
3
python/ql/src/change-notes/released/0.9.2.md
Normal file
3
python/ql/src/change-notes/released/0.9.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.9.2
|
||||
|
||||
No user-facing changes.
|
||||
5
python/ql/src/change-notes/released/0.9.3.md
Normal file
5
python/ql/src/change-notes/released/0.9.3.md
Normal 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.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.7.2
|
||||
lastReleaseVersion: 0.9.3
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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 { }
|
||||
}
|
||||
@@ -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>;
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
110
python/ql/src/experimental/Security/CWE-091/XsltConcept.qll
Normal file
110
python/ql/src/experimental/Security/CWE-091/XsltConcept.qll
Normal 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()
|
||||
}
|
||||
21
python/ql/src/experimental/Security/CWE-091/XsltInjection.ql
Normal file
21
python/ql/src/experimental/Security/CWE-091/XsltInjection.ql
Normal 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"
|
||||
@@ -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 { }
|
||||
}
|
||||
@@ -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>;
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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']
|
||||
@@ -0,0 +1,9 @@
|
||||
"""Flask App configuration."""
|
||||
|
||||
aConstant = 'CHANGEME2'
|
||||
|
||||
SECRET_KEY = aConstant
|
||||
|
||||
|
||||
class Config:
|
||||
SECRET_KEY = aConstant
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -0,0 +1,3 @@
|
||||
import os
|
||||
|
||||
SECRET_KEY = "REDASH_COOKIE_SECRET"
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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."
|
||||
@@ -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
Reference in New Issue
Block a user