Merge branch 'main' into timing-attack-py

This commit is contained in:
Ahmed Farid
2022-07-27 14:40:55 +01:00
committed by GitHub
1023 changed files with 21153 additions and 6788 deletions

View File

@@ -1,3 +1,19 @@
## 0.3.0
### Breaking Changes
* Contextual queries and the query libraries they depend on have been moved to the `codeql/python-all` package.
## 0.2.0
### Major Analysis Improvements
* Improved library modeling for the query "Request without certificate validation" (`py/request-without-cert-validation`), so it now also covers `httpx`, `aiohttp.client`, and `urllib3`.
### Minor Analysis Improvements
* The query "Use of a broken or weak cryptographic algorithm" (`py/weak-cryptographic-algorithm`) now reports if a cryptographic operation is potentially insecure due to use of a weak block mode.
## 0.1.4
## 0.1.3

View File

@@ -53,7 +53,7 @@ predicate matchesBeginningOfString(RegExpTerm term) {
}
/**
* Holds if the given sequence contains top-level domain preceded by a dot, such as `.com`,
* Holds if the given sequence `seq` 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.

View File

@@ -13,170 +13,10 @@
*/
import python
import semmle.python.security.Paths
import semmle.python.dataflow.TaintTracking
import semmle.python.security.strings.Basic
import semmle.python.security.dataflow.TarSlipQuery
import DataFlow::PathGraph
/** A TaintKind to represent open tarfile objects. That is, the result of calling `tarfile.open(...)` */
class OpenTarFile extends TaintKind {
OpenTarFile() { this = "tarfile.open" }
override TaintKind getTaintOfMethodResult(string name) {
name = "getmember" and result instanceof TarFileInfo
or
name = "getmembers" and result.(SequenceKind).getItem() instanceof TarFileInfo
}
override ClassValue getType() { result = Value::named("tarfile.TarFile") }
override TaintKind getTaintForIteration() { result instanceof TarFileInfo }
}
/** The source of open tarfile objects. That is, any call to `tarfile.open(...)` */
class TarfileOpen extends TaintSource {
TarfileOpen() {
Value::named("tarfile.open").getACall() = this and
/*
* If argument refers to a string object, then it's a hardcoded path and
* this tarfile is safe.
*/
not this.(CallNode).getAnArg().pointsTo(any(StringValue str)) and
/* Ignore opens within the tarfile module itself */
not this.(ControlFlowNode).getLocation().getFile().getBaseName() = "tarfile.py"
}
override predicate isSourceOf(TaintKind kind) { kind instanceof OpenTarFile }
}
class TarFileInfo extends TaintKind {
TarFileInfo() { this = "tarfile.entry" }
override TaintKind getTaintOfMethodResult(string name) { name = "next" and result = this }
override TaintKind getTaintOfAttribute(string name) {
name = "name" and result instanceof TarFileInfo
}
}
/*
* For efficiency we don't want to track the flow of taint
* around the tarfile module.
*/
class ExcludeTarFilePy extends Sanitizer {
ExcludeTarFilePy() { this = "Tar sanitizer" }
override predicate sanitizingNode(TaintKind taint, ControlFlowNode node) {
node.getLocation().getFile().getBaseName() = "tarfile.py" and
(
taint instanceof OpenTarFile
or
taint instanceof TarFileInfo
or
taint.(SequenceKind).getItem() instanceof TarFileInfo
)
}
}
/* Any call to an extractall method */
class ExtractAllSink extends TaintSink {
ExtractAllSink() {
exists(CallNode call |
this = call.getFunction().(AttrNode).getObject("extractall") and
not exists(call.getAnArg())
)
}
override predicate sinks(TaintKind kind) { kind instanceof OpenTarFile }
}
/* Argument to extract method */
class ExtractSink extends TaintSink {
CallNode call;
ExtractSink() {
call.getFunction().(AttrNode).getName() = "extract" and
this = call.getArg(0)
}
override predicate sinks(TaintKind kind) { kind instanceof TarFileInfo }
}
/* Members argument to extract method */
class ExtractMembersSink extends TaintSink {
CallNode call;
ExtractMembersSink() {
call.getFunction().(AttrNode).getName() = "extractall" and
(this = call.getArg(0) or this = call.getArgByName("members"))
}
override predicate sinks(TaintKind kind) {
kind.(SequenceKind).getItem() instanceof TarFileInfo
or
kind instanceof OpenTarFile
}
}
class TarFileInfoSanitizer extends Sanitizer {
TarFileInfoSanitizer() { this = "TarInfo sanitizer" }
/* 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())
}
private predicate clears_taint_on_false_edge(ControlFlowNode test, boolean sense) {
path_sanitizing_test(test) and
sense = false
or
// handle `not` (also nested)
test.(UnaryExprNode).getNode().getOp() instanceof Not and
clears_taint_on_false_edge(test.(UnaryExprNode).getOperand(), sense.booleanNot())
}
}
private predicate path_sanitizing_test(ControlFlowNode test) {
/* Assume that any test with "path" in it is a sanitizer */
test.getAChild+().(AttrNode).getName().matches("%path")
or
test.getAChild+().(NameNode).getId().matches("%path")
}
class TarSlipConfiguration extends TaintTracking::Configuration {
TarSlipConfiguration() { this = "TarSlip configuration" }
override predicate isSource(TaintTracking::Source source) { source instanceof TarfileOpen }
override predicate isSink(TaintTracking::Sink sink) {
sink instanceof ExtractSink or
sink instanceof ExtractAllSink or
sink instanceof ExtractMembersSink
}
override predicate isSanitizer(Sanitizer sanitizer) {
sanitizer instanceof TarFileInfoSanitizer
or
sanitizer instanceof ExcludeTarFilePy
}
override predicate isBarrier(DataFlow::Node node) {
// Avoid flow into the tarfile module
exists(ParameterDefinition def |
node.asVariable().getDefinition() = def
or
node.asCfgNode() = def.getDefiningNode()
|
def.getScope() = Value::named("tarfile.open").(CallableValue).getScope()
or
def.isSelf() and def.getScope().getEnclosingModule().getName() = "tarfile"
)
}
}
from TarSlipConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "Extraction of tarfile from $@", src.getSource(),
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Extraction of tarfile from $@", source.getNode(),
"a potentially untrusted source"

