Skip to content

Commit 57e0b3c

Browse files
committed
iterate on the java/path-injection qhelp
1 parent 4958c19 commit 57e0b3c

File tree

5 files changed

+100
-45
lines changed

5 files changed

+100
-45
lines changed

java/ql/src/Security/CWE/CWE-022/TaintedPath.md

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
# Uncontrolled data used in path expression
22
Accessing paths controlled by users can allow an attacker to access unexpected resources. This can result in sensitive information being revealed or deleted, or an attacker being able to influence behavior by modifying unexpected files.
33

4-
Paths that are naively constructed from data controlled by a user may contain unexpected special characters, such as "..". Such a path may potentially point anywhere on the file system.
4+
Paths that are naively constructed from data controlled by a user may be absolute paths or contain unexpected special characters, such as "..". Such a path may potentially point anywhere on the file system.
55

66

77
## Recommendation
88
Validate user input before using it to construct a file path.
99

10-
The choice of validation depends on whether you want to allow the user to specify complex paths with multiple components that may span multiple folders, or only simple filenames without a path component.
10+
Common validation methods include checking that the normalized path is relative and does not contain any ".." components, or that the path is contained within a safe folder. The validation method to use depends on how the path is used in the application and whether the path is supposed to be a single path component.
1111

12-
In the former case, a common strategy is to make sure that the constructed file path is contained within a safe root folder, for example by checking that the path starts with the root folder. Additionally, you need to ensure that the path does not contain any ".." components, since otherwise even a path that starts with the root folder could be used to access files outside the root folder.
12+
If the path is supposed to be a single path component (such as a file name) you can check for the existence of any path separators ("/" or "\\") or ".." sequences in the input, and reject the input if any are found.
1313

14-
In the latter case, if you want to ensure that the user input is interpreted as a simple filename without a path component, you can remove all path separators ("/" or "\\") and all ".." sequences from the input before using it to construct a file path. Note that it is *not* sufficient to only remove "../" sequences: for example, applying this filter to ".../...//" would still result in the string "../".
14+
Note that removing "../" sequences is *not* sufficient, since the input could still contain a path separator followed by "..". For example, the input ".../...//" would still result in the string "../" if only "../" sequences are removed.
1515

1616
Finally, the simplest (but most restrictive) option is to use an allow list of safe patterns and make sure that the user input matches one of these patterns.
1717

1818

1919
## Example
20-
In this example, a file name is read from a `java.net.Socket` and then used to access a file and send it back over the socket. However, a malicious user could enter a file name anywhere on the file system, such as "/etc/passwd".
20+
In this example, a file name is read from a `java.net.Socket` and then used to access a file and send it back over the socket. However, a malicious user could enter a file name anywhere on the file system, such as "/etc/passwd" or "../../../etc/passwd".
2121

2222

2323
```java
@@ -35,25 +35,50 @@ public void sendUserFile(Socket sock, String user) {
3535
}
3636

3737
```
38-
Simply checking that the path is under a trusted location (such as a known public folder) is not enough, however, since the path could contain relative components such as "..". To fix this, check that it does not contain ".." and starts with the public folder.
38+
If the input is just supposed to be a file name, you can check that it doesn't contain any path separators or ".." sequences.
3939

4040

4141
```java
4242
public void sendUserFileGood(Socket sock, String user) {
4343
BufferedReader filenameReader = new BufferedReader(
4444
new InputStreamReader(sock.getInputStream(), "UTF-8"));
4545
String filename = filenameReader.readLine();
46-
// GOOD: ensure that the file is in a designated folder in the user's home directory
47-
if (!filename.contains("..") && filename.startsWith("/home/" + user + "/public/")) {
48-
BufferedReader fileReader = new BufferedReader(new FileReader(filename));
49-
String fileLine = fileReader.readLine();
50-
while(fileLine != null) {
51-
sock.getOutputStream().write(fileLine.getBytes());
52-
fileLine = fileReader.readLine();
53-
}
46+
// GOOD: ensure that the filename has no path separators or parent directory references
47+
if (filename.contains("..") || filename.contains("/") || filename.contains("\\")) {
48+
throw new IllegalArgumentException("Invalid filename");
49+
}
50+
BufferedReader fileReader = new BufferedReader(new FileReader(filename));
51+
String fileLine = fileReader.readLine();
52+
while(fileLine != null) {
53+
sock.getOutputStream().write(fileLine.getBytes());
54+
fileLine = fileReader.readLine();
5455
}
5556
}
5657

58+
```
59+
If the input is supposed to be found within a specific directory, you can check that the resolved path is still contained within that directory.
60+
61+
62+
```java
63+
public void sendUserFileGood(Socket sock, String user) {
64+
BufferedReader filenameReader = new BufferedReader(
65+
new InputStreamReader(sock.getInputStream(), "UTF-8"));
66+
String filename = filenameReader.readLine();
67+
68+
Path publicFolder = Paths.get("/home/" + user + "/public").normalize().toAbsolutePath();
69+
Path filePath = publicFolder.resolve(filename).normalize().toAbsolutePath();
70+
71+
// GOOD: ensure that the path stays within the public folder
72+
if (!filePath.startsWith(publicFolder)) {
73+
throw new IllegalArgumentException("Invalid filename");
74+
}
75+
BufferedReader fileReader = new BufferedReader(new FileReader(filePath.toString()));
76+
String fileLine = fileReader.readLine();
77+
while(fileLine != null) {
78+
sock.getOutputStream().write(fileLine.getBytes());
79+
fileLine = fileReader.readLine();
80+
}
81+
}
5782
```
5883

