Skip to content

Commit dc3ea6c

Browse files
authored
Merge pull request #11703 from Kwstubbs/go-taintedpath-additions
Go: Add and Modify Sanitizers For TaintedPath
2 parents 0e67aa5 + 5acc15b commit dc3ea6c

File tree

4 files changed

+56
-8
lines changed

4 files changed

+56
-8
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
* ---
3+
category: minorAnalysis
4+
---
5+
* Added strings.ReplaceAll, http.ParseMultipartForm sanitizers and remove path sanitizer.

go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import go
77
import semmle.go.dataflow.barrierguardutil.RegexpCheck
8+
import DataFlow
89

910
/**
1011
* Provides extension points for customizing the taint tracking configuration for reasoning about
@@ -74,20 +75,29 @@ module TaintedPath {
7475
}
7576

7677
/**
77-
* A call to `[file]path.Clean("/" + e)`, considered to sanitize `e` against path traversal.
78+
* A call to `filepath.Clean("/" + e)`, considered to sanitize `e` against path traversal.
7879
*/
7980
class FilepathCleanSanitizer extends Sanitizer {
8081
FilepathCleanSanitizer() {
8182
exists(DataFlow::CallNode cleanCall, StringOps::Concatenation concatNode |
82-
cleanCall =
83-
any(Function f | f.hasQualifiedName(["path", "path/filepath"], "Clean")).getACall() and
83+
cleanCall = any(Function f | f.hasQualifiedName("path/filepath", "Clean")).getACall() and
8484
concatNode = cleanCall.getArgument(0) and
8585
concatNode.getOperand(0).asExpr().(StringLit).getValue() = "/" and
8686
this = cleanCall.getResult()
8787
)
8888
}
8989
}
9090

