mirror of
https://github.com/github/codeql.git
synced 2026-04-28 18:25:24 +02:00
Merge branch 'main' into python/simple-csrf
This commit is contained in:
@@ -1,3 +1,17 @@
|
||||
## 0.1.1
|
||||
|
||||
## 0.1.0
|
||||
|
||||
## 0.0.13
|
||||
|
||||
## 0.0.12
|
||||
|
||||
## 0.0.11
|
||||
|
||||
### New Queries
|
||||
|
||||
* The query "XPath query built from user-controlled sources" (`py/xpath-injection`) has been promoted from experimental to the main query pack. Its results will now appear by default. This query was originally [submitted as an experimental query by @porcupineyhairs](https://github.com/github/codeql/pull/6331).
|
||||
|
||||
## 0.0.10
|
||||
|
||||
### New Queries
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import python
|
||||
private import semmle.python.pointsto.PointsTo
|
||||
|
||||
/** Helper class for UndefinedClassAttribute.ql and MaybeUndefinedClassAttribute.ql */
|
||||
/** A helper class for UndefinedClassAttribute.ql and MaybeUndefinedClassAttribute.ql */
|
||||
class CheckClass extends ClassObject {
|
||||
private predicate ofInterest() {
|
||||
not this.unknowableAttributes() and
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.SelfAttribute
|
||||
import Equality
|
||||
|
||||
predicate class_stores_to_attribute(ClassValue cls, SelfAttributeStore store, string name) {
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.SelfAttribute
|
||||
import ClassAttributes
|
||||
|
||||
predicate guarded_by_other_attribute(SelfAttributeRead a, CheckClass c) {
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.SelfAttribute
|
||||
import ClassAttributes
|
||||
|
||||
predicate undefined_class_attribute(SelfAttributeRead a, CheckClass c, int line, string name) {
|
||||
|
||||
@@ -32,7 +32,11 @@ predicate guarded_not_empty_sequence(EssaVariable sequence) {
|
||||
sequence.getDefinition() instanceof EssaEdgeRefinement
|
||||
}
|
||||
|
||||
/** The pattern `next(iter(x))` is often used where `x` is known not be empty. Check for that. */
|
||||
/**
|
||||
* Holds if `iterator` is not exhausted.
|
||||
*
|
||||
* The pattern `next(iter(x))` is often used where `x` is known not be empty. Check for that.
|
||||
*/
|
||||
predicate iter_not_exhausted(EssaVariable iterator) {
|
||||
exists(EssaVariable sequence |
|
||||
call_to_iter(iterator.getDefinition().(AssignmentDefinition).getValue(), sequence) and
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import python
|
||||
|
||||
/** A string constant that looks like it may be used in string formatting operations. */
|
||||
library class PossibleAdvancedFormatString extends StrConst {
|
||||
class PossibleAdvancedFormatString extends StrConst {
|
||||
PossibleAdvancedFormatString() { this.getText().matches("%{%}%") }
|
||||
|
||||
private predicate field(int start, int end) {
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
* @id py/str-format/surplus-argument
|
||||
*/
|
||||
|
||||
import python
|
||||
import python
|
||||
import AdvancedFormatting
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import python
|
||||
|
||||
/**
|
||||
* The module `name` was deprecated in Python version `major`.`minor`,
|
||||
* Holds if the module `name` was deprecated in Python version `major`.`minor`,
|
||||
* and module `instead` should be used instead (or `instead = "no replacement"`)
|
||||
*/
|
||||
predicate deprecated_module(string name, string instead, int major, int minor) {
|
||||
|
||||
@@ -40,16 +40,6 @@ private predicate class_statement(Comment c) {
|
||||
|
||||
private predicate triple_quote(Comment c) { c.getText().regexpMatch("#.*(\"\"\"|''').*") }
|
||||
|
||||
private predicate triple_quoted_string_part(Comment start, Comment end) {
|
||||
triple_quote(start) and end = start
|
||||
or
|
||||
exists(Comment mid |
|
||||
triple_quoted_string_part(start, mid) and
|
||||
end = non_empty_following(mid) and
|
||||
not triple_quote(end)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate maybe_code(Comment c) {
|
||||
not non_code(c) and not filler(c) and not endline_comment(c) and not file_or_url(c)
|
||||
or
|
||||
@@ -158,11 +148,11 @@ private predicate commented_out_code_block(Comment start, Comment end) {
|
||||
not commented_out_code(non_empty_following(end))
|
||||
}
|
||||
|
||||
/* A single line comment that appears to be commented out code */
|
||||
/** A single line comment that appears to be commented out code */
|
||||
class CommentedOutCodeLine extends Comment {
|
||||
CommentedOutCodeLine() { exists(CommentedOutCodeBlock b | b.contains(this)) }
|
||||
|
||||
/* Whether this commented-out code line is likely to be example code embedded in a larger comment. */
|
||||
/** Holds if this commented-out code line is likely to be example code embedded in a larger comment. */
|
||||
predicate maybeExampleCode() {
|
||||
exists(CommentedOutCodeBlock block |
|
||||
block.contains(this) and
|
||||
@@ -178,7 +168,7 @@ class CommentedOutCodeBlock extends @py_comment {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = "Commented out code" }
|
||||
|
||||
/** Whether this commented-out code block contains the comment c */
|
||||
/** Holds if this commented-out code block contains the comment c */
|
||||
predicate contains(Comment c) {
|
||||
this = c
|
||||
or
|
||||
@@ -189,7 +179,7 @@ class CommentedOutCodeBlock extends @py_comment {
|
||||
)
|
||||
}
|
||||
|
||||
/** The length of this comment block (in comments) */
|
||||
/** Gets the length of this comment block (in comments) */
|
||||
int length() { result = count(Comment c | this.contains(c)) }
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
import python
|
||||
import Lexical.CommentedOutCode
|
||||
import python
|
||||
|
||||
from File f, int n
|
||||
where n = count(CommentedOutCodeLine c | not c.maybeExampleCode() and c.getLocation().getFile() = f)
|
||||
|
||||
@@ -10,36 +10,30 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
predicate squareOp(BinaryExpr e) {
|
||||
e.getOp() instanceof Pow and e.getRight().(IntegerLiteral).getN() = "2"
|
||||
}
|
||||
|
||||
predicate squareMul(BinaryExpr e) {
|
||||
e.getOp() instanceof Mult and e.getRight().(Name).getId() = e.getLeft().(Name).getId()
|
||||
}
|
||||
|
||||
predicate squareRef(Name e) {
|
||||
e.isUse() and
|
||||
exists(SsaVariable v, Expr s | v.getVariable() = e.getVariable() |
|
||||
s = v.getDefinition().getNode().getParentNode().(AssignStmt).getValue() and
|
||||
square(s)
|
||||
DataFlow::ExprNode squareOp() {
|
||||
exists(BinaryExpr e | e = result.asExpr() |
|
||||
e.getOp() instanceof Pow and e.getRight().(IntegerLiteral).getN() = "2"
|
||||
)
|
||||
}
|
||||
|
||||
predicate square(Expr e) {
|
||||
squareOp(e)
|
||||
or
|
||||
squareMul(e)
|
||||
or
|
||||
squareRef(e)
|
||||
DataFlow::ExprNode squareMul() {
|
||||
exists(BinaryExpr e | e = result.asExpr() |
|
||||
e.getOp() instanceof Mult and e.getRight().(Name).getId() = e.getLeft().(Name).getId()
|
||||
)
|
||||
}
|
||||
|
||||
from Call c, BinaryExpr s
|
||||
DataFlow::ExprNode square() { result in [squareOp(), squareMul()] }
|
||||
|
||||
from DataFlow::CallCfgNode c, BinaryExpr s, DataFlow::ExprNode left, DataFlow::ExprNode right
|
||||
where
|
||||
c.getFunc().toString() = "sqrt" and
|
||||
c.getArg(0) = s and
|
||||
c = API::moduleImport("math").getMember("sqrt").getACall() and
|
||||
c.getArg(0).asExpr() = s and
|
||||
s.getOp() instanceof Add and
|
||||
square(s.getLeft()) and
|
||||
square(s.getRight())
|
||||
left.asExpr() = s.getLeft() and
|
||||
right.asExpr() = s.getRight() and
|
||||
left.getALocalSource() = square() and
|
||||
right.getALocalSource() = square()
|
||||
select c, "Pythagorean calculation with sub-optimal numerics"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/** Contains predicates concerning when and where files are opened and closed. */
|
||||
|
||||
import python
|
||||
import semmle.python.GuardedControlFlow
|
||||
import semmle.python.pointsto.Filters
|
||||
|
||||
/** Holds if `open` is a call that returns a newly opened file */
|
||||
@@ -66,7 +65,7 @@ predicate passes_open_files(Variable v, ControlFlowNode test, boolean sense) {
|
||||
)
|
||||
}
|
||||
|
||||
/* Helper for `def_is_open` to give better join order */
|
||||
// Helper for `def_is_open` to give better join order
|
||||
private predicate passes_open_files(PyEdgeRefinement refinement) {
|
||||
passes_open_files(refinement.getSourceVariable(), refinement.getPredecessor().getLastNode(),
|
||||
refinement.getSense())
|
||||
|
||||
@@ -40,13 +40,16 @@ private import semmle.python.objects.ObjectInternal
|
||||
/**
|
||||
* A callable that is considered a "safe" external API from a security perspective.
|
||||
*/
|
||||
class SafeExternalAPI extends Unit {
|
||||
class SafeExternalApi extends Unit {
|
||||
/** Gets a callable that is considered a "safe" external API from a security perspective. */
|
||||
abstract DataFlowPrivate::DataFlowCallable getSafeCallable();
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for SafeExternalApi */
|
||||
deprecated class SafeExternalAPI = SafeExternalApi;
|
||||
|
||||
/** The default set of "safe" external APIs. */
|
||||
private class DefaultSafeExternalAPI extends SafeExternalAPI {
|
||||
private class DefaultSafeExternalApi extends SafeExternalApi {
|
||||
override DataFlowPrivate::DataFlowCallable getSafeCallable() {
|
||||
exists(CallableValue cv | cv = result.getCallableValue() |
|
||||
cv = Value::named(["len", "isinstance", "getattr", "hasattr"])
|
||||
@@ -61,15 +64,15 @@ private class DefaultSafeExternalAPI extends SafeExternalAPI {
|
||||
}
|
||||
|
||||
/** A node representing data being passed to an external API through a call. */
|
||||
class ExternalAPIDataNode extends DataFlow::Node {
|
||||
class ExternalApiDataNode extends DataFlow::Node {
|
||||
DataFlowPrivate::DataFlowCall call;
|
||||
DataFlowPrivate::DataFlowCallable callable;
|
||||
int i;
|
||||
|
||||
ExternalAPIDataNode() {
|
||||
ExternalApiDataNode() {
|
||||
exists(call.getLocation().getFile().getRelativePath()) and
|
||||
callable = call.getCallable() and
|
||||
not any(SafeExternalAPI safe).getSafeCallable() = callable and
|
||||
not any(SafeExternalApi safe).getSafeCallable() = callable and
|
||||
exists(Value cv | cv = callable.getCallableValue() |
|
||||
cv.isAbsent()
|
||||
or
|
||||
@@ -98,38 +101,47 @@ class ExternalAPIDataNode extends DataFlow::Node {
|
||||
DataFlowPrivate::DataFlowCallable getCallable() { result = callable }
|
||||
}
|
||||
|
||||
/** A configuration for tracking flow from `RemoteFlowSource`s to `ExternalAPIDataNode`s. */
|
||||
class UntrustedDataToExternalAPIConfig extends TaintTracking::Configuration {
|
||||
UntrustedDataToExternalAPIConfig() { this = "UntrustedDataToExternalAPIConfig" }
|
||||
/** DEPRECATED: Alias for ExternalApiDataNode */
|
||||
deprecated class ExternalAPIDataNode = ExternalApiDataNode;
|
||||
|
||||
/** A configuration for tracking flow from `RemoteFlowSource`s to `ExternalApiDataNode`s. */
|
||||
class UntrustedDataToExternalApiConfig extends TaintTracking::Configuration {
|
||||
UntrustedDataToExternalApiConfig() { this = "UntrustedDataToExternalAPIConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof ExternalAPIDataNode }
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof ExternalApiDataNode }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for UntrustedDataToExternalApiConfig */
|
||||
deprecated class UntrustedDataToExternalAPIConfig = UntrustedDataToExternalApiConfig;
|
||||
|
||||
/** A node representing untrusted data being passed to an external API. */
|
||||
class UntrustedExternalAPIDataNode extends ExternalAPIDataNode {
|
||||
UntrustedExternalAPIDataNode() { any(UntrustedDataToExternalAPIConfig c).hasFlow(_, this) }
|
||||
class UntrustedExternalApiDataNode extends ExternalApiDataNode {
|
||||
UntrustedExternalApiDataNode() { any(UntrustedDataToExternalApiConfig c).hasFlow(_, 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)
|
||||
any(UntrustedDataToExternalApiConfig c).hasFlow(result, this)
|
||||
}
|
||||
}
|
||||
|
||||
private newtype TExternalAPI =
|
||||
TExternalAPIParameter(DataFlowPrivate::DataFlowCallable callable, int index) {
|
||||
exists(UntrustedExternalAPIDataNode n |
|
||||
/** DEPRECATED: Alias for UntrustedExternalApiDataNode */
|
||||
deprecated class UntrustedExternalAPIDataNode = UntrustedExternalApiDataNode;
|
||||
|
||||
private newtype TExternalApi =
|
||||
TExternalApiParameter(DataFlowPrivate::DataFlowCallable callable, int index) {
|
||||
exists(UntrustedExternalApiDataNode n |
|
||||
callable = n.getCallable() and
|
||||
index = n.getIndex()
|
||||
)
|
||||
}
|
||||
|
||||
/** An external API which is used with untrusted data. */
|
||||
class ExternalAPIUsedWithUntrustedData extends TExternalAPI {
|
||||
class ExternalApiUsedWithUntrustedData extends TExternalApi {
|
||||
/** Gets a possibly untrusted use of this external API. */
|
||||
UntrustedExternalAPIDataNode getUntrustedDataNode() {
|
||||
this = TExternalAPIParameter(result.getCallable(), result.getIndex())
|
||||
UntrustedExternalApiDataNode getUntrustedDataNode() {
|
||||
this = TExternalApiParameter(result.getCallable(), result.getIndex())
|
||||
}
|
||||
|
||||
/** Gets the number of untrusted sources used with this external API. */
|
||||
@@ -143,7 +155,7 @@ class ExternalAPIUsedWithUntrustedData extends TExternalAPI {
|
||||
DataFlowPrivate::DataFlowCallable callable, int index, string callableString,
|
||||
string indexString
|
||||
|
|
||||
this = TExternalAPIParameter(callable, index) and
|
||||
this = TExternalApiParameter(callable, index) and
|
||||
indexString = "param " + index and
|
||||
exists(CallableValue cv | cv = callable.getCallableValue() |
|
||||
callableString =
|
||||
@@ -167,6 +179,9 @@ class ExternalAPIUsedWithUntrustedData extends TExternalAPI {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for ExternalApiUsedWithUntrustedData */
|
||||
deprecated class ExternalAPIUsedWithUntrustedData = ExternalApiUsedWithUntrustedData;
|
||||
|
||||
/** Gets the fully qualified name for the `BuiltinFunctionValue` bfv. */
|
||||
private string pretty_builtin_function_value(BuiltinFunctionValue bfv) {
|
||||
exists(Builtin b | b = bfv.(BuiltinFunctionObjectInternal).getBuiltin() |
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import python
|
||||
import ExternalAPIs
|
||||
|
||||
from ExternalAPIUsedWithUntrustedData externalAPI
|
||||
select externalAPI, count(externalAPI.getUntrustedDataNode()) as numberOfUses,
|
||||
externalAPI.getNumberOfUntrustedSources() as numberOfUntrustedSources order by
|
||||
from ExternalApiUsedWithUntrustedData externalApi
|
||||
select externalApi, count(externalApi.getUntrustedDataNode()) as numberOfUses,
|
||||
externalApi.getNumberOfUntrustedSources() as numberOfUntrustedSources order by
|
||||
numberOfUntrustedSources desc
|
||||
|
||||
@@ -14,11 +14,11 @@ import ExternalAPIs
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from
|
||||
UntrustedDataToExternalAPIConfig config, DataFlow::PathNode source, DataFlow::PathNode sink,
|
||||
ExternalAPIUsedWithUntrustedData externalAPI
|
||||
UntrustedDataToExternalApiConfig config, DataFlow::PathNode source, DataFlow::PathNode sink,
|
||||
ExternalApiUsedWithUntrustedData externalApi
|
||||
where
|
||||
sink.getNode() = externalAPI.getUntrustedDataNode() and
|
||||
sink.getNode() = externalApi.getUntrustedDataNode() and
|
||||
config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"Call to " + externalAPI.toString() + " with untrusted data from $@.", source.getNode(),
|
||||
"Call to " + externalApi.toString() + " with untrusted data from $@.", source.getNode(),
|
||||
source.toString()
|
||||
|
||||
202
python/ql/src/Security/CWE-020/HostnameRegexpShared.qll
Normal file
202
python/ql/src/Security/CWE-020/HostnameRegexpShared.qll
Normal file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* Provides predicates for reasoning about regular expressions
|
||||
* that match URLs and hostname patterns.
|
||||
*/
|
||||
|
||||
private import HostnameRegexpSpecific
|
||||
|
||||
/**
|
||||
* Holds if the given constant is unlikely to occur in the origin part of a URL.
|
||||
*/
|
||||
predicate isConstantInvalidInsideOrigin(RegExpConstant term) {
|
||||
// Look for any of these cases:
|
||||
// - A character that can't occur in the origin
|
||||
// - Two dashes in a row
|
||||
// - A colon that is not part of port or scheme separator
|
||||
// - A slash that is not part of scheme separator
|
||||
term.getValue().regexpMatch(".*(?:[^a-zA-Z0-9.:/-]|--|:[^0-9/]|(?<![/:]|^)/).*")
|
||||
}
|
||||
|
||||
/** Holds if `term` is a dot constant of form `\.` or `[.]`. */
|
||||
predicate isDotConstant(RegExpTerm term) {
|
||||
term.(RegExpCharEscape).getValue() = "."
|
||||
or
|
||||
exists(RegExpCharacterClass cls |
|
||||
term = cls and
|
||||
not cls.isInverted() and
|
||||
cls.getNumChild() = 1 and
|
||||
cls.getAChild().(RegExpConstant).getValue() = "."
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `term` is a wildcard `.` or an actual `.` character. */
|
||||
predicate isDotLike(RegExpTerm term) {
|
||||
term instanceof RegExpDot
|
||||
or
|
||||
isDotConstant(term)
|
||||
}
|
||||
|
||||
/** Holds if `term` will only ever be matched against the beginning of the input. */
|
||||
predicate matchesBeginningOfString(RegExpTerm term) {
|
||||
term.isRootTerm()
|
||||
or
|
||||
exists(RegExpTerm parent | matchesBeginningOfString(parent) |
|
||||
term = parent.(RegExpSequence).getChild(0)
|
||||
or
|
||||
parent.(RegExpSequence).getChild(0) instanceof RegExpCaret and
|
||||
term = parent.(RegExpSequence).getChild(1)
|
||||
or
|
||||
term = parent.(RegExpAlt).getAChild()
|
||||
or
|
||||
term = parent.(RegExpGroup).getAChild()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the given sequence contains top-level domain preceded by a dot, such as `.com`,
|
||||
* excluding cases where this is at the very beginning of the regexp.
|
||||
*
|
||||
* `i` is bound to the index of the last child in the top-level domain part.
|
||||
*/
|
||||
predicate hasTopLevelDomainEnding(RegExpSequence seq, int i) {
|
||||
seq.getChild(i)
|
||||
.(RegExpConstant)
|
||||
.getValue()
|
||||
.regexpMatch("(?i)" + RegExpPatterns::getACommonTld() + "(:\\d+)?([/?#].*)?") and
|
||||
isDotLike(seq.getChild(i - 1)) and
|
||||
not (i = 1 and matchesBeginningOfString(seq))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the given regular expression term contains top-level domain preceded by a dot,
|
||||
* such as `.com`.
|
||||
*/
|
||||
predicate hasTopLevelDomainEnding(RegExpSequence seq) { hasTopLevelDomainEnding(seq, _) }
|
||||
|
||||
/**
|
||||
* Holds if `term` will always match a hostname, that is, all disjunctions contain
|
||||
* a hostname pattern that isn't inside a quantifier.
|
||||
*/
|
||||
predicate alwaysMatchesHostname(RegExpTerm term) {
|
||||
hasTopLevelDomainEnding(term, _)
|
||||
or
|
||||
// `localhost` is considered a hostname pattern, but has no TLD
|
||||
term.(RegExpConstant).getValue().regexpMatch("\\blocalhost\\b")
|
||||
or
|
||||
not term instanceof RegExpAlt and
|
||||
not term instanceof RegExpQuantifier and
|
||||
alwaysMatchesHostname(term.getAChild())
|
||||
or
|
||||
alwaysMatchesHostnameAlt(term)
|
||||
}
|
||||
|
||||
/** Holds if every child of `alt` contains a hostname pattern. */
|
||||
predicate alwaysMatchesHostnameAlt(RegExpAlt alt) {
|
||||
alwaysMatchesHostnameAlt(alt, alt.getNumChild() - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the first `i` children of `alt` contains a hostname pattern.
|
||||
*
|
||||
* This is used instead of `forall` to avoid materializing the set of alternatives
|
||||
* that don't contains hostnames, which is much larger.
|
||||
*/
|
||||
predicate alwaysMatchesHostnameAlt(RegExpAlt alt, int i) {
|
||||
alwaysMatchesHostname(alt.getChild(0)) and i = 0
|
||||
or
|
||||
alwaysMatchesHostnameAlt(alt, i - 1) and
|
||||
alwaysMatchesHostname(alt.getChild(i))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `term` occurs inside a quantifier or alternative (and thus
|
||||
* can not be expected to correspond to a unique match), or as part of
|
||||
* a lookaround assertion (which are rarely used for capture groups).
|
||||
*/
|
||||
predicate isInsideChoiceOrSubPattern(RegExpTerm term) {
|
||||
exists(RegExpParent parent | parent = term.getParent() |
|
||||
parent instanceof RegExpAlt
|
||||
or
|
||||
parent instanceof RegExpQuantifier
|
||||
or
|
||||
parent instanceof RegExpSubPattern
|
||||
or
|
||||
isInsideChoiceOrSubPattern(parent)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `group` is likely to be used as a capture group.
|
||||
*/
|
||||
predicate isLikelyCaptureGroup(RegExpGroup group) {
|
||||
group.isCapture() and
|
||||
not isInsideChoiceOrSubPattern(group)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `seq` contains two consecutive dots `..` or escaped dots.
|
||||
*
|
||||
* At least one of these dots is not intended to be a subdomain separator,
|
||||
* so we avoid flagging the pattern in this case.
|
||||
*/
|
||||
predicate hasConsecutiveDots(RegExpSequence seq) {
|
||||
exists(int i |
|
||||
isDotLike(seq.getChild(i)) and
|
||||
isDotLike(seq.getChild(i + 1))
|
||||
)
|
||||
}
|
||||
|
||||
predicate isIncompleteHostNameRegExpPattern(RegExpTerm regexp, RegExpSequence seq, string msg) {
|
||||
seq = regexp.getAChild*() and
|
||||
exists(RegExpDot unescapedDot, int i, string hostname |
|
||||
hasTopLevelDomainEnding(seq, i) and
|
||||
not isConstantInvalidInsideOrigin(seq.getChild([0 .. i - 1]).getAChild*()) and
|
||||
not isLikelyCaptureGroup(seq.getChild([i .. seq.getNumChild() - 1]).getAChild*()) and
|
||||
unescapedDot = seq.getChild([0 .. i - 1]).getAChild*() and
|
||||
unescapedDot != seq.getChild(i - 1) and // Should not be the '.' immediately before the TLD
|
||||
not hasConsecutiveDots(unescapedDot.getParent()) and
|
||||
hostname =
|
||||
seq.getChild(i - 2).getRawValue() + seq.getChild(i - 1).getRawValue() +
|
||||
seq.getChild(i).getRawValue()
|
||||
|
|
||||
if unescapedDot.getParent() instanceof RegExpQuantifier
|
||||
then
|
||||
// `.*\.example.com` can match `evil.com/?x=.example.com`
|
||||
//
|
||||
// This problem only occurs when the pattern is applied against a full URL, not just a hostname/origin.
|
||||
// We therefore check if the pattern includes a suffix after the TLD, such as `.*\.example.com/`.
|
||||
// Note that a post-anchored pattern (`.*\.example.com$`) will usually fail to match a full URL,
|
||||
// and patterns with neither a suffix nor an anchor fall under the purview of MissingRegExpAnchor.
|
||||
seq.getChild(0) instanceof RegExpCaret and
|
||||
not seq.getAChild() instanceof RegExpDollar and
|
||||
seq.getChild([i .. i + 1]).(RegExpConstant).getValue().regexpMatch(".*[/?#].*") and
|
||||
msg =
|
||||
"has an unrestricted wildcard '" + unescapedDot.getParent().(RegExpQuantifier).getRawValue()
|
||||
+ "' which may cause '" + hostname +
|
||||
"' to be matched anywhere in the URL, outside the hostname."
|
||||
else
|
||||
msg =
|
||||
"has an unescaped '.' before '" + hostname +
|
||||
"', so it might match more hosts than expected."
|
||||
)
|
||||
}
|
||||
|
||||
predicate incompleteHostnameRegExp(
|
||||
RegExpSequence hostSequence, string message, DataFlow::Node aux, string label
|
||||
) {
|
||||
exists(RegExpPatternSource re, RegExpTerm regexp, string msg, string kind |
|
||||
regexp = re.getRegExpTerm() and
|
||||
isIncompleteHostNameRegExpPattern(regexp, hostSequence, msg) and
|
||||
(
|
||||
if re.getAParse() != re
|
||||
then (
|
||||
kind = "string, which is used as a regular expression $@," and
|
||||
aux = re.getAParse()
|
||||
) else (
|
||||
kind = "regular expression" and aux = re
|
||||
)
|
||||
)
|
||||
|
|
||||
message = "This " + kind + " " + msg and label = "here"
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import semmle.python.RegexTreeView
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.Regexp
|
||||
@@ -30,7 +30,7 @@
|
||||
<p>
|
||||
|
||||
Escape all meta-characters appropriately when constructing
|
||||
regular expressions for security checks, pay special attention to the
|
||||
regular expressions for security checks, and pay special attention to the
|
||||
<code>.</code> meta-character.
|
||||
|
||||
</p>
|
||||
|
||||
@@ -8,35 +8,9 @@
|
||||
* @id py/incomplete-hostname-regexp
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-20
|
||||
* external/cwe/cwe-020
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.regex
|
||||
import HostnameRegexpShared
|
||||
|
||||
private string commonTopLevelDomainRegex() { result = "com|org|edu|gov|uk|net|io" }
|
||||
|
||||
/**
|
||||
* Holds if `pattern` is a regular expression pattern for URLs with a host matched by `hostPart`,
|
||||
* and `pattern` contains a subtle mistake that allows it to match unexpected hosts.
|
||||
*/
|
||||
bindingset[pattern]
|
||||
predicate isIncompleteHostNameRegExpPattern(string pattern, string hostPart) {
|
||||
hostPart =
|
||||
pattern
|
||||
.regexpCapture("(?i).*" +
|
||||
// an unescaped single `.`
|
||||
"(?<!\\\\)[.]" +
|
||||
// immediately followed by a sequence of subdomains, perhaps with some regex characters mixed in, followed by a known TLD
|
||||
"([():|?a-z0-9-]+(\\\\)?[.](" + commonTopLevelDomainRegex() + "))" + ".*", 1)
|
||||
}
|
||||
|
||||
from Regex r, string pattern, string hostPart
|
||||
where
|
||||
r.getText() = pattern and
|
||||
isIncompleteHostNameRegExpPattern(pattern, hostPart) and
|
||||
// ignore patterns with capture groups after the TLD
|
||||
not pattern.regexpMatch("(?i).*[.](" + commonTopLevelDomainRegex() + ").*[(][?]:.*[)].*")
|
||||
select r,
|
||||
"This regular expression has an unescaped '.' before '" + hostPart +
|
||||
"', so it might match more hosts than expected."
|
||||
query predicate problems = incompleteHostnameRegExp/4;
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.PathInjection
|
||||
import semmle.python.security.dataflow.PathInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from PathInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This path depends on $@.", source.getNode(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -122,7 +122,7 @@ class ExtractMembersSink extends TaintSink {
|
||||
class TarFileInfoSanitizer extends Sanitizer {
|
||||
TarFileInfoSanitizer() { this = "TarInfo sanitizer" }
|
||||
|
||||
/** The test `if <path_sanitizing_test>:` clears taint on its `false` edge. */
|
||||
/* The test `if <path_sanitizing_test>:` clears taint on its `false` edge. */
|
||||
override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) {
|
||||
taint instanceof TarFileInfo and
|
||||
clears_taint_on_false_edge(test.getTest(), test.getSense())
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.CommandInjection
|
||||
import semmle.python.security.dataflow.CommandInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from CommandInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This command depends on $@.", source.getNode(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.ReflectedXSS
|
||||
import semmle.python.security.dataflow.ReflectedXssQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from ReflectedXSS::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.",
|
||||
source.getNode(), "a user-provided value"
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.SqlInjection
|
||||
import semmle.python.security.dataflow.SqlInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from SqlInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This SQL query depends on $@.", source.getNode(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -13,15 +13,15 @@
|
||||
|
||||
// Determine precision above
|
||||
import python
|
||||
import semmle.python.security.dataflow.LdapInjection
|
||||
import semmle.python.security.dataflow.LdapInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from DataFlow::PathNode source, DataFlow::PathNode sink, string parameterName
|
||||
where
|
||||
any(LdapInjection::DnConfiguration dnConfig).hasFlowPath(source, sink) and
|
||||
any(DnConfiguration dnConfig).hasFlowPath(source, sink) and
|
||||
parameterName = "DN"
|
||||
or
|
||||
any(LdapInjection::FilterConfiguration filterConfig).hasFlowPath(source, sink) and
|
||||
any(FilterConfiguration filterConfig).hasFlowPath(source, sink) and
|
||||
parameterName = "filter"
|
||||
select sink.getNode(), source, sink,
|
||||
"$@ LDAP query parameter (" + parameterName + ") comes from $@.", sink.getNode(), "This",
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.CodeInjection
|
||||
import semmle.python.security.dataflow.CodeInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from CodeInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ flows to here and is interpreted as code.",
|
||||
source.getNode(), "A user-provided value"
|
||||
|
||||
@@ -16,6 +16,6 @@
|
||||
|
||||
import semmle.python.security.BadTagFilterQuery
|
||||
|
||||
from HTMLMatchingRegExp regexp, string msg
|
||||
from HtmlMatchingRegExp regexp, string msg
|
||||
where msg = min(string m | isBadRegexpFilter(regexp, m) | m order by m.length(), m) // there might be multiple, we arbitrarily pick the shortest one
|
||||
select regexp, msg
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.LogInjection
|
||||
import semmle.python.security.dataflow.LogInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from LogInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ flows to log entry.", source.getNode(),
|
||||
"User-provided value"
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.StackTraceExposure
|
||||
import semmle.python.security.dataflow.StackTraceExposureQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from StackTraceExposure::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ may be exposed to an external user", source.getNode(),
|
||||
"Error information"
|
||||
|
||||
@@ -27,9 +27,9 @@ private DataFlow::TypeTrackingNode truthyLiteral(DataFlow::TypeTracker t) {
|
||||
/** Gets a reference to a truthy literal. */
|
||||
DataFlow::Node truthyLiteral() { truthyLiteral(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
from DataFlow::CallCfgNode call, DataFlow::Node debugArg
|
||||
from API::CallNode call, DataFlow::Node debugArg
|
||||
where
|
||||
call.getFunction() = Flask::FlaskApp::instance().getMember("run").getAUse() and
|
||||
call = Flask::FlaskApp::instance().getMember("run").getACall() and
|
||||
debugArg in [call.getArg(2), call.getArgByName("debug")] and
|
||||
debugArg = truthyLiteral()
|
||||
select call,
|
||||
|
||||
@@ -15,41 +15,11 @@ private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* Gets a call to a method that makes an outgoing request using the `requests` module,
|
||||
* such as `requests.get` or `requests.put`, with the specified HTTP verb `verb`
|
||||
*/
|
||||
DataFlow::CallCfgNode outgoingRequestCall(string verb) {
|
||||
verb = HTTP::httpVerbLower() and
|
||||
result = API::moduleImport("requests").getMember(verb).getACall()
|
||||
}
|
||||
|
||||
/** Gets the "verfiy" argument to a outgoingRequestCall. */
|
||||
DataFlow::Node verifyArg(DataFlow::CallCfgNode call) {
|
||||
call = outgoingRequestCall(_) and
|
||||
result = call.getArgByName("verify")
|
||||
}
|
||||
|
||||
/** Gets a back-reference to the verify argument `arg`. */
|
||||
private DataFlow::TypeTrackingNode verifyArgBacktracker(
|
||||
DataFlow::TypeBackTracker t, DataFlow::Node arg
|
||||
) {
|
||||
t.start() and
|
||||
arg = verifyArg(_) and
|
||||
result = arg.getALocalSource()
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 | result = verifyArgBacktracker(t2, arg).backtrack(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a back-reference to the verify argument `arg`. */
|
||||
DataFlow::LocalSourceNode verifyArgBacktracker(DataFlow::Node arg) {
|
||||
result = verifyArgBacktracker(DataFlow::TypeBackTracker::end(), arg)
|
||||
}
|
||||
|
||||
from DataFlow::CallCfgNode call, DataFlow::Node falseyOrigin, string verb
|
||||
from API::CallNode call, DataFlow::Node falseyOrigin, string verb
|
||||
where
|
||||
call = outgoingRequestCall(verb) and
|
||||
falseyOrigin = verifyArgBacktracker(verifyArg(call)) and
|
||||
verb = HTTP::httpVerbLower() and
|
||||
call = API::moduleImport("requests").getMember(verb).getACall() and
|
||||
falseyOrigin = call.getKeywordParameter("verify").getAValueReachingRhs() and
|
||||
// requests treats `None` as the default and all other "falsey" values as `False`.
|
||||
falseyOrigin.asExpr().(ImmutableLiteral).booleanValue() = false and
|
||||
not falseyOrigin.asExpr() instanceof None
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
import DataFlow::PathGraph
|
||||
import semmle.python.security.dataflow.CleartextLogging::CleartextLogging
|
||||
import semmle.python.security.dataflow.CleartextLoggingQuery
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, string classification
|
||||
where
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
import DataFlow::PathGraph
|
||||
import semmle.python.security.dataflow.CleartextStorage::CleartextStorage
|
||||
import semmle.python.security.dataflow.CleartextStorageQuery
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, string classification
|
||||
where
|
||||
|
||||
@@ -85,7 +85,7 @@ abstract class TlsLibrary extends string {
|
||||
bindingset[this]
|
||||
TlsLibrary() { any() }
|
||||
|
||||
/** The name of a specific protocol version. */
|
||||
/** Gets the name of a specific protocol version. */
|
||||
abstract string specific_version_name(ProtocolVersion version);
|
||||
|
||||
/** Gets a name, which is a member of `version_constants`, that can be used to specify the protocol family `family`. */
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.WeakSensitiveDataHashing
|
||||
import semmle.python.security.dataflow.WeakSensitiveDataHashingQuery
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import DataFlow::PathGraph
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.UnsafeDeserialization
|
||||
import semmle.python.security.dataflow.UnsafeDeserializationQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from UnsafeDeserialization::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Deserializing of $@.", source.getNode(), "untrusted input"
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.UrlRedirect
|
||||
import semmle.python.security.dataflow.UrlRedirectQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from UrlRedirect::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Untrusted URL redirection due to $@.", source.getNode(),
|
||||
"A user-provided value"
|
||||
|
||||
74
python/ql/src/Security/CWE-611/Xxe.qhelp
Normal file
74
python/ql/src/Security/CWE-611/Xxe.qhelp
Normal file
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Parsing untrusted XML files with a weakly configured XML parser may lead to an
|
||||
XML External Entity (XXE) attack. This type of attack uses external entity references
|
||||
to access arbitrary files on a system, carry out denial-of-service (DoS) attacks, or server-side
|
||||
request forgery. Even when the result of parsing is not returned to the user, DoS attacks are still possible
|
||||
and out-of-band data retrieval techniques may allow attackers to steal sensitive data.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
The easiest way to prevent XXE attacks is to disable external entity handling when
|
||||
parsing untrusted data. How this is done depends on the library being used. Note that some
|
||||
libraries, such as recent versions of the XML libraries in the standard library of Python 3,
|
||||
disable entity expansion by default,
|
||||
so unless you have explicitly enabled entity expansion, no further action needs to be taken.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We recommend using the <a href="https://pypi.org/project/defusedxml/">defusedxml</a>
|
||||
PyPI package, which has been created to prevent XML attacks (both XXE and XML bombs).
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example uses the <code>lxml</code> XML parser to parse a string
|
||||
<code>xml_src</code>. That string is from an untrusted source, so this code is
|
||||
vulnerable to an XXE attack, since the <a href="https://lxml.de/apidoc/lxml.etree.html#lxml.etree.XMLParser">
|
||||
default parser</a> from <code>lxml.etree</code> allows local external entities to be resolved.
|
||||
</p>
|
||||
<sample src="examples/XxeBad.py"/>
|
||||
|
||||
<p>
|
||||
To guard against XXE attacks with the <code>lxml</code> library, you should create a
|
||||
parser with <code>resolve_entities</code> set to <code>false</code>. This means that no
|
||||
entity expansion is undertaken, althuogh standard predefined entities such as
|
||||
<code>&gt;</code>, for writing <code>></code> inside the text of an XML element,
|
||||
are still allowed.
|
||||
</p>
|
||||
<sample src="examples/XxeGood.py"/>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing">XML External Entity (XXE) Processing</a>.
|
||||
</li>
|
||||
<li>
|
||||
Timothy Morgen:
|
||||
<a href="https://research.nccgroup.com/2014/05/19/xml-schema-dtd-and-entity-attacks-a-compendium-of-known-techniques/">XML Schema, DTD, and Entity Attacks</a>.
|
||||
</li>
|
||||
<li>
|
||||
Timur Yunusov, Alexey Osipov:
|
||||
<a href="https://www.slideshare.net/qqlan/bh-ready-v4">XML Out-Of-Band Data Retrieval</a>.
|
||||
</li>
|
||||
<li>
|
||||
Python 3 standard library:
|
||||
<a href="https://docs.python.org/3/library/xml.html#xml-vulnerabilities">XML Vulnerabilities</a>.
|
||||
</li>
|
||||
<li>
|
||||
Python 2 standard library:
|
||||
<a href="https://docs.python.org/2/library/xml.html#xml-vulnerabilities">XML Vulnerabilities</a>.
|
||||
</li>
|
||||
<li>
|
||||
PortSwigger:
|
||||
<a href="https://portswigger.net/web-security/xxe">XML external entity (XXE) injection</a>.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
23
python/ql/src/Security/CWE-611/Xxe.ql
Normal file
23
python/ql/src/Security/CWE-611/Xxe.ql
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @name XML external entity expansion
|
||||
* @description Parsing user input as an XML document with external
|
||||
* entity expansion is vulnerable to XXE attacks.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 9.1
|
||||
* @precision high
|
||||
* @id py/xxe
|
||||
* @tags security
|
||||
* external/cwe/cwe-611
|
||||
* external/cwe/cwe-827
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.XxeQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"A $@ is parsed as XML without guarding against external entity expansion.", source.getNode(),
|
||||
"user-provided value"
|
||||
10
python/ql/src/Security/CWE-611/examples/XxeBad.py
Normal file
10
python/ql/src/Security/CWE-611/examples/XxeBad.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from flask import Flask, request
|
||||
import lxml.etree
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.post("/upload")
|
||||
def upload():
|
||||
xml_src = request.get_data()
|
||||
doc = lxml.etree.fromstring(xml_src)
|
||||
return lxml.etree.tostring(doc)
|
||||
11
python/ql/src/Security/CWE-611/examples/XxeGood.py
Normal file
11
python/ql/src/Security/CWE-611/examples/XxeGood.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from flask import Flask, request
|
||||
import lxml.etree
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.post("/upload")
|
||||
def upload():
|
||||
xml_src = request.get_data()
|
||||
parser = lxml.etree.XMLParser(resolve_entities=False)
|
||||
doc = lxml.etree.fromstring(xml_src, parser=parser)
|
||||
return lxml.etree.tostring(doc)
|
||||
24
python/ql/src/Security/CWE-643/XpathInjection.qhelp
Normal file
24
python/ql/src/Security/CWE-643/XpathInjection.qhelp
Normal file
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
If an XPath expression is built using string concatenation, and the components of the concatenation
|
||||
include user input, it makes it very easy for a user to create a malicious XPath expression.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
If user input must be included in an XPath expression, either sanitize the data or use variable
|
||||
references to safely embed it without altering the structure of the expression.
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In the example below, the xpath query is controlled by the user and hence leads to a vulnerability.</p>
|
||||
<sample src="xpathBad.py" />
|
||||
<p> This can be fixed by using a parameterized query as shown below.</p>
|
||||
<sample src="xpathGood.py" />
|
||||
</example>
|
||||
<references>
|
||||
<li>OWASP XPath injection : <a href="https://owasp.org/www-community/attacks/XPATH_Injection"></a>/>> </li>
|
||||
</references>
|
||||
</qhelp>
|
||||
20
python/ql/src/Security/CWE-643/XpathInjection.ql
Normal file
20
python/ql/src/Security/CWE-643/XpathInjection.ql
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @name XPath query built from user-controlled sources
|
||||
* @description Building a XPath query from user-controlled sources is vulnerable to insertion of
|
||||
* malicious Xpath code by the user.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 9.8
|
||||
* @precision high
|
||||
* @id py/xpath-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-643
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.XpathInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink, source, sink, "This Xpath query depends on $@.", source, "a user-provided value"
|
||||
@@ -1,6 +0,0 @@
|
||||
import python
|
||||
import semmle.python.security.performance.SuperlinearBackTracking
|
||||
|
||||
from PolynomialBackTrackingTerm t
|
||||
where t.getLocation().getFile().getBaseName() = "KnownCVEs.py"
|
||||
select t.getRegex(), t, t.getReason()
|
||||
@@ -14,12 +14,12 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.performance.SuperlinearBackTracking
|
||||
import semmle.python.security.dataflow.PolynomialReDoS
|
||||
import semmle.python.security.dataflow.PolynomialReDoSQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from
|
||||
PolynomialReDoS::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
|
||||
PolynomialReDoS::Sink sinkNode, PolynomialBackTrackingTerm regexp
|
||||
Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode,
|
||||
PolynomialBackTrackingTerm regexp
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
sinkNode = sink.getNode() and
|
||||
|
||||
@@ -14,15 +14,15 @@
|
||||
|
||||
import python
|
||||
private import semmle.python.Concepts
|
||||
import semmle.python.security.dataflow.RegexInjection
|
||||
import semmle.python.security.dataflow.RegexInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from
|
||||
RegexInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
|
||||
Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
|
||||
RegexExecution regexExecution
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
regexExecution = sink.getNode().(RegexInjection::Sink).getRegexExecution()
|
||||
regexExecution = sink.getNode().(Sink).getRegexExecution()
|
||||
select sink.getNode(), source, sink,
|
||||
"$@ regular expression is constructed from a $@ and executed by $@.", sink.getNode(), "This",
|
||||
source.getNode(), "user-provided value", regexExecution, regexExecution.getName()
|
||||
|
||||
74
python/ql/src/Security/CWE-776/XmlBomb.qhelp
Normal file
74
python/ql/src/Security/CWE-776/XmlBomb.qhelp
Normal file
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Parsing untrusted XML files with a weakly configured XML parser may be vulnerable to
|
||||
denial-of-service (DoS) attacks exploiting uncontrolled internal entity expansion.
|
||||
</p>
|
||||
<p>
|
||||
In XML, so-called <i>internal entities</i> are a mechanism for introducing an abbreviation
|
||||
for a piece of text or part of a document. When a parser that has been configured
|
||||
to expand entities encounters a reference to an internal entity, it replaces the entity
|
||||
by the data it represents. The replacement text may itself contain other entity references,
|
||||
which are expanded recursively. This means that entity expansion can increase document size
|
||||
dramatically.
|
||||
</p>
|
||||
<p>
|
||||
If untrusted XML is parsed with entity expansion enabled, a malicious attacker could
|
||||
submit a document that contains very deeply nested entity definitions, causing the parser
|
||||
to take a very long time or use large amounts of memory. This is sometimes called an
|
||||
<i>XML bomb</i> attack.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
The safest way to prevent XML bomb attacks is to disable entity expansion when parsing untrusted
|
||||
data. Whether this can be done depends on the library being used. Note that some libraries, such as
|
||||
<code>lxml</code>, have measures enabled by default to prevent such DoS XML attacks, so
|
||||
unless you have explicitly set <code>huge_tree</code> to <code>True</code>, no further action is needed.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We recommend using the <a href="https://pypi.org/project/defusedxml/">defusedxml</a>
|
||||
PyPI package, which has been created to prevent XML attacks (both XXE and XML bombs).
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example uses the <code>xml.etree</code> XML parser provided by the Python standard library to
|
||||
parse a string <code>xml_src</code>. That string is from an untrusted source, so this code is
|
||||
vulnerable to a DoS attack, since the <code>xml.etree</code> XML parser expands internal entities by default:
|
||||
</p>
|
||||
<sample src="examples/XmlBombBad.py"/>
|
||||
|
||||
<p>
|
||||
It is not possible to guard against internal entity expansion with
|
||||
<code>xml.etree</code>, so to guard against these attacks, the following example uses
|
||||
the <a href="https://pypi.org/project/defusedxml/">defusedxml</a>
|
||||
PyPI package instead, which is not exposed to such internal entity expansion attacks.
|
||||
</p>
|
||||
<sample src="examples/XmlBombGood.py"/>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
Wikipedia:
|
||||
<a href="https://en.wikipedia.org/wiki/Billion_laughs">Billion Laughs</a>.
|
||||
</li>
|
||||
<li>
|
||||
Bryan Sullivan:
|
||||
<a href="https://msdn.microsoft.com/en-us/magazine/ee335713.aspx">Security Briefs - XML Denial of Service Attacks and Defenses</a>.
|
||||
</li>
|
||||
<li>
|
||||
Python 3 standard library:
|
||||
<a href="https://docs.python.org/3/library/xml.html#xml-vulnerabilities">XML Vulnerabilities</a>.
|
||||
</li>
|
||||
<li>
|
||||
Python 2 standard library:
|
||||
<a href="https://docs.python.org/2/library/xml.html#xml-vulnerabilities">XML Vulnerabilities</a>.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
23
python/ql/src/Security/CWE-776/XmlBomb.ql
Normal file
23
python/ql/src/Security/CWE-776/XmlBomb.ql
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @name XML internal entity expansion
|
||||
* @description Parsing user input as an XML document with arbitrary internal
|
||||
* entity expansion is vulnerable to denial-of-service attacks.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 7.5
|
||||
* @precision high
|
||||
* @id py/xml-bomb
|
||||
* @tags security
|
||||
* external/cwe/cwe-776
|
||||
* external/cwe/cwe-400
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.XmlBombQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"A $@ is parsed as XML without guarding against uncontrolled entity expansion.", source.getNode(),
|
||||
"user-provided value"
|
||||
10
python/ql/src/Security/CWE-776/examples/XmlBombBad.py
Normal file
10
python/ql/src/Security/CWE-776/examples/XmlBombBad.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from flask import Flask, request
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.post("/upload")
|
||||
def upload():
|
||||
xml_src = request.get_data()
|
||||
doc = ET.fromstring(xml_src)
|
||||
return ET.tostring(doc)
|
||||
10
python/ql/src/Security/CWE-776/examples/XmlBombGood.py
Normal file
10
python/ql/src/Security/CWE-776/examples/XmlBombGood.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from flask import Flask, request
|
||||
import defusedxml.ElementTree as ET
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.post("/upload")
|
||||
def upload():
|
||||
xml_src = request.get_data()
|
||||
doc = ET.fromstring(xml_src)
|
||||
return ET.tostring(doc)
|
||||
@@ -11,14 +11,14 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.ServerSideRequestForgery
|
||||
import semmle.python.security.dataflow.ServerSideRequestForgeryQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from
|
||||
FullServerSideRequestForgery::Configuration fullConfig, DataFlow::PathNode source,
|
||||
FullServerSideRequestForgeryConfiguration fullConfig, DataFlow::PathNode source,
|
||||
DataFlow::PathNode sink, HTTP::Client::Request request
|
||||
where
|
||||
request = sink.getNode().(FullServerSideRequestForgery::Sink).getRequest() and
|
||||
request = sink.getNode().(Sink).getRequest() and
|
||||
fullConfig.hasFlowPath(source, sink) and
|
||||
fullyControlledRequest(request)
|
||||
select request, source, sink, "The full URL of this request depends on $@.", source.getNode(),
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.ServerSideRequestForgery
|
||||
import semmle.python.security.dataflow.ServerSideRequestForgeryQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from
|
||||
PartialServerSideRequestForgery::Configuration partialConfig, DataFlow::PathNode source,
|
||||
PartialServerSideRequestForgeryConfiguration partialConfig, DataFlow::PathNode source,
|
||||
DataFlow::PathNode sink, HTTP::Client::Request request
|
||||
where
|
||||
request = sink.getNode().(PartialServerSideRequestForgery::Sink).getRequest() and
|
||||
request = sink.getNode().(Sink).getRequest() and
|
||||
partialConfig.hasFlowPath(source, sink) and
|
||||
not fullyControlledRequest(request)
|
||||
select request, source, sink, "Part of the URL of this request depends on $@.", source.getNode(),
|
||||
|
||||
@@ -14,6 +14,14 @@
|
||||
|
||||
import python
|
||||
|
||||
predicate isInsideLoop(AstNode node) {
|
||||
node.getParentNode() instanceof While
|
||||
or
|
||||
node.getParentNode() instanceof For
|
||||
or
|
||||
exists(AstNode prev | isInsideLoop(prev) | node = prev.getAChildNode())
|
||||
}
|
||||
|
||||
from Delete del, Expr e, Function f
|
||||
where
|
||||
f.getLastStatement() = del and
|
||||
@@ -21,7 +29,7 @@ where
|
||||
f.containsInScope(e) and
|
||||
not e instanceof Subscript and
|
||||
not e instanceof Attribute and
|
||||
not exists(Stmt s | s.(While).contains(del) or s.(For).contains(del)) and
|
||||
not isInsideLoop(del) and
|
||||
// False positive: calling `sys.exc_info` within a function results in a
|
||||
// reference cycle, and an explicit call to `del` helps break this cycle.
|
||||
not exists(FunctionValue ex |
|
||||
|
||||
@@ -5,12 +5,12 @@ import python
|
||||
*/
|
||||
class Definition extends NameNode, DefinitionNode {
|
||||
/**
|
||||
* The variable defined by this control-flow node.
|
||||
* Gets the variable defined by this control-flow node.
|
||||
*/
|
||||
Variable getVariable() { this.defines(result) }
|
||||
|
||||
/**
|
||||
* The SSA variable corresponding to the current definition. Since SSA variables
|
||||
* Gets the SSA variable corresponding to the current definition. Since SSA variables
|
||||
* are only generated for definitions with at least one use, not all definitions
|
||||
* will have an SSA variable.
|
||||
*/
|
||||
@@ -67,7 +67,7 @@ class Definition extends NameNode, DefinitionNode {
|
||||
}
|
||||
|
||||
/**
|
||||
* An immediate re-definition of this definition's variable.
|
||||
* Gets an immediate re-definition of this definition's variable.
|
||||
*/
|
||||
Definition getARedef() {
|
||||
result != this and
|
||||
|
||||
@@ -47,6 +47,8 @@ predicate simple_literal(Expr e) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the redefinition is uninteresting.
|
||||
*
|
||||
* A multiple definition is 'uninteresting' if it sets a variable to a
|
||||
* simple literal before reassigning it.
|
||||
* x = None
|
||||
|
||||
@@ -17,30 +17,13 @@ import Shadowing
|
||||
import semmle.python.types.Builtins
|
||||
|
||||
predicate allow_list(string name) {
|
||||
/* These are rarely used and thus unlikely to be confusing */
|
||||
name = "iter" or
|
||||
name = "next" or
|
||||
name = "input" or
|
||||
name = "file" or
|
||||
name = "apply" or
|
||||
name = "slice" or
|
||||
name = "buffer" or
|
||||
name = "coerce" or
|
||||
name = "intern" or
|
||||
name = "exit" or
|
||||
name = "quit" or
|
||||
name = "license" or
|
||||
/* These are short and/or hard to avoid */
|
||||
name = "dir" or
|
||||
name = "id" or
|
||||
name = "max" or
|
||||
name = "min" or
|
||||
name = "sum" or
|
||||
name = "cmp" or
|
||||
name = "chr" or
|
||||
name = "ord" or
|
||||
name = "bytes" or
|
||||
name = "_"
|
||||
name in [
|
||||
/* These are rarely used and thus unlikely to be confusing */
|
||||
"iter", "next", "input", "file", "apply", "slice", "buffer", "coerce", "intern", "exit",
|
||||
"quit", "license",
|
||||
/* These are short and/or hard to avoid */
|
||||
"dir", "id", "max", "min", "sum", "cmp", "chr", "ord", "bytes", "_",
|
||||
]
|
||||
}
|
||||
|
||||
predicate shadows(Name d, string name, Function scope, int line) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import python
|
||||
import Loop
|
||||
import semmle.python.dataflow.TaintTracking
|
||||
|
||||
/** Marker for "uninitialized". */
|
||||
/** A marker for "uninitialized". */
|
||||
class Uninitialized extends TaintKind {
|
||||
Uninitialized() { this = "undefined" }
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ predicate file_consistency(string clsname, string problem, string what) {
|
||||
exists(Container f |
|
||||
clsname = f.getAQlClass() and
|
||||
uniqueness_error(count(f.toString()), "toString", problem) and
|
||||
what = "file " + f.getName()
|
||||
what = "file " + f.getAbsolutePath()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -467,10 +467,10 @@ Definition getUniqueDefinition(Expr use) {
|
||||
not result = TLocalDefinition(use)
|
||||
}
|
||||
|
||||
/** Helper class to get suitable locations for attributes */
|
||||
class NiceLocationExpr extends @py_expr {
|
||||
/** A helper class to get suitable locations for attributes */
|
||||
class NiceLocationExpr extends Expr {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = this.(Expr).toString() }
|
||||
override string toString() { result = this.(Expr).toString() }
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
|
||||
@@ -21,7 +21,7 @@ Ensure that all required modules and packages can be found when running the extr
|
||||
</recommendation>
|
||||
<references>
|
||||
|
||||
<li>Semmle Tutorial: <a href="https://help.semmle.com/codeql/codeql-cli/procedures/create-codeql-database.html">Creating a CodeQL database</a>.</li>
|
||||
<li>CodeQL Tutorial: <a href="https://codeql.github.com/docs/codeql-cli/creating-codeql-databases">Creating CodeQL databases</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
|
||||
@@ -58,7 +58,7 @@ predicate ok_to_fail(ImportExpr ie) {
|
||||
os_specific_import(ie) != get_os()
|
||||
}
|
||||
|
||||
class VersionTest extends @py_flow_node {
|
||||
class VersionTest extends ControlFlowNode {
|
||||
VersionTest() {
|
||||
exists(string name |
|
||||
name.matches("%version%") and
|
||||
@@ -66,7 +66,7 @@ class VersionTest extends @py_flow_node {
|
||||
)
|
||||
}
|
||||
|
||||
string toString() { result = "VersionTest" }
|
||||
override string toString() { result = "VersionTest" }
|
||||
}
|
||||
|
||||
/** A guard on the version of the Python interpreter */
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* "XML external entity expansion" (`py/xxe`). Results will appear by default. This query was based on [an experimental query by @jorgectf](https://github.com/github/codeql/pull/6112).
|
||||
* "XML internal entity expansion" (`py/xml-bomb`). Results will appear by default. This query was based on [an experimental query by @jorgectf](https://github.com/github/codeql/pull/6112).
|
||||
5
python/ql/src/change-notes/released/0.0.11.md
Normal file
5
python/ql/src/change-notes/released/0.0.11.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 0.0.11
|
||||
|
||||
### New Queries
|
||||
|
||||
* The query "XPath query built from user-controlled sources" (`py/xpath-injection`) has been promoted from experimental to the main query pack. Its results will now appear by default. This query was originally [submitted as an experimental query by @porcupineyhairs](https://github.com/github/codeql/pull/6331).
|
||||
1
python/ql/src/change-notes/released/0.0.12.md
Normal file
1
python/ql/src/change-notes/released/0.0.12.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.0.12
|
||||
1
python/ql/src/change-notes/released/0.0.13.md
Normal file
1
python/ql/src/change-notes/released/0.0.13.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.0.13
|
||||
1
python/ql/src/change-notes/released/0.1.0.md
Normal file
1
python/ql/src/change-notes/released/0.1.0.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.1.0
|
||||
1
python/ql/src/change-notes/released/0.1.1.md
Normal file
1
python/ql/src/change-notes/released/0.1.1.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.1.1
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.0.10
|
||||
lastReleaseVersion: 0.1.1
|
||||
|
||||
56
python/ql/src/experimental/Security/CWE-022/ZipSlip.qhelp
Normal file
56
python/ql/src/experimental/Security/CWE-022/ZipSlip.qhelp
Normal file
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>Extracting files from a malicious zip archive without validating that the destination file path
|
||||
is within the destination directory can cause files outside the destination directory to be
|
||||
overwritten, due to the possible presence of directory traversal elements (<code>..</code>) in
|
||||
archive paths.</p>
|
||||
|
||||
<p>Zip archives contain archive entries representing each file in the archive. These entries
|
||||
include a file path for the entry, but these file paths are not restricted and may contain
|
||||
unexpected special elements such as the directory traversal element (<code>..</code>). If these
|
||||
file paths are used to determine an output file to write the contents of the archive item to, then
|
||||
the file may be written to an unexpected location. This can result in sensitive information being
|
||||
revealed or deleted, or an attacker being able to influence behavior by modifying unexpected
|
||||
files.</p>
|
||||
|
||||
<p>For example, if a Zip archive contains a file entry <code>..\sneaky-file</code>, and the Zip archive
|
||||
is extracted to the directory <code>c:\output</code>, then naively combining the paths would result
|
||||
in an output file path of <code>c:\output\..\sneaky-file</code>, which would cause the file to be
|
||||
written to <code>c:\sneaky-file</code>.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Ensure that output paths constructed from Zip archive entries are validated
|
||||
to prevent writing files to unexpected locations.</p>
|
||||
|
||||
<p>The recommended way of writing an output file from a Zip archive entry is to call <code>extract()</code> or <code>extractall()</code>.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In this example an archive is extracted without validating file paths.
|
||||
</p>
|
||||
|
||||
<sample src="zipslip_bad.py" />
|
||||
|
||||
<p>To fix this vulnerability, we need to call the function <code>extractall()</code>.
|
||||
</p>
|
||||
|
||||
<sample src="zipslip_good.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
<li>
|
||||
Snyk:
|
||||
<a href="https://snyk.io/research/zip-slip-vulnerability">Zip Slip Vulnerability</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
22
python/ql/src/experimental/Security/CWE-022/ZipSlip.ql
Normal file
22
python/ql/src/experimental/Security/CWE-022/ZipSlip.ql
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @name Arbitrary file write during archive extraction ("Zip Slip")
|
||||
* @description Extracting files from a malicious archive without validating that the
|
||||
* destination file path is within the destination directory can cause files outside
|
||||
* the destination directory to be overwritten.
|
||||
* @kind path-problem
|
||||
* @id py/zipslip
|
||||
* @problem.severity error
|
||||
* @security-severity 7.5
|
||||
* @precision high
|
||||
* @tags security
|
||||
* external/cwe/cwe-022
|
||||
*/
|
||||
|
||||
import python
|
||||
import experimental.semmle.python.security.ZipSlip
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from ZipSlipConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Extraction of zipfile from $@", source.getNode(),
|
||||
"a potentially untrusted source"
|
||||
16
python/ql/src/experimental/Security/CWE-022/zipslip_bad.py
Normal file
16
python/ql/src/experimental/Security/CWE-022/zipslip_bad.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import zipfile
|
||||
import shutil
|
||||
|
||||
def unzip(filename):
|
||||
with tarfile.open(filename) as zipf:
|
||||
#BAD : This could write any file on the filesystem.
|
||||
for entry in zipf:
|
||||
shutil.copyfile(entry, "/tmp/unpack/")
|
||||
|
||||
def unzip4(filename):
|
||||
zf = zipfile.ZipFile(filename)
|
||||
filelist = zf.namelist()
|
||||
for x in filelist:
|
||||
with zf.open(x) as srcf:
|
||||
shutil.copyfileobj(srcf, dstfile)
|
||||
|
||||
10
python/ql/src/experimental/Security/CWE-022/zipslip_good.py
Normal file
10
python/ql/src/experimental/Security/CWE-022/zipslip_good.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import zipfile
|
||||
|
||||
def unzip(filename, dir):
|
||||
zf = zipfile.ZipFile(filename)
|
||||
zf.extractall(dir)
|
||||
|
||||
|
||||
def unzip1(filename, dir):
|
||||
zf = zipfile.ZipFile(filename)
|
||||
zf.extract(dir)
|
||||
@@ -13,7 +13,7 @@ import python
|
||||
import experimental.semmle.python.Concepts
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
|
||||
predicate authenticatesImproperly(LDAPBind ldapBind) {
|
||||
predicate authenticatesImproperly(LdapBind ldapBind) {
|
||||
(
|
||||
DataFlow::localFlow(DataFlow::exprNode(any(None noneName)), ldapBind.getPassword()) or
|
||||
not exists(ldapBind.getPassword())
|
||||
@@ -25,6 +25,6 @@ predicate authenticatesImproperly(LDAPBind ldapBind) {
|
||||
)
|
||||
}
|
||||
|
||||
from LDAPBind ldapBind
|
||||
from LdapBind ldapBind
|
||||
where authenticatesImproperly(ldapBind)
|
||||
select ldapBind, "The following LDAP bind operation is executed without authentication"
|
||||
|
||||
@@ -12,7 +12,7 @@ import python
|
||||
import experimental.semmle.python.Concepts
|
||||
import experimental.semmle.python.frameworks.JWT
|
||||
|
||||
from JWTEncoding jwtEncoding, string affectedComponent
|
||||
from JwtEncoding jwtEncoding, string affectedComponent
|
||||
where
|
||||
affectedComponent = "algorithm" and
|
||||
isEmptyOrNone(jwtEncoding.getAlgorithm())
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
import python
|
||||
import experimental.semmle.python.Concepts
|
||||
|
||||
from JWTDecoding jwtDecoding
|
||||
from JwtDecoding jwtDecoding
|
||||
where not jwtDecoding.verifiesSignature()
|
||||
select jwtDecoding.getPayload(), "is not verified with a cryptographic secret or public key."
|
||||
|
||||
@@ -18,7 +18,7 @@ import ClientSuppliedIpUsedInSecurityCheckLib
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* Taint-tracking configuration tracing flow from obtaining a client ip from an HTTP header to a sensitive use.
|
||||
* A taint-tracking configuration tracing flow from obtaining a client ip from an HTTP header to a sensitive use.
|
||||
*/
|
||||
class ClientSuppliedIpUsedInSecurityCheckConfig extends TaintTracking::Configuration {
|
||||
ClientSuppliedIpUsedInSecurityCheckConfig() { this = "ClientSuppliedIpUsedInSecurityCheckConfig" }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
private import python
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
|
||||
/**
|
||||
* A data flow source of the client ip obtained according to the remote endpoint identifier specified
|
||||
|
||||
@@ -14,7 +14,7 @@ import python
|
||||
import DataFlow::PathGraph
|
||||
import experimental.semmle.python.security.LDAPInsecureAuth
|
||||
|
||||
from LDAPInsecureAuthConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
from LdapInsecureAuthConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ is authenticated insecurely.", sink.getNode(),
|
||||
"This LDAP host"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @name SimpleXMLRPCServer DoS vulnerability
|
||||
* @description SimpleXMLRPCServer is vulnerable to DoS attacks from untrusted user input
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @precision high
|
||||
* @id py/simple-xml-rpc-server-dos
|
||||
* @tags security
|
||||
* external/cwe/cwe-776
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
from DataFlow::CallCfgNode call
|
||||
where
|
||||
call = API::moduleImport("xmlrpc").getMember("server").getMember("SimpleXMLRPCServer").getACall()
|
||||
select call, "SimpleXMLRPCServer is vulnerable to XML bombs"
|
||||
@@ -1,30 +0,0 @@
|
||||
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Using user-supplied information to construct an XPath query for XML data can
|
||||
result in an XPath injection flaw. By sending intentionally malformed information,
|
||||
an attacker can access data that he may not normally have access to.
|
||||
He/She may even be able to elevate his privileges on the web site if the XML data
|
||||
is being used for authentication (such as an XML based user file).
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
XPath injection can be prevented using parameterized XPath interface or escaping the user input to make it safe to include in a dynamically constructed query.
|
||||
If you are using quotes to terminate untrusted input in a dynamically constructed XPath query, then you need to escape that quote in the untrusted input to ensure the untrusted data can’t try to break out of that quoted context.
|
||||
</p>
|
||||
<p>
|
||||
Another better mitigation option is to use a precompiled XPath query. Precompiled XPath queries are already preset before the program executes, rather than created on the fly after the user’s input has been added to the string. This is a better route because you don’t have to worry about missing a character that should have been escaped.
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In the example below, the xpath query is controlled by the user and hence leads to a vulnerability.</p>
|
||||
<sample src="xpathBad.py" />
|
||||
<p> This can be fixed by using a parameterized query as shown below.</p>
|
||||
<sample src="xpathGood.py" />
|
||||
</example>
|
||||
<references>
|
||||
<li>OWASP XPath injection : <a href="https://owasp.org/www-community/attacks/XPATH_Injection"></a>/>> </li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* @name XPath query built from user-controlled sources
|
||||
* @description Building a XPath query from user-controlled sources is vulnerable to insertion of
|
||||
* malicious Xpath code by the user.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id py/xpath-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-643
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.BarrierGuards
|
||||
import XpathInjection::XpathInjection
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class XpathInjectionConfiguration extends TaintTracking::Configuration {
|
||||
XpathInjectionConfiguration() { this = "PathNotNormalizedConfiguration" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
}
|
||||
|
||||
from XpathInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink, source, sink, "This Xpath query depends on $@.", source, "a user-provided value"
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `XpathInjection::Configuration` is needed, otherwise
|
||||
* `XpathInjectionCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
private import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
|
||||
*/
|
||||
module XpathInjection {
|
||||
import XpathInjectionCustomizations::XpathInjection
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "Xpath Injection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/**
|
||||
* Provides class and predicates to track external data that
|
||||
* may represent malicious xpath query objects.
|
||||
*
|
||||
* This module is intended to be imported into a taint-tracking query.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.BarrierGuards
|
||||
|
||||
/** Models Xpath Injection related classes and functions */
|
||||
module XpathInjection {
|
||||
/**
|
||||
* A data flow source for "XPath injection" vulnerabilities.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for "XPath injection" vulnerabilities.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for "XPath injection" vulnerabilities.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer guard for "XPath injection" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
*/
|
||||
class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
|
||||
|
||||
/** Returns an API node referring to `lxml.etree` */
|
||||
API::Node etree() { result = API::moduleImport("lxml").getMember("etree") }
|
||||
|
||||
/** Returns an API node referring to `lxml.etree` */
|
||||
API::Node etreeFromString() { result = etree().getMember("fromstring") }
|
||||
|
||||
/** Returns an API node referring to `lxml.etree.parse` */
|
||||
API::Node etreeParse() { result = etree().getMember("parse") }
|
||||
|
||||
/** Returns an API node referring to `lxml.etree.parse` */
|
||||
API::Node libxml2parseFile() { result = API::moduleImport("libxml2").getMember("parseFile") }
|
||||
|
||||
/**
|
||||
* A Sink representing an argument to `etree.XPath` or `etree.ETXPath` call.
|
||||
*
|
||||
* from lxml import etree
|
||||
* root = etree.XML("<xmlContent>")
|
||||
* find_text = etree.XPath("`sink`")
|
||||
* find_text = etree.ETXPath("`sink`")
|
||||
*/
|
||||
private class EtreeXpathArgument extends Sink {
|
||||
EtreeXpathArgument() { this = etree().getMember(["XPath", "ETXPath"]).getACall().getArg(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Sink representing an argument to the `etree.XPath` call.
|
||||
*
|
||||
* from lxml import etree
|
||||
* root = etree.fromstring(file(XML_DB).read(), XMLParser())
|
||||
* find_text = root.xpath("`sink`")
|
||||
*/
|
||||
private class EtreeFromstringXpathArgument extends Sink {
|
||||
EtreeFromstringXpathArgument() {
|
||||
this = etreeFromString().getReturn().getMember("xpath").getACall().getArg(0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Sink representing an argument to the `xpath` call to a parsed xml document.
|
||||
*
|
||||
* from lxml import etree
|
||||
* from io import StringIO
|
||||
* f = StringIO('<foo><bar></bar></foo>')
|
||||
* tree = etree.parse(f)
|
||||
* r = tree.xpath('`sink`')
|
||||
*/
|
||||
private class ParseXpathArgument extends Sink {
|
||||
ParseXpathArgument() { this = etreeParse().getReturn().getMember("xpath").getACall().getArg(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Sink representing an argument to the `xpathEval` call to a parsed libxml2 document.
|
||||
*
|
||||
* import libxml2
|
||||
* tree = libxml2.parseFile("file.xml")
|
||||
* r = tree.xpathEval('`sink`')
|
||||
*/
|
||||
private class ParseFileXpathEvalArgument extends Sink {
|
||||
ParseFileXpathEvalArgument() {
|
||||
this = libxml2parseFile().getReturn().getMember("xpathEval").getACall().getArg(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import python
|
||||
import experimental.semmle.python.security.injection.NoSQLInjection
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from NoSQLInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
from NoSqlInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink, source, sink, "$@ NoSQL query contains an unsanitized $@", sink, "This", source,
|
||||
"user-provided value"
|
||||
|
||||
@@ -14,8 +14,75 @@ private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import experimental.semmle.python.Frameworks
|
||||
|
||||
/** Provides classes for modeling copying file related APIs. */
|
||||
module CopyFile {
|
||||
/**
|
||||
* A data flow node for copying file.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `CopyFile` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Gets the argument containing the path.
|
||||
*/
|
||||
abstract DataFlow::Node getAPathArgument();
|
||||
|
||||
/**
|
||||
* Gets fsrc argument.
|
||||
*/
|
||||
abstract DataFlow::Node getfsrcArgument();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node for copying file.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `CopyFile::Range` instead.
|
||||
*/
|
||||
class CopyFile extends DataFlow::Node {
|
||||
CopyFile::Range range;
|
||||
|
||||
CopyFile() { this = range }
|
||||
|
||||
DataFlow::Node getAPathArgument() { result = range.getAPathArgument() }
|
||||
|
||||
DataFlow::Node getfsrcArgument() { result = range.getfsrcArgument() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling log related APIs. */
|
||||
module LogOutput {
|
||||
/**
|
||||
* A data flow node for log output.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `LogOutput` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Get the parameter value of the log output function.
|
||||
*/
|
||||
abstract DataFlow::Node getAnInput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node for log output.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `LogOutput::Range` instead.
|
||||
*/
|
||||
class LogOutput extends DataFlow::Node {
|
||||
LogOutput::Range range;
|
||||
|
||||
LogOutput() { this = range }
|
||||
|
||||
DataFlow::Node getAnInput() { result = range.getAnInput() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling LDAP query execution-related APIs. */
|
||||
module LDAPQuery {
|
||||
module LdapQuery {
|
||||
/**
|
||||
* A data-flow node that collects methods executing a LDAP query.
|
||||
*
|
||||
@@ -30,16 +97,19 @@ module LDAPQuery {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for LdapQuery */
|
||||
deprecated module LDAPQuery = LdapQuery;
|
||||
|
||||
/**
|
||||
* A data-flow node that collect methods executing a LDAP query.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `LDAPQuery::Range` instead.
|
||||
*/
|
||||
class LDAPQuery extends DataFlow::Node {
|
||||
LDAPQuery::Range range;
|
||||
class LdapQuery extends DataFlow::Node {
|
||||
LdapQuery::Range range;
|
||||
|
||||
LDAPQuery() { this = range }
|
||||
LdapQuery() { this = range }
|
||||
|
||||
/**
|
||||
* Gets the argument containing the executed expression.
|
||||
@@ -47,8 +117,11 @@ class LDAPQuery extends DataFlow::Node {
|
||||
DataFlow::Node getQuery() { result = range.getQuery() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for LdapQuery */
|
||||
deprecated class LDAPQuery = LdapQuery;
|
||||
|
||||
/** Provides classes for modeling LDAP components escape-related APIs. */
|
||||
module LDAPEscape {
|
||||
module LdapEscape {
|
||||
/**
|
||||
* A data-flow node that collects functions escaping LDAP components.
|
||||
*
|
||||
@@ -63,16 +136,19 @@ module LDAPEscape {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for LdapEscape */
|
||||
deprecated module LDAPEscape = LdapEscape;
|
||||
|
||||
/**
|
||||
* A data-flow node that collects functions escaping LDAP components.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `LDAPEscape::Range` instead.
|
||||
*/
|
||||
class LDAPEscape extends DataFlow::Node {
|
||||
LDAPEscape::Range range;
|
||||
class LdapEscape extends DataFlow::Node {
|
||||
LdapEscape::Range range;
|
||||
|
||||
LDAPEscape() { this = range }
|
||||
LdapEscape() { this = range }
|
||||
|
||||
/**
|
||||
* Gets the argument containing the escaped expression.
|
||||
@@ -80,8 +156,11 @@ class LDAPEscape extends DataFlow::Node {
|
||||
DataFlow::Node getAnInput() { result = range.getAnInput() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for LdapEscape */
|
||||
deprecated class LDAPEscape = LdapEscape;
|
||||
|
||||
/** Provides classes for modeling LDAP bind-related APIs. */
|
||||
module LDAPBind {
|
||||
module LdapBind {
|
||||
/**
|
||||
* A data-flow node that collects methods binding a LDAP connection.
|
||||
*
|
||||
@@ -106,16 +185,19 @@ module LDAPBind {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for LdapBind */
|
||||
deprecated module LDAPBind = LdapBind;
|
||||
|
||||
/**
|
||||
* A data-flow node that collects methods binding a LDAP connection.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `LDAPBind::Range` instead.
|
||||
*/
|
||||
class LDAPBind extends DataFlow::Node {
|
||||
LDAPBind::Range range;
|
||||
class LdapBind extends DataFlow::Node {
|
||||
LdapBind::Range range;
|
||||
|
||||
LDAPBind() { this = range }
|
||||
LdapBind() { this = range }
|
||||
|
||||
/**
|
||||
* Gets the argument containing the binding host.
|
||||
@@ -133,8 +215,11 @@ class LDAPBind extends DataFlow::Node {
|
||||
predicate useSSL() { range.useSSL() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for LdapBind */
|
||||
deprecated class LDAPBind = LdapBind;
|
||||
|
||||
/** Provides classes for modeling SQL sanitization libraries. */
|
||||
module SQLEscape {
|
||||
module SqlEscape {
|
||||
/**
|
||||
* A data-flow node that collects functions that escape SQL statements.
|
||||
*
|
||||
@@ -149,16 +234,19 @@ module SQLEscape {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for SqlEscape */
|
||||
deprecated module SQLEscape = SqlEscape;
|
||||
|
||||
/**
|
||||
* A data-flow node that collects functions escaping SQL statements.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `SQLEscape::Range` instead.
|
||||
*/
|
||||
class SQLEscape extends DataFlow::Node {
|
||||
SQLEscape::Range range;
|
||||
class SqlEscape extends DataFlow::Node {
|
||||
SqlEscape::Range range;
|
||||
|
||||
SQLEscape() { this = range }
|
||||
SqlEscape() { this = range }
|
||||
|
||||
/**
|
||||
* Gets the argument containing the raw SQL statement.
|
||||
@@ -166,8 +254,11 @@ class SQLEscape extends DataFlow::Node {
|
||||
DataFlow::Node getAnInput() { result = range.getAnInput() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling NoSQL execution APIs. */
|
||||
module NoSQLQuery {
|
||||
/** DEPRECATED: Alias for SqlEscape */
|
||||
deprecated class SQLEscape = SqlEscape;
|
||||
|
||||
/** Provides a class for modeling NoSql execution APIs. */
|
||||
module NoSqlQuery {
|
||||
/**
|
||||
* A data-flow node that executes NoSQL queries.
|
||||
*
|
||||
@@ -175,28 +266,34 @@ module NoSQLQuery {
|
||||
* extend `NoSQLQuery` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the NoSQL query to be executed. */
|
||||
/** Gets the argument that specifies the NoSql query to be executed. */
|
||||
abstract DataFlow::Node getQuery();
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for NoSqlQuery */
|
||||
deprecated module NoSQLQuery = NoSqlQuery;
|
||||
|
||||
/**
|
||||
* A data-flow node that executes NoSQL queries.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `NoSQLQuery::Range` instead.
|
||||
*/
|
||||
class NoSQLQuery extends DataFlow::Node {
|
||||
NoSQLQuery::Range range;
|
||||
class NoSqlQuery extends DataFlow::Node {
|
||||
NoSqlQuery::Range range;
|
||||
|
||||
NoSQLQuery() { this = range }
|
||||
NoSqlQuery() { this = range }
|
||||
|
||||
/** Gets the argument that specifies the NoSQL query to be executed. */
|
||||
/** Gets the argument that specifies the NoSql query to be executed. */
|
||||
DataFlow::Node getQuery() { result = range.getQuery() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling NoSQL sanitization-related APIs. */
|
||||
module NoSQLSanitizer {
|
||||
/** DEPRECATED: Alias for NoSqlQuery */
|
||||
deprecated class NoSQLQuery = NoSqlQuery;
|
||||
|
||||
/** Provides classes for modeling NoSql sanitization-related APIs. */
|
||||
module NoSqlSanitizer {
|
||||
/**
|
||||
* A data-flow node that collects functions sanitizing NoSQL queries.
|
||||
*
|
||||
@@ -204,26 +301,32 @@ module NoSQLSanitizer {
|
||||
* extend `NoSQLSanitizer` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the NoSQL query to be sanitized. */
|
||||
/** Gets the argument that specifies the NoSql query to be sanitized. */
|
||||
abstract DataFlow::Node getAnInput();
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for NoSqlSanitizer */
|
||||
deprecated module NoSQLSanitizer = NoSqlSanitizer;
|
||||
|
||||
/**
|
||||
* A data-flow node that collects functions sanitizing NoSQL queries.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `NoSQLSanitizer::Range` instead.
|
||||
*/
|
||||
class NoSQLSanitizer extends DataFlow::Node {
|
||||
NoSQLSanitizer::Range range;
|
||||
class NoSqlSanitizer extends DataFlow::Node {
|
||||
NoSqlSanitizer::Range range;
|
||||
|
||||
NoSQLSanitizer() { this = range }
|
||||
NoSqlSanitizer() { this = range }
|
||||
|
||||
/** Gets the argument that specifies the NoSQL query to be sanitized. */
|
||||
/** Gets the argument that specifies the NoSql query to be sanitized. */
|
||||
DataFlow::Node getAnInput() { result = range.getAnInput() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for NoSqlSanitizer */
|
||||
deprecated class NoSQLSanitizer = NoSqlSanitizer;
|
||||
|
||||
/** Provides classes for modeling HTTP Header APIs. */
|
||||
module HeaderDeclaration {
|
||||
/**
|
||||
@@ -268,7 +371,7 @@ class HeaderDeclaration extends DataFlow::Node {
|
||||
}
|
||||
|
||||
/** Provides classes for modeling JWT encoding-related APIs. */
|
||||
module JWTEncoding {
|
||||
module JwtEncoding {
|
||||
/**
|
||||
* A data-flow node that collects methods encoding a JWT token.
|
||||
*
|
||||
@@ -298,13 +401,16 @@ module JWTEncoding {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for JwtEncoding */
|
||||
deprecated module JWTEncoding = JwtEncoding;
|
||||
|
||||
/**
|
||||
* A data-flow node that collects methods encoding a JWT token.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `JWTEncoding::Range` instead.
|
||||
*/
|
||||
class JWTEncoding extends DataFlow::Node instanceof JWTEncoding::Range {
|
||||
class JwtEncoding extends DataFlow::Node instanceof JwtEncoding::Range {
|
||||
/**
|
||||
* Gets the argument containing the payload.
|
||||
*/
|
||||
@@ -326,8 +432,11 @@ class JWTEncoding extends DataFlow::Node instanceof JWTEncoding::Range {
|
||||
string getAlgorithmString() { result = super.getAlgorithmString() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for JwtEncoding */
|
||||
deprecated class JWTEncoding = JwtEncoding;
|
||||
|
||||
/** Provides classes for modeling JWT decoding-related APIs. */
|
||||
module JWTDecoding {
|
||||
module JwtDecoding {
|
||||
/**
|
||||
* A data-flow node that collects methods decoding a JWT token.
|
||||
*
|
||||
@@ -367,13 +476,16 @@ module JWTDecoding {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for JwtDecoding */
|
||||
deprecated module JWTDecoding = JwtDecoding;
|
||||
|
||||
/**
|
||||
* A data-flow node that collects methods encoding a JWT token.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `JWTDecoding::Range` instead.
|
||||
*/
|
||||
class JWTDecoding extends DataFlow::Node instanceof JWTDecoding::Range {
|
||||
class JwtDecoding extends DataFlow::Node instanceof JwtDecoding::Range {
|
||||
/**
|
||||
* Gets the argument containing the payload.
|
||||
*/
|
||||
@@ -404,3 +516,6 @@ class JWTDecoding extends DataFlow::Node instanceof JWTDecoding::Range {
|
||||
*/
|
||||
predicate verifiesSignature() { super.verifiesSignature() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for JwtDecoding */
|
||||
deprecated class JWTDecoding = JwtDecoding;
|
||||
|
||||
@@ -13,3 +13,4 @@ private import experimental.semmle.python.libraries.PyJWT
|
||||
private import experimental.semmle.python.libraries.Python_JWT
|
||||
private import experimental.semmle.python.libraries.Authlib
|
||||
private import experimental.semmle.python.libraries.PythonJose
|
||||
private import experimental.semmle.python.frameworks.CopyFile
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
private import python
|
||||
private import experimental.semmle.python.Concepts
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
private module CopyFileImpl {
|
||||
/**
|
||||
* The `shutil` module provides methods to copy or move files.
|
||||
* See:
|
||||
* - https://docs.python.org/3/library/shutil.html#shutil.copyfile
|
||||
* - https://docs.python.org/3/library/shutil.html#shutil.copy
|
||||
* - https://docs.python.org/3/library/shutil.html#shutil.copy2
|
||||
* - https://docs.python.org/3/library/shutil.html#shutil.copytree
|
||||
* - https://docs.python.org/3/library/shutil.html#shutil.move
|
||||
*/
|
||||
private class CopyFiles extends DataFlow::CallCfgNode, CopyFile::Range {
|
||||
CopyFiles() {
|
||||
this =
|
||||
API::moduleImport("shutil")
|
||||
.getMember(["copyfile", "copy", "copy2", "copytree", "move"])
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() {
|
||||
result in [this.getArg(0), this.getArgByName("src")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getfsrcArgument() { none() }
|
||||
}
|
||||
|
||||
// TODO: once we have flow summaries, model `shutil.copyfileobj` which copies the content between its' file-like arguments.
|
||||
// See https://docs.python.org/3/library/shutil.html#shutil.copyfileobj
|
||||
private class CopyFileobj extends DataFlow::CallCfgNode, CopyFile::Range {
|
||||
CopyFileobj() { this = API::moduleImport("shutil").getMember("copyfileobj").getACall() }
|
||||
|
||||
override DataFlow::Node getfsrcArgument() {
|
||||
result in [this.getArg(0), this.getArgByName("fsrc")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() { none() }
|
||||
}
|
||||
}
|
||||
@@ -11,15 +11,15 @@ private import semmle.python.ApiGraphs
|
||||
import semmle.python.dataflow.new.RemoteFlowSources
|
||||
|
||||
private module ExperimentalPrivateDjango {
|
||||
private module django {
|
||||
private module DjangoMod {
|
||||
API::Node http() { result = API::moduleImport("django").getMember("http") }
|
||||
|
||||
module http {
|
||||
module Http {
|
||||
API::Node response() { result = http().getMember("response") }
|
||||
|
||||
API::Node request() { result = http().getMember("request") }
|
||||
|
||||
module request {
|
||||
module Request {
|
||||
module HttpRequest {
|
||||
class DjangoGETParameter extends DataFlow::Node, RemoteFlowSource::Range {
|
||||
DjangoGETParameter() { this = request().getMember("GET").getMember("get").getACall() }
|
||||
@@ -29,7 +29,7 @@ private module ExperimentalPrivateDjango {
|
||||
}
|
||||
}
|
||||
|
||||
module response {
|
||||
module Response {
|
||||
module HttpResponse {
|
||||
API::Node baseClassRef() {
|
||||
result = response().getMember("HttpResponse").getReturn()
|
||||
|
||||
@@ -27,17 +27,8 @@ module ExperimentalFlask {
|
||||
}
|
||||
|
||||
/** Gets a reference to a header instance. */
|
||||
private DataFlow::LocalSourceNode headerInstance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result.(DataFlow::AttrRead).getObject().getALocalSource() =
|
||||
[Flask::Response::classRef(), flaskMakeResponse()].getReturn().getAUse()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = headerInstance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to a header instance use. */
|
||||
private DataFlow::Node headerInstance() {
|
||||
headerInstance(DataFlow::TypeTracker::end()).flowsTo(result)
|
||||
private DataFlow::LocalSourceNode headerInstance() {
|
||||
result = [Flask::Response::classRef(), flaskMakeResponse()].getReturn().getAMember().getAUse()
|
||||
}
|
||||
|
||||
/** Gets a reference to a header instance call/subscript */
|
||||
|
||||
@@ -60,7 +60,7 @@ private module LDAP {
|
||||
*
|
||||
* See `LDAP2QueryMethods`
|
||||
*/
|
||||
private class LDAP2Query extends DataFlow::CallCfgNode, LDAPQuery::Range {
|
||||
private class LDAP2Query extends DataFlow::CallCfgNode, LdapQuery::Range {
|
||||
LDAP2Query() { this.getFunction() = ldapQuery() }
|
||||
|
||||
override DataFlow::Node getQuery() {
|
||||
@@ -98,7 +98,7 @@ private module LDAP {
|
||||
*
|
||||
* See `LDAP2BindMethods`
|
||||
*/
|
||||
private class LDAP2Bind extends DataFlow::CallCfgNode, LDAPBind::Range {
|
||||
private class LDAP2Bind extends DataFlow::CallCfgNode, LdapBind::Range {
|
||||
LDAP2Bind() { this.getFunction() = ldapBind() }
|
||||
|
||||
override DataFlow::Node getPassword() {
|
||||
@@ -149,7 +149,7 @@ private module LDAP {
|
||||
*
|
||||
* See https://github.com/python-ldap/python-ldap/blob/7ce471e238cdd9a4dd8d17baccd1c9e05e6f894a/Lib/ldap/dn.py#L17
|
||||
*/
|
||||
private class LDAP2EscapeDNCall extends DataFlow::CallCfgNode, LDAPEscape::Range {
|
||||
private class LDAP2EscapeDNCall extends DataFlow::CallCfgNode, LdapEscape::Range {
|
||||
LDAP2EscapeDNCall() { this = ldap().getMember("dn").getMember("escape_dn_chars").getACall() }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
@@ -160,7 +160,7 @@ private module LDAP {
|
||||
*
|
||||
* See https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap-filter.html#ldap.filter.escape_filter_chars
|
||||
*/
|
||||
private class LDAP2EscapeFilterCall extends DataFlow::CallCfgNode, LDAPEscape::Range {
|
||||
private class LDAP2EscapeFilterCall extends DataFlow::CallCfgNode, LdapEscape::Range {
|
||||
LDAP2EscapeFilterCall() {
|
||||
this = ldap().getMember("filter").getMember("escape_filter_chars").getACall()
|
||||
}
|
||||
@@ -190,7 +190,7 @@ private module LDAP {
|
||||
/**
|
||||
* A class to find `ldap3` methods executing a query.
|
||||
*/
|
||||
private class LDAP3Query extends DataFlow::CallCfgNode, LDAPQuery::Range {
|
||||
private class LDAP3Query extends DataFlow::CallCfgNode, LdapQuery::Range {
|
||||
LDAP3Query() {
|
||||
this.getFunction().(DataFlow::AttrRead).getObject().getALocalSource() =
|
||||
ldap3Connection().getACall() and
|
||||
@@ -203,7 +203,7 @@ private module LDAP {
|
||||
/**
|
||||
* A class to find `ldap3` methods binding a connection.
|
||||
*/
|
||||
class LDAP3Bind extends DataFlow::CallCfgNode, LDAPBind::Range {
|
||||
class LDAP3Bind extends DataFlow::CallCfgNode, LdapBind::Range {
|
||||
LDAP3Bind() { this = ldap3Connection().getACall() }
|
||||
|
||||
override DataFlow::Node getPassword() {
|
||||
@@ -241,7 +241,7 @@ private module LDAP {
|
||||
*
|
||||
* See https://github.com/cannatag/ldap3/blob/4d33166f0869b929f59c6e6825a1b9505eb99967/ldap3/utils/dn.py#L390
|
||||
*/
|
||||
private class LDAP3EscapeDNCall extends DataFlow::CallCfgNode, LDAPEscape::Range {
|
||||
private class LDAP3EscapeDNCall extends DataFlow::CallCfgNode, LdapEscape::Range {
|
||||
LDAP3EscapeDNCall() { this = ldap3Utils().getMember("dn").getMember("escape_rdn").getACall() }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
@@ -252,7 +252,7 @@ private module LDAP {
|
||||
*
|
||||
* See https://github.com/cannatag/ldap3/blob/4d33166f0869b929f59c6e6825a1b9505eb99967/ldap3/utils/conv.py#L91
|
||||
*/
|
||||
private class LDAP3EscapeFilterCall extends DataFlow::CallCfgNode, LDAPEscape::Range {
|
||||
private class LDAP3EscapeFilterCall extends DataFlow::CallCfgNode, LdapEscape::Range {
|
||||
LDAP3EscapeFilterCall() {
|
||||
this = ldap3Utils().getMember("conv").getMember("escape_filter_chars").getACall()
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import experimental.semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
private module NoSQL {
|
||||
private module NoSql {
|
||||
// API Nodes returning `Mongo` instances.
|
||||
/** Gets a reference to `pymongo.MongoClient` */
|
||||
private API::Node pyMongo() {
|
||||
@@ -64,7 +64,7 @@ private module NoSQL {
|
||||
or
|
||||
result.(DataFlow::AttrRead).getObject() = mongoInstance().getAUse()
|
||||
or
|
||||
result = mongoDBInstance().getAUse()
|
||||
result = mongoDBInstance().getAnImmediateUse()
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = mongoDB(t2).track(t2, t))
|
||||
@@ -153,7 +153,7 @@ private module NoSQL {
|
||||
*
|
||||
* `mongo.db.user.find({'name': safe_search})` would be a collection method call, and so the result.
|
||||
*/
|
||||
private class MongoCollectionCall extends DataFlow::CallCfgNode, NoSQLQuery::Range {
|
||||
private class MongoCollectionCall extends DataFlow::CallCfgNode, NoSqlQuery::Range {
|
||||
MongoCollectionCall() { this.getFunction() = mongoCollectionMethod() }
|
||||
|
||||
override DataFlow::Node getQuery() { result = this.getArg(0) }
|
||||
@@ -174,7 +174,7 @@ private module NoSQL {
|
||||
*
|
||||
* `Movie.objects(__raw__=json_search)` would be the result.
|
||||
*/
|
||||
private class MongoEngineObjectsCall extends DataFlow::CallCfgNode, NoSQLQuery::Range {
|
||||
private class MongoEngineObjectsCall extends DataFlow::CallCfgNode, NoSqlQuery::Range {
|
||||
MongoEngineObjectsCall() {
|
||||
this =
|
||||
[mongoEngine(), flask_MongoEngine()]
|
||||
@@ -188,7 +188,7 @@ private module NoSQL {
|
||||
}
|
||||
|
||||
/** Gets a reference to `mongosanitizer.sanitizer.sanitize` */
|
||||
private class MongoSanitizerCall extends DataFlow::CallCfgNode, NoSQLSanitizer::Range {
|
||||
private class MongoSanitizerCall extends DataFlow::CallCfgNode, NoSqlSanitizer::Range {
|
||||
MongoSanitizerCall() {
|
||||
this =
|
||||
API::moduleImport("mongosanitizer").getMember("sanitizer").getMember("sanitize").getACall()
|
||||
@@ -202,7 +202,7 @@ private module NoSQL {
|
||||
* If at any time ObjectId can't parse it's input (like when a tainted dict in passed in),
|
||||
* then ObjectId will throw an error preventing the query from running.
|
||||
*/
|
||||
private class BsonObjectIdCall extends DataFlow::CallCfgNode, NoSQLSanitizer::Range {
|
||||
private class BsonObjectIdCall extends DataFlow::CallCfgNode, NoSqlSanitizer::Range {
|
||||
BsonObjectIdCall() {
|
||||
this =
|
||||
API::moduleImport(["bson", "bson.objectid", "bson.json_util"])
|
||||
|
||||
@@ -12,7 +12,7 @@ private import experimental.semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
private module Werkzeug {
|
||||
module datastructures {
|
||||
module Datastructures {
|
||||
module Headers {
|
||||
class WerkzeugHeaderAddCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
|
||||
WerkzeugHeaderAddCall() {
|
||||
|
||||
@@ -5,7 +5,7 @@ private import experimental.semmle.python.frameworks.JWT
|
||||
|
||||
private module Authlib {
|
||||
/** Gets a reference to `authlib.jose.(jwt|JsonWebToken)` */
|
||||
private API::Node authlibJWT() {
|
||||
private API::Node authlibJwt() {
|
||||
result in [
|
||||
API::moduleImport("authlib").getMember("jose").getMember("jwt"),
|
||||
API::moduleImport("authlib").getMember("jose").getMember("JsonWebToken").getReturn()
|
||||
@@ -13,10 +13,10 @@ private module Authlib {
|
||||
}
|
||||
|
||||
/** Gets a reference to `jwt.encode` */
|
||||
private API::Node authlibJWTEncode() { result = authlibJWT().getMember("encode") }
|
||||
private API::Node authlibJwtEncode() { result = authlibJwt().getMember("encode") }
|
||||
|
||||
/** Gets a reference to `jwt.decode` */
|
||||
private API::Node authlibJWTDecode() { result = authlibJWT().getMember("decode") }
|
||||
private API::Node authlibJwtDecode() { result = authlibJwt().getMember("decode") }
|
||||
|
||||
/**
|
||||
* Gets a call to `authlib.jose.(jwt|JsonWebToken).encode`.
|
||||
@@ -33,8 +33,8 @@ private module Authlib {
|
||||
* * `getAlgorithm()`'s result would be `"HS256"`.
|
||||
* * `getAlgorithmstring()`'s result would be `HS256`.
|
||||
*/
|
||||
private class AuthlibJWTEncodeCall extends DataFlow::CallCfgNode, JWTEncoding::Range {
|
||||
AuthlibJWTEncodeCall() { this = authlibJWTEncode().getACall() }
|
||||
private class AuthlibJwtEncodeCall extends DataFlow::CallCfgNode, JwtEncoding::Range {
|
||||
AuthlibJwtEncodeCall() { this = authlibJwtEncode().getACall() }
|
||||
|
||||
override DataFlow::Node getPayload() { result = this.getArg(1) }
|
||||
|
||||
@@ -69,8 +69,8 @@ private module Authlib {
|
||||
* * `getPayload()`'s result would be `token`.
|
||||
* * `getKey()`'s result would be `key`.
|
||||
*/
|
||||
private class AuthlibJWTDecodeCall extends DataFlow::CallCfgNode, JWTDecoding::Range {
|
||||
AuthlibJWTDecodeCall() { this = authlibJWTDecode().getACall() }
|
||||
private class AuthlibJwtDecodeCall extends DataFlow::CallCfgNode, JwtDecoding::Range {
|
||||
AuthlibJwtDecodeCall() { this = authlibJwtDecode().getACall() }
|
||||
|
||||
override DataFlow::Node getPayload() { result = this.getArg(0) }
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ private import experimental.semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import experimental.semmle.python.frameworks.JWT
|
||||
|
||||
private module PyJWT {
|
||||
private module PyJwt {
|
||||
/** Gets a reference to `jwt.encode` */
|
||||
private API::Node pyjwtEncode() { result = API::moduleImport("jwt").getMember("encode") }
|
||||
|
||||
@@ -25,8 +25,8 @@ private module PyJWT {
|
||||
* * `getAlgorithm()`'s result would be `"HS256"`.
|
||||
* * `getAlgorithmstring()`'s result would be `HS256`.
|
||||
*/
|
||||
private class PyJWTEncodeCall extends DataFlow::CallCfgNode, JWTEncoding::Range {
|
||||
PyJWTEncodeCall() { this = pyjwtEncode().getACall() }
|
||||
private class PyJwtEncodeCall extends DataFlow::CallCfgNode, JwtEncoding::Range {
|
||||
PyJwtEncodeCall() { this = pyjwtEncode().getACall() }
|
||||
|
||||
override DataFlow::Node getPayload() {
|
||||
result in [this.getArg(0), this.getArgByName("payload")]
|
||||
@@ -63,8 +63,8 @@ private module PyJWT {
|
||||
* * `getOptions()`'s result would be `{"verify_signature": True}`.
|
||||
* * `verifiesSignature()` predicate would succeed.
|
||||
*/
|
||||
private class PyJWTDecodeCall extends DataFlow::CallCfgNode, JWTDecoding::Range {
|
||||
PyJWTDecodeCall() { this = pyjwtDecode().getACall() }
|
||||
private class PyJwtDecodeCall extends DataFlow::CallCfgNode, JwtDecoding::Range {
|
||||
PyJwtDecodeCall() { this = pyjwtDecode().getACall() }
|
||||
|
||||
override DataFlow::Node getPayload() { result in [this.getArg(0), this.getArgByName("jwt")] }
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@ private import experimental.semmle.python.frameworks.JWT
|
||||
|
||||
private module PythonJose {
|
||||
/** Gets a reference to `jwt` */
|
||||
private API::Node joseJWT() { result = API::moduleImport("jose").getMember("jwt") }
|
||||
private API::Node joseJwt() { result = API::moduleImport("jose").getMember("jwt") }
|
||||
|
||||
/** Gets a reference to `jwt.encode` */
|
||||
private API::Node joseJWTEncode() { result = joseJWT().getMember("encode") }
|
||||
private API::Node joseJwtEncode() { result = joseJwt().getMember("encode") }
|
||||
|
||||
/** Gets a reference to `jwt.decode` */
|
||||
private API::Node joseJWTDecode() { result = joseJWT().getMember("decode") }
|
||||
private API::Node joseJwtDecode() { result = joseJwt().getMember("decode") }
|
||||
|
||||
/**
|
||||
* Gets a call to `jwt.encode`.
|
||||
@@ -28,8 +28,8 @@ private module PythonJose {
|
||||
* * `getAlgorithm()`'s result would be `"HS256"`.
|
||||
* * `getAlgorithmstring()`'s result would be `HS256`.
|
||||
*/
|
||||
private class JoseJWTEncodeCall extends DataFlow::CallCfgNode, JWTEncoding::Range {
|
||||
JoseJWTEncodeCall() { this = joseJWTEncode().getACall() }
|
||||
private class JoseJwtEncodeCall extends DataFlow::CallCfgNode, JwtEncoding::Range {
|
||||
JoseJwtEncodeCall() { this = joseJwtEncode().getACall() }
|
||||
|
||||
override DataFlow::Node getPayload() { result = this.getArg(0) }
|
||||
|
||||
@@ -64,8 +64,8 @@ private module PythonJose {
|
||||
* * `getOptions()`'s result would be none.
|
||||
* * `verifiesSignature()` predicate would succeed.
|
||||
*/
|
||||
private class JoseJWTDecodeCall extends DataFlow::CallCfgNode, JWTDecoding::Range {
|
||||
JoseJWTDecodeCall() { this = joseJWTDecode().getACall() }
|
||||
private class JoseJwtDecodeCall extends DataFlow::CallCfgNode, JwtDecoding::Range {
|
||||
JoseJwtDecodeCall() { this = joseJwtDecode().getACall() }
|
||||
|
||||
override DataFlow::Node getPayload() { result = this.getArg(0) }
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ private import python
|
||||
private import experimental.semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
private module Python_JWT {
|
||||
private module Python_Jwt {
|
||||
/**
|
||||
* Gets a call to `python_jwt.process_jwt`.
|
||||
*
|
||||
@@ -21,7 +21,7 @@ private module Python_JWT {
|
||||
* * `getOptions()`'s result would be `none()`.
|
||||
* * `verifiesSignature()` predicate would succeed.
|
||||
*/
|
||||
private class PythonJwtProcessCall extends DataFlow::CallCfgNode, JWTDecoding::Range {
|
||||
private class PythonJwtProcessCall extends DataFlow::CallCfgNode, JwtDecoding::Range {
|
||||
PythonJwtProcessCall() {
|
||||
this = API::moduleImport("python_jwt").getMember("process_jwt").getACall()
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ string getPrivateHostRegex() {
|
||||
}
|
||||
|
||||
// "ldap://somethingon.theinternet.com"
|
||||
class LDAPFullHost extends StrConst {
|
||||
LDAPFullHost() {
|
||||
class LdapFullHost extends StrConst {
|
||||
LdapFullHost() {
|
||||
exists(string s |
|
||||
s = this.getText() and
|
||||
s.regexpMatch(getFullHostRegex()) and
|
||||
@@ -29,27 +29,39 @@ class LDAPFullHost extends StrConst {
|
||||
}
|
||||
}
|
||||
|
||||
class LDAPSchema extends StrConst {
|
||||
LDAPSchema() { this.getText().regexpMatch(getSchemaRegex()) }
|
||||
/** DEPRECATED: Alias for LdapFullHost */
|
||||
deprecated class LDAPFullHost = LdapFullHost;
|
||||
|
||||
class LdapSchema extends StrConst {
|
||||
LdapSchema() { this.getText().regexpMatch(getSchemaRegex()) }
|
||||
}
|
||||
|
||||
class LDAPPrivateHost extends StrConst {
|
||||
LDAPPrivateHost() { this.getText().regexpMatch(getPrivateHostRegex()) }
|
||||
/** DEPRECATED: Alias for LdapSchema */
|
||||
deprecated class LDAPSchema = LdapSchema;
|
||||
|
||||
class LdapPrivateHost extends StrConst {
|
||||
LdapPrivateHost() { this.getText().regexpMatch(getPrivateHostRegex()) }
|
||||
}
|
||||
|
||||
predicate concatAndCompareAgainstFullHostRegex(LDAPSchema schema, StrConst host) {
|
||||
not host instanceof LDAPPrivateHost and
|
||||
/** DEPRECATED: Alias for LdapPrivateHost */
|
||||
deprecated class LDAPPrivateHost = LdapPrivateHost;
|
||||
|
||||
predicate concatAndCompareAgainstFullHostRegex(LdapSchema schema, StrConst host) {
|
||||
not host instanceof LdapPrivateHost and
|
||||
(schema.getText() + host.getText()).regexpMatch(getFullHostRegex())
|
||||
}
|
||||
|
||||
// "ldap://" + "somethingon.theinternet.com"
|
||||
class LDAPBothStrings extends BinaryExpr {
|
||||
LDAPBothStrings() { concatAndCompareAgainstFullHostRegex(this.getLeft(), this.getRight()) }
|
||||
class LdapBothStrings extends BinaryExpr {
|
||||
LdapBothStrings() { concatAndCompareAgainstFullHostRegex(this.getLeft(), this.getRight()) }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for LdapBothStrings */
|
||||
deprecated class LDAPBothStrings = LdapBothStrings;
|
||||
|
||||
// schema + host
|
||||
class LDAPBothVar extends BinaryExpr {
|
||||
LDAPBothVar() {
|
||||
class LdapBothVar extends BinaryExpr {
|
||||
LdapBothVar() {
|
||||
exists(SsaVariable schemaVar, SsaVariable hostVar |
|
||||
this.getLeft() = schemaVar.getVariable().getALoad() and // getAUse is incompatible with Expr
|
||||
this.getRight() = hostVar.getVariable().getALoad() and
|
||||
@@ -61,9 +73,12 @@ class LDAPBothVar extends BinaryExpr {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for LdapBothVar */
|
||||
deprecated class LDAPBothVar = LdapBothVar;
|
||||
|
||||
// schema + "somethingon.theinternet.com"
|
||||
class LDAPVarString extends BinaryExpr {
|
||||
LDAPVarString() {
|
||||
class LdapVarString extends BinaryExpr {
|
||||
LdapVarString() {
|
||||
exists(SsaVariable schemaVar |
|
||||
this.getLeft() = schemaVar.getVariable().getALoad() and
|
||||
concatAndCompareAgainstFullHostRegex(schemaVar
|
||||
@@ -74,9 +89,12 @@ class LDAPVarString extends BinaryExpr {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for LdapVarString */
|
||||
deprecated class LDAPVarString = LdapVarString;
|
||||
|
||||
// "ldap://" + host
|
||||
class LDAPStringVar extends BinaryExpr {
|
||||
LDAPStringVar() {
|
||||
class LdapStringVar extends BinaryExpr {
|
||||
LdapStringVar() {
|
||||
exists(SsaVariable hostVar |
|
||||
this.getRight() = hostVar.getVariable().getALoad() and
|
||||
concatAndCompareAgainstFullHostRegex(this.getLeft(),
|
||||
@@ -85,22 +103,28 @@ class LDAPStringVar extends BinaryExpr {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for LdapStringVar */
|
||||
deprecated class LDAPStringVar = LdapStringVar;
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting LDAP insecure authentications.
|
||||
*/
|
||||
class LDAPInsecureAuthConfig extends TaintTracking::Configuration {
|
||||
LDAPInsecureAuthConfig() { this = "LDAPInsecureAuthConfig" }
|
||||
class LdapInsecureAuthConfig extends TaintTracking::Configuration {
|
||||
LdapInsecureAuthConfig() { this = "LDAPInsecureAuthConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) {
|
||||
source instanceof RemoteFlowSource or
|
||||
source.asExpr() instanceof LDAPFullHost or
|
||||
source.asExpr() instanceof LDAPBothStrings or
|
||||
source.asExpr() instanceof LDAPBothVar or
|
||||
source.asExpr() instanceof LDAPVarString or
|
||||
source.asExpr() instanceof LDAPStringVar
|
||||
source.asExpr() instanceof LdapFullHost or
|
||||
source.asExpr() instanceof LdapBothStrings or
|
||||
source.asExpr() instanceof LdapBothVar or
|
||||
source.asExpr() instanceof LdapVarString or
|
||||
source.asExpr() instanceof LdapStringVar
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
exists(LDAPBind ldapBind | not ldapBind.useSSL() and sink = ldapBind.getHost())
|
||||
exists(LdapBind ldapBind | not ldapBind.useSSL() and sink = ldapBind.getHost())
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for LdapInsecureAuthConfig */
|
||||
deprecated class LDAPInsecureAuthConfig = LdapInsecureAuthConfig;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user