5984
## References

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

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,28 @@
77
can result in sensitive information being revealed or deleted, or an attacker being able to influence
88
behavior by modifying unexpected files.</p>
99

10-
<p>Paths that are naively constructed from data controlled by a user may contain unexpected special characters,
11-
such as "..". Such a path may potentially point anywhere on the file system.</p>
10+
<p>Paths that are naively constructed from data controlled by a user may be absolute paths or contain
11+
unexpected special characters, such as "..". Such a path may potentially point anywhere on the file system.</p>
1212

1313
</overview>
1414
<recommendation>
1515

1616
<p>Validate user input before using it to construct a file path.</p>
1717

18-
<p>The choice of validation depends on whether you want to allow the user to specify complex paths with
19-
multiple components that may span multiple folders, or only simple filenames without a path component.</p>
18+
<p>Common validation methods include checking that the normalized path is relative and does not contain
19+
any ".." components, or that the path is contained within a safe folder. The validation method to use depends
20+
on how the path is used in the application and whether the path is supposed to be a single path component.
21+
</p>
2022

21-
<p>In the former case, a common strategy is to make sure that the constructed file path is contained within
22-
a safe root folder, for example by checking that the path starts with the root folder. Additionally,
23-
you need to ensure that the path does not contain any ".." components, since otherwise
24-
even a path that starts with the root folder could be used to access files outside the root folder.</p>
23+
<p>If the path is supposed to be a single path component (such as a file name) you can check for the existence
24+
of any path separators ("/" or "\") or ".." sequences in the input, and reject the input if any are found.
25+
</p>
2526

26-
<p>In the latter case, if you want to ensure that the user input is interpreted as a simple filename without
27-
a path component, you can remove all path separators ("/" or "\") and all ".." sequences from the input
28-
before using it to construct a file path. Note that it is <i>not</i> sufficient to only remove "../" sequences:
29-
for example, applying this filter to ".../...//" would still result in the string "../".</p>
27+
<p>
28+
Note that removing "../" sequences is <i>not</i> sufficient, since the input could still contain a path separator
29+
followed by "..". For example, the input ".../...//" would still result in the string "../" if only "../" sequences
30+
are removed.
31+
</p>
3032

3133
<p>Finally, the simplest (but most restrictive) option is to use an allow list of safe patterns and make sure that
3234
the user input matches one of these patterns.</p>
@@ -36,15 +38,23 @@ the user input matches one of these patterns.</p>
3638

3739
<p>In this example, a file name is read from a <code>java.net.Socket</code> and then used to access a file
3840
and send it back over the socket. However, a malicious user could enter a file name anywhere on the file system,
39-
such as "/etc/passwd".</p>
41+
such as "/etc/passwd" or "../../../etc/passwd".</p>
4042

4143
<sample src="examples/TaintedPath.java" />
4244

43-
<p>Simply checking that the path is under a trusted location (such as a known public folder) is not enough,
44-
however, since the path could contain relative components such as "..". To fix this, check that it does
45-
not contain ".." and starts with the public folder.</p>
45+
<p>
46+
If the input is just supposed to be a file name, you can check that it doesn't contain any path separators
47+
or ".." sequences.
48+
</p>
4649

47-
<sample src="examples/TaintedPathGood.java" />
50+
<sample src="examples/TaintedPathGoodNormalize.java" />
51+
52+
<p>
53+
If the input is supposed to be found within a specific directory, you can check that the resolved path
54+
is still contained within that directory.
55+
</p>
56+
57+
<sample src="examples/TaintedPathGoodFolder.java" />
4858

4959
</example>
5060
<references>

java/ql/src/Security/CWE/CWE-022/examples/TaintedPathGood.java

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
public void sendUserFileGood(Socket sock, String user) {
2+
BufferedReader filenameReader = new BufferedReader(
3+
new InputStreamReader(sock.getInputStream(), "UTF-8"));
4+
String filename = filenameReader.readLine();
5+
6+
Path publicFolder = Paths.get("/home/" + user + "/public").normalize().toAbsolutePath();
7+
Path filePath = publicFolder.resolve(filename).normalize().toAbsolutePath();
8+
9+
// GOOD: ensure that the path stays within the public folder
10+
if (!filePath.startsWith(publicFolder)) {
11+
throw new IllegalArgumentException("Invalid filename");
12+
}
13+
BufferedReader fileReader = new BufferedReader(new FileReader(filePath.toString()));
14+
String fileLine = fileReader.readLine();
15+
while(fileLine != null) {
16+
sock.getOutputStream().write(fileLine.getBytes());
17+
fileLine = fileReader.readLine();
18+
}
19+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
public void sendUserFileGood(Socket sock, String user) {
2+
BufferedReader filenameReader = new BufferedReader(
3+
new InputStreamReader(sock.getInputStream(), "UTF-8"));
4+
String filename = filenameReader.readLine();
5+
// GOOD: ensure that the filename has no path separators or parent directory references
6+
if (filename.contains("..") || filename.contains("/") || filename.contains("\\")) {
7+
throw new IllegalArgumentException("Invalid filename");
8+
}
9+
BufferedReader fileReader = new BufferedReader(new FileReader(filename));
10+
String fileLine = fileReader.readLine();
11+
while(fileLine != null) {
12+
sock.getOutputStream().write(fileLine.getBytes());
13+
fileLine = fileReader.readLine();
14+
}
15+
}

0 commit comments

Comments
 (0)