91+
/**An call to ParseMultipartForm creates multipart.Form and cleans multipart.Form.FileHeader.Filename using path.Base() */
92+
class MultipartClean extends Sanitizer {
93+
MultipartClean() {
94+
exists(DataFlow::FieldReadNode frn |
95+
frn.getField().hasQualifiedName("mime/multipart", "FileHeader", "Filename") and
96+
this = frn
97+
)
98+
}
99+
}
100+
91101
/**
92102
* A check of the form `!strings.Contains(nd, "..")`, considered as a sanitizer guard for
93103
* path traversal.
@@ -106,6 +116,21 @@ module TaintedPath {
106116
}
107117
}
108118

119+
/**
120+
* A replacement of the form `!strings.ReplaceAll(nd, "..")` or `!strings.ReplaceAll(nd, ".")`, considered as a sanitizer for
121+
* path traversal.
122+
*/
123+
class DotDotReplace extends Sanitizer {
124+
DotDotReplace() {
125+
exists(DataFlow::CallNode cleanCall, DataFlow::Node valueNode |
126+
cleanCall = any(Function f | f.hasQualifiedName("strings", "ReplaceAll")).getACall() and
127+
valueNode = cleanCall.getArgument(1) and
128+
valueNode.asExpr().(StringLit).getValue() = ["..", "."] and
129+
this = cleanCall.getResult()
130+
)
131+
}
132+
}
133+
109134
/**
110135
* A node `nd` guarded by a check that ensures it is contained within some root folder,
111136
* considered as a sanitizer for path traversal.

go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@ edges
22
| TaintedPath.go:13:18:13:22 | selection of URL | TaintedPath.go:13:18:13:30 | call to Query | provenance | |
33
| TaintedPath.go:13:18:13:30 | call to Query | TaintedPath.go:16:29:16:40 | tainted_path | provenance | |
44
| TaintedPath.go:13:18:13:30 | call to Query | TaintedPath.go:20:57:20:68 | tainted_path | provenance | |
5+
| TaintedPath.go:13:18:13:30 | call to Query | TaintedPath.go:67:39:67:56 | ...+... | provenance | |
56
| TaintedPath.go:20:57:20:68 | tainted_path | TaintedPath.go:20:28:20:69 | call to Join | provenance | |
6-
| tst.go:14:2:14:39 | ... := ...[1] | tst.go:17:41:17:56 | selection of Filename | provenance | |
7+
| TaintedPath.go:67:39:67:56 | ...+... | TaintedPath.go:67:28:67:57 | call to Clean | provenance | |
78
nodes
89
| TaintedPath.go:13:18:13:22 | selection of URL | semmle.label | selection of URL |
910
| TaintedPath.go:13:18:13:30 | call to Query | semmle.label | call to Query |
1011
| TaintedPath.go:16:29:16:40 | tainted_path | semmle.label | tainted_path |
1112
| TaintedPath.go:20:28:20:69 | call to Join | semmle.label | call to Join |
1213
| TaintedPath.go:20:57:20:68 | tainted_path | semmle.label | tainted_path |
13-
| tst.go:14:2:14:39 | ... := ...[1] | semmle.label | ... := ...[1] |
14-
| tst.go:17:41:17:56 | selection of Filename | semmle.label | selection of Filename |
14+
| TaintedPath.go:67:28:67:57 | call to Clean | semmle.label | call to Clean |
15+
| TaintedPath.go:67:39:67:56 | ...+... | semmle.label | ...+... |
1516
subpaths
1617
#select
1718
| TaintedPath.go:16:29:16:40 | tainted_path | TaintedPath.go:13:18:13:22 | selection of URL | TaintedPath.go:16:29:16:40 | tainted_path | This path depends on a $@. | TaintedPath.go:13:18:13:22 | selection of URL | user-provided value |
1819
| TaintedPath.go:20:28:20:69 | call to Join | TaintedPath.go:13:18:13:22 | selection of URL | TaintedPath.go:20:28:20:69 | call to Join | This path depends on a $@. | TaintedPath.go:13:18:13:22 | selection of URL | user-provided value |
19-
| tst.go:17:41:17:56 | selection of Filename | tst.go:14:2:14:39 | ... := ...[1] | tst.go:17:41:17:56 | selection of Filename | This path depends on a $@. | tst.go:14:2:14:39 | ... := ...[1] | user-provided value |
20+
| TaintedPath.go:67:28:67:57 | call to Clean | TaintedPath.go:13:18:13:22 | selection of URL | TaintedPath.go:67:28:67:57 | call to Clean | This path depends on a $@. | TaintedPath.go:13:18:13:22 | selection of URL | user-provided value |

go/ql/test/query-tests/Security/CWE-022/TaintedPath.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ func handler(w http.ResponseWriter, r *http.Request) {
3131
w.Write(data)
3232
}
3333

34+
// GOOD: Sanitized by strings.ReplaceAll and replaces all .. with empty string
35+
data, _ = ioutil.ReadFile(strings.ReplaceAll(tainted_path, "..", ""))
36+
w.Write(data)
37+
3438
// GOOD: This can only read inside the provided safe path
3539
_, err := filepath.Rel("/home/user/safepath", tainted_path)
3640
if err == nil {
@@ -53,10 +57,23 @@ func handler(w http.ResponseWriter, r *http.Request) {
5357
w.Write(data)
5458
}
5559

56-
// GOOD: Sanitized by [file]path.Clean with a prepended '/' forcing interpretation
60+
// GOOD: Sanitized by filepath.Clean with a prepended '/' forcing interpretation
5761
// as an absolute path, so that Clean will throw away any leading `..` components.
5862
data, _ = ioutil.ReadFile(filepath.Clean("/" + tainted_path))
5963
w.Write(data)
64+
65+
// BAD: Sanitized by path.Clean with a prepended '/' forcing interpretation
66+
// as an absolute path, however is not sufficient for Windows paths.
6067
data, _ = ioutil.ReadFile(path.Clean("/" + tainted_path))
6168
w.Write(data)
69+
70+
// GOOD: Multipart.Form.FileHeader.Filename sanitized by filepath.Base when calling ParseMultipartForm
71+
r.ParseMultipartForm(32 << 20)
72+
form := r.MultipartForm
73+
files, ok := form.File["files"]
74+
if !ok {
75+
return
76+
}
77+
data, _ = ioutil.ReadFile(files[0].Filename)
78+
w.Write(data)
6279
}

0 commit comments

Comments
 (0)