mirror of
https://github.com/github/codeql.git
synced 2026-04-29 10:45:15 +02:00
Merge pull request #2739 from RasmusWL/python-modernise-security
Python: modernise Security/ queries
This commit is contained in:
@@ -15,24 +15,24 @@ import python
|
||||
Value aSocket() { result.getClass() = Value::named("socket.socket") }
|
||||
|
||||
CallNode socketBindCall() {
|
||||
result = aSocket().attr("bind").(CallableValue).getACall() and major_version() = 3
|
||||
or
|
||||
result.getFunction().(AttrNode).getObject("bind").pointsTo(aSocket()) and
|
||||
major_version() = 2
|
||||
result = aSocket().attr("bind").(CallableValue).getACall() and major_version() = 3
|
||||
or
|
||||
result.getFunction().(AttrNode).getObject("bind").pointsTo(aSocket()) and
|
||||
major_version() = 2
|
||||
}
|
||||
|
||||
string allInterfaces() { result = "0.0.0.0" or result = "" }
|
||||
|
||||
Value getTextValue(string address) {
|
||||
result = Value::forUnicode(address) and major_version() = 3
|
||||
or
|
||||
result = Value::forString(address) and major_version() = 2
|
||||
result = Value::forUnicode(address) and major_version() = 3
|
||||
or
|
||||
result = Value::forString(address) and major_version() = 2
|
||||
}
|
||||
|
||||
from CallNode call, TupleValue args, string address
|
||||
where
|
||||
call = socketBindCall() and
|
||||
call.getArg(0).pointsTo(args) and
|
||||
args.getItem(0) = getTextValue(address) and
|
||||
address = allInterfaces()
|
||||
call = socketBindCall() and
|
||||
call.getArg(0).pointsTo(args) and
|
||||
args.getItem(0) = getTextValue(address) and
|
||||
address = allInterfaces()
|
||||
select call.getNode(), "'" + address + "' binds a socket to all interfaces."
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
import python
|
||||
import semmle.python.regex
|
||||
|
||||
private string commonTopLevelDomainRegex() {
|
||||
result = "com|org|edu|gov|uk|net|io"
|
||||
}
|
||||
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`,
|
||||
@@ -23,22 +21,20 @@ private string commonTopLevelDomainRegex() {
|
||||
*/
|
||||
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)
|
||||
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() + ").*[(][?]:.*[)].*")
|
||||
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."
|
||||
"This regular expression has an unescaped '.' before '" + hostPart +
|
||||
"', so it might match more hosts than expected."
|
||||
|
||||
@@ -10,20 +10,16 @@
|
||||
* external/cwe/cwe-20
|
||||
*/
|
||||
|
||||
|
||||
import python
|
||||
import semmle.python.regex
|
||||
|
||||
private string commonTopLevelDomainRegex() {
|
||||
result = "com|org|edu|gov|uk|net|io"
|
||||
}
|
||||
private string commonTopLevelDomainRegex() { result = "com|org|edu|gov|uk|net|io" }
|
||||
|
||||
predicate looksLikeUrl(StrConst s) {
|
||||
exists(string text |
|
||||
text = s.getText()
|
||||
|
|
||||
text.regexpMatch("(?i)([a-z]*:?//)?\\.?([a-z0-9-]+\\.)+(" +
|
||||
commonTopLevelDomainRegex() +")(:[0-9]+)?/?")
|
||||
exists(string text | text = s.getText() |
|
||||
text
|
||||
.regexpMatch("(?i)([a-z]*:?//)?\\.?([a-z0-9-]+\\.)+(" + commonTopLevelDomainRegex() +
|
||||
")(:[0-9]+)?/?")
|
||||
or
|
||||
// target is a HTTP URL to a domain on any TLD
|
||||
text.regexpMatch("(?i)https?://([a-z0-9-]+\\.)+([a-z]+)(:[0-9]+)?/?")
|
||||
@@ -42,10 +38,8 @@ predicate incomplete_sanitization(Expr sanitizer, StrConst url) {
|
||||
}
|
||||
|
||||
predicate unsafe_call_to_startswith(Call sanitizer, StrConst url) {
|
||||
sanitizer.getFunc().(Attribute).getName() = "startswith"
|
||||
and
|
||||
sanitizer.getArg(0) = url
|
||||
and
|
||||
sanitizer.getFunc().(Attribute).getName() = "startswith" and
|
||||
sanitizer.getArg(0) = url and
|
||||
not url.getText().regexpMatch("(?i)https?://[\\.a-z0-9-]+/.*")
|
||||
}
|
||||
|
||||
|
||||
@@ -18,18 +18,17 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
|
||||
/* Sources */
|
||||
import semmle.python.web.HttpRequest
|
||||
|
||||
/* Sinks */
|
||||
import semmle.python.security.injection.Path
|
||||
|
||||
class PathInjectionConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
PathInjectionConfiguration() { this = "Path injection configuration" }
|
||||
|
||||
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
|
||||
override predicate isSource(TaintTracking::Source source) {
|
||||
source instanceof HttpRequestTaintSource
|
||||
}
|
||||
|
||||
override predicate isSink(TaintTracking::Sink sink) { sink instanceof OpenNode }
|
||||
|
||||
@@ -41,10 +40,9 @@ class PathInjectionConfiguration extends TaintTracking::Configuration {
|
||||
override predicate isExtension(TaintTracking::Extension extension) {
|
||||
extension instanceof AbsPath
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
from PathInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
|
||||
where config.hasFlowPath(src, sink)
|
||||
select sink.getSink(), src, sink, "This path depends on $@.", src.getSource(), "a user-provided value"
|
||||
select sink.getSink(), src, sink, "This path depends on $@.", src.getSource(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @description Extracting files from a malicious tar 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
|
||||
* @kind path-problem
|
||||
* @id py/tarslip
|
||||
* @problem.severity error
|
||||
* @precision medium
|
||||
@@ -13,15 +13,12 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.security.strings.Basic
|
||||
|
||||
/** A TaintKind to represent open tarfile objects. That is, the result of calling `tarfile.open(...)` */
|
||||
class OpenTarFile extends TaintKind {
|
||||
OpenTarFile() {
|
||||
this = "tarfile.open"
|
||||
}
|
||||
OpenTarFile() { this = "tarfile.open" }
|
||||
|
||||
override TaintKind getTaintOfMethodResult(string name) {
|
||||
name = "getmember" and result instanceof TarFileInfo
|
||||
@@ -29,60 +26,45 @@ class OpenTarFile extends TaintKind {
|
||||
name = "getmembers" and result.(SequenceKind).getItem() instanceof TarFileInfo
|
||||
}
|
||||
|
||||
override ClassValue getType() {
|
||||
result = Module::named("tarfile").attr("TarFile")
|
||||
}
|
||||
|
||||
override TaintKind getTaintForIteration() {
|
||||
result 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() {
|
||||
Module::named("tarfile").attr("open").getACall() = this
|
||||
and
|
||||
/* If argument refers to a string object, then it's a hardcoded path and
|
||||
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().refersTo(any(StringObject str))
|
||||
and
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
override predicate isSourceOf(TaintKind kind) { kind instanceof OpenTarFile }
|
||||
}
|
||||
|
||||
class TarFileInfo extends TaintKind {
|
||||
TarFileInfo() { this = "tarfile.entry" }
|
||||
|
||||
TarFileInfo() {
|
||||
this = "tarfile.entry"
|
||||
}
|
||||
|
||||
override TaintKind getTaintOfMethodResult(string name) {
|
||||
name = "next" and result = this
|
||||
}
|
||||
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.
|
||||
*/
|
||||
|
||||
/* For efficiency we don't want to track the flow of taint
|
||||
* around the tarfile module. */
|
||||
class ExcludeTarFilePy extends Sanitizer {
|
||||
|
||||
ExcludeTarFilePy() {
|
||||
this = "Tar sanitizer"
|
||||
}
|
||||
ExcludeTarFilePy() { this = "Tar sanitizer" }
|
||||
|
||||
override predicate sanitizingNode(TaintKind taint, ControlFlowNode node) {
|
||||
node.getLocation().getFile().getBaseName() = "tarfile.py" and
|
||||
@@ -94,12 +76,10 @@ class ExcludeTarFilePy extends Sanitizer {
|
||||
taint.(SequenceKind).getItem() instanceof TarFileInfo
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Any call to an extractall method */
|
||||
class ExtractAllSink extends TaintSink {
|
||||
|
||||
CallNode call;
|
||||
|
||||
ExtractAllSink() {
|
||||
@@ -107,15 +87,11 @@ class ExtractAllSink extends TaintSink {
|
||||
count(call.getAnArg()) = 0
|
||||
}
|
||||
|
||||
override predicate sinks(TaintKind kind) {
|
||||
kind instanceof OpenTarFile
|
||||
}
|
||||
|
||||
override predicate sinks(TaintKind kind) { kind instanceof OpenTarFile }
|
||||
}
|
||||
|
||||
/* Argument to extract method */
|
||||
class ExtractSink extends TaintSink {
|
||||
|
||||
CallNode call;
|
||||
|
||||
ExtractSink() {
|
||||
@@ -123,16 +99,11 @@ class ExtractSink extends TaintSink {
|
||||
this = call.getArg(0)
|
||||
}
|
||||
|
||||
override predicate sinks(TaintKind kind) {
|
||||
kind instanceof TarFileInfo
|
||||
}
|
||||
|
||||
override predicate sinks(TaintKind kind) { kind instanceof TarFileInfo }
|
||||
}
|
||||
|
||||
|
||||
/* Members argument to extract method */
|
||||
class ExtractMembersSink extends TaintSink {
|
||||
|
||||
CallNode call;
|
||||
|
||||
ExtractMembersSink() {
|
||||
@@ -145,21 +116,15 @@ class ExtractMembersSink extends TaintSink {
|
||||
or
|
||||
kind instanceof OpenTarFile
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TarFileInfoSanitizer extends Sanitizer {
|
||||
|
||||
TarFileInfoSanitizer() {
|
||||
this = "TarInfo sanitizer"
|
||||
}
|
||||
TarFileInfoSanitizer() { this = "TarInfo sanitizer" }
|
||||
|
||||
override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) {
|
||||
path_sanitizing_test(test.getTest()) and
|
||||
taint instanceof TarFileInfo
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private predicate path_sanitizing_test(ControlFlowNode test) {
|
||||
@@ -170,7 +135,6 @@ private predicate path_sanitizing_test(ControlFlowNode test) {
|
||||
}
|
||||
|
||||
class TarSlipConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
TarSlipConfiguration() { this = "TarSlip configuration" }
|
||||
|
||||
override predicate isSource(TaintTracking::Source source) { source instanceof TarfileOpen }
|
||||
@@ -193,7 +157,7 @@ class TarSlipConfiguration extends TaintTracking::Configuration {
|
||||
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"
|
||||
@@ -201,8 +165,7 @@ class TarSlipConfiguration extends TaintTracking::Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
from TarSlipConfiguration config, TaintedPathSource src, TaintedPathSink sink
|
||||
where config.hasFlowPath(src, sink)
|
||||
select sink.getSink(), src, sink, "Extraction of tarfile from $@", src.getSource(), "a potentially untrusted source"
|
||||
|
||||
select sink.getSink(), src, sink, "Extraction of tarfile from $@", src.getSource(),
|
||||
"a potentially untrusted source"
|
||||
|
||||
@@ -11,7 +11,6 @@ urlpatterns = [
|
||||
|
||||
def user_picture1(request):
|
||||
"""A view that is vulnerable to malicious file access."""
|
||||
base_path = '/server/static/images'
|
||||
filename = request.GET.get('p')
|
||||
# BAD: This could read any file on the file system
|
||||
data = open(filename, 'rb').read()
|
||||
|
||||
@@ -16,18 +16,17 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
|
||||
/* Sources */
|
||||
import semmle.python.web.HttpRequest
|
||||
|
||||
/* Sinks */
|
||||
import semmle.python.security.injection.Command
|
||||
|
||||
class CommandInjectionConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
CommandInjectionConfiguration() { this = "Command injection configuration" }
|
||||
|
||||
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
|
||||
override predicate isSource(TaintTracking::Source source) {
|
||||
source instanceof HttpRequestTaintSource
|
||||
}
|
||||
|
||||
override predicate isSink(TaintTracking::Sink sink) {
|
||||
sink instanceof OsCommandFirstArgument or
|
||||
@@ -37,9 +36,9 @@ class CommandInjectionConfiguration extends TaintTracking::Configuration {
|
||||
override predicate isExtension(TaintTracking::Extension extension) {
|
||||
extension instanceof FirstElementFlow
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
from CommandInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
|
||||
where config.hasFlowPath(src, sink)
|
||||
select sink.getSink(), src, sink, "This command depends on $@.", src.getSource(), "a user-provided value"
|
||||
select sink.getSink(), src, sink, "This command depends on $@.", src.getSource(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -20,5 +20,4 @@ def command_execution_safe(request):
|
||||
if request.method == 'POST':
|
||||
action = request.POST.get('action', '')
|
||||
#GOOD -- Use a whitelist
|
||||
subprocess.call(["application", COMMAND[action]])
|
||||
|
||||
subprocess.call(["application", COMMANDS[action]])
|
||||
|
||||
@@ -12,37 +12,39 @@
|
||||
|
||||
import python
|
||||
|
||||
ClassObject jinja2EnvironmentOrTemplate() {
|
||||
exists(ModuleObject jinja2, string name |
|
||||
jinja2.getName() = "jinja2" and
|
||||
jinja2.attr(name) = result |
|
||||
name = "Environment" or
|
||||
name = "Template"
|
||||
)
|
||||
/* Jinja 2 Docs:
|
||||
* https://jinja.palletsprojects.com/en/2.11.x/api/#jinja2.Environment
|
||||
* https://jinja.palletsprojects.com/en/2.11.x/api/#jinja2.Template
|
||||
*
|
||||
* Although the docs doesn't say very clearly, autoescape is a valid argument when constructing
|
||||
* a Template manually
|
||||
*
|
||||
* unsafe_tmpl = Template('Hello {{ name }}!')
|
||||
* safe1_tmpl = Template('Hello {{ name }}!', autoescape=True)
|
||||
*/
|
||||
|
||||
ClassValue jinja2EnvironmentOrTemplate() {
|
||||
|
||||
result = Value::named("jinja2.Environment")
|
||||
or
|
||||
result = Value::named("jinja2.Template")
|
||||
}
|
||||
|
||||
ControlFlowNode getAutoEscapeParameter(CallNode call) {
|
||||
exists(Object callable |
|
||||
call.getFunction().refersTo(callable) |
|
||||
callable = jinja2EnvironmentOrTemplate() and
|
||||
result = call.getArgByName("autoescape")
|
||||
)
|
||||
result = call.getArgByName("autoescape")
|
||||
}
|
||||
|
||||
from CallNode call
|
||||
where
|
||||
not exists(call.getNode().getStarargs()) and
|
||||
not exists(call.getNode().getKwargs()) and
|
||||
(
|
||||
not exists(getAutoEscapeParameter(call)) and
|
||||
exists(Object env |
|
||||
call.getFunction().refersTo(env) and
|
||||
env = jinja2EnvironmentOrTemplate()
|
||||
call.getFunction().pointsTo(jinja2EnvironmentOrTemplate()) and
|
||||
not exists(call.getNode().getStarargs()) and
|
||||
not exists(call.getNode().getKwargs()) and
|
||||
(
|
||||
not exists(getAutoEscapeParameter(call))
|
||||
or
|
||||
exists(Value isFalse |
|
||||
getAutoEscapeParameter(call).pointsTo(isFalse) and
|
||||
isFalse.getDefiniteBooleanValue() = false
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(Object isFalse |
|
||||
getAutoEscapeParameter(call).refersTo(isFalse) and isFalse.booleanValue() = false
|
||||
)
|
||||
)
|
||||
|
||||
select call, "Using jinja2 templates with autoescape=False can potentially allow XSS attacks."
|
||||
|
||||
@@ -14,28 +14,24 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
|
||||
/* Sources */
|
||||
import semmle.python.web.HttpRequest
|
||||
|
||||
/* Sinks */
|
||||
|
||||
import semmle.python.web.HttpResponse
|
||||
|
||||
/* Flow */
|
||||
import semmle.python.security.strings.Untrusted
|
||||
|
||||
|
||||
class ReflectedXssConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
ReflectedXssConfiguration() { this = "Reflected XSS configuration" }
|
||||
|
||||
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
|
||||
override predicate isSource(TaintTracking::Source source) {
|
||||
source instanceof HttpRequestTaintSource
|
||||
}
|
||||
|
||||
override predicate isSink(TaintTracking::Sink sink) { sink instanceof HttpResponseTaintSink }
|
||||
|
||||
}
|
||||
|
||||
from ReflectedXssConfiguration config, TaintedPathSource src, TaintedPathSink sink
|
||||
where config.hasFlowPath(src, sink)
|
||||
select sink.getSink(), src, sink, "Cross-site scripting vulnerability due to $@.", src.getSource(), "user-provided value"
|
||||
select sink.getSink(), src, sink, "Cross-site scripting vulnerability due to $@.", src.getSource(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -13,37 +13,37 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
|
||||
/* Sources */
|
||||
import semmle.python.web.HttpRequest
|
||||
|
||||
/* Sinks */
|
||||
import semmle.python.security.injection.Sql
|
||||
import semmle.python.web.django.Db
|
||||
import semmle.python.web.django.Model
|
||||
|
||||
class SQLInjectionConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
SQLInjectionConfiguration() { this = "SQL injection configuration" }
|
||||
|
||||
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
|
||||
override predicate isSource(TaintTracking::Source source) {
|
||||
source instanceof HttpRequestTaintSource
|
||||
}
|
||||
|
||||
override predicate isSink(TaintTracking::Sink sink) { sink instanceof SqlInjectionSink }
|
||||
|
||||
}
|
||||
|
||||
/* Additional configuration to support tracking of DB objects. Connections, cursors, etc. */
|
||||
/* Additional configuration to support tracking of DB objects. Connections, cursors, etc.
|
||||
* Without this configuration (or the LegacyConfiguration), the pattern of
|
||||
* `any(MyTaintKind k).taints(control_flow_node)` used in DbConnectionExecuteArgument would not work.
|
||||
*/
|
||||
class DbConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
DbConfiguration() { this = "DB configuration" }
|
||||
|
||||
override predicate isSource(TaintTracking::Source source) {
|
||||
source instanceof DjangoModelObjects or
|
||||
source instanceof DbConnectionSource
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
from SQLInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
|
||||
where config.hasFlowPath(src, sink)
|
||||
select sink.getSink(), src, sink, "This SQL query depends on $@.", src.getSource(), "a user-provided value"
|
||||
select sink.getSink(), src, sink, "This SQL query depends on $@.", src.getSource(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -16,24 +16,22 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
|
||||
/* Sources */
|
||||
import semmle.python.web.HttpRequest
|
||||
|
||||
/* Sinks */
|
||||
import semmle.python.security.injection.Exec
|
||||
|
||||
class CodeInjectionConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
CodeInjectionConfiguration() { this = "Code injection configuration" }
|
||||
|
||||
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
|
||||
override predicate isSource(TaintTracking::Source source) {
|
||||
source instanceof HttpRequestTaintSource
|
||||
}
|
||||
|
||||
override predicate isSink(TaintTracking::Sink sink) { sink instanceof StringEvaluationNode }
|
||||
|
||||
}
|
||||
|
||||
|
||||
from CodeInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
|
||||
where config.hasFlowPath(src, sink)
|
||||
select sink.getSink(), src, sink, "$@ flows to here and is interpreted as code.", src.getSource(), "User-provided value"
|
||||
select sink.getSink(), src, sink, "$@ flows to here and is interpreted as code.", src.getSource(),
|
||||
"A user-provided value"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @name Information exposure through an exception
|
||||
* @description Leaking information about an exception, such as messages and stack traces, to an
|
||||
* external user can expose implementation details that are useful to an attacker for
|
||||
* developing a subsequent exploit.
|
||||
* developing a subsequent exploit.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
@@ -14,22 +14,18 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
|
||||
import semmle.python.security.Exceptions
|
||||
import semmle.python.web.HttpResponse
|
||||
|
||||
class StackTraceExposureConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
StackTraceExposureConfiguration() { this = "Stack trace exposure configuration" }
|
||||
|
||||
override predicate isSource(TaintTracking::Source source) { source instanceof ErrorInfoSource }
|
||||
|
||||
override predicate isSink(TaintTracking::Sink sink) {
|
||||
sink instanceof HttpResponseTaintSink
|
||||
}
|
||||
|
||||
override predicate isSink(TaintTracking::Sink sink) { sink instanceof HttpResponseTaintSink }
|
||||
}
|
||||
|
||||
from StackTraceExposureConfiguration config, TaintedPathSource src, TaintedPathSink sink
|
||||
where config.hasFlowPath(src, sink)
|
||||
select sink.getSink(), src, sink, "$@ may be exposed to an external user", src.getSource(), "Error information"
|
||||
select sink.getSink(), src, sink, "$@ may be exposed to an external user", src.getSource(),
|
||||
"Error information"
|
||||
|
||||
@@ -11,13 +11,12 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
import semmle.python.web.flask.General
|
||||
|
||||
|
||||
from CallNode call, Object isTrue
|
||||
from CallNode call, Value isTrue
|
||||
where
|
||||
call = theFlaskClass().declaredAttribute("run").(FunctionValue).getACall() and
|
||||
call.getArgByName("debug").refersTo(isTrue) and
|
||||
isTrue.booleanValue() = true
|
||||
select call, "A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger."
|
||||
call.getArgByName("debug").pointsTo(isTrue) and
|
||||
isTrue.getDefiniteBooleanValue() = true
|
||||
select call,
|
||||
"A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger."
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
|
||||
import python
|
||||
|
||||
private ModuleObject theParamikoClientModule() { result = ModuleObject::named("paramiko.client") }
|
||||
private ModuleValue theParamikoClientModule() { result = Value::named("paramiko.client") }
|
||||
|
||||
private ClassObject theParamikoSSHClientClass() {
|
||||
private ClassValue theParamikoSSHClientClass() {
|
||||
result = theParamikoClientModule().attr("SSHClient")
|
||||
}
|
||||
|
||||
private ClassObject unsafe_paramiko_policy(string name) {
|
||||
private ClassValue unsafe_paramiko_policy(string name) {
|
||||
(name = "AutoAddPolicy" or name = "WarningPolicy") and
|
||||
result = theParamikoClientModule().attr(name)
|
||||
}
|
||||
@@ -25,12 +25,12 @@ private ClassObject unsafe_paramiko_policy(string name) {
|
||||
from CallNode call, ControlFlowNode arg, string name
|
||||
where
|
||||
call = theParamikoSSHClientClass()
|
||||
.lookupAttribute("set_missing_host_key_policy")
|
||||
.(FunctionObject)
|
||||
.getACall() and
|
||||
.lookup("set_missing_host_key_policy")
|
||||
.(FunctionValue)
|
||||
.getACall() and
|
||||
arg = call.getAnArg() and
|
||||
(
|
||||
arg.refersTo(unsafe_paramiko_policy(name)) or
|
||||
arg.refersTo(_, unsafe_paramiko_policy(name), _)
|
||||
arg.pointsTo(unsafe_paramiko_policy(name)) or
|
||||
arg.pointsTo().getClass() = unsafe_paramiko_policy(name)
|
||||
)
|
||||
select call, "Setting missing host key policy to " + name + " may be unsafe."
|
||||
|
||||
@@ -10,24 +10,17 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
import semmle.python.web.Http
|
||||
|
||||
|
||||
FunctionObject requestFunction() {
|
||||
result = ModuleObject::named("requests").attr(httpVerbLower())
|
||||
}
|
||||
FunctionValue requestFunction() { result = Module::named("requests").attr(httpVerbLower()) }
|
||||
|
||||
/** requests treats None as the default and all other "falsey" values as False */
|
||||
predicate falseNotNone(Object o) {
|
||||
o.booleanValue() = false and not o = theNoneObject()
|
||||
}
|
||||
|
||||
from CallNode call, FunctionObject func, Object falsey, ControlFlowNode origin
|
||||
where
|
||||
func = requestFunction() and
|
||||
func.getACall() = call and
|
||||
falseNotNone(falsey) and
|
||||
call.getArgByName("verify").refersTo(falsey, origin)
|
||||
predicate falseNotNone(Value v) { v.getDefiniteBooleanValue() = false and not v = Value::none_() }
|
||||
|
||||
from CallNode call, FunctionValue func, Value falsey, ControlFlowNode origin
|
||||
where
|
||||
func = requestFunction() and
|
||||
func.getACall() = call and
|
||||
falseNotNone(falsey) and
|
||||
call.getArgByName("verify").pointsTo(falsey, origin)
|
||||
select call, "Call to $@ with verify=$@", func, "requests." + func.getName(), origin, "False"
|
||||
|
||||
@@ -14,15 +14,12 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.security.SensitiveData
|
||||
import semmle.python.security.ClearText
|
||||
|
||||
|
||||
class CleartextLoggingConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
CleartextLoggingConfiguration() { this = "ClearTextLogging" }
|
||||
CleartextLoggingConfiguration() { this = "ClearTextLogging" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src, TaintKind kind) {
|
||||
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
|
||||
@@ -32,11 +29,9 @@ class CleartextLoggingConfiguration extends TaintTracking::Configuration {
|
||||
sink.asCfgNode() instanceof ClearTextLogging::Sink and
|
||||
kind instanceof SensitiveData
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
from CleartextLoggingConfiguration config, TaintedPathSource source, TaintedPathSink sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getSink(), source, sink, "Sensitive data returned by $@ is logged here.",
|
||||
source.getSource(), source.getCfgNode().(SensitiveData::Source).repr()
|
||||
source.getSource(), source.getCfgNode().(SensitiveData::Source).repr()
|
||||
|
||||
@@ -14,14 +14,12 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.security.SensitiveData
|
||||
import semmle.python.security.ClearText
|
||||
|
||||
class CleartextStorageConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
CleartextStorageConfiguration() { this = "ClearTextStorage" }
|
||||
CleartextStorageConfiguration() { this = "ClearTextStorage" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src, TaintKind kind) {
|
||||
src.asCfgNode().(SensitiveData::Source).isSourceOf(kind)
|
||||
@@ -31,11 +29,9 @@ class CleartextStorageConfiguration extends TaintTracking::Configuration {
|
||||
sink.asCfgNode() instanceof ClearTextStorage::Sink and
|
||||
kind instanceof SensitiveData
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
from CleartextStorageConfiguration config, TaintedPathSource source, TaintedPathSink sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getSink(), source, sink, "Sensitive data from $@ is stored here.",
|
||||
source.getSource(), source.getCfgNode().(SensitiveData::Source).repr()
|
||||
select sink.getSink(), source, sink, "Sensitive data from $@ is stored here.", source.getSource(),
|
||||
source.getCfgNode().(SensitiveData::Source).repr()
|
||||
|
||||
@@ -19,63 +19,63 @@ int minimumSecureKeySize(string algo) {
|
||||
algo = "ECC" and result = 224
|
||||
}
|
||||
|
||||
predicate dsaRsaKeySizeArg(FunctionObject obj, string algorithm, string arg) {
|
||||
exists(ModuleObject mod |
|
||||
mod.attr(_) = obj |
|
||||
predicate dsaRsaKeySizeArg(FunctionValue func, string algorithm, string arg) {
|
||||
exists(ModuleValue mod | func = mod.attr(_) |
|
||||
algorithm = "DSA" and
|
||||
(
|
||||
mod.getName() = "cryptography.hazmat.primitives.asymmetric.dsa" and arg = "key_size"
|
||||
mod = Module::named("cryptography.hazmat.primitives.asymmetric.dsa") and arg = "key_size"
|
||||
or
|
||||
mod.getName() = "Crypto.PublicKey.DSA" and arg = "bits"
|
||||
mod = Module::named("Crypto.PublicKey.DSA") and arg = "bits"
|
||||
or
|
||||
mod.getName() = "Cryptodome.PublicKey.DSA" and arg = "bits"
|
||||
mod = Module::named("Cryptodome.PublicKey.DSA") and arg = "bits"
|
||||
)
|
||||
or
|
||||
algorithm = "RSA" and
|
||||
(
|
||||
mod.getName() = "cryptography.hazmat.primitives.asymmetric.rsa" and arg = "key_size"
|
||||
mod = Module::named("cryptography.hazmat.primitives.asymmetric.rsa") and arg = "key_size"
|
||||
or
|
||||
mod.getName() = "Crypto.PublicKey.RSA" and arg = "bits"
|
||||
mod = Module::named("Crypto.PublicKey.RSA") and arg = "bits"
|
||||
or
|
||||
mod.getName() = "Cryptodome.PublicKey.RSA" and arg = "bits"
|
||||
mod = Module::named("Cryptodome.PublicKey.RSA") and arg = "bits"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate ecKeySizeArg(FunctionObject obj, string arg) {
|
||||
exists(ModuleObject mod |
|
||||
mod.attr(_) = obj |
|
||||
mod.getName() = "cryptography.hazmat.primitives.asymmetric.ec" and arg = "curve"
|
||||
predicate ecKeySizeArg(FunctionValue func, string arg) {
|
||||
exists(ModuleValue mod | func = mod.attr(_) |
|
||||
mod = Module::named("cryptography.hazmat.primitives.asymmetric.ec") and arg = "curve"
|
||||
)
|
||||
}
|
||||
|
||||
int keySizeFromCurve(ClassObject curveClass) {
|
||||
result = curveClass.declaredAttribute("key_size").(NumericObject).intValue()
|
||||
int keySizeFromCurve(ClassValue curveClass) {
|
||||
result = curveClass.declaredAttribute("key_size").(NumericValue).getIntValue()
|
||||
}
|
||||
|
||||
predicate algorithmAndKeysizeForCall(CallNode call, string algorithm, int keySize, ControlFlowNode keyOrigin) {
|
||||
exists(FunctionObject func, string argname, ControlFlowNode arg |
|
||||
arg = func.getNamedArgumentForCall(call, argname) |
|
||||
exists(NumericObject key |
|
||||
arg.refersTo(key, keyOrigin) and
|
||||
predicate algorithmAndKeysizeForCall(
|
||||
CallNode call, string algorithm, int keySize, ControlFlowNode keyOrigin
|
||||
) {
|
||||
exists(FunctionValue func, string argname, ControlFlowNode arg |
|
||||
arg = func.getNamedArgumentForCall(call, argname)
|
||||
|
|
||||
exists(NumericValue key |
|
||||
arg.pointsTo(key, keyOrigin) and
|
||||
dsaRsaKeySizeArg(func, algorithm, argname) and
|
||||
keySize = key.intValue()
|
||||
keySize = key.getIntValue()
|
||||
)
|
||||
or
|
||||
exists(ClassObject curve |
|
||||
arg.refersTo(_, curve, keyOrigin) and
|
||||
ecKeySizeArg(func, argname) and
|
||||
exists(Value curveClassInstance |
|
||||
algorithm = "ECC" and
|
||||
keySize = keySizeFromCurve(curve)
|
||||
ecKeySizeArg(func, argname) and
|
||||
arg.pointsTo(_, curveClassInstance, keyOrigin) and
|
||||
keySize = keySizeFromCurve(curveClassInstance.getClass())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
from CallNode call, ControlFlowNode origin, string algo, int keySize
|
||||
from CallNode call, string algo, int keySize, ControlFlowNode origin
|
||||
where
|
||||
algorithmAndKeysizeForCall(call, algo, keySize, origin) and
|
||||
keySize < minimumSecureKeySize(algo)
|
||||
select call, "Creation of an " + algo + " key uses $@ bits, which is below " + minimumSecureKeySize(algo) + " and considered breakable.", origin, keySize.toString()
|
||||
|
||||
|
||||
select call,
|
||||
"Creation of an " + algo + " key uses $@ bits, which is below " + minimumSecureKeySize(algo) +
|
||||
" and considered breakable.", origin, keySize.toString()
|
||||
|
||||
@@ -12,13 +12,9 @@
|
||||
|
||||
import python
|
||||
|
||||
FunctionObject ssl_wrap_socket() {
|
||||
result = ModuleObject::named("ssl").attr("wrap_socket")
|
||||
}
|
||||
FunctionValue ssl_wrap_socket() { result = Value::named("ssl.wrap_socket") }
|
||||
|
||||
ClassObject ssl_Context_class() {
|
||||
result = ModuleObject::named("ssl").attr("SSLContext")
|
||||
}
|
||||
ClassValue ssl_Context_class() { result = Value::named("ssl.SSLContext") }
|
||||
|
||||
CallNode unsafe_call(string method_name) {
|
||||
result = ssl_wrap_socket().getACall() and
|
||||
@@ -32,10 +28,7 @@ CallNode unsafe_call(string method_name) {
|
||||
}
|
||||
|
||||
from CallNode call, string method_name
|
||||
where
|
||||
call = unsafe_call(method_name)
|
||||
select call, "Call to " + method_name + " does not specify a protocol, which may result in an insecure default being used."
|
||||
|
||||
|
||||
|
||||
|
||||
where call = unsafe_call(method_name)
|
||||
select call,
|
||||
"Call to " + method_name +
|
||||
" does not specify a protocol, which may result in an insecure default being used."
|
||||
|
||||
@@ -11,12 +11,16 @@
|
||||
|
||||
import python
|
||||
|
||||
FunctionObject ssl_wrap_socket() {
|
||||
result = the_ssl_module().attr("wrap_socket")
|
||||
}
|
||||
private ModuleValue the_ssl_module() { result = Module::named("ssl") }
|
||||
|
||||
ClassObject ssl_Context_class() {
|
||||
result = the_ssl_module().attr("SSLContext")
|
||||
FunctionValue ssl_wrap_socket() { result = the_ssl_module().attr("wrap_socket") }
|
||||
|
||||
ClassValue ssl_Context_class() { result = the_ssl_module().attr("SSLContext") }
|
||||
|
||||
private ModuleValue the_pyOpenSSL_module() { result = Value::named("pyOpenSSL.SSL") }
|
||||
|
||||
ClassValue the_pyOpenSSL_Context_class() {
|
||||
result = Value::named("pyOpenSSL.SSL.Context")
|
||||
}
|
||||
|
||||
string insecure_version_name() {
|
||||
@@ -33,25 +37,21 @@ string insecure_version_name() {
|
||||
result = "PROTOCOL_TLSv1"
|
||||
}
|
||||
|
||||
private ModuleObject the_ssl_module() {
|
||||
result = ModuleObject::named("ssl")
|
||||
}
|
||||
|
||||
private ModuleObject the_pyOpenSSL_module() {
|
||||
result = ModuleObject::named("pyOpenSSL.SSL")
|
||||
}
|
||||
|
||||
/* A syntactic check for cases where points-to analysis cannot infer the presence of
|
||||
/*
|
||||
* A syntactic check for cases where points-to analysis cannot infer the presence of
|
||||
* a protocol constant, e.g. if it has been removed in later versions of the `ssl`
|
||||
* library.
|
||||
*/
|
||||
|
||||
bindingset[named_argument]
|
||||
predicate probable_insecure_ssl_constant(CallNode call, string insecure_version, string named_argument) {
|
||||
predicate probable_insecure_ssl_constant(
|
||||
CallNode call, string insecure_version, string named_argument
|
||||
) {
|
||||
exists(ControlFlowNode arg |
|
||||
arg = call.getArgByName(named_argument) or
|
||||
arg = call.getArg(0)
|
||||
|
|
||||
arg.(AttrNode).getObject(insecure_version).refersTo(the_ssl_module())
|
||||
arg.(AttrNode).getObject(insecure_version).pointsTo(the_ssl_module())
|
||||
or
|
||||
arg.(NameNode).getId() = insecure_version and
|
||||
exists(Import imp |
|
||||
@@ -61,7 +61,9 @@ predicate probable_insecure_ssl_constant(CallNode call, string insecure_version,
|
||||
)
|
||||
}
|
||||
|
||||
predicate unsafe_ssl_wrap_socket_call(CallNode call, string method_name, string insecure_version, string named_argument) {
|
||||
predicate unsafe_ssl_wrap_socket_call(
|
||||
CallNode call, string method_name, string insecure_version, string named_argument
|
||||
) {
|
||||
(
|
||||
call = ssl_wrap_socket().getACall() and
|
||||
method_name = "deprecated method ssl.wrap_socket" and
|
||||
@@ -70,30 +72,26 @@ predicate unsafe_ssl_wrap_socket_call(CallNode call, string method_name, string
|
||||
call = ssl_Context_class().getACall() and
|
||||
named_argument = "protocol" and
|
||||
method_name = "ssl.SSLContext"
|
||||
)
|
||||
and
|
||||
insecure_version = insecure_version_name()
|
||||
and
|
||||
) and
|
||||
insecure_version = insecure_version_name() and
|
||||
(
|
||||
call.getArgByName(named_argument).refersTo(the_ssl_module().attr(insecure_version))
|
||||
call.getArgByName(named_argument).pointsTo(the_ssl_module().attr(insecure_version))
|
||||
or
|
||||
probable_insecure_ssl_constant(call, insecure_version, named_argument)
|
||||
)
|
||||
}
|
||||
|
||||
ClassObject the_pyOpenSSL_Context_class() {
|
||||
result = ModuleObject::named("pyOpenSSL.SSL").attr("Context")
|
||||
}
|
||||
|
||||
predicate unsafe_pyOpenSSL_Context_call(CallNode call, string insecure_version) {
|
||||
call = the_pyOpenSSL_Context_class().getACall() and
|
||||
insecure_version = insecure_version_name() and
|
||||
call.getArg(0).refersTo(the_pyOpenSSL_module().attr(insecure_version))
|
||||
call.getArg(0).pointsTo(the_pyOpenSSL_module().attr(insecure_version))
|
||||
}
|
||||
|
||||
from CallNode call, string method_name, string insecure_version
|
||||
where
|
||||
unsafe_ssl_wrap_socket_call(call, method_name, insecure_version, _)
|
||||
or
|
||||
or
|
||||
unsafe_pyOpenSSL_Context_call(call, insecure_version) and method_name = "pyOpenSSL.SSL.Context"
|
||||
select call, "Insecure SSL/TLS protocol version " + insecure_version + " specified in call to " + method_name + "."
|
||||
select call,
|
||||
"Insecure SSL/TLS protocol version " + insecure_version + " specified in call to " + method_name +
|
||||
"."
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
import python
|
||||
|
||||
FunctionObject temporary_name_function(string mod, string function) {
|
||||
FunctionValue temporary_name_function(string mod, string function) {
|
||||
(
|
||||
mod = "tempfile" and function = "mktemp"
|
||||
or
|
||||
@@ -23,10 +23,9 @@ FunctionObject temporary_name_function(string mod, string function) {
|
||||
function = "tempnam"
|
||||
)
|
||||
) and
|
||||
result = ModuleObject::named(mod).attr(function)
|
||||
result = Module::named(mod).attr(function)
|
||||
}
|
||||
|
||||
from Call c, string mod, string function
|
||||
where
|
||||
temporary_name_function(mod, function).getACall().getNode() = c
|
||||
where temporary_name_function(mod, function).getACall().getNode() = c
|
||||
select c, "Call to deprecated function " + mod + "." + function + " may be insecure."
|
||||
|
||||
@@ -10,28 +10,26 @@
|
||||
* security
|
||||
* serialization
|
||||
*/
|
||||
import python
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
// Sources -- Any untrusted input
|
||||
import semmle.python.web.HttpRequest
|
||||
import semmle.python.security.Paths
|
||||
|
||||
// Flow -- untrusted string
|
||||
import semmle.python.security.strings.Untrusted
|
||||
|
||||
// Sink -- Unpickling and other deserialization formats.
|
||||
import semmle.python.security.injection.Pickle
|
||||
import semmle.python.security.injection.Marshal
|
||||
import semmle.python.security.injection.Yaml
|
||||
|
||||
class UnsafeDeserializationConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
class UnsafeDeserializationConfiguration extends TaintTracking::Configuration {
|
||||
UnsafeDeserializationConfiguration() { this = "Unsafe deserialization configuration" }
|
||||
|
||||
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
|
||||
override predicate isSource(TaintTracking::Source source) {
|
||||
source instanceof HttpRequestTaintSource
|
||||
}
|
||||
|
||||
override predicate isSink(TaintTracking::Sink sink) { sink instanceof DeserializationSink }
|
||||
|
||||
}
|
||||
|
||||
from UnsafeDeserializationConfiguration config, TaintedPathSource src, TaintedPathSink sink
|
||||
|
||||
@@ -13,34 +13,29 @@
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
|
||||
import semmle.python.web.HttpRedirect
|
||||
import semmle.python.web.HttpRequest
|
||||
import semmle.python.security.strings.Untrusted
|
||||
|
||||
/** Url redirection is a problem only if the user controls the prefix of the URL */
|
||||
class UntrustedPrefixStringKind extends UntrustedStringKind {
|
||||
|
||||
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
|
||||
result = UntrustedStringKind.super.getTaintForFlowStep(fromnode, tonode) and
|
||||
not tonode.(BinaryExprNode).getRight() = fromnode
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class UrlRedirectConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
UrlRedirectConfiguration() { this = "URL redirect configuration" }
|
||||
|
||||
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
|
||||
|
||||
override predicate isSink(TaintTracking::Sink sink) {
|
||||
sink instanceof HttpRedirectTaintSink
|
||||
override predicate isSource(TaintTracking::Source source) {
|
||||
source instanceof HttpRequestTaintSource
|
||||
}
|
||||
|
||||
override predicate isSink(TaintTracking::Sink sink) { sink instanceof HttpRedirectTaintSink }
|
||||
}
|
||||
|
||||
from UrlRedirectConfiguration config, TaintedPathSource src, TaintedPathSink sink
|
||||
where config.hasFlowPath(src, sink)
|
||||
select sink.getSink(), src, sink, "Untrusted URL redirection due to $@.", src.getSource(), "a user-provided value"
|
||||
|
||||
select sink.getSink(), src, sink, "Untrusted URL redirection due to $@.", src.getSource(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
When creating a file POSIX systems allow permissions to be specified
|
||||
When creating a file, POSIX systems allow permissions to be specified
|
||||
for owner, group and others separately. Permissions should be kept as
|
||||
strict as possible, preventing access to the files contents by other users.
|
||||
</p>
|
||||
|
||||
@@ -9,22 +9,20 @@
|
||||
* @tags external/cwe/cwe-732
|
||||
* security
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
bindingset[p]
|
||||
int world_permission(int p) {
|
||||
result = p % 8
|
||||
}
|
||||
int world_permission(int p) { result = p % 8 }
|
||||
|
||||
bindingset[p]
|
||||
int group_permission(int p) {
|
||||
result = (p/8) % 8
|
||||
}
|
||||
int group_permission(int p) { result = (p / 8) % 8 }
|
||||
|
||||
bindingset[p]
|
||||
string access(int p) {
|
||||
p%4 >= 2 and result = "writable" or
|
||||
p%4 < 2 and p != 0 and result = "readable"
|
||||
p % 4 >= 2 and result = "writable"
|
||||
or
|
||||
p % 4 < 2 and p != 0 and result = "readable"
|
||||
}
|
||||
|
||||
bindingset[p]
|
||||
@@ -34,20 +32,20 @@ string permissive_permission(int p) {
|
||||
world_permission(p) = 0 and result = "group " + access(group_permission(p))
|
||||
}
|
||||
|
||||
predicate chmod_call(CallNode call, FunctionObject chmod, NumericObject num) {
|
||||
ModuleObject::named("os").attr("chmod") = chmod and
|
||||
chmod.getACall() = call and call.getArg(1).refersTo(num)
|
||||
predicate chmod_call(CallNode call, FunctionValue chmod, NumericValue num) {
|
||||
Value::named("os.chmod") = chmod and
|
||||
chmod.getACall() = call and
|
||||
call.getArg(1).pointsTo(num)
|
||||
}
|
||||
|
||||
predicate open_call(CallNode call, FunctionObject open, NumericObject num) {
|
||||
ModuleObject::named("os").attr("open") = open and
|
||||
open.getACall() = call and call.getArg(2).refersTo(num)
|
||||
predicate open_call(CallNode call, FunctionValue open, NumericValue num) {
|
||||
Value::named("os.open") = open and
|
||||
open.getACall() = call and
|
||||
call.getArg(2).pointsTo(num)
|
||||
}
|
||||
|
||||
|
||||
from CallNode call, FunctionObject func, NumericObject num, string permission
|
||||
from CallNode call, FunctionValue func, NumericValue num, string permission
|
||||
where
|
||||
(chmod_call(call, func, num) or open_call(call, func, num))
|
||||
and
|
||||
permission = permissive_permission(num.intValue())
|
||||
(chmod_call(call, func, num) or open_call(call, func, num)) and
|
||||
permission = permissive_permission(num.getIntValue())
|
||||
select call, "Overly permissive mask in " + func.getName() + " sets file to " + permission + "."
|
||||
|
||||
@@ -33,15 +33,15 @@ predicate fewer_characters_than(StrConst str, string char, float fraction) {
|
||||
}
|
||||
|
||||
predicate possible_reflective_name(string name) {
|
||||
exists(any(ModuleObject m).attr(name))
|
||||
exists(any(ModuleValue m).attr(name))
|
||||
or
|
||||
exists(any(ClassObject c).lookupAttribute(name))
|
||||
exists(any(ClassValue c).lookup(name))
|
||||
or
|
||||
any(ClassObject c).getName() = name
|
||||
any(ClassValue c).getName() = name
|
||||
or
|
||||
exists(ModuleObject::named(name))
|
||||
exists(Module::named(name))
|
||||
or
|
||||
exists(Object::builtin(name))
|
||||
exists(Value::named(name))
|
||||
}
|
||||
|
||||
int char_count(StrConst str) { result = count(string c | c = str.getText().charAt(_)) }
|
||||
@@ -89,7 +89,7 @@ class CredentialSink extends TaintSink {
|
||||
name.regexpMatch(getACredentialRegex()) and
|
||||
not name.suffix(name.length() - 4) = "file"
|
||||
|
|
||||
any(FunctionObject func).getNamedArgumentForCall(_, name) = this
|
||||
any(FunctionValue func).getNamedArgumentForCall(_, name) = this
|
||||
or
|
||||
exists(Keyword k | k.getArg() = name and k.getValue().getAFlowNode() = this)
|
||||
or
|
||||
|
||||
@@ -118,10 +118,20 @@ class Value extends TObject {
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the boolean value of this value. */
|
||||
boolean booleanValue() {
|
||||
/** Gets the boolean interpretation of this value.
|
||||
* Could be both `true` and `false`, if we can't determine the result more precisely.
|
||||
*/
|
||||
boolean getABooleanValue() {
|
||||
result = this.(ObjectInternal).booleanValue()
|
||||
}
|
||||
|
||||
/** Gets the boolean interpretation of this value, only if we can determine the result precisely.
|
||||
* The result can be `none()`, but never both `true` and `false`.
|
||||
*/
|
||||
boolean getDefiniteBooleanValue() {
|
||||
result = getABooleanValue() and
|
||||
not (getABooleanValue() = true and getABooleanValue() = false)
|
||||
}
|
||||
}
|
||||
|
||||
/** Class representing modules in the Python program
|
||||
@@ -241,14 +251,14 @@ module Value {
|
||||
name = "False" and result = TFalse()
|
||||
}
|
||||
|
||||
/** Gets the `Value` for the integer constant `i`, if it exists.
|
||||
* There will be no `Value` for most integers, but the following are
|
||||
/** Gets the `NumericValue` for the integer constant `i`, if it exists.
|
||||
* There will be no `NumericValue` for most integers, but the following are
|
||||
* guaranteed to exist:
|
||||
* * From zero to 511 inclusive.
|
||||
* * All powers of 2 (up to 2**30)
|
||||
* * Any integer explicitly mentioned in the source program.
|
||||
*/
|
||||
Value forInt(int i) {
|
||||
NumericValue forInt(int i) {
|
||||
result.(IntObjectInternal).intValue() = i
|
||||
}
|
||||
|
||||
@@ -256,7 +266,7 @@ module Value {
|
||||
* There will be no `Value` for most byte strings, unless it is explicitly
|
||||
* declared in the source program.
|
||||
*/
|
||||
Value forBytes(string bytes) {
|
||||
StringValue forBytes(string bytes) {
|
||||
result.(BytesObjectInternal).strValue() = bytes
|
||||
}
|
||||
|
||||
@@ -264,7 +274,7 @@ module Value {
|
||||
* There will be no `Value` for most text strings, unless it is explicitly
|
||||
* declared in the source program.
|
||||
*/
|
||||
Value forUnicode(string text) {
|
||||
StringValue forUnicode(string text) {
|
||||
result.(UnicodeObjectInternal).strValue() = text
|
||||
}
|
||||
|
||||
@@ -272,7 +282,7 @@ module Value {
|
||||
* There will be no `Value` for most strings, unless it is explicitly
|
||||
* declared in the source program.
|
||||
*/
|
||||
Value forString(string text) {
|
||||
StringValue forString(string text) {
|
||||
result.(UnicodeObjectInternal).strValue() = text
|
||||
or
|
||||
major_version() = 2 and
|
||||
@@ -620,6 +630,26 @@ class StringValue extends Value {
|
||||
}
|
||||
}
|
||||
|
||||
/** A class representing numbers (ints and floats), either present in the source as a literal,
|
||||
* or in a builtin as a value.
|
||||
*/
|
||||
class NumericValue extends Value {
|
||||
NumericValue() {
|
||||
this instanceof IntObjectInternal or
|
||||
this instanceof FloatObjectInternal
|
||||
}
|
||||
|
||||
/** Gets the integer-value if it is a constant integer, and it fits in a QL int */
|
||||
int getIntValue() {
|
||||
result = this.(IntObjectInternal).intValue()
|
||||
}
|
||||
|
||||
/** Gets the float-value if it is a constant float */
|
||||
int getFloatValue() {
|
||||
result = this.(FloatObjectInternal).floatValue()
|
||||
}
|
||||
}
|
||||
|
||||
/** A method-resolution-order sequence of classes */
|
||||
class MRO extends TClassList {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user