View File

@@ -42,7 +42,7 @@ where
not exists(call.getArgByName("autoescape"))
or
call.getKeywordParameter("autoescape")
.getAValueReachingRhs()
.getAValueReachingSink()
.asExpr()
.(ImmutableLiteral)
.booleanValue() = false

View File

@@ -18,9 +18,9 @@ import semmle.python.dataflow.new.TaintTracking
API::Node libPam() {
exists(API::CallNode findLibCall, API::CallNode cdllCall |
findLibCall = API::moduleImport("ctypes").getMember("util").getMember("find_library").getACall() and
findLibCall.getParameter(0).getAValueReachingRhs().asExpr().(StrConst).getText() = "pam" and
findLibCall.getParameter(0).getAValueReachingSink().asExpr().(StrConst).getText() = "pam" and
cdllCall = API::moduleImport("ctypes").getMember("CDLL").getACall() and
cdllCall.getParameter(0).getAValueReachingRhs() = findLibCall
cdllCall.getParameter(0).getAValueReachingSink() = findLibCall
|
result = cdllCall.getReturn()
)

View File

@@ -29,7 +29,7 @@ where
call = paramikoSSHClientInstance().getMember("set_missing_host_key_policy").getACall() and
arg in [call.getArg(0), call.getArgByName("policy")] and
(
arg = unsafe_paramiko_policy(name).getAUse() or
arg = unsafe_paramiko_policy(name).getReturn().getAUse()
arg = unsafe_paramiko_policy(name).getAValueReachableFromSource() or
arg = unsafe_paramiko_policy(name).getReturn().getAValueReachableFromSource()
)
select call, "Setting missing host key policy to " + name + " may be unsafe."

View File

@@ -17,7 +17,8 @@ class PyOpenSSLContextCreation extends ContextCreation, DataFlow::CallCfgNode {
protocolArg in [this.getArg(0), this.getArgByName("method")]
|
protocolArg in [
pyo.specific_version(result).getAUse(), pyo.unspecific_version(result).getAUse()
pyo.specific_version(result).getAValueReachableFromSource(),
pyo.unspecific_version(result).getAValueReachableFromSource()
]
)
}
@@ -43,9 +44,10 @@ class SetOptionsCall extends ProtocolRestriction, DataFlow::CallCfgNode {
}
override ProtocolVersion getRestriction() {
API::moduleImport("OpenSSL").getMember("SSL").getMember("OP_NO_" + result).getAUse() in [
this.getArg(0), this.getArgByName("options")
]
API::moduleImport("OpenSSL")
.getMember("SSL")
.getMember("OP_NO_" + result)
.getAValueReachableFromSource() in [this.getArg(0), this.getArgByName("options")]
}
}

View File

