Merge branch 'master' into python-3521-revived

This commit is contained in:
Rasmus Wriedt Larsen
2020-06-25 12:02:21 +02:00
820 changed files with 25680 additions and 11379 deletions

View File

@@ -31,8 +31,8 @@ predicate calls_super(FunctionObject f) {
)
}
/** Holds if the given name is white-listed for some reason */
predicate whitelisted(string name) {
/** Holds if the given name is allowed for some reason */
predicate allowed(string name) {
/*
* The standard library specifically recommends this :(
* See https://docs.python.org/3/library/socketserver.html#asynchronous-mixins
@@ -53,7 +53,7 @@ where
not name.matches("\\_\\_%\\_\\_") and
not calls_super(o1) and
not does_nothing(o2) and
not whitelisted(name) and
not allowed(name) and
not o1.overrides(o2) and
not o2.overrides(o1) and
not c.declaresAttribute(name)

View File

@@ -14,8 +14,8 @@
import python
from ClassObject c
where not c.isC() and not c.isContextManager() and exists(c.declaredAttribute("__del__"))
from ClassValue c
where not c.isBuiltin() and not c.isContextManager() and exists(c.declaredAttribute("__del__"))
select c,
"Class " + c.getName() +
" implements __del__ (presumably to release some resource). Consider making it a context manager."

View File

@@ -4,8 +4,8 @@
* the arguments with which it is called, and if it were called, would be likely to cause an error.
* @kind problem
* @tags maintainability
* @problem.severity error
* @sub-severity low
* @problem.severity recommendation
* @sub-severity high
* @precision high
* @id py/inheritance/incorrect-overridden-signature
*/

View File

@@ -20,7 +20,7 @@ where
count(int line |
exists(DuplicateBlock d | d.sourceFile() = f |
line in [d.sourceStartLine() .. d.sourceEndLine()] and
not whitelistedLineForDuplication(f, line)
not allowlistedLineForDuplication(f, line)
)
)
select f, n order by n desc

View File

@@ -20,7 +20,7 @@ where
count(int line |
exists(SimilarBlock d | d.sourceFile() = f |
line in [d.sourceStartLine() .. d.sourceEndLine()] and
not whitelistedLineForDuplication(f, line)
not allowlistedLineForDuplication(f, line)
)
)
select f, n order by n desc

View File

@@ -68,7 +68,7 @@
<p>
The second two examples show safe checks.
In <code>safe1</code>, a white-list is used. Although fairly inflexible,
In <code>safe1</code>, an allowlist is used. Although fairly inflexible,
this is easy to get right and is most likely to be safe.
</p>
<p>

View File

@@ -21,16 +21,16 @@ def unsafe2(request):
#Simplest and safest approach is to use a white-list
#Simplest and safest approach is to use an allowlist
@app.route('/some/path/good1')
def safe1(request):
whitelist = [
allowlist = [
"example.com/home",
"example.com/login",
]
target = request.args.get('target', '')
if target in whitelist:
if target in allowlist:
return redirect(target)
#More complex example allowing sub-domains.

View File

@@ -26,7 +26,7 @@ Ideally, follow these rules:
<li>Do not allow directory separators such as "/" or "\" (depending on the file system).</li>
<li>Do not rely on simply replacing problematic sequences such as "../". For example, after
applying this filter to ".../...//", the resulting string would still be "../".</li>
<li>Use a whitelist of known good patterns.</li>
<li>Use an allowlist of known good patterns.</li>
</ul>
</recommendation>

View File

@@ -25,7 +25,7 @@ safe before using it.</p>
<p>The following example shows two functions. The first is unsafe as it takes a shell script that can be changed
by a user, and passes it straight to <code>subprocess.call()</code> without examining it first.
The second is safe as it selects the command from a predefined white-list.</p>
The second is safe as it selects the command from a predefined allowlist.</p>
<sample src="examples/command_injection.py" />

View File

@@ -32,6 +32,8 @@ class CommandInjectionConfiguration extends TaintTracking::Configuration {
override predicate isExtension(TaintTracking::Extension extension) {
extension instanceof FirstElementFlow
or
extension instanceof FabricExecuteExtension
}
}

View File

@@ -19,5 +19,5 @@ def command_execution_unsafe(request):
def command_execution_safe(request):
if request.method == 'POST':
action = request.POST.get('action', '')
#GOOD -- Use a whitelist
#GOOD -- Use an allowlist
subprocess.call(["application", COMMANDS[action]])

View File

@@ -16,7 +16,7 @@ import python
import Shadowing
import semmle.python.types.Builtins
predicate white_list(string name) {
predicate allow_list(string name) {
/* These are rarely used and thus unlikely to be confusing */
name = "iter" or
name = "next" or
@@ -51,7 +51,7 @@ predicate shadows(Name d, string name, Function scope, int line) {
) and
d.getScope() = scope and
d.getLocation().getStartLine() = line and
not white_list(name) and
not allow_list(name) and
not optimizing_parameter(d)
}

View File

@@ -33,12 +33,27 @@ predicate mutates_globals(ModuleValue m) {
exists(SubscriptNode sub | sub.getObject() = globals and sub.isStore())
)
or
exists(Value enum_convert, ClassValue enum_class |
// Enum (added in 3.4) has method `_convert_` that alters globals
// This was called `_convert` until 3.8, but that name will be removed in 3.9
exists(ClassValue enum_class |
enum_class.getASuperType() = Value::named("enum.Enum") and
enum_convert = enum_class.attr("_convert") and
exists(CallNode call | call.getScope() = m.getScope() |
enum_convert.getACall() = call or
call.getFunction().pointsTo(enum_convert)
(
// In Python < 3.8, Enum._convert can be found with points-to
exists(Value enum_convert |
enum_convert = enum_class.attr("_convert") and
exists(CallNode call | call.getScope() = m.getScope() |
enum_convert.getACall() = call or
call.getFunction().pointsTo(enum_convert)
)
)
or
// In Python 3.8, Enum._convert_ is implemented using a metaclass, and our points-to
// analysis doesn't handle that well enough. So we need a special case for this
not exists(Value enum_convert | enum_convert = enum_class.attr("_convert")) and
exists(CallNode call | call.getScope() = m.getScope() |
call.getFunction().(AttrNode).getObject(["_convert", "_convert_"]).pointsTo() =
enum_class
)
)
)
}

View File

@@ -1,7 +1,7 @@
/**
* @name Sanity check
* @description General sanity check to be run on any and all code. Should never produce any results.
* @id py/sanity-check
* @name Consistency check
* @description General consistency check to be run on any and all code. Should never produce any results.
* @id py/consistency-check
*/
import python
@@ -24,7 +24,7 @@ predicate uniqueness_error(int number, string what, string problem) {
)
}
predicate ast_sanity(string clsname, string problem, string what) {
predicate ast_consistency(string clsname, string problem, string what) {
exists(AstNode a | clsname = a.getAQlClass() |
uniqueness_error(count(a.toString()), "toString", problem) and
what = "at " + a.getLocation().toString()
@@ -39,7 +39,7 @@ predicate ast_sanity(string clsname, string problem, string what) {
)
}
predicate location_sanity(string clsname, string problem, string what) {
predicate location_consistency(string clsname, string problem, string what) {
exists(Location l | clsname = l.getAQlClass() |
uniqueness_error(count(l.toString()), "toString", problem) and what = "at " + l.toString()
or
@@ -65,7 +65,7 @@ predicate location_sanity(string clsname, string problem, string what) {
)
}
predicate cfg_sanity(string clsname, string problem, string what) {
predicate cfg_consistency(string clsname, string problem, string what) {
exists(ControlFlowNode f | clsname = f.getAQlClass() |
uniqueness_error(count(f.getNode()), "getNode", problem) and
what = "at " + f.getLocation().toString()
@@ -80,7 +80,7 @@ predicate cfg_sanity(string clsname, string problem, string what) {
)
}
predicate scope_sanity(string clsname, string problem, string what) {
predicate scope_consistency(string clsname, string problem, string what) {
exists(Scope s | clsname = s.getAQlClass() |
uniqueness_error(count(s.getEntryNode()), "getEntryNode", problem) and
what = "at " + s.getLocation().toString()
@@ -125,7 +125,7 @@ private predicate introspected_builtin_object(Object o) {
py_cobject_sources(o, 0)
}
predicate builtin_object_sanity(string clsname, string problem, string what) {
predicate builtin_object_consistency(string clsname, string problem, string what) {
exists(Object o |
clsname = o.getAQlClass() and
what = best_description_builtin_object(o) and
@@ -146,7 +146,7 @@ predicate builtin_object_sanity(string clsname, string problem, string what) {
)
}
predicate source_object_sanity(string clsname, string problem, string what) {
predicate source_object_consistency(string clsname, string problem, string what) {
exists(Object o | clsname = o.getAQlClass() and not o.isBuiltin() |
uniqueness_error(count(o.getOrigin()), "getOrigin", problem) and
what = "at " + o.getOrigin().getLocation().toString()
@@ -161,7 +161,7 @@ predicate source_object_sanity(string clsname, string problem, string what) {
)
}
predicate ssa_sanity(string clsname, string problem, string what) {
predicate ssa_consistency(string clsname, string problem, string what) {
/* Zero or one definitions of each SSA variable */
exists(SsaVariable var | clsname = var.getAQlClass() |
uniqueness_error(strictcount(var.getDefinition()), "getDefinition", problem) and
@@ -196,7 +196,7 @@ predicate ssa_sanity(string clsname, string problem, string what) {
)
}
predicate function_object_sanity(string clsname, string problem, string what) {
predicate function_object_consistency(string clsname, string problem, string what) {
exists(FunctionObject func | clsname = func.getAQlClass() |
what = func.getName() and
(
@@ -229,7 +229,7 @@ predicate intermediate_origins(ControlFlowNode use, ControlFlowNode inter, Objec
)
}
predicate points_to_sanity(string clsname, string problem, string what) {
predicate points_to_consistency(string clsname, string problem, string what) {
exists(Object obj |
multiple_origins_per_object(obj) and
clsname = obj.getAQlClass() and
@@ -245,7 +245,7 @@ predicate points_to_sanity(string clsname, string problem, string what) {
)
}
predicate jump_to_definition_sanity(string clsname, string problem, string what) {
predicate jump_to_definition_consistency(string clsname, string problem, string what) {
problem = "multiple (jump-to) definitions" and
exists(Expr use |
strictcount(getUniqueDefinition(use)) > 1 and
@@ -254,7 +254,7 @@ predicate jump_to_definition_sanity(string clsname, string problem, string what)
)
}
predicate file_sanity(string clsname, string problem, string what) {
predicate file_consistency(string clsname, string problem, string what) {
exists(File file, Folder folder |
clsname = file.getAQlClass() and
problem = "has same name as a folder" and
@@ -269,7 +269,7 @@ predicate file_sanity(string clsname, string problem, string what) {
)
}
predicate class_value_sanity(string clsname, string problem, string what) {
predicate class_value_consistency(string clsname, string problem, string what) {
exists(ClassValue value, ClassValue sup, string attr |
what = value.getName() and
sup = value.getASuperType() and
@@ -283,16 +283,16 @@ predicate class_value_sanity(string clsname, string problem, string what) {
from string clsname, string problem, string what
where
ast_sanity(clsname, problem, what) or
location_sanity(clsname, problem, what) or
scope_sanity(clsname, problem, what) or
cfg_sanity(clsname, problem, what) or
ssa_sanity(clsname, problem, what) or
builtin_object_sanity(clsname, problem, what) or
source_object_sanity(clsname, problem, what) or
function_object_sanity(clsname, problem, what) or
points_to_sanity(clsname, problem, what) or
jump_to_definition_sanity(clsname, problem, what) or
file_sanity(clsname, problem, what) or
class_value_sanity(clsname, problem, what)
ast_consistency(clsname, problem, what) or
location_consistency(clsname, problem, what) or
scope_consistency(clsname, problem, what) or
cfg_consistency(clsname, problem, what) or
ssa_consistency(clsname, problem, what) or
builtin_object_consistency(clsname, problem, what) or
source_object_consistency(clsname, problem, what) or
function_object_consistency(clsname, problem, what) or
points_to_consistency(clsname, problem, what) or
jump_to_definition_consistency(clsname, problem, what) or
file_consistency(clsname, problem, what) or
class_value_consistency(clsname, problem, what)
select clsname + " " + what + " has " + problem

View File

@@ -7,7 +7,7 @@ import DefinitionTracking
predicate want_to_have_definition(Expr e) {
/* not builtin object like len, tuple, etc. */
not exists(Object cobj | e.refersTo(cobj) and cobj.isC()) and
not exists(Value builtin | e.pointsTo(builtin) and builtin.isBuiltin()) and
(
e instanceof Name and e.(Name).getCtx() instanceof Load
or

View File

@@ -0,0 +1,4 @@
- description: Security-and-quality queries for Python
- qlpack: codeql-python
- apply: security-and-quality-selectors.yml
from: codeql-suite-helpers

View File

@@ -0,0 +1,4 @@
- description: Security-extended queries for Python
- qlpack: codeql-python
- apply: security-extended-selectors.yml
from: codeql-suite-helpers

View File

@@ -0,0 +1,30 @@
<!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 cant 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 users input has been added to the string. This is a better route because you dont 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>

View File

@@ -0,0 +1,35 @@
/**
* @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
*/
import python
import semmle.python.security.Paths
/* Sources */
import semmle.python.web.HttpRequest
/* Sinks */
import experimental.semmle.python.security.injection.Xpath
class XpathInjectionConfiguration extends TaintTracking::Configuration {
XpathInjectionConfiguration() { this = "Xpath injection configuration" }
override predicate isSource(TaintTracking::Source source) {
source instanceof HttpRequestTaintSource
}
override predicate isSink(TaintTracking::Sink sink) {
sink instanceof XpathInjection::XpathInjectionSink
}
}
from XpathInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
where config.hasFlowPath(src, sink)
select sink.getSink(), src, sink, "This Xpath query depends on $@.", src.getSource(),
"a user-provided value"

View File

@@ -0,0 +1,18 @@
from lxml import etree
from io import StringIO
from django.urls import path
from django.http import HttpResponse
from django.template import Template, Context, Engine, engines
def a(request):
value = request.GET['xpath']
f = StringIO('<foo><bar></bar></foo>')
tree = etree.parse(f)
r = tree.xpath("/tag[@id='%s']" % value)
urlpatterns = [
path('a', a)
]

View File

@@ -0,0 +1,18 @@
from lxml import etree
from io import StringIO
from django.urls import path
from django.http import HttpResponse
from django.template import Template, Context, Engine, engines
def a(request):
value = request.GET['xpath']
f = StringIO('<foo><bar></bar></foo>')
tree = etree.parse(f)
r = tree.xpath("/tag[@id=$tagid]", tagid=value)
urlpatterns = [
path('a', a)
]

View File

@@ -0,0 +1,115 @@
/**
* 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
* to extend `TaintKind` and `TaintSink`.
*/
import python
import semmle.python.dataflow.TaintTracking
import semmle.python.web.HttpRequest
/** Models Xpath Injection related classes and functions */
module XpathInjection {
/** Returns a class value which refers to `lxml.etree` */
Value etree() { result = Value::named("lxml.etree") }
/** Returns a class value which refers to `lxml.etree` */
Value libxml2parseFile() { result = Value::named("libxml2.parseFile") }
/** A generic taint sink that is vulnerable to Xpath injection. */
abstract class XpathInjectionSink extends TaintSink { }
/**
* A Sink representing an argument to the `etree.Xpath` call.
*
* from lxml import etree
* root = etree.XML("<xmlContent>")
* find_text = etree.XPath("`sink`")
*/
private class EtreeXpathArgument extends XpathInjectionSink {
override string toString() { result = "lxml.etree.Xpath" }
EtreeXpathArgument() {
exists(CallNode call | call.getFunction().(AttrNode).getObject("XPath").pointsTo(etree()) |
call.getArg(0) = this
)
}
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}
/**
* A Sink representing an argument to the `etree.EtXpath` call.
*
* from lxml import etree
* root = etree.XML("<xmlContent>")
* find_text = etree.EtXPath("`sink`")
*/
private class EtreeETXpathArgument extends XpathInjectionSink {
override string toString() { result = "lxml.etree.ETXpath" }
EtreeETXpathArgument() {
exists(CallNode call | call.getFunction().(AttrNode).getObject("ETXPath").pointsTo(etree()) |
call.getArg(0) = this
)
}
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}
/**
* 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 XpathInjectionSink {
override string toString() { result = "lxml.etree.parse.xpath" }
ParseXpathArgument() {
exists(
CallNode parseCall, CallNode xpathCall, ControlFlowNode obj, Variable var, AssignStmt assign
|
parseCall.getFunction().(AttrNode).getObject("parse").pointsTo(etree()) and
assign.getValue().(Call).getAFlowNode() = parseCall and
xpathCall.getFunction().(AttrNode).getObject("xpath") = obj and
var.getAUse() = obj and
assign.getATarget() = var.getAStore() and
xpathCall.getArg(0) = this
)
}
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}
/**
* 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 XpathInjectionSink {
override string toString() { result = "libxml2.parseFile.xpathEval" }
ParseFileXpathEvalArgument() {
exists(
CallNode parseCall, CallNode xpathCall, ControlFlowNode obj, Variable var, AssignStmt assign
|
parseCall.getFunction().(AttrNode).pointsTo(libxml2parseFile()) and
assign.getValue().(Call).getAFlowNode() = parseCall and
xpathCall.getFunction().(AttrNode).getObject("xpathEval") = obj and
var.getAUse() = obj and
assign.getATarget() = var.getAStore() and
xpathCall.getArg(0) = this
)
}
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}
}

View File

@@ -268,6 +268,6 @@ predicate similarScopes(Scope s, Scope other, float percent, string message) {
* Holds if the line is acceptable as a duplicate.
* This is true for blocks of import statements.
*/
predicate whitelistedLineForDuplication(File f, int line) {
predicate allowlistedLineForDuplication(File f, int line) {
exists(ImportingStmt i | i.getLocation().getFile() = f and i.getLocation().getStartLine() = line)
}

View File

@@ -517,7 +517,14 @@ class ClassValue extends Value {
/** Holds if this class is a container(). That is, does it have a __getitem__ method. */
predicate isContainer() { exists(this.lookup("__getitem__")) }
/** Holds if this class is probably a sequence. */
/**
* Holds if this class is a sequence. Mutually exclusive with `isMapping()`.
*
* Following the definition from
* https://docs.python.org/3/glossary.html#term-sequence.
* We don't look at the keys accepted by `__getitem__, but default to treating a class
* as a sequence (so might treat some mappings as sequences).
*/
predicate isSequence() {
/*
* To determine whether something is a sequence or a mapping is not entirely clear,
@@ -538,16 +545,26 @@ class ClassValue extends Value {
or
major_version() = 3 and this.getASuperType() = Value::named("collections.abc.Sequence")
or
/* Does it have an index or __reversed__ method? */
this.isContainer() and
(
this.hasAttribute("index") or
this.hasAttribute("__reversed__")
)
this.hasAttribute("__getitem__") and
this.hasAttribute("__len__") and
not this.getASuperType() = ClassValue::dict() and
not this.getASuperType() = Value::named("collections.Mapping") and
not this.getASuperType() = Value::named("collections.abc.Mapping")
}
/** Holds if this class is a mapping. */
/**
* Holds if this class is a mapping. Mutually exclusive with `isSequence()`.
*
* Although a class will satisfy the requirement by the definition in
* https://docs.python.org/3.8/glossary.html#term-mapping, we don't look at the keys
* accepted by `__getitem__, but default to treating a class as a sequence (so might
* treat some mappings as sequences).
*/
predicate isMapping() {
major_version() = 2 and this.getASuperType() = Value::named("collections.Mapping")
or
major_version() = 3 and this.getASuperType() = Value::named("collections.abc.Mapping")
or
this.hasAttribute("__getitem__") and
not this.isSequence()
}
@@ -632,6 +649,10 @@ class ClassValue extends Value {
* Note that this does not include other callables such as bound-methods.
*/
abstract class FunctionValue extends CallableValue {
/**
* Gets the qualified name for this function.
* Should return the same name as the `__qualname__` attribute on functions in Python 3.
*/
abstract string getQualifiedName();
/** Gets a longer, more descriptive version of toString() */

View File

@@ -28,7 +28,8 @@ predicate used_as_regex(Expr s, string mode) {
/* Call to re.xxx(regex, ... [mode]) */
exists(CallNode call, string name |
call.getArg(0).refersTo(_, _, s.getAFlowNode()) and
call.getFunction().pointsTo(Module::named("re").attr(name))
call.getFunction().pointsTo(Module::named("re").attr(name)) and
not name = "escape"
|
mode = "None"
or
@@ -124,16 +125,40 @@ abstract class RegexString extends Expr {
)
}
/** Named unicode characters, eg \N{degree sign} */
private predicate escapedName(int start, int end) {
this.escapingChar(start) and
this.getChar(start + 1) = "N" and
this.getChar(start + 2) = "{" and
this.getChar(end - 1) = "}" and
end > start and
not exists(int i | start + 2 < i and i < end - 1 |
this.getChar(i) = "}"
)
}
private predicate escapedCharacter(int start, int end) {
this.escapingChar(start) and
not exists(this.getText().substring(start + 1, end + 1).toInt()) and
(
// hex value \xhh
this.getChar(start + 1) = "x" and end = start + 4
or
// octal value \ooo
end in [start + 2 .. start + 4] and
exists(this.getText().substring(start + 1, end).toInt())
or
this.getChar(start + 1) != "x" and end = start + 2
// 16-bit hex value \uhhhh
this.getChar(start + 1) = "u" and end = start + 6
or
// 32-bit hex value \Uhhhhhhhh
this.getChar(start + 1) = "U" and end = start + 10
or
escapedName(start, end)
or
// escape not handled above, update when adding a new case
not this.getChar(start + 1) in ["x", "u", "U", "N"] and
end = start + 2
)
}
@@ -472,8 +497,12 @@ abstract class RegexString extends Expr {
this.getChar(endin) = "}" and
end > start and
exists(string multiples | multiples = this.getText().substring(start + 1, endin) |
multiples.regexpMatch("0+") and maybe_empty = true
or
multiples.regexpMatch("0*,[0-9]*") and maybe_empty = true
or
multiples.regexpMatch("0*[1-9][0-9]*") and maybe_empty = false
or
multiples.regexpMatch("0*[1-9][0-9]*,[0-9]*") and maybe_empty = false
) and
not exists(int mid |
@@ -618,9 +647,13 @@ abstract class RegexString extends Expr {
start = 0 and end = this.getText().length()
or
exists(int y | this.lastPart(start, y) |
this.emptyMatchAtEndGroup(end, y) or
this.qualifiedItem(end, y, true) or
this.emptyMatchAtEndGroup(end, y)
or
this.qualifiedItem(end, y, true)
or
this.specialCharacter(end, y, "$")
or
y = end + 2 and this.escapingChar(end) and this.getChar(end + 1) = "Z"
)
or
exists(int x |

View File

@@ -231,3 +231,41 @@ class FabricV1Commands extends CommandSink {
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}
/**
* An extension that propagates taint from the arguments of `fabric.api.execute(func, arg0, arg1, ...)`
* to the parameters of `func`, since this will call `func(arg0, arg1, ...)`.
*/
class FabricExecuteExtension extends DataFlowExtension::DataFlowNode {
CallNode call;
FabricExecuteExtension() {
call = Value::named("fabric.api.execute").getACall() and
(
this = call.getArg(any(int i | i > 0))
or
this = call.getArgByName(any(string s | not s = "task"))
)
}
override ControlFlowNode getASuccessorNode(TaintKind fromkind, TaintKind tokind) {
tokind = fromkind and
exists(CallableValue func |
(
call.getArg(0).pointsTo(func)
or
call.getArgByName("task").pointsTo(func)
) and
exists(int i |
// execute(func, arg0, arg1) => func(arg0, arg1)
this = call.getArg(i) and
result = func.getParameter(i - 1)
)
or
exists(string name |
this = call.getArgByName(name) and
result = func.getParameterByName(name)
)
)
}
}

View File

@@ -0,0 +1,12 @@
| mapping | builtin-class collections.defaultdict |
| mapping | builtin-class dict |
| mapping | class MyDictSubclass |
| mapping | class MyMappingABC |
| mapping | class OrderedDict |
| neither sequence nor mapping | builtin-class set |
| sequence | builtin-class list |
| sequence | builtin-class str |
| sequence | builtin-class tuple |
| sequence | builtin-class unicode |
| sequence | class MySequenceABC |
| sequence | class MySequenceImpl |

View File

@@ -0,0 +1,20 @@
import python
from ClassValue cls, string res
where
exists(CallNode call |
call.getFunction().(NameNode).getId() = "test" and
call.getAnArg().pointsTo(cls)
) and
(
cls.isSequence() and
cls.isMapping() and
res = "IS BOTH. SHOULD NOT HAPPEN. THEY ARE MUTUALLY EXCLUSIVE."
or
cls.isSequence() and not cls.isMapping() and res = "sequence"
or
not cls.isSequence() and cls.isMapping() and res = "mapping"
or
not cls.isSequence() and not cls.isMapping() and res = "neither sequence nor mapping"
)
select res, cls.toString()

View File

@@ -0,0 +1 @@
semmle-extractor-options: --lang=2 --max-import-depth=2

View File

@@ -0,0 +1,50 @@
from collections import OrderedDict, defaultdict
# Python 2 specific
from collections import Sequence, Mapping
def test(*args):
pass
class MySequenceABC(Sequence):
pass
class MyMappingABC(Mapping):
pass
class MySequenceImpl(object):
def __getitem__(self, key):
pass
def __len__(self):
pass
class MyDictSubclass(dict):
pass
test(
list,
tuple,
str,
unicode,
bytes,
MySequenceABC,
MySequenceImpl,
set,
dict,
OrderedDict,
defaultdict,
MyMappingABC,
MyDictSubclass,
)
for seq_cls in (list, tuple, str, bytes):
assert issubclass(seq_cls, collections.abc.Sequence)
assert not issubclass(seq_cls, collections.abc.Mapping)
for map_cls in (dict, OrderedDict, defaultdict):
assert not issubclass(map_cls, collections.abc.Sequence)
assert issubclass(map_cls, collections.abc.Mapping)
assert not issubclass(set, collections.abc.Sequence)
assert not issubclass(set, collections.abc.Mapping)

View File

@@ -0,0 +1,13 @@
| mapping | builtin-class collections.OrderedDict |
| mapping | builtin-class collections.defaultdict |
| mapping | builtin-class dict |
| mapping | class MyDictSubclass |
| mapping | class MyMappingABC |
| mapping | class OrderedDict |
| neither sequence nor mapping | builtin-class set |
| sequence | builtin-class bytes |
| sequence | builtin-class list |
| sequence | builtin-class str |
| sequence | builtin-class tuple |
| sequence | class MySequenceABC |
| sequence | class MySequenceImpl |

View File

@@ -0,0 +1,20 @@
import python
from ClassValue cls, string res
where
exists(CallNode call |
call.getFunction().(NameNode).getId() = "test" and
call.getAnArg().pointsTo(cls)
) and
(
cls.isSequence() and
cls.isMapping() and
res = "IS BOTH. SHOULD NOT HAPPEN. THEY ARE MUTUALLY EXCLUSIVE."
or
cls.isSequence() and not cls.isMapping() and res = "sequence"
or
not cls.isSequence() and cls.isMapping() and res = "mapping"
or
not cls.isSequence() and not cls.isMapping() and res = "neither sequence nor mapping"
)
select res, cls.toString()

View File

@@ -0,0 +1 @@
semmle-extractor-options: --max-import-depth=2

View File

@@ -0,0 +1,50 @@
from collections import OrderedDict, defaultdict
# Python 3 specific
from collections.abc import Sequence, Mapping
def test(*args):
pass
class MySequenceABC(Sequence):
pass
class MyMappingABC(Mapping):
pass
class MySequenceImpl(object):
def __getitem__(self, key):
pass
def __len__(self):
pass
class MyDictSubclass(dict):
pass
test(
list,
tuple,
str,
unicode,
bytes,
MySequenceABC,
MySequenceImpl,
set,
dict,
OrderedDict,
defaultdict,
MyMappingABC,
MyDictSubclass,
)
for seq_cls in (list, tuple, str, bytes):
assert issubclass(seq_cls, collections.abc.Sequence)
assert not issubclass(seq_cls, collections.abc.Mapping)
for map_cls in (dict, OrderedDict, defaultdict):
assert not issubclass(map_cls, collections.abc.Sequence)
assert issubclass(map_cls, collections.abc.Mapping)
assert not issubclass(set, collections.abc.Sequence)
assert not issubclass(set, collections.abc.Mapping)

View File

@@ -0,0 +1 @@
semmle-extractor-options: --max-import-depth=3 -p ../../query-tests/Security/lib/

View File

@@ -0,0 +1,38 @@
edges
| xpathBad.py:9:7:9:13 | django.request.HttpRequest | xpathBad.py:10:13:10:19 | django.request.HttpRequest |
| xpathBad.py:9:7:9:13 | django.request.HttpRequest | xpathBad.py:10:13:10:19 | django.request.HttpRequest |
| xpathBad.py:10:13:10:19 | django.request.HttpRequest | xpathBad.py:10:13:10:23 | django.http.request.QueryDict |
| xpathBad.py:10:13:10:19 | django.request.HttpRequest | xpathBad.py:10:13:10:23 | django.http.request.QueryDict |
| xpathBad.py:10:13:10:23 | django.http.request.QueryDict | xpathBad.py:10:13:10:32 | externally controlled string |
| xpathBad.py:10:13:10:23 | django.http.request.QueryDict | xpathBad.py:10:13:10:32 | externally controlled string |
| xpathBad.py:10:13:10:32 | externally controlled string | xpathBad.py:13:39:13:43 | externally controlled string |
| xpathBad.py:10:13:10:32 | externally controlled string | xpathBad.py:13:39:13:43 | externally controlled string |
| xpathBad.py:13:39:13:43 | externally controlled string | xpathBad.py:13:20:13:43 | externally controlled string |
| xpathBad.py:13:39:13:43 | externally controlled string | xpathBad.py:13:20:13:43 | externally controlled string |
| xpathFlow.py:11:18:11:29 | dict of externally controlled string | xpathFlow.py:11:18:11:44 | externally controlled string |
| xpathFlow.py:11:18:11:29 | dict of externally controlled string | xpathFlow.py:11:18:11:44 | externally controlled string |
| xpathFlow.py:11:18:11:44 | externally controlled string | xpathFlow.py:14:20:14:29 | externally controlled string |
| xpathFlow.py:11:18:11:44 | externally controlled string | xpathFlow.py:14:20:14:29 | externally controlled string |
| xpathFlow.py:20:18:20:29 | dict of externally controlled string | xpathFlow.py:20:18:20:44 | externally controlled string |
| xpathFlow.py:20:18:20:29 | dict of externally controlled string | xpathFlow.py:20:18:20:44 | externally controlled string |
| xpathFlow.py:20:18:20:44 | externally controlled string | xpathFlow.py:23:29:23:38 | externally controlled string |
| xpathFlow.py:20:18:20:44 | externally controlled string | xpathFlow.py:23:29:23:38 | externally controlled string |
| xpathFlow.py:30:18:30:29 | dict of externally controlled string | xpathFlow.py:30:18:30:44 | externally controlled string |
| xpathFlow.py:30:18:30:29 | dict of externally controlled string | xpathFlow.py:30:18:30:44 | externally controlled string |
| xpathFlow.py:30:18:30:44 | externally controlled string | xpathFlow.py:32:29:32:38 | externally controlled string |
| xpathFlow.py:30:18:30:44 | externally controlled string | xpathFlow.py:32:29:32:38 | externally controlled string |
| xpathFlow.py:39:18:39:29 | dict of externally controlled string | xpathFlow.py:39:18:39:44 | externally controlled string |
| xpathFlow.py:39:18:39:29 | dict of externally controlled string | xpathFlow.py:39:18:39:44 | externally controlled string |
| xpathFlow.py:39:18:39:44 | externally controlled string | xpathFlow.py:41:31:41:40 | externally controlled string |
| xpathFlow.py:39:18:39:44 | externally controlled string | xpathFlow.py:41:31:41:40 | externally controlled string |
| xpathFlow.py:47:18:47:29 | dict of externally controlled string | xpathFlow.py:47:18:47:44 | externally controlled string |
| xpathFlow.py:47:18:47:29 | dict of externally controlled string | xpathFlow.py:47:18:47:44 | externally controlled string |
| xpathFlow.py:47:18:47:44 | externally controlled string | xpathFlow.py:49:29:49:38 | externally controlled string |
| xpathFlow.py:47:18:47:44 | externally controlled string | xpathFlow.py:49:29:49:38 | externally controlled string |
#select
| xpathBad.py:13:20:13:43 | BinaryExpr | xpathBad.py:9:7:9:13 | django.request.HttpRequest | xpathBad.py:13:20:13:43 | externally controlled string | This Xpath query depends on $@. | xpathBad.py:9:7:9:13 | request | a user-provided value |
| xpathFlow.py:14:20:14:29 | xpathQuery | xpathFlow.py:11:18:11:29 | dict of externally controlled string | xpathFlow.py:14:20:14:29 | externally controlled string | This Xpath query depends on $@. | xpathFlow.py:11:18:11:29 | Attribute | a user-provided value |
| xpathFlow.py:23:29:23:38 | xpathQuery | xpathFlow.py:20:18:20:29 | dict of externally controlled string | xpathFlow.py:23:29:23:38 | externally controlled string | This Xpath query depends on $@. | xpathFlow.py:20:18:20:29 | Attribute | a user-provided value |
| xpathFlow.py:32:29:32:38 | xpathQuery | xpathFlow.py:30:18:30:29 | dict of externally controlled string | xpathFlow.py:32:29:32:38 | externally controlled string | This Xpath query depends on $@. | xpathFlow.py:30:18:30:29 | Attribute | a user-provided value |
| xpathFlow.py:41:31:41:40 | xpathQuery | xpathFlow.py:39:18:39:29 | dict of externally controlled string | xpathFlow.py:41:31:41:40 | externally controlled string | This Xpath query depends on $@. | xpathFlow.py:39:18:39:29 | Attribute | a user-provided value |
| xpathFlow.py:49:29:49:38 | xpathQuery | xpathFlow.py:47:18:47:29 | dict of externally controlled string | xpathFlow.py:49:29:49:38 | externally controlled string | This Xpath query depends on $@. | xpathFlow.py:47:18:47:29 | Attribute | a user-provided value |

View File

@@ -0,0 +1,40 @@
from lxml import etree
from io import StringIO
def a():
f = StringIO('<foo><bar></bar></foo>')
tree = etree.parse(f)
r = tree.xpath('/foo/bar')
def b():
root = etree.XML("<root><a>TEXT</a></root>")
find_text = etree.XPath("//text()")
text = find_text(root)[0]
def c():
root = etree.XML("<root><a>TEXT</a></root>")
find_text = etree.XPath("//text()", smart_strings=False)
text = find_text(root)[0]
def d():
root = etree.XML("<root><a>TEXT</a></root>")
find_text = find = etree.ETXPath("//{ns}b")
text = find_text(root)[0]
def e():
import libxml2
doc = libxml2.parseFile('xpath_injection/credential.xml')
results = doc.xpathEval('sink')
if __name__ == "__main__":
a()
b()
c()
d()
e()

View File

@@ -0,0 +1 @@
experimental/CWE-643/xpath.ql

View File

@@ -0,0 +1,18 @@
from lxml import etree
from io import StringIO
from django.urls import path
from django.http import HttpResponse
from django.template import Template, Context, Engine, engines
def a(request):
value = request.GET['xpath']
f = StringIO('<foo><bar></bar></foo>')
tree = etree.parse(f)
r = tree.xpath("/tag[@id='%s']" % value)
urlpatterns = [
path('a', a)
]

View File

@@ -0,0 +1,49 @@
from io import StringIO
from flask import Flask, request
app = Flask(__name__)
@app.route("/xpath1")
def xpath1():
from lxml import etree
xpathQuery = request.args.get('xml', '')
f = StringIO('<foo><bar></bar></foo>')
tree = etree.parse(f)
r = tree.xpath(xpathQuery)
@app.route("/xpath2")
def xpath2():
from lxml import etree
xpathQuery = request.args.get('xml', '')
root = etree.XML("<root><a>TEXT</a></root>")
find_text = etree.XPath(xpathQuery)
text = find_text(root)[0]
@app.route("/xpath3")
def xpath3():
from lxml import etree
xpathQuery = request.args.get('xml', '')
root = etree.XML("<root><a>TEXT</a></root>")
find_text = etree.XPath(xpathQuery, smart_strings=False)
text = find_text(root)[0]
@app.route("/xpath4")
def xpath4():
from lxml import etree
xpathQuery = request.args.get('xml', '')
root = etree.XML("<root><a>TEXT</a></root>")
find_text = etree.ETXPath(xpathQuery)
text = find_text(root)[0]
@app.route("/xpath5")
def xpath5():
import libxml2
xpathQuery = request.args.get('xml', '')
doc = libxml2.parseFile('xpath_injection/credential.xml')
results = doc.xpathEval(xpathQuery)

View File

@@ -0,0 +1,18 @@
from lxml import etree
from io import StringIO
from django.urls import path
from django.http import HttpResponse
from django.template import Template, Context, Engine, engines
def a(request):
value = request.GET['xpath']
f = StringIO('<foo><bar></bar></foo>')
tree = etree.parse(f)
r = tree.xpath("/tag[@id=$tagid]", tagid=value)
urlpatterns = [
path('a', a)
]

View File

@@ -0,0 +1,12 @@
| xpath.py:8:20:8:29 | lxml.etree.parse.xpath | externally controlled string |
| xpath.py:13:29:13:38 | lxml.etree.Xpath | externally controlled string |
| xpath.py:19:29:19:38 | lxml.etree.Xpath | externally controlled string |
| xpath.py:25:38:25:46 | lxml.etree.ETXpath | externally controlled string |
| xpath.py:32:29:32:34 | libxml2.parseFile.xpathEval | externally controlled string |
| xpathBad.py:13:20:13:43 | lxml.etree.parse.xpath | externally controlled string |
| xpathFlow.py:14:20:14:29 | lxml.etree.parse.xpath | externally controlled string |
| xpathFlow.py:23:29:23:38 | lxml.etree.Xpath | externally controlled string |
| xpathFlow.py:32:29:32:38 | lxml.etree.Xpath | externally controlled string |
| xpathFlow.py:41:31:41:40 | lxml.etree.ETXpath | externally controlled string |
| xpathFlow.py:49:29:49:38 | libxml2.parseFile.xpathEval | externally controlled string |
| xpathGood.py:13:20:13:37 | lxml.etree.parse.xpath | externally controlled string |

View File

@@ -0,0 +1,6 @@
import python
import experimental.semmle.python.security.injection.Xpath
from XpathInjection::XpathInjectionSink sink, TaintKind kind
where sink.sinks(kind)
select sink, kind

View File

@@ -2,7 +2,7 @@ import python
import semmle.python.pointsto.PointsTo
import semmle.python.objects.ObjectInternal
predicate ssa_sanity(string clsname, string problem, string what) {
predicate ssa_consistency(string clsname, string problem, string what) {
/* Exactly one definition of each SSA variable */
exists(EssaVariable var | clsname = var.getAQlClass() |
/* Exactly one definition of each SSA variable */
@@ -130,7 +130,7 @@ predicate ssa_sanity(string clsname, string problem, string what) {
)
}
predicate undefined_sanity(string clsname, string problem, string what) {
predicate undefined_consistency(string clsname, string problem, string what) {
/* Variables may be undefined, but values cannot be */
exists(ControlFlowNode f |
PointsToInternal::pointsTo(f, _, ObjectInternal::undefined(), _) and
@@ -142,5 +142,5 @@ predicate undefined_sanity(string clsname, string problem, string what) {
}
from string clsname, string problem, string what
where ssa_sanity(clsname, problem, what) or undefined_sanity(clsname, problem, what)
where ssa_consistency(clsname, problem, what) or undefined_consistency(clsname, problem, what)
select clsname, what, problem

View File

@@ -57,7 +57,7 @@ def loop(seq):
if v:
use(v)
#This was causing the sanity check to fail,
#This was causing the consistency check to fail,
def double_attr_check(x, y):
if x.b == 3:
return

View File

@@ -95,7 +95,7 @@ def h():
if not x:
pass
def complex_test(x): # Was failing sanity check.
def complex_test(x): # Was failing consistency check.
if not (foo(x) and bar(x)):
use(x)
pass

View File

@@ -0,0 +1 @@
| test.py:6:5:6:22 | Function Foo.foo | test.py:9:1:9:11 | ControlFlowNode for Attribute() |

View File

@@ -0,0 +1,4 @@
import python
from PythonFunctionValue func
select func, func.getACall()

View File

@@ -0,0 +1,25 @@
# Simple classmethod
class Foo(object):
@classmethod
def foo(cls, arg):
print(cls, arg)
Foo.foo(42)
# classmethod defined by metaclass
class BarMeta(type):
def bar(cls, arg):
print(cls, arg)
class Bar(metaclass=BarMeta):
pass
Bar.bar(42) # TODO: No points-to
# If this is solved, please update python/ql/src/Variables/UndefinedExport.ql which has a
# work-around for this behavior

View File

@@ -110,7 +110,6 @@
| ax{3,} | 5 | 6 |
| ax{3} | 0 | 1 |
| ax{3} | 1 | 2 |
| ax{3} | 2 | 3 |
| ax{3} | 3 | 4 |
| ax{3} | 4 | 5 |
| ax{,3} | 0 | 1 |

View File

@@ -84,6 +84,8 @@
| ax{3,} | last | 1 | 6 |
| ax{3,} | last | 5 | 6 |
| ax{3} | first | 0 | 1 |
| ax{3} | last | 1 | 2 |
| ax{3} | last | 1 | 5 |
| ax{3} | last | 4 | 5 |
| ax{,3} | first | 0 | 1 |
| ax{,3} | last | 0 | 1 |

View File

@@ -11,4 +11,5 @@
| ^[A-Z_]+$(?<!not-this) | 1 | 8 | false |
| ax{01,3} | 1 | 8 | false |
| ax{3,} | 1 | 6 | false |
| ax{3} | 1 | 5 | false |
| ax{,3} | 1 | 6 | true |

View File

@@ -207,9 +207,9 @@
| ax{3,} | sequence | 0 | 6 |
| ax{3} | char | 0 | 1 |
| ax{3} | char | 1 | 2 |
| ax{3} | char | 2 | 3 |
| ax{3} | char | 3 | 4 |
| ax{3} | char | 4 | 5 |
| ax{3} | qualified | 1 | 5 |
| ax{3} | sequence | 0 | 5 |
| ax{,3} | char | 0 | 1 |
| ax{,3} | char | 1 | 2 |

View File

@@ -62,3 +62,7 @@ re.compile(r'(?:(?P<n1>^(?:|x)))')
re.compile(r"\[(?P<txt>[^[]*)\]\((?P<uri>[^)]*)")
re.compile("", re.M) # ODASA-8056
# FP reported in https://github.com/github/codeql/issues/3712
# This does not define a regex (but could be used by other code to do so)
escaped = re.escape("https://www.humblebundle.com/home/library")

View File

@@ -0,0 +1,24 @@
import python
import semmle.python.dataflow.TaintTracking
import semmle.python.security.strings.Untrusted
import semmle.python.security.injection.Command
class SimpleSource extends TaintSource {
SimpleSource() { this.(NameNode).getId() = "TAINTED_STRING" }
override predicate isSourceOf(TaintKind kind) { kind instanceof ExternalStringKind }
override string toString() { result = "taint source" }
}
class FabricExecuteTestConfiguration extends TaintTracking::Configuration {
FabricExecuteTestConfiguration() { this = "FabricExecuteTestConfiguration" }
override predicate isSource(TaintTracking::Source source) { source instanceof SimpleSource }
override predicate isSink(TaintTracking::Sink sink) { sink instanceof CommandSink }
override predicate isExtension(TaintTracking::Extension extension) {
extension instanceof FabricExecuteExtension
}
}

View File

@@ -0,0 +1,10 @@
| test.py:8 | ok | unsafe | cmd | externally controlled string |
| test.py:8 | ok | unsafe | cmd2 | externally controlled string |
| test.py:9 | ok | unsafe | safe_arg | <NO TAINT> |
| test.py:9 | ok | unsafe | safe_optional | <NO TAINT> |
| test.py:16 | ok | unsafe | cmd | externally controlled string |
| test.py:16 | ok | unsafe | cmd2 | externally controlled string |
| test.py:17 | ok | unsafe | safe_arg | <NO TAINT> |
| test.py:17 | ok | unsafe | safe_optional | <NO TAINT> |
| test.py:23 | ok | some_http_handler | cmd | externally controlled string |
| test.py:23 | ok | some_http_handler | cmd2 | externally controlled string |

View File

@@ -0,0 +1,34 @@
import python
import semmle.python.security.TaintTracking
import semmle.python.web.HttpRequest
import semmle.python.security.strings.Untrusted
import Taint
from
Call call, Expr arg, boolean expected_taint, boolean has_taint, string test_res,
string taint_string
where
call.getLocation().getFile().getShortName() = "test.py" and
(
call.getFunc().(Name).getId() = "ensure_tainted" and
expected_taint = true
or
call.getFunc().(Name).getId() = "ensure_not_tainted" and
expected_taint = false
) and
arg = call.getAnArg() and
(
not exists(TaintedNode tainted | tainted.getAstNode() = arg) and
taint_string = "<NO TAINT>" and
has_taint = false
or
exists(TaintedNode tainted | tainted.getAstNode() = arg |
taint_string = tainted.getTaintKind().toString()
) and
has_taint = true
) and
if expected_taint = has_taint then test_res = "ok " else test_res = "fail"
// if expected_taint = has_taint then test_res = "✓" else test_res = "✕"
select arg.getLocation().toString(), test_res, call.getScope().(Function).getName(), arg.toString(),
taint_string

View File

@@ -0,0 +1 @@
semmle-extractor-options: --max-import-depth=2 -p ../../../query-tests/Security/lib/

View File

@@ -0,0 +1,28 @@
"""Test that shows fabric.api.execute propagates taint"""
from fabric.api import run, execute
def unsafe(cmd, safe_arg, cmd2=None, safe_optional=5):
run('./venv/bin/activate && {}'.format(cmd))
ensure_tainted(cmd, cmd2)
ensure_not_tainted(safe_arg, safe_optional)
class Foo(object):
def unsafe(self, cmd, safe_arg, cmd2=None, safe_optional=5):
run('./venv/bin/activate && {}'.format(cmd))
ensure_tainted(cmd, cmd2)
ensure_not_tainted(safe_arg, safe_optional)
def some_http_handler():
cmd = TAINTED_STRING
cmd2 = TAINTED_STRING
ensure_tainted(cmd, cmd2)
execute(unsafe, cmd=cmd, safe_arg='safe_arg', cmd2=cmd2)
foo = Foo()
execute(foo.unsafe, cmd, 'safe_arg', cmd2)

View File

@@ -1,3 +1,3 @@
| test.py:41:12:41:18 | Str | This regular expression includes duplicate character 'A' in a set of characters. |
| test.py:42:12:42:19 | Str | This regular expression includes duplicate character '0' in a set of characters. |
| test.py:43:12:43:21 | Str | This regular expression includes duplicate character '-' in a set of characters. |
| test.py:46:12:46:18 | Str | This regular expression includes duplicate character 'A' in a set of characters. |
| test.py:47:12:47:19 | Str | This regular expression includes duplicate character '0' in a set of characters. |
| test.py:48:12:48:21 | Str | This regular expression includes duplicate character '-' in a set of characters. |

View File

@@ -1,4 +1,4 @@
| test.py:4:12:4:19 | Str | This regular expression includes an unmatchable caret at offset 1. |
| test.py:5:12:5:23 | Str | This regular expression includes an unmatchable caret at offset 5. |
| test.py:6:12:6:21 | Str | This regular expression includes an unmatchable caret at offset 2. |
| test.py:74:12:74:27 | Str | This regular expression includes an unmatchable caret at offset 8. |
| test.py:79:12:79:27 | Str | This regular expression includes an unmatchable caret at offset 8. |

View File

@@ -1,4 +1,4 @@
| test.py:29:12:29:19 | Str | This regular expression includes an unmatchable dollar at offset 3. |
| test.py:30:12:30:23 | Str | This regular expression includes an unmatchable dollar at offset 3. |
| test.py:31:12:31:20 | Str | This regular expression includes an unmatchable dollar at offset 2. |
| test.py:75:12:75:26 | Str | This regular expression includes an unmatchable dollar at offset 3. |
| test.py:80:12:80:26 | Str | This regular expression includes an unmatchable dollar at offset 3. |

View File

@@ -30,12 +30,17 @@ re.compile(b"abc$ ")
re.compile(b"abc$ (?s)")
re.compile(b"\[$] ")
#Likely false positives for unmatchable dollar
re.compile(b"[$] ")
re.compile(b"\$ ")
re.compile(b"abc$(?m)")
re.compile(b"abc$()")
#Not unmatchable dollar
re.match(b"[$] ", b"$ ")
re.match(b"\$ ", b"$ ")
re.match(b"abc$(?m)", b"abc")
re.match(b"abc$()", b"abc")
re.match(b"((a$)|b)*", b"bba")
re.match(b"((a$)|b){4}", b"bbba") # Inspired by FP report here: https://github.com/github/codeql/issues/2403
re.match(b"((a$).*)", b"a")
re.match("(\Aab$|\Aba$)$\Z", "ab")
re.match(b"((a$\Z)|b){4}", b"bbba")
re.match(b"(a){00}b", b"b")
#Duplicate character in set
re.compile(b"[AA]")
@@ -139,3 +144,10 @@ re.compile(r'(?:(?P<n1>^(?:|x)))')
#Potentially mis-parsed character set
re.compile(r"\[(?P<txt>[^[]*)\]\((?P<uri>[^)]*)")
#Allow unicode in raw strings
re.compile(r"[\U00010000-\U0010FFFF]")
re.compile(r"[\u0000-\uFFFF]")
#Allow unicode names
re.compile(r"[\N{degree sign}\N{EM DASH}]")

View File

@@ -17,3 +17,7 @@ def safe(request):
target = request.args.get('target', '')
if SAFE_REGEX.match(target):
return redirect(target)
# FP reported in https://github.com/github/codeql/issues/3712
# This does not define a regex (but could be used by other code to do so)
escaped = re.escape("https://www.humblebundle.com/home/library")

View File

@@ -17,16 +17,16 @@ def unsafe2(request):
#Simplest and safest approach is to use a white-list
#Simplest and safest approach is to use an allowlist
@app.route('/some/path/good1')
def safe1(request):
whitelist = [
allowlist = [
"example.com/home",
"example.com/login",
]
target = request.args.get('target', '')
if target in whitelist:
if target in allowlist:
return redirect(target)
#More complex example allowing sub-domains.

View File

@@ -23,3 +23,7 @@ def sudo(command, shell=True, pty=True, combine_stderr=None, user=None,
quiet=False, warn_only=False, stdout=None, stderr=None, group=None,
timeout=None, shell_escape=None, capture_buffer_size=None):
pass
# https://github.com/fabric/fabric/blob/1.14/fabric/tasks.py#L281
def execute(task, *args, **kwargs):
pass

View File

@@ -0,0 +1,10 @@
def parseFile(filename):
return xmlDoc(_obj=None)
class xmlDoc(Object):
def __init__(self, _obj=None):
pass
def xpathEval(self, expr):
pass

View File

@@ -11,7 +11,7 @@ class ETXPath(object):
pass
class Xpath(object):
class XPath(object):
def __init__(self, path, namespaces=None, extensions=None, regexp=True, smart_strings=True):
pass

View File

@@ -0,0 +1 @@
analysis/Consistency.ql

View File

@@ -1 +0,0 @@
analysis/Sanity.ql