Skip to content

Commit 6f15c84

Browse files
committed
Python: Tarslip query; Add sink for members and sanitizers for tarinfo objects.
1 parent e14f7ef commit 6f15c84

File tree

4 files changed

+103
-13
lines changed

4 files changed

+103
-13
lines changed

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

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ class TarfileOpen extends TaintSource {
4949
* this tarfile is safe.
5050
*/
5151
not this.(CallNode).getAnArg().refersTo(any(StringObject str))
52+
and
53+
/* Ignore opens within the tarfile module itself */
54+
not this.(ControlFlowNode).getLocation().getFile().getBaseName() = "tarfile.py"
5255
}
5356

5457
override predicate isSourceOf(TaintKind kind) {
@@ -73,13 +76,33 @@ class TarFileInfo extends TaintKind {
7376
}
7477

7578

79+
/* For efficiency we don't want to track the flow of taint
80+
* around the tarfile module. */
81+
class ExcludeTarFilePy extends Sanitizer {
82+
83+
ExcludeTarFilePy() {
84+
this = "Tar sanitizer"
85+
}
86+
87+
override predicate sanitizingNode(TaintKind taint, ControlFlowNode node) {
88+
node.getLocation().getFile().getBaseName() = "tarfile.py" and
89+
(
90+
taint instanceof OpenTarFile
91+
or
92+
taint instanceof TarFileInfo
93+
)
94+
}
95+
96+
}
97+
7698
/* Any call to an extractall method */
7799
class ExtractAllSink extends TaintSink {
78100

79101
CallNode call;
80102

81103
ExtractAllSink() {
82-
this = call.getFunction().(AttrNode).getObject("extractall")
104+
this = call.getFunction().(AttrNode).getObject("extractall") and
105+
count(call.getAnArg()) = 0
83106
}
84107

85108
override predicate sinks(TaintKind kind) {
@@ -104,6 +127,25 @@ class ExtractSink extends TaintSink {
104127

105128
}
106129

130+
131+
/* Members argument to extract method */
132+
class ExtractMembersSink extends TaintSink {
133+
134+
CallNode call;
135+
136+
ExtractMembersSink() {
137+
call.getFunction().(AttrNode).getName() = "extractall" and
138+
(this = call.getArg(0) or this = call.getArgByName("members"))
139+
}
140+
141+
override predicate sinks(TaintKind kind) {
142+
kind.(SequenceKind).getItem() instanceof TarFileInfo
143+
or
144+
kind instanceof OpenTarFile
145+
}
146+
147+
}
148+
107149
class TarFileInfoSanitizer extends Sanitizer {
108150

109151
TarFileInfoSanitizer() {
@@ -114,38 +156,39 @@ class TarFileInfoSanitizer extends Sanitizer {
114156
path_sanitizing_test(test.getTest()) and
115157
taint instanceof TarFileInfo
116158
}
117-
}
118159

119-
private predicate path_sanitizing_test(ControlFlowNode test) {
120-
checks_not_absolute(test) and
121-
test.getAChild+().getNode().(StrConst).getText() = ".."
160+
122161
}
123162

124-
private predicate checks_not_absolute(ControlFlowNode test) {
125-
test.getAChild+().(CallNode).getFunction().pointsTo(Module::named("os.path").attr("absfile"))
163+
private predicate path_sanitizing_test(ControlFlowNode test) {
164+
/* Assume that any test with "path" in it is a sanitizer */
165+
test.getAChild+().(AttrNode).getName() = "path"
126166
or
127-
test.getAChild+().getNode().(StrConst).getText() = "/"
167+
test.getAChild+().(NameNode).getId() = "path"
128168
}
129169

130-
131170
class TarSlipConfiguration extends TaintTracking::Configuration {
132171

133172
TarSlipConfiguration() { this = "TarSlip configuration" }
134173

135174
override predicate isSource(TaintTracking::Source source) { source instanceof TarfileOpen }
136175

137-
override predicate isSink(TaintTracking::Sink sink) {
138-
sink instanceof ExtractSink or sink instanceof ExtractAllSink
176+
override predicate isSink(TaintTracking::Sink sink) {
177+
sink instanceof ExtractSink or
178+
sink instanceof ExtractAllSink or
179+
sink instanceof ExtractMembersSink
139180
}
140181

141182
override predicate isSanitizer(Sanitizer sanitizer) {
142183
sanitizer instanceof TarFileInfoSanitizer
184+
or
185+
sanitizer instanceof ExcludeTarFilePy
143186
}
187+
144188
}
145189

146190

147191
from TarSlipConfiguration config, TaintedPathSource src, TaintedPathSink sink
148192
where config.hasFlowPath(src, sink)
149193
select sink.getSink(), src, sink, "Extraction of tarfile from $@", src.getSource(), "a potentially untrusted source"
150194

151-

python/ql/src/semmle/python/security/TaintTracking.qll

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,7 @@ class TaintedNode extends TTaintedNode {
703703
/** Holds if the underlying CFG node for this node is a vulnerable node
704704
* and is vulnerable to this node's taint.
705705
*/
706-
predicate isVulnerableSink() {
706+
predicate isSink() {
707707
exists(TaintedNode src, TaintSink vuln |
708708
src.isSource() and
709709
src.getASuccessor*() = this and
@@ -712,6 +712,13 @@ class TaintedNode extends TTaintedNode {
712712
)
713713
}
714714

715+
/** DEPRECATED -- Use `TaintedNode.isSink()` instead
716+
* Sinks are not necessarily vulnerable
717+
* For removal 2020-07-01 */
718+
deprecated predicate isVulnerableSink() {
719+
this.isSink()
720+
}
721+
715722
TaintFlowImplementation::TrackedTaint fromAttribute(string name) {
716723
result = this.getTrackedValue().(TaintFlowImplementation::TrackedAttribute).fromAttribute(name)
717724
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,38 @@
11
edges
22
| tarslip.py:12:7:12:39 | tarfile.open | tarslip.py:13:1:13:3 | tarfile.open |
3+
| tarslip.py:12:7:12:39 | tarfile.open | tarslip.py:14:1:14:3 | tarfile.open |
34
| tarslip.py:16:7:16:39 | tarfile.open | tarslip.py:17:14:17:16 | tarfile.open |
5+
| tarslip.py:16:7:16:39 | tarfile.open | tarslip.py:18:5:18:7 | tarfile.open |
46
| tarslip.py:17:5:17:9 | tarfile.entry | tarslip.py:18:17:18:21 | tarfile.entry |
57
| tarslip.py:17:14:17:16 | tarfile.open | tarslip.py:17:5:17:9 | tarfile.entry |
8+
| tarslip.py:26:7:26:39 | tarfile.open | tarslip.py:27:14:27:16 | tarfile.open |
9+
| tarslip.py:26:7:26:39 | tarfile.open | tarslip.py:30:5:30:7 | tarfile.open |
10+
| tarslip.py:27:5:27:9 | tarfile.entry | tarslip.py:28:22:28:26 | tarfile.entry |
11+
| tarslip.py:27:14:27:16 | tarfile.open | tarslip.py:27:5:27:9 | tarfile.entry |
12+
| tarslip.py:28:22:28:26 | tarfile.entry | tarslip.py:28:22:28:31 | tarfile.entry |
613
| tarslip.py:33:7:33:39 | tarfile.open | tarslip.py:34:14:34:16 | tarfile.open |
14+
| tarslip.py:33:7:33:39 | tarfile.open | tarslip.py:37:5:37:7 | tarfile.open |
15+
| tarslip.py:34:5:34:9 | tarfile.entry | tarslip.py:35:16:35:20 | tarfile.entry |
716
| tarslip.py:34:5:34:9 | tarfile.entry | tarslip.py:37:17:37:21 | tarfile.entry |
817
| tarslip.py:34:14:34:16 | tarfile.open | tarslip.py:34:5:34:9 | tarfile.entry |
18+
| tarslip.py:35:16:35:20 | tarfile.entry | tarslip.py:35:16:35:25 | tarfile.entry |
19+
| tarslip.py:40:7:40:39 | tarfile.open | tarslip.py:41:1:41:3 | tarfile.open |
20+
| tarslip.py:40:7:40:39 | tarfile.open | tarslip.py:41:24:41:26 | tarfile.open |
21+
| tarslip.py:45:17:45:23 | tarfile.open | tarslip.py:46:17:46:23 | tarfile.open |
22+
| tarslip.py:46:9:46:12 | tarfile.entry | tarslip.py:47:20:47:23 | tarfile.entry |
23+
| tarslip.py:46:9:46:12 | tarfile.entry | tarslip.py:49:15:49:18 | tarfile.entry |
24+
| tarslip.py:46:17:46:23 | tarfile.open | tarslip.py:46:9:46:12 | tarfile.entry |
25+
| tarslip.py:51:7:51:39 | tarfile.open | tarslip.py:52:1:52:3 | tarfile.open |
26+
| tarslip.py:51:7:51:39 | tarfile.open | tarslip.py:52:36:52:38 | tarfile.open |
27+
| tarslip.py:52:36:52:38 | tarfile.open | tarslip.py:45:17:45:23 | tarfile.open |
928
parents
29+
| tarslip.py:45:17:45:23 | tarfile.open | tarslip.py:52:36:52:38 | tarfile.open |
30+
| tarslip.py:46:9:46:12 | tarfile.entry | tarslip.py:52:36:52:38 | tarfile.open |
31+
| tarslip.py:46:17:46:23 | tarfile.open | tarslip.py:52:36:52:38 | tarfile.open |
32+
| tarslip.py:47:20:47:23 | tarfile.entry | tarslip.py:52:36:52:38 | tarfile.open |
33+
| tarslip.py:49:15:49:18 | tarfile.entry | tarslip.py:52:36:52:38 | tarfile.open |
1034
#select
1135
| tarslip.py:13:1:13:3 | Taint sink | tarslip.py:12:7:12:39 | tarfile.open | tarslip.py:13:1:13:3 | tarfile.open | Extraction of tarfile from $@ | tarslip.py:12:7:12:39 | Taint source | a potentially untrusted source |
1236
| tarslip.py:18:17:18:21 | Taint sink | tarslip.py:16:7:16:39 | tarfile.open | tarslip.py:18:17:18:21 | tarfile.entry | Extraction of tarfile from $@ | tarslip.py:16:7:16:39 | Taint source | a potentially untrusted source |
1337
| tarslip.py:37:17:37:21 | Taint sink | tarslip.py:33:7:33:39 | tarfile.open | tarslip.py:37:17:37:21 | tarfile.entry | Extraction of tarfile from $@ | tarslip.py:33:7:33:39 | Taint source | a potentially untrusted source |
38+
| tarslip.py:41:24:41:26 | Taint sink | tarslip.py:40:7:40:39 | tarfile.open | tarslip.py:41:24:41:26 | tarfile.open | Extraction of tarfile from $@ | tarslip.py:40:7:40:39 | Taint source | a potentially untrusted source |

python/ql/test/query-tests/Security/CWE-022/tarslip.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,18 @@
3535
if ".." in entry.name:
3636
raise ValueError("Illegal tar archive entry")
3737
tar.extract(entry, "/tmp/unpack/")
38+
39+
#Unsanitized members
40+
tar = tarfile.open(unsafe_filename_tar)
41+
tar.extractall(members=tar)
42+
43+
44+
#Sanitize members
45+
def safemembers(members):
46+
for info in members:
47+
if badpath(info):
48+
raise
49+
yield info
50+
51+
tar = tarfile.open(unsafe_filename_tar)
52+
tar.extractall(members=safemembers(tar))

0 commit comments

Comments
 (0)