@@ -15,7 +15,10 @@ class SSLContextCreation extends ContextCreation, DataFlow::CallCfgNode {
protocolArg in [this.getArg(0), this.getArgByName("protocol")]
|
protocolArg =
[ssl.specific_version(result).getAUse(), ssl.unspecific_version(result).getAUse()]
[
ssl.specific_version(result).getAValueReachableFromSource(),
ssl.unspecific_version(result).getAValueReachableFromSource()
]
)
or
not exists(this.getArg(_)) and
@@ -54,7 +57,11 @@ class OptionsAugOr extends ProtocolRestriction, DataFlow::CfgNode {
aa.getTarget() = attr.getNode() and
attr.getName() = "options" and
attr.getObject() = node and
flag = API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() and
flag =
API::moduleImport("ssl")
.getMember("OP_NO_" + restriction)
.getAValueReachableFromSource()
.asExpr() and
(
aa.getValue() = flag
or
@@ -79,7 +86,11 @@ class OptionsAugAndNot extends ProtocolUnrestriction, DataFlow::CfgNode {
attr.getObject() = node and
notFlag.getOp() instanceof Invert and
notFlag.getOperand() = flag and
flag = API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() and
flag =
API::moduleImport("ssl")
.getMember("OP_NO_" + restriction)
.getAValueReachableFromSource()
.asExpr() and
(
aa.getValue() = notFlag
or
@@ -134,7 +145,10 @@ class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction, Data
this = aw.getObject() and
aw.getAttributeName() = "minimum_version" and
aw.getValue() =
API::moduleImport("ssl").getMember("TLSVersion").getMember(restriction).getAUse()
API::moduleImport("ssl")
.getMember("TLSVersion")
.getMember(restriction)
.getAValueReachableFromSource()
)
}
@@ -188,7 +202,8 @@ class Ssl extends TlsLibrary {
override DataFlow::CallCfgNode insecure_connection_creation(ProtocolVersion version) {
result = API::moduleImport("ssl").getMember("wrap_socket").getACall() and
this.specific_version(version).getAUse() = result.getArgByName("ssl_version") and
this.specific_version(version).getAValueReachableFromSource() =
result.getArgByName("ssl_version") and
version.isInsecure()
}

View File

@@ -36,13 +36,13 @@ string permissive_permission(int p) {
predicate chmod_call(API::CallNode call, string name, int mode) {
call = API::moduleImport("os").getMember("chmod").getACall() and
mode = call.getParameter(1, "mode").getAValueReachingRhs().asExpr().(IntegerLiteral).getValue() and
mode = call.getParameter(1, "mode").getAValueReachingSink().asExpr().(IntegerLiteral).getValue() and
name = "chmod"
}
predicate open_call(API::CallNode call, string name, int mode) {
call = API::moduleImport("os").getMember("open").getACall() and
mode = call.getParameter(2, "mode").getAValueReachingRhs().asExpr().(IntegerLiteral).getValue() and
mode = call.getParameter(2, "mode").getAValueReachingSink().asExpr().(IntegerLiteral).getValue() and
name = "open"
}

View File

@@ -5,7 +5,7 @@
*/
import python
import DefinitionTracking
import analysis.DefinitionTracking
predicate uniqueness_error(int number, string what, string problem) {
what in [

View File

@@ -1,516 +0,0 @@
/**
* Definition tracking for jump-to-defn query.
*/
import python
import semmle.python.pointsto.PointsTo
import IDEContextual
private newtype TDefinition =
TLocalDefinition(AstNode a) { a instanceof Expr or a instanceof Stmt or a instanceof Module }
/** A definition for the purposes of jump-to-definition. */
class Definition extends TLocalDefinition {
/** Gets a textual representation of this element. */
string toString() { result = "Definition " + this.getAstNode().getLocation().toString() }
AstNode getAstNode() { this = TLocalDefinition(result) }
Module getModule() { result = this.getAstNode().getScope().getEnclosingModule() }
Location getLocation() { result = this.getAstNode().getLocation() }
}
private predicate jump_to_defn(ControlFlowNode use, Definition defn) {
exists(EssaVariable var |
use = var.getASourceUse() and
ssa_variable_defn(var, defn)
)
or
exists(string name |
use.isLoad() and
jump_to_defn_attribute(use.(AttrNode).getObject(name), name, defn)
)
or
exists(PythonModuleObject mod |
use.(ImportExprNode).refersTo(mod) and
defn.getAstNode() = mod.getModule()
)
or
exists(PythonModuleObject mod, string name |
use.(ImportMemberNode).getModule(name).refersTo(mod) and
scope_jump_to_defn_attribute(mod.getModule(), name, defn)
)
or
exists(PackageObject package |
use.(ImportExprNode).refersTo(package) and
defn.getAstNode() = package.getInitModule().getModule()
)
or
exists(PackageObject package, string name |
use.(ImportMemberNode).getModule(name).refersTo(package) and
scope_jump_to_defn_attribute(package.getInitModule().getModule(), name, defn)
)
or
(use instanceof PyFunctionObject or use instanceof ClassObject) and
defn.getAstNode() = use.getNode()
}
/* Prefer class and functions to class-expressions and function-expressions. */
private predicate preferred_jump_to_defn(Expr use, Definition def) {
not use instanceof ClassExpr and
not use instanceof FunctionExpr and
jump_to_defn(use.getAFlowNode(), def)
}
private predicate unique_jump_to_defn(Expr use, Definition def) {
preferred_jump_to_defn(use, def) and
not exists(Definition other |
other != def and
preferred_jump_to_defn(use, other)
)
}
private predicate ssa_variable_defn(EssaVariable var, Definition defn) {
ssa_defn_defn(var.getDefinition(), defn)
}
/** Holds if the phi-function `phi` refers to (`value`, `cls`, `origin`) given the context `context`. */
private predicate ssa_phi_defn(PhiFunction phi, Definition defn) {
ssa_variable_defn(phi.getAnInput(), defn)
}
/** Holds if the ESSA defn `def` refers to (`value`, `cls`, `origin`) given the context `context`. */
private predicate ssa_defn_defn(EssaDefinition def, Definition defn) {
ssa_phi_defn(def, defn)
or
ssa_node_defn(def, defn)
or
ssa_filter_defn(def, defn)
or
ssa_node_refinement_defn(def, defn)
}
/** Holds if ESSA edge refinement, `def`, is defined by `defn` */
predicate ssa_filter_defn(PyEdgeRefinement def, Definition defn) {
ssa_variable_defn(def.getInput(), defn)
}
/** Holds if ESSA defn, `uniphi`,is defined by `defn` */
predicate uni_edged_phi_defn(SingleSuccessorGuard uniphi, Definition defn) {
ssa_variable_defn(uniphi.getInput(), defn)
}
pragma[noinline]
private predicate ssa_node_defn(EssaNodeDefinition def, Definition defn) {
assignment_jump_to_defn(def, defn)
or
parameter_defn(def, defn)
or
delete_defn(def, defn)
or
scope_entry_defn(def, defn)
or
implicit_submodule_defn(def, defn)
}
/* Definition for normal assignments `def = ...` */
private predicate assignment_jump_to_defn(AssignmentDefinition def, Definition defn) {
defn = TLocalDefinition(def.getValue().getNode())
}
pragma[noinline]
private predicate ssa_node_refinement_defn(EssaNodeRefinement def, Definition defn) {
method_callsite_defn(def, defn)
or
import_star_defn(def, defn)
or
attribute_assignment_defn(def, defn)
or
callsite_defn(def, defn)
or
argument_defn(def, defn)
or
attribute_delete_defn(def, defn)
or
uni_edged_phi_defn(def, defn)
}
/* Definition for parameter. `def foo(param): ...` */
private predicate parameter_defn(ParameterDefinition def, Definition defn) {
defn.getAstNode() = def.getDefiningNode().getNode()
}
/* Definition for deletion: `del name` */
private predicate delete_defn(DeletionDefinition def, Definition defn) { none() }
/* Implicit "defn" of the names of submodules at the start of an `__init__.py` file. */
private predicate implicit_submodule_defn(ImplicitSubModuleDefinition def, Definition defn) {
exists(PackageObject package, ModuleObject mod |
package.getInitModule().getModule() = def.getDefiningNode().getScope() and
mod = package.submodule(def.getSourceVariable().getName()) and
defn.getAstNode() = mod.getModule()
)
}
/*
* Helper for scope_entry_value_transfer(...).
* Transfer of values from the callsite to the callee, for enclosing variables, but not arguments/parameters
*/
private predicate scope_entry_value_transfer_at_callsite(
EssaVariable pred_var, ScopeEntryDefinition succ_def
) {
exists(CallNode callsite, FunctionObject f |
f.getACall() = callsite and
pred_var.getSourceVariable() = succ_def.getSourceVariable() and
pred_var.getAUse() = callsite and
succ_def.getDefiningNode() = f.getFunction().getEntryNode()
)
}
/* Model the transfer of values at scope-entry points. Transfer from `pred_var, pred_context` to `succ_def, succ_context` */
private predicate scope_entry_value_transfer(EssaVariable pred_var, ScopeEntryDefinition succ_def) {
BaseFlow::scope_entry_value_transfer_from_earlier(pred_var, _, succ_def, _)
or
scope_entry_value_transfer_at_callsite(pred_var, succ_def)
or
class_entry_value_transfer(pred_var, succ_def)
}
/* Helper for scope_entry_value_transfer */
private predicate class_entry_value_transfer(EssaVariable pred_var, ScopeEntryDefinition succ_def) {
exists(ImportTimeScope scope, ControlFlowNode class_def |
class_def = pred_var.getAUse() and
scope.entryEdge(class_def, succ_def.getDefiningNode()) and
pred_var.getSourceVariable() = succ_def.getSourceVariable()
)
}
/* Definition for implicit variable declarations at scope-entry. */
pragma[noinline]
private predicate scope_entry_defn(ScopeEntryDefinition def, Definition defn) {
/* Transfer from another scope */
exists(EssaVariable var |
scope_entry_value_transfer(var, def) and
ssa_variable_defn(var, defn)
)
}
/*
* Definition for a variable (possibly) redefined by a call:
* Just assume that call does not define variable
*/
pragma[noinline]
private predicate callsite_defn(CallsiteRefinement def, Definition defn) {
ssa_variable_defn(def.getInput(), defn)
}
/* Pass through for `self` for the implicit re-defn of `self` in `self.foo()` */
private predicate method_callsite_defn(MethodCallsiteRefinement def, Definition defn) {
/* The value of self remains the same, only the attributes may change */
ssa_variable_defn(def.getInput(), defn)
}
/** Helpers for import_star_defn */
pragma[noinline]
private predicate module_and_name_for_import_star(
ModuleObject mod, string name, ImportStarRefinement def
) {
exists(ImportStarNode im_star |
module_and_name_for_import_star_helper(mod, name, im_star, def) and
mod.exports(name)
)
}
pragma[noinline]
private predicate module_and_name_for_import_star_helper(
ModuleObject mod, string name, ImportStarNode im_star, ImportStarRefinement def
) {
im_star = def.getDefiningNode() and
im_star.getModule().refersTo(mod) and
name = def.getSourceVariable().getName()
}
/** Holds if `def` is technically a defn of `var`, but the `from ... import *` does not in fact define `var` */
pragma[noinline]
private predicate variable_not_redefined_by_import_star(EssaVariable var, ImportStarRefinement def) {
var = def.getInput() and
exists(ModuleObject mod |
def.getDefiningNode().(ImportStarNode).getModule().refersTo(mod) and
not mod.exports(var.getSourceVariable().getName())
)
}
/* Definition for `from ... import *` */
private predicate import_star_defn(ImportStarRefinement def, Definition defn) {
exists(ModuleObject mod, string name | module_and_name_for_import_star(mod, name, def) |
/* Attribute from imported module */
scope_jump_to_defn_attribute(mod.getModule(), name, defn)
)
or
exists(EssaVariable var |
/* Retain value held before import */
variable_not_redefined_by_import_star(var, def) and
ssa_variable_defn(var, defn)
)
}
/** Attribute assignments have no effect as far as defn tracking is concerned */
private predicate attribute_assignment_defn(AttributeAssignment def, Definition defn) {
ssa_variable_defn(def.getInput(), defn)
}
/** Ignore the effects of calls on their arguments. This is an approximation, but attempting to improve accuracy would be very expensive for very little gain. */
private predicate argument_defn(ArgumentRefinement def, Definition defn) {
ssa_variable_defn(def.getInput(), defn)
}
/** Attribute deletions have no effect as far as value tracking is concerned. */
pragma[noinline]
private predicate attribute_delete_defn(EssaAttributeDeletion def, Definition defn) {
ssa_variable_defn(def.getInput(), defn)
}
/*
* Definition flow for attributes. These mirror the "normal" defn predicates.
* For each defn predicate `xxx_defn(XXX def, Definition defn)`
* There is an equivalent predicate that tracks the values in attributes:
* `xxx_jump_to_defn_attribute(XXX def, string name, Definition defn)`
*/
/**
* INTERNAL -- Public for testing only.
* Holds if the attribute `name` of the ssa variable `var` refers to (`value`, `cls`, `origin`)
*/
predicate ssa_variable_jump_to_defn_attribute(EssaVariable var, string name, Definition defn) {
ssa_defn_jump_to_defn_attribute(var.getDefinition(), name, defn)
}
/** Helper for ssa_variable_jump_to_defn_attribute */
private predicate ssa_defn_jump_to_defn_attribute(EssaDefinition def, string name, Definition defn) {
ssa_phi_jump_to_defn_attribute(def, name, defn)
or
ssa_node_jump_to_defn_attribute(def, name, defn)
or
ssa_node_refinement_jump_to_defn_attribute(def, name, defn)
or
ssa_filter_jump_to_defn_attribute(def, name, defn)
}
/** Holds if ESSA edge refinement, `def`, is defined by `defn` of `priority` */
predicate ssa_filter_jump_to_defn_attribute(PyEdgeRefinement def, string name, Definition defn) {
ssa_variable_jump_to_defn_attribute(def.getInput(), name, defn)
}
/** Holds if the attribute `name` of the ssa phi-function defn `phi` refers to (`value`, `cls`, `origin`) */
private predicate ssa_phi_jump_to_defn_attribute(PhiFunction phi, string name, Definition defn) {
ssa_variable_jump_to_defn_attribute(phi.getAnInput(), name, defn)
}
/** Helper for ssa_defn_jump_to_defn_attribute */
pragma[noinline]
private predicate ssa_node_jump_to_defn_attribute(
EssaNodeDefinition def, string name, Definition defn
) {
assignment_jump_to_defn_attribute(def, name, defn)
or
self_parameter_jump_to_defn_attribute(def, name, defn)
or
scope_entry_jump_to_defn_attribute(def, name, defn)
}
/** Helper for ssa_defn_jump_to_defn_attribute */
pragma[noinline]
private predicate ssa_node_refinement_jump_to_defn_attribute(
EssaNodeRefinement def, string name, Definition defn
) {
attribute_assignment_jump_to_defn_attribute(def, name, defn)
or
argument_jump_to_defn_attribute(def, name, defn)
}
pragma[noinline]
private predicate scope_entry_jump_to_defn_attribute(
ScopeEntryDefinition def, string name, Definition defn
) {
exists(EssaVariable var |
scope_entry_value_transfer(var, def) and
ssa_variable_jump_to_defn_attribute(var, name, defn)
)
}
private predicate scope_jump_to_defn_attribute(ImportTimeScope s, string name, Definition defn) {
exists(EssaVariable var |
BaseFlow::reaches_exit(var) and
var.getScope() = s and
var.getName() = name
|
ssa_variable_defn(var, defn)
)
}
private predicate jump_to_defn_attribute(ControlFlowNode use, string name, Definition defn) {
/* Local attribute */
exists(EssaVariable var |
use = var.getASourceUse() and
ssa_variable_jump_to_defn_attribute(var, name, defn)
)
or
/* Instance attributes */
exists(ClassObject cls | use.refersTo(_, cls, _) |
scope_jump_to_defn_attribute(cls.getPyClass(), name, defn)
)
or
/* Super attributes */
exists(AttrNode f, SuperBoundMethod sbm, Object function |
use = f.getObject(name) and
f.refersTo(sbm) and
function = sbm.getFunction(_) and
function.getOrigin() = defn.getAstNode()
)
or
/* Class or module attribute */
exists(Object obj, Scope scope |
use.refersTo(obj) and
scope_jump_to_defn_attribute(scope, name, defn)
|
obj.(ClassObject).getPyClass() = scope
or
obj.(PythonModuleObject).getModule() = scope
or
obj.(PackageObject).getInitModule().getModule() = scope
)
}
pragma[noinline]
private predicate assignment_jump_to_defn_attribute(
AssignmentDefinition def, string name, Definition defn
) {
jump_to_defn_attribute(def.getValue(), name, defn)
}
pragma[noinline]
private predicate attribute_assignment_jump_to_defn_attribute(
AttributeAssignment def, string name, Definition defn
) {
defn.getAstNode() = def.getDefiningNode().getNode() and name = def.getName()
or
ssa_variable_jump_to_defn_attribute(def.getInput(), name, defn) and not name = def.getName()
}
/**
* Holds if `def` defines the attribute `name`
* `def` takes the form `setattr(use, "name")` where `use` is the input to the defn.
*/
private predicate sets_attribute(ArgumentRefinement def, string name) {
exists(CallNode call |
call = def.getDefiningNode() and
call.getFunction().refersTo(Object::builtin("setattr")) and
def.getInput().getAUse() = call.getArg(0) and
call.getArg(1).getNode().(StrConst).getText() = name
)
}
pragma[noinline]
private predicate argument_jump_to_defn_attribute(
ArgumentRefinement def, string name, Definition defn
) {
if sets_attribute(def, name)
then jump_to_defn(def.getDefiningNode().(CallNode).getArg(2), defn)
else ssa_variable_jump_to_defn_attribute(def.getInput(), name, defn)
}
/** Gets the (temporally) preceding variable for "self", e.g. `def` is in method foo() and `result` is in `__init__()`. */
private EssaVariable preceding_self_variable(ParameterDefinition def) {
def.isSelf() and
exists(Function preceding, Function method |
method = def.getScope() and
// Only methods
preceding.isMethod() and
preceding.precedes(method) and
BaseFlow::reaches_exit(result) and
result.getSourceVariable().(Variable).isSelf() and
result.getScope() = preceding
)
}
pragma[noinline]
private predicate self_parameter_jump_to_defn_attribute(
ParameterDefinition def, string name, Definition defn
) {
ssa_variable_jump_to_defn_attribute(preceding_self_variable(def), name, defn)
}
/**
* Gets a definition for 'use'.
* This exists primarily for testing use `getPreferredDefinition()` instead.
*/
Definition getADefinition(Expr use) {
jump_to_defn(use.getAFlowNode(), result) and
not use instanceof Call and
not use.isArtificial() and
// Not the use itself
not result = TLocalDefinition(use)
}
/**
* Gets the unique definition for 'use', if one can be found.
* Helper for the jump-to-definition query.
*/
Definition getUniqueDefinition(Expr use) {
unique_jump_to_defn(use, result) and
not use instanceof Call and
not use.isArtificial() and
// Not the use itself
not result = TLocalDefinition(use)
}
/** A helper class to get suitable locations for attributes */
class NiceLocationExpr extends Expr {
/** Gets a textual representation of this element. */
override string toString() { result = this.(Expr).toString() }
/**
* Holds if this element is at the specified location.
* The location spans column `bc` of line `bl` to
* column `ec` of line `el` in file `f`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(string f, int bl, int bc, int el, int ec) {
/* Attribute location for x.y is that of 'y' so that url does not overlap with that of 'x' */
exists(int abl, int abc | this.(Attribute).getLocation().hasLocationInfo(f, abl, abc, el, ec) |
bl = el and bc = ec - this.(Attribute).getName().length() + 1
)
or
this.(Name).getLocation().hasLocationInfo(f, bl, bc, el, ec)
or
// Show xxx for `xxx` in `from xxx import y` or
// for `import xxx` or for `import xxx as yyy`.
this.(ImportExpr).getLocation().hasLocationInfo(f, bl, bc, el, ec)
or
/* Show y for `y` in `from xxx import y` */
exists(string name |
name = this.(ImportMember).getName() and
this.(ImportMember).getLocation().hasLocationInfo(f, _, _, el, ec) and
bl = el and
bc = ec - name.length() + 1
)
}
}
/**
* Gets the definition (of kind `kind`) for the expression `use`, if one can be found.
*/
cached
Definition definitionOf(NiceLocationExpr use, string kind) {
exists(string f, int l |
result = getUniqueDefinition(use) and
kind = "Definition" and
use.hasLocationInfo(f, l, _, _, _) and
// Ignore if the definition is on the same line as the use
not result.getLocation().hasLocationInfo(f, l, _, _, _)
)
}

View File

@@ -6,7 +6,7 @@
*/
import python
import DefinitionTracking
import analysis.DefinitionTracking
from NiceLocationExpr use, Definition defn, string kind
where defn = definitionOf(use, kind)

View File

@@ -1,22 +0,0 @@
/**
* Provides shared predicates related to contextual queries in the code viewer.
*/
import semmle.files.FileSystem
/**
* Returns the `File` matching the given source file name as encoded by the VS
* Code extension.
*/
cached
File getFileBySourceArchiveName(string name) {
// The name provided for a file in the source archive by the VS Code extension
// has some differences from the absolute path in the database:
// 1. colons are replaced by underscores
// 2. there's a leading slash, even for Windows paths: "C:/foo/bar" ->
// "/C_/foo/bar"
// 3. double slashes in UNC prefixes are replaced with a single slash
// We can handle 2 and 3 together by unconditionally adding a leading slash
// before replacing double slashes.
name = ("/" + result.getAbsolutePath().replaceAll(":", "_")).replaceAll("//", "/")
}

View File

@@ -1,20 +0,0 @@
/**
* @name Jump-to-definition links
* @description Generates use-definition pairs that provide the data
* for jump-to-definition in the code viewer.
* @kind definitions
* @id py/ide-jump-to-definition
* @tags ide-contextual-queries/local-definitions
*/
import python
import DefinitionTracking
external string selectedSourceFile();
from NiceLocationExpr use, Definition defn, string kind, string f
where
defn = definitionOf(use, kind) and
use.hasLocationInfo(f, _, _, _, _) and
getFileBySourceArchiveName(selectedSourceFile()).getAbsolutePath() = f
select use, defn, kind

View File

@@ -1,19 +0,0 @@
/**
* @name Find-references links
* @description Generates use-definition pairs that provide the data
* for find-references in the code viewer.
* @kind definitions
* @id py/ide-find-references
* @tags ide-contextual-queries/local-references
*/
import python
import DefinitionTracking
external string selectedSourceFile();
from NiceLocationExpr use, Definition defn, string kind
where
defn = definitionOf(use, kind) and
defn.getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile())
select use, defn, kind

View File

@@ -3,7 +3,7 @@
*/
import python
import DefinitionTracking
import analysis.DefinitionTracking
predicate want_to_have_definition(Expr e) {
/* not builtin object like len, tuple, etc. */

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* The query "Use of a broken or weak cryptographic algorithm" (`py/weak-cryptographic-algorithm`) now report if a cryptographic operation is potentially insecure due to use of a weak block mode.

View File

@@ -1,4 +0,0 @@
---
category: majorAnalysis
---
* Improved library modeling for the query "Request without certificate validation" (`py/request-without-cert-validation`), so it now also covers `httpx`, `aiohttp.client`, and `urllib3`.

View File

@@ -0,0 +1,5 @@
---
category: breaking
---
* Contextual queries and the query libraries they depend on have been moved to the `codeql/python-all` package.

View File

@@ -0,0 +1,9 @@
## 0.2.0
### Major Analysis Improvements
* Improved library modeling for the query "Request without certificate validation" (`py/request-without-cert-validation`), so it now also covers `httpx`, `aiohttp.client`, and `urllib3`.
### Minor Analysis Improvements
* The query "Use of a broken or weak cryptographic algorithm" (`py/weak-cryptographic-algorithm`) now reports if a cryptographic operation is potentially insecure due to use of a weak block mode.

View File

@@ -0,0 +1,5 @@
## 0.3.0
### Breaking Changes
* Contextual queries and the query libraries they depend on have been moved to the `codeql/python-all` package.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.1.4
lastReleaseVersion: 0.3.0

View File

@@ -0,0 +1,7 @@
blob_client = blob_service_client.get_blob_client(container=container_name, blob=blob_name)
blob_client.require_encryption = True
blob_client.key_encryption_key = kek
# GOOD: Must use `encryption_version` set to `2.0`
blob_client.encryption_version = '2.0' # Use Version 2.0!
with open("decryptedcontentfile.txt", "rb") as stream:
blob_client.upload_blob(stream, overwrite=OVERWRITE_EXISTING)

View File

@@ -0,0 +1,29 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>Azure Storage .NET, Java, and Python SDKs support encryption on the client with a customer-managed key that is maintained in Azure Key Vault or another key store.</p>
<p>Current release versions of the Azure Storage SDKs use cipher block chaining (CBC mode) for client-side encryption (referred to as <code>v1</code>).</p>
</overview>
<recommendation>
<p>Consider switching to <code>v2</code> client-side encryption.</p>
</recommendation>
<example>
<sample src="UnsafeUsageOfClientSideEncryptionVersion.py" />
</example>
<references>
<li>
<a href="http://aka.ms/azstorageclientencryptionblog">Azure Storage Client Encryption Blog.</a>
</li>
<li>
<a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-30187">CVE-2022-30187</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,90 @@
/**
* @name Unsafe usage of v1 version of Azure Storage client-side encryption.
* @description Using version v1 of Azure Storage client-side encryption is insecure, and may enable an attacker to decrypt encrypted data
* @kind problem
* @tags security
* cryptography
* external/cwe/cwe-327
* @id py/azure-storage/unsafe-client-side-encryption-in-use
* @problem.severity error
* @precision medium
*/
import python
import semmle.python.ApiGraphs
predicate isUnsafeClientSideAzureStorageEncryptionViaAttributes(Call call, AttrNode node) {
exists(
API::Node n, API::Node n2, Attribute a, AssignStmt astmt, API::Node uploadBlob,
ControlFlowNode ctrlFlowNode, string s
|
s in ["key_encryption_key", "key_resolver_function"] and
n =
API::moduleImport("azure")
.getMember("storage")
.getMember("blob")
.getMember("BlobClient")
.getReturn()
.getMember(s) and
n2 =
API::moduleImport("azure")
.getMember("storage")
.getMember("blob")
.getMember("BlobClient")
.getReturn()
.getMember("upload_blob") and
n.getAValueReachableFromSource().asExpr() = a and
astmt.getATarget() = a and
a.getAFlowNode() = node and
uploadBlob =
API::moduleImport("azure")
.getMember("storage")
.getMember("blob")
.getMember("BlobClient")
.getReturn()
.getMember("upload_blob") and
uploadBlob.getACall().asExpr() = call and
ctrlFlowNode = call.getAFlowNode() and
node.strictlyReaches(ctrlFlowNode) and
node != ctrlFlowNode and
not exists(
AssignStmt astmt2, Attribute a2, AttrNode encryptionVersionSet, StrConst uc,
API::Node encryptionVersion
|
uc = astmt2.getValue() and
uc.getText() in ["'2.0'", "2.0"] and
encryptionVersion =
API::moduleImport("azure")
.getMember("storage")
.getMember("blob")
.getMember("BlobClient")
.getReturn()
.getMember("encryption_version") and
encryptionVersion.getAValueReachableFromSource().asExpr() = a2 and
astmt2.getATarget() = a2 and
a2.getAFlowNode() = encryptionVersionSet and
encryptionVersionSet.strictlyReaches(ctrlFlowNode)
)
)
}
predicate isUnsafeClientSideAzureStorageEncryptionViaObjectCreation(Call call, ControlFlowNode node) {
exists(API::Node c, string s, Keyword k | k.getAFlowNode() = node |
c.getACall().asExpr() = call and
c = API::moduleImport("azure").getMember("storage").getMember("blob").getMember(s) and
s in ["ContainerClient", "BlobClient", "BlobServiceClient"] and
k.getArg() = "key_encryption_key" and
k = call.getANamedArg() and
not k.getValue() instanceof None and
not exists(Keyword k2 | k2 = call.getANamedArg() |
k2.getArg() = "encryption_version" and
k2.getValue().(StrConst).getText() in ["'2.0'", "2.0"]
)
)
}
from Call call, ControlFlowNode node
where
isUnsafeClientSideAzureStorageEncryptionViaAttributes(call, node) or
isUnsafeClientSideAzureStorageEncryptionViaObjectCreation(call, node)
select node, "Unsafe usage of v1 version of Azure Storage client-side encryption."

View File

@@ -86,11 +86,13 @@ private module ExperimentalPrivateDjango {
t.start() and
(
exists(SubscriptNode subscript |
subscript.getObject() = baseClassRef().getReturn().getAUse().asCfgNode() and
subscript.getObject() =
baseClassRef().getReturn().getAValueReachableFromSource().asCfgNode() and
result.asCfgNode() = subscript
)
or
result.(DataFlow::AttrRead).getObject() = baseClassRef().getReturn().getAUse()
result.(DataFlow::AttrRead).getObject() =
baseClassRef().getReturn().getAValueReachableFromSource()
)
or
exists(DataFlow::TypeTracker t2 | result = headerInstance(t2).track(t2, t))

View File

@@ -29,7 +29,11 @@ module ExperimentalFlask {
/** Gets a reference to a header instance. */
private DataFlow::LocalSourceNode headerInstance() {
result = [Flask::Response::classRef(), flaskMakeResponse()].getReturn().getAMember().getAUse()
result =
[Flask::Response::classRef(), flaskMakeResponse()]
.getReturn()
.getAMember()
.getAValueReachableFromSource()
}
/** Gets a reference to a header instance call/subscript */

View File

@@ -90,7 +90,9 @@ private module LDAP {
/**List of SSL-demanding options */
private class LDAPSSLOptions extends DataFlow::Node {
LDAPSSLOptions() { this = ldap().getMember("OPT_X_TLS_" + ["DEMAND", "HARD"]).getAUse() }
LDAPSSLOptions() {
this = ldap().getMember("OPT_X_TLS_" + ["DEMAND", "HARD"]).getAValueReachableFromSource()
}
}
/**

View File

@@ -50,11 +50,11 @@ private module NoSql {
t.start() and
(
exists(SubscriptNode subscript |
subscript.getObject() = mongoClientInstance().getAUse().asCfgNode() and
subscript.getObject() = mongoClientInstance().getAValueReachableFromSource().asCfgNode() and
result.asCfgNode() = subscript
)
or
result.(DataFlow::AttrRead).getObject() = mongoClientInstance().getAUse()
result.(DataFlow::AttrRead).getObject() = mongoClientInstance().getAValueReachableFromSource()
or
result = mongoEngine().getMember(["get_db", "connect"]).getACall()
or

View File

@@ -1,28 +0,0 @@
/**
* @name Print AST
* @description Outputs a representation of a file's Abstract Syntax Tree. This
* query is used by the VS Code extension.
* @id py/print-ast
* @kind graph
* @tags ide-contextual-queries/print-ast
*/
import python
import semmle.python.PrintAst
import analysis.DefinitionTracking
/**
* Gets the source file that will be used to generate the AST.
*/
external string selectedSourceFile();
class PrintAstConfigurationOverride extends PrintAstConfiguration {
/**
* Holds if the location matches the selected file in the VS Code extension and
* the element is not a synthetic constructor.
*/
override predicate shouldPrint(AstNode e, Location l) {
super.shouldPrint(e, l) and
l.getFile() = getFileBySourceArchiveName(selectedSourceFile())
}
}

View File

@@ -1,5 +1,5 @@
name: codeql/python-queries
version: 0.2.0-dev
version: 0.3.1-dev
groups:
- python
- queries