Skip to content

Commit d91c999

Browse files
committed
Add documentation and additional test cases
1 parent e1efd2e commit d91c999

File tree

5 files changed

+122
-3
lines changed

5 files changed

+122
-3
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import java.io.File;
2+
import java.io.IOException;
3+
import java.nio.file.Files;
4+
import java.nio.file.Path;
5+
6+
public class TempDirCreationSafe {
7+
File exampleSafeTemporaryDirectoryCreation() {
8+
// Best option!
9+
Path tempDir =
10+
Files.createTempDirectory("temporary-stuff"); // GOOD! - Files.createTempDirectory creates a temporary directory with safe permissions.
11+
return tempDir.toFile();
12+
}
13+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import java.io.File;
2+
import java.io.IOException;
3+
4+
public class TempDirCreationVulnerable {
5+
File exampleVulnerableTemporaryDirectoryCreation() throws IOException {
6+
File temp = File.createTempFile("temporary", "stuff"); // Attacker knows the full path of the directory that will be generated.
7+
// delete the file that was created
8+
temp.delete(); // Attacker sees file is deleted and begins a race to create their own directory with wide file permissions.
9+
// and make a directory of the same name.
10+
// SECURITY VULNERABILITY: Race Condition! - If the attacker beats your application to directory creation they will own this directory.
11+
temp.mkdir(); // BAD! Race condition
12+
return temp;
13+
}
14+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>Local temporary directory hijacking can occur when files/directories are created within directories that are shared between all users on the system.</p>
7+
8+
<p>On most <a href="https://en.wikipedia.org/wiki/Unix-like">unix-like</a> systems,
9+
the system temporary directory is shared between local users.
10+
If directories are created within the system temporary directory without using
11+
APIs that explicitly set the correct file permissions, vulnerabilties ranging from
12+
local information disclosure, to directory hijacking, to local privilege escalation, can occur.</p>
13+
14+
<p>In the worst case, where local privilege escalation occurs, this vulnerability can have a
15+
<a href="https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H&amp;version=3.1">CVSS v3.1 base score of 7.8</a>.</p>
16+
</overview>
17+
18+
<recommendation>
19+
<p>Use JDK methods that specifically protect against this vulnerability:</p>
20+
<ul>
21+
<li><a href="https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#createTempDirectory-java.nio.file.Path-java.lang.String-java.nio.file.attribute.FileAttribute...-">java.nio.file.Files.createTempDirectory</a></li>
22+
<li><a href="https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#createTempFile-java.nio.file.Path-java.lang.String-java.lang.String-java.nio.file.attribute.FileAttribute...-">java.nio.file.Files.createTempFile</a></li>
23+
</ul>
24+
<p>Otherwise, create the file/directory by manually specifying the expected posix file permissions.
25+
For example: <code>PosixFilePermissions.asFileAttribute(EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE))</code></p>
26+
<ul>
27+
<li><a href="https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#createFile-java.nio.file.Path-java.nio.file.attribute.FileAttribute...-">java.nio.file.Files.createFile</a></li>
28+
<li><a href="https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#createDirectory-java.nio.file.Path-java.nio.file.attribute.FileAttribute...-">java.nio.file.Files.createDirectory</a></li>
29+
<li><a href="https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#createDirectories-java.nio.file.Path-java.nio.file.attribute.FileAttribute...-">java.nio.file.Files.createDirectories</a></li>
30+
</ul>
31+
</recommendation>
32+
33+
<example>
34+
<p>In the following example, the directory that is created can be hijacked during the creation process due to a race condition.</p>
35+
<sample src="TempDirCreationVulnerable.java"/>
36+
37+
<p>In the following example, the <code>Files.createTempDirectory</code> API is used which creates a temporary directory that is guarnteed to be safe.</p>
38+
<sample src="TempDirCreationSafe.java"/>
39+
</example>
40+
41+
<references>
42+
<li>OSWAP: <a href="https://owasp.org/www-community/vulnerabilities/Insecure_Temporary_File">Insecure Temporary File</a>.</li>
43+
<li>CERT: <a href="https://wiki.sei.cmu.edu/confluence/display/java/FIO00-J.+Do+not+operate+on+files+in+shared+directories">FIO00-J. Do not operate on files in shared directories</a></li>
44+
</references>
45+
</qhelp>

java/ql/src/Security/CWE/CWE-378/TempDirHijackingVulnerability.ql

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,32 @@ private predicate safeUse(Expr e) {
9595
exists(AssignAndExpr assignAndExp |
9696
assignAndExp.getType() instanceof BooleanType and assignAndExp.getSource() = e
9797
)
98+
or
99+
// File is being concatenated to a string, probably for a log or exception message
100+
exists(AddExpr addExp |
101+
addExp.getType() instanceof TypeString and
102+
(
103+
addExp.getRightOperand() = e
104+
or
105+
// A method call, like `File.getAbsolutePath()` is being called and concatenated into a string
106+
exists(MethodAccess fileMethodAccess |
107+
fileMethodAccess.getQualifier() = e and
108+
addExp.getRightOperand() = fileMethodAccess and
109+
fileMethodAccess.getMethod().getReturnType() instanceof TypeString
110+
)
111+
)
112+
)
98113
}
99114

100115
from
101116
DataFlow::PathNode source, DataFlow::PathNode deleteCheckpoint, DataFlow2::Node deleteCheckpoint2,
102-
DataFlow2::Node sink, MethodAccess creationCall, TempDirHijackingToDeleteConfig toDeleteConfig,
117+
DataFlow2::Node sink, MethodAccess creationCall, Expr unsafe, TempDirHijackingToDeleteConfig toDeleteConfig,
103118
TempDirHijackingFromDeleteConfig fromDeleteConfig
104119
where
105120
toDeleteConfig.hasFlowPath(source, deleteCheckpoint) and
106121
fromDeleteConfig.hasFlow(deleteCheckpoint2, sink) and
107122
deleteCheckpoint.getNode().asExpr() = deleteCheckpoint2.asExpr() and
108-
isUnsafeUseUnconstrainedByIfCheck(sink, _) and
123+
isUnsafeUseUnconstrainedByIfCheck(sink, unsafe) and
109124
isNonThrowingDirectoryCreationExpression(sink.asExpr(), creationCall)
110125
select deleteCheckpoint.getNode(), source, deleteCheckpoint,
111-
"Local temporary directory hijacking race condition $@", creationCall, "here"
126+
"Local temporary directory hijacking race condition $@ file $@ may have been hijacked", creationCall, "here", unsafe, "here"

java/ql/test/query-tests/security/CWE-378/semmle/tests/Test.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,37 @@ static File safe6() throws IOException {
9292
throw new RuntimeException("Failed to create directory");
9393
}
9494
}
95+
96+
static File safe7() throws IOException {
97+
File temp = File.createTempFile("test", "directory");
98+
if (!temp.delete() || !temp.mkdir()) {
99+
throw new IOException("Can not convert temporary file " + temp + "to directory");
100+
} else {
101+
return temp;
102+
}
103+
}
104+
105+
/**
106+
* When `isDirectory` is true, create a temporary directory, else create a temporary file.
107+
*/
108+
static File safe8(boolean isDirectory) throws IOException {
109+
File temp = File.createTempFile("test", "directory");
110+
if (isDirectory && (!temp.delete() || !temp.mkdir())) {
111+
throw new IOException("Can not convert temporary file " + temp + "to directory");
112+
} else {
113+
return temp;
114+
}
115+
}
116+
117+
static File safe9() throws IOException {
118+
File temp = File.createTempFile("test", "directory");
119+
if (!temp.delete()) {
120+
throw new IOException("Could not delete temp file: " + temp.getAbsolutePath());
121+
}
122+
if (!temp.mkdir()) {
123+
throw new IOException("Could not create temp directory: " + temp.getAbsolutePath());
124+
}
125+
return temp;
126+
}
95127

96128
}

0 commit comments

Comments
 (0)