Skip to content

Commit ffbe3e6

Browse files
authored
Merge pull request #16020 from github/max-schaefer/go-path-injection-qhelp
Go: Update query help for `go/path-injection` to include example fixes.
2 parents f2db9ce + 034ed17 commit ffbe3e6

File tree

3 files changed

+87
-10
lines changed

3 files changed

+87
-10
lines changed

go/ql/src/Security/CWE-022/TaintedPath.qhelp

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,35 @@ Accessing files using paths constructed from user-controlled data can allow an a
99
unexpected resources. This can result in sensitive information being revealed or deleted, or an
1010
attacker being able to influence behavior by modifying unexpected files.
1111
</p>
12+
<p>
13+
Paths that are naively constructed from data controlled by a user may be absolute paths,
14+
or may contain unexpected special characters such as "..". Such a path could point anywhere
15+
on the file system.
16+
</p>
1217
</overview>
1318

1419
<recommendation>
1520
<p>
16-
Validate user input before using it to construct a file path, either using an off-the-shelf library
17-
or by performing custom validation.
21+
Validate user input before using it to construct a file path.
22+
</p>
23+
<p>
24+
Common validation methods include checking that the normalized path is relative and
25+
does not contain any ".." components, or checking that the path is contained within a safe folder. The method you should use depends on how the path is used in the application, and whether the path should be a single path component.
26+
</p>
27+
<p>
28+
If the path should be a single path component (such as a file name), you can check for the
29+
existence of any path separators ("/" or "\"), or ".." sequences in the input, and reject
30+
the input if any are found.
31+
</p>
32+
<p>
33+
Note that removing "../" sequences is <i>not</i> sufficient, since the input could still
34+
contain a path separator followed by "..". For example, the input ".../...//" would still
35+
result in the string "../" if only "../" sequences are removed.
1836
</p>
1937
<p>
20-
Ideally, follow these rules:
38+
Finally, the simplest (but most restrictive) option is to use an allow list of safe patterns
39+
and make sure that the user input matches one of these patterns.
2140
</p>
22-
<ul>
23-
<li>Do not allow more than a single "." character.</li>
24-
<li>Do not allow directory separators such as "/" or "\" (depending on the file system).</li>
25-
<li>Do not rely on simply replacing problematic sequences such as "../". For example, after
26-
applying this filter to ".../...//", the resulting string would still be "../".</li>
27-
<li>Use an allowlist of known good patterns.</li>
28-
</ul>
2941
</recommendation>
3042

3143
<example>
@@ -43,6 +55,27 @@ password file. This file would then be sent back to the user, giving them access
4355
information.
4456
</p>
4557
<sample src="TaintedPath.go" />
58+
<p>
59+
If the input should only be a file name, you can check that it doesn't contain any
60+
path separators or ".." sequences.
61+
</p>
62+
<sample src="TaintedPathGood.go" />
63+
<p>
64+
Note that this approach is only suitable if the input is expected to be a single file name.
65+
</p>
66+
<p>
67+
If the input can be a path with multiple components, you can make it safe by verifying
68+
that the path is within a specific directory that is considered safe.
69+
You can do this by resolving the input with respect to that directory, and then checking
70+
that the resulting path is still within it.
71+
</p>
72+
<sample src="TaintedPathGood2.go" />
73+
<p>
74+
Note that <code>/home/user</code> is just an example, you should replace it with the actual
75+
safe directory in your application. Also, while in this example the path of the safe
76+
directory is absolute, this may not always be the case, and you may need to resolve it
77+
first before checking the input.
78+
</p>
4679
</example>
4780

4881
<references>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package main
2+
3+
import (
4+
"io/ioutil"
5+
"net/http"
6+
"path/filepath"
7+
"strings"
8+
)
9+
10+
func handler(w http.ResponseWriter, r *http.Request) {
11+
path := r.URL.Query()["path"][0]
12+
13+
// GOOD: ensure that the filename has no path separators or parent directory references
14+
// (Note that this is only suitable if `path` is expected to have a single component!)
15+
if strings.Contains(path, "/") || strings.Contains(path, "\\") || strings.Contains(path, "..") {
16+
http.Error(w, "Invalid file name", http.StatusBadRequest)
17+
return
18+
}
19+
data, _ := ioutil.ReadFile(filepath.Join("/home/user/", path))
20+
w.Write(data)
21+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package main
2+
3+
import (
4+
"io/ioutil"
5+
"net/http"
6+
"path/filepath"
7+
"strings"
8+
)
9+
10+
const safeDir = "/home/user/"
11+
12+
func handler(w http.ResponseWriter, r *http.Request) {
13+
path := r.URL.Query()["path"][0]
14+
15+
// GOOD: ensure that the resolved path is within the safe directory
16+
absPath, err := filepath.Abs(filepath.Join(safeDir, path))
17+
if err != nil || !strings.HasPrefix(absPath, safeDir) {
18+
http.Error(w, "Invalid file name", http.StatusBadRequest)
19+
return
20+
}
21+
data, _ := ioutil.ReadFile(absPath)
22+
w.Write(data)
23+
}

0 commit comments

Comments
 (0)