Skip to content

Commit ffbb5d0

Browse files
authored
Merge pull request #2739 from RasmusWL/python-modernise-security
Python: modernise Security/ queries
2 parents abe7aee + f3ab52b commit ffbb5d0

35 files changed

+319
-361
lines changed

python/ql/src/Security/CVE-2018-1281/BindToAllInterfaces.ql

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,24 @@ import python
1515
Value aSocket() { result.getClass() = Value::named("socket.socket") }
1616

1717
CallNode socketBindCall() {
18-
result = aSocket().attr("bind").(CallableValue).getACall() and major_version() = 3
19-
or
20-
result.getFunction().(AttrNode).getObject("bind").pointsTo(aSocket()) and
21-
major_version() = 2
18+
result = aSocket().attr("bind").(CallableValue).getACall() and major_version() = 3
19+
or
20+
result.getFunction().(AttrNode).getObject("bind").pointsTo(aSocket()) and
21+
major_version() = 2
2222
}
2323

2424
string allInterfaces() { result = "0.0.0.0" or result = "" }
2525

2626
Value getTextValue(string address) {
27-
result = Value::forUnicode(address) and major_version() = 3
28-
or
29-
result = Value::forString(address) and major_version() = 2
27+
result = Value::forUnicode(address) and major_version() = 3
28+
or
29+
result = Value::forString(address) and major_version() = 2
3030
}
3131

3232
from CallNode call, TupleValue args, string address
3333
where
34-
call = socketBindCall() and
35-
call.getArg(0).pointsTo(args) and
36-
args.getItem(0) = getTextValue(address) and
37-
address = allInterfaces()
34+
call = socketBindCall() and
35+
call.getArg(0).pointsTo(args) and
36+
args.getItem(0) = getTextValue(address) and
37+
address = allInterfaces()
3838
select call.getNode(), "'" + address + "' binds a socket to all interfaces."

python/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,28 @@
1313
import python
1414
import semmle.python.regex
1515

16-
private string commonTopLevelDomainRegex() {
17-
result = "com|org|edu|gov|uk|net|io"
18-
}
16+
private string commonTopLevelDomainRegex() { result = "com|org|edu|gov|uk|net|io" }
1917

2018
/**
2119
* Holds if `pattern` is a regular expression pattern for URLs with a host matched by `hostPart`,
2220
* and `pattern` contains a subtle mistake that allows it to match unexpected hosts.
2321
*/
2422
bindingset[pattern]
2523
predicate isIncompleteHostNameRegExpPattern(string pattern, string hostPart) {
26-
hostPart = pattern
27-
.regexpCapture("(?i).*" +
28-
// an unescaped single `.`
29-
"(?<!\\\\)[.]" +
30-
// immediately followed by a sequence of subdomains, perhaps with some regex characters mixed in, followed by a known TLD
31-
"([():|?a-z0-9-]+(\\\\)?[.](" + commonTopLevelDomainRegex() + "))" + ".*", 1)
24+
hostPart = pattern
25+
.regexpCapture("(?i).*" +
26+
// an unescaped single `.`
27+
"(?<!\\\\)[.]" +
28+
// immediately followed by a sequence of subdomains, perhaps with some regex characters mixed in, followed by a known TLD
29+
"([():|?a-z0-9-]+(\\\\)?[.](" + commonTopLevelDomainRegex() + "))" + ".*", 1)
3230
}
3331

3432
from Regex r, string pattern, string hostPart
3533
where
36-
(
37-
r.getText() = pattern
38-
) and
39-
isIncompleteHostNameRegExpPattern(pattern, hostPart) and
40-
// ignore patterns with capture groups after the TLD
41-
not pattern.regexpMatch("(?i).*[.](" + commonTopLevelDomainRegex() + ").*[(][?]:.*[)].*")
34+
r.getText() = pattern and
35+
isIncompleteHostNameRegExpPattern(pattern, hostPart) and
36+
// ignore patterns with capture groups after the TLD
37+
not pattern.regexpMatch("(?i).*[.](" + commonTopLevelDomainRegex() + ").*[(][?]:.*[)].*")
4238
select r,
43-
"This regular expression has an unescaped '.' before '" + hostPart +
44-
"', so it might match more hosts than expected."
39+
"This regular expression has an unescaped '.' before '" + hostPart +
40+
"', so it might match more hosts than expected."

