Skip to content

Commit e14f7ef

Browse files
committed
Python: Tarslip query; track info objects and handle sanitization.
1 parent ea4e263 commit e14f7ef

File tree

3 files changed

+100
-11
lines changed

3 files changed

+100
-11
lines changed

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

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,28 @@ import python
1515
import semmle.python.security.Paths
1616

1717
import semmle.python.security.TaintTracking
18+
import semmle.python.security.strings.Basic
1819

1920
/** A TaintKind to represent open tarfile objects. That is, the result of calling `tarfile.open(...)` */
2021
class OpenTarFile extends TaintKind {
2122
OpenTarFile() {
2223
this = "tarfile.open"
2324
}
25+
26+
override TaintKind getTaintOfMethodResult(string name) {
27+
name = "getmember" and result instanceof TarFileInfo
28+
or
29+
name = "getmembers" and result.(SequenceKind).getItem() instanceof TarFileInfo
30+
}
31+
32+
override ClassValue getType() {
33+
result = Module::named("tarfile").attr("TarFile")
34+
}
35+
36+
override TaintKind getTaintForIteration() {
37+
result instanceof TarFileInfo
38+
}
39+
2440
}
2541

2642
/** The source of open tarfile objects. That is, any call to `tarfile.open(...)` */
@@ -41,13 +57,29 @@ class TarfileOpen extends TaintSource {
4157

4258
}
4359

44-
/* Any call to an extract method */
45-
class ExtractionSink extends TaintSink {
60+
class TarFileInfo extends TaintKind {
61+
62+
TarFileInfo() {
63+
this = "tarfile.entry"
64+
}
65+
66+
override TaintKind getTaintOfMethodResult(string name) {
67+
name = "next" and result = this
68+
}
69+
70+
override TaintKind getTaintOfAttribute(string name) {
71+
name = "name" and result instanceof TarFileInfo
72+
}
73+
}
74+
75+
76+
/* Any call to an extractall method */
77+
class ExtractAllSink extends TaintSink {
4678

4779
CallNode call;
4880

49-
ExtractionSink() {
50-
this = call.getFunction().(AttrNode).getObject(extract())
81+
ExtractAllSink() {
82+
this = call.getFunction().(AttrNode).getObject("extractall")
5183
}
5284

5385
override predicate sinks(TaintKind kind) {
@@ -56,21 +88,59 @@ class ExtractionSink extends TaintSink {
5688

5789
}
5890

59-
private string extract() {
60-
result = "extract" or result = "extractall"
91+
/* Argument to extract method */
92+
class ExtractSink extends TaintSink {
93+
94+
CallNode call;
95+
96+
ExtractSink() {
97+
call.getFunction().(AttrNode).getName() = "extract" and
98+
this = call.getArg(0)
99+
}
100+
101+
override predicate sinks(TaintKind kind) {
102+
kind instanceof TarFileInfo
103+
}
104+
61105
}
62106

107+
class TarFileInfoSanitizer extends Sanitizer {
108+
109+
TarFileInfoSanitizer() {
110+
this = "TarInfo sanitizer"
111+
}
112+
113+
override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) {
114+
path_sanitizing_test(test.getTest()) and
115+
taint instanceof TarFileInfo
116+
}
117+
}
118+
119+
private predicate path_sanitizing_test(ControlFlowNode test) {
120+
checks_not_absolute(test) and
121+
test.getAChild+().getNode().(StrConst).getText() = ".."
122+
}
123+
124+
private predicate checks_not_absolute(ControlFlowNode test) {
125+
test.getAChild+().(CallNode).getFunction().pointsTo(Module::named("os.path").attr("absfile"))
126+
or
127+
test.getAChild+().getNode().(StrConst).getText() = "/"
128+
}
63129

64-
//evil = [e for e in members if os.path.relpath(e).startswith(('/', '..'))]
65130

66131
class TarSlipConfiguration extends TaintTracking::Configuration {
67132

68133
TarSlipConfiguration() { this = "TarSlip configuration" }
69134

70135
override predicate isSource(TaintTracking::Source source) { source instanceof TarfileOpen }
71136

72-
override predicate isSink(TaintTracking::Sink sink) { sink instanceof ExtractionSink }
137+
override predicate isSink(TaintTracking::Sink sink) {
138+
sink instanceof ExtractSink or sink instanceof ExtractAllSink
139+
}
73140

141+
override predicate isSanitizer(Sanitizer sanitizer) {
142+
sanitizer instanceof TarFileInfoSanitizer
143+
}
74144
}
75145

76146

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
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 |
43
| 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 |
4+
| tarslip.py:17:5:17:9 | tarfile.entry | tarslip.py:18:17:18:21 | tarfile.entry |
5+
| tarslip.py:17:14:17:16 | tarfile.open | tarslip.py:17:5:17:9 | tarfile.entry |
6+
| tarslip.py:33:7:33:39 | tarfile.open | tarslip.py:34:14:34:16 | tarfile.open |
7+
| tarslip.py:34:5:34:9 | tarfile.entry | tarslip.py:37:17:37:21 | tarfile.entry |
8+
| tarslip.py:34:14:34:16 | tarfile.open | tarslip.py:34:5:34:9 | tarfile.entry |
69
parents
710
#select
811
| 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 |
9-
| tarslip.py:18:5:18:7 | Taint sink | tarslip.py:16:7:16:39 | tarfile.open | tarslip.py:18:5:18:7 | tarfile.open | Extraction of tarfile from $@ | tarslip.py:16:7:16:39 | Taint source | a potentially untrusted source |
12+
| 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 |
13+
| 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 |

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,18 @@
2020
tar = tarfile.open(safe_filename_tar)
2121
tar.extractall()
2222
tar.close()
23+
24+
25+
#Sanitized
26+
tar = tarfile.open(unsafe_filename_tar)
27+
for entry in tar:
28+
if os.path.isabs(entry.name) or ".." in entry.name:
29+
raise ValueError("Illegal tar archive entry")
30+
tar.extract(entry, "/tmp/unpack/")
31+
32+
#Part Sanitized
33+
tar = tarfile.open(unsafe_filename_tar)
34+
for entry in tar:
35+
if ".." in entry.name:
36+
raise ValueError("Illegal tar archive entry")
37+
tar.extract(entry, "/tmp/unpack/")

0 commit comments

Comments
 (0)