python/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,16 @@
1010
* external/cwe/cwe-20
1111
*/
1212

13-
1413
import python
1514
import semmle.python.regex
1615

17-
private string commonTopLevelDomainRegex() {
18-
result = "com|org|edu|gov|uk|net|io"
19-
}
16+
private string commonTopLevelDomainRegex() { result = "com|org|edu|gov|uk|net|io" }
2017

2118
predicate looksLikeUrl(StrConst s) {
22-
exists(string text |
23-
text = s.getText()
24-
|
25-
text.regexpMatch("(?i)([a-z]*:?//)?\\.?([a-z0-9-]+\\.)+(" +
26-
commonTopLevelDomainRegex() +")(:[0-9]+)?/?")
19+
exists(string text | text = s.getText() |
20+
text
21+
.regexpMatch("(?i)([a-z]*:?//)?\\.?([a-z0-9-]+\\.)+(" + commonTopLevelDomainRegex() +
22+
")(:[0-9]+)?/?")
2723
or
2824
// target is a HTTP URL to a domain on any TLD
2925
text.regexpMatch("(?i)https?://([a-z0-9-]+\\.)+([a-z]+)(:[0-9]+)?/?")
@@ -42,10 +38,8 @@ predicate incomplete_sanitization(Expr sanitizer, StrConst url) {
4238
}
4339

4440
predicate unsafe_call_to_startswith(Call sanitizer, StrConst url) {
45-
sanitizer.getFunc().(Attribute).getName() = "startswith"
46-
and
47-
sanitizer.getArg(0) = url
48-
and
41+
sanitizer.getFunc().(Attribute).getName() = "startswith" and
42+
sanitizer.getArg(0) = url and
4943
not url.getText().regexpMatch("(?i)https?://[\\.a-z0-9-]+/.*")
5044
}
5145

python/ql/src/Security/CWE-022/PathInjection.ql

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,17 @@
1818

1919
import python
2020
import semmle.python.security.Paths
21-
2221
/* Sources */
2322
import semmle.python.web.HttpRequest
24-
2523
/* Sinks */
2624
import semmle.python.security.injection.Path
2725

2826
class PathInjectionConfiguration extends TaintTracking::Configuration {
29-
3027
PathInjectionConfiguration() { this = "Path injection configuration" }
3128

32-
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
29+
override predicate isSource(TaintTracking::Source source) {
30+
source instanceof HttpRequestTaintSource
31+
}
3332

3433
override predicate isSink(TaintTracking::Sink sink) { sink instanceof OpenNode }
3534

@@ -41,10 +40,9 @@ class PathInjectionConfiguration extends TaintTracking::Configuration {
4140
override predicate isExtension(TaintTracking::Extension extension) {
4241
extension instanceof AbsPath
4342
}
44-
4543
}
4644

47-
4845
from PathInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
4946
where config.hasFlowPath(src, sink)
50-
select sink.getSink(), src, sink, "This path depends on $@.", src.getSource(), "a user-provided value"
47+
select sink.getSink(), src, sink, "This path depends on $@.", src.getSource(),
48+
"a user-provided value"

python/ql/src/Security/CWE-022/TarSlip.ql

Lines changed: 23 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @description Extracting files from a malicious tar archive without validating that the
44
* destination file path is within the destination directory can cause files outside
55
* the destination directory to be overwritten.
6-
* @kind path-problem
6+
* @kind path-problem
77
* @id py/tarslip
88
* @problem.severity error
99
* @precision medium
@@ -13,76 +13,58 @@
1313

1414
import python
1515
import semmle.python.security.Paths
16-
1716
import semmle.python.security.TaintTracking
1817
import semmle.python.security.strings.Basic
1918

2019
/** A TaintKind to represent open tarfile objects. That is, the result of calling `tarfile.open(...)` */
2120
class OpenTarFile extends TaintKind {
22-
OpenTarFile() {
23-
this = "tarfile.open"
24-
}
21+
OpenTarFile() { this = "tarfile.open" }
2522

2623
override TaintKind getTaintOfMethodResult(string name) {
2724
name = "getmember" and result instanceof TarFileInfo
2825
or
2926
name = "getmembers" and result.(SequenceKind).getItem() instanceof TarFileInfo
3027
}
3128

32-
override ClassValue getType() {
33-
result = Module::named("tarfile").attr("TarFile")
34-
}
35-
36-
override TaintKind getTaintForIteration() {
37-
result instanceof TarFileInfo
38-
}
29+
override ClassValue getType() { result = Value::named("tarfile.TarFile") }
3930

31+
override TaintKind getTaintForIteration() { result instanceof TarFileInfo }
4032
}
4133

4234
/** The source of open tarfile objects. That is, any call to `tarfile.open(...)` */
4335
class TarfileOpen extends TaintSource {
44-
4536
TarfileOpen() {
46-
Module::named("tarfile").attr("open").getACall() = this
47-
and
48-
/* If argument refers to a string object, then it's a hardcoded path and
37+
Value::named("tarfile.open").getACall() = this and
38+
/*
39+
* If argument refers to a string object, then it's a hardcoded path and
4940
* this tarfile is safe.
5041
*/
51-
not this.(CallNode).getAnArg().refersTo(any(StringObject str))
52-
and
42+
43+
not this.(CallNode).getAnArg().pointsTo(any(StringValue str)) and
5344
/* Ignore opens within the tarfile module itself */
5445
not this.(ControlFlowNode).getLocation().getFile().getBaseName() = "tarfile.py"
5546
}
5647

57-
override predicate isSourceOf(TaintKind kind) {
58-
kind instanceof OpenTarFile
59-
}
60-
48+
override predicate isSourceOf(TaintKind kind) { kind instanceof OpenTarFile }
6149
}
6250

6351
class TarFileInfo extends TaintKind {
52+
TarFileInfo() { this = "tarfile.entry" }
6453

65-
TarFileInfo() {
66-
this = "tarfile.entry"
67-
}
68-
69-
override TaintKind getTaintOfMethodResult(string name) {
70-
name = "next" and result = this
71-
}
54+
override TaintKind getTaintOfMethodResult(string name) { name = "next" and result = this }
7255

7356
override TaintKind getTaintOfAttribute(string name) {
7457
name = "name" and result instanceof TarFileInfo
7558
}
7659
}
7760

61+
/*
62+
* For efficiency we don't want to track the flow of taint
63+
* around the tarfile module.
64+
*/
7865

79-
/* For efficiency we don't want to track the flow of taint
80-
* around the tarfile module. */
8166
class ExcludeTarFilePy extends Sanitizer {
82-
83-
ExcludeTarFilePy() {
84-
this = "Tar sanitizer"
85-
}
67+
ExcludeTarFilePy() { this = "Tar sanitizer" }
8668

8769
override predicate sanitizingNode(TaintKind taint, ControlFlowNode node) {
8870
node.getLocation().getFile().getBaseName() = "tarfile.py" and
@@ -94,45 +76,34 @@ class ExcludeTarFilePy extends Sanitizer {
9476
taint.(SequenceKind).getItem() instanceof TarFileInfo
9577
)
9678
}
97-
9879
}
9980

10081
/* Any call to an extractall method */
10182
class ExtractAllSink extends TaintSink {
102-
10383
CallNode call;
10484

10585
ExtractAllSink() {
10686
this = call.getFunction().(AttrNode).getObject("extractall") and
10787
count(call.getAnArg()) = 0
10888
}
10989

110-
override predicate sinks(TaintKind kind) {
111-
kind instanceof OpenTarFile
112-
}
113-
90+
override predicate sinks(TaintKind kind) { kind instanceof OpenTarFile }
11491
}
11592

11693
/* Argument to extract method */
11794
class ExtractSink extends TaintSink {
118-
11995
CallNode call;
12096

12197
ExtractSink() {
12298
call.getFunction().(AttrNode).getName() = "extract" and
12399
this = call.getArg(0)
124100
}
125101

126-
override predicate sinks(TaintKind kind) {
127-
kind instanceof TarFileInfo
128-
}
129-
102+
override predicate sinks(TaintKind kind) { kind instanceof TarFileInfo }
130103
}
131104

132-
133105
/* Members argument to extract method */
134106
class ExtractMembersSink extends TaintSink {
135-
136107
CallNode call;
137108

138109
ExtractMembersSink() {
@@ -145,21 +116,15 @@ class ExtractMembersSink extends TaintSink {
145116
or
146117
kind instanceof OpenTarFile
147118
}
148-
149119
}
150120

151121
class TarFileInfoSanitizer extends Sanitizer {
152-
153-
TarFileInfoSanitizer() {
154-
this = "TarInfo sanitizer"
155-
}
122+
TarFileInfoSanitizer() { this = "TarInfo sanitizer" }
156123

157124
override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) {
158125
path_sanitizing_test(test.getTest()) and
159126
taint instanceof TarFileInfo
160127
}
161-
162-
163128
}
164129

165130
private predicate path_sanitizing_test(ControlFlowNode test) {
@@ -170,7 +135,6 @@ private predicate path_sanitizing_test(ControlFlowNode test) {
170135
}
171136

172137
class TarSlipConfiguration extends TaintTracking::Configuration {
173-
174138
TarSlipConfiguration() { this = "TarSlip configuration" }
175139

176140
override predicate isSource(TaintTracking::Source source) { source instanceof TarfileOpen }
@@ -193,16 +157,15 @@ class TarSlipConfiguration extends TaintTracking::Configuration {
193157
node.asVariable().getDefinition() = def
194158
or
195159
node.asCfgNode() = def.getDefiningNode()
196-
|
160+
|
197161
def.getScope() = Value::named("tarfile.open").(CallableValue).getScope()
198162
or
199163
def.isSelf() and def.getScope().getEnclosingModule().getName() = "tarfile"
200164
)
201165
}
202166
}
203167

204-
205168
from TarSlipConfiguration config, TaintedPathSource src, TaintedPathSink sink
206169
where config.hasFlowPath(src, sink)
207-
select sink.getSink(), src, sink, "Extraction of tarfile from $@", src.getSource(), "a potentially untrusted source"
208-
170+
select sink.getSink(), src, sink, "Extraction of tarfile from $@", src.getSource(),
171+
"a potentially untrusted source"

python/ql/src/Security/CWE-022/examples/tainted_path.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
def user_picture1(request):
1313
"""A view that is vulnerable to malicious file access."""
14-
base_path = '/server/static/images'
1514
filename = request.GET.get('p')
1615
# BAD: This could read any file on the file system
1716
data = open(filename, 'rb').read()

python/ql/src/Security/CWE-078/CommandInjection.ql

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,17 @@
1616

1717
import python
1818
import semmle.python.security.Paths
19-
2019
/* Sources */
2120
import semmle.python.web.HttpRequest
22-
2321
/* Sinks */
2422
import semmle.python.security.injection.Command
2523

2624
class CommandInjectionConfiguration extends TaintTracking::Configuration {
27-
2825
CommandInjectionConfiguration() { this = "Command injection configuration" }
2926

30-
override predicate isSource(TaintTracking::Source source) { source instanceof HttpRequestTaintSource }
27+
override predicate isSource(TaintTracking::Source source) {
28+
source instanceof HttpRequestTaintSource
29+
}
3130

3231
override predicate isSink(TaintTracking::Sink sink) {
3332
sink instanceof OsCommandFirstArgument or
@@ -37,9 +36,9 @@ class CommandInjectionConfiguration extends TaintTracking::Configuration {
3736
override predicate isExtension(TaintTracking::Extension extension) {
3837
extension instanceof FirstElementFlow
3938
}
40-
4139
}
4240

4341
from CommandInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
4442
where config.hasFlowPath(src, sink)
45-
select sink.getSink(), src, sink, "This command depends on $@.", src.getSource(), "a user-provided value"
43+
select sink.getSink(), src, sink, "This command depends on $@.", src.getSource(),
44+
"a user-provided value"

0 commit comments

Comments
 (0)