-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Java: CWE-200: Temp directory local information disclosure vulnerability #4388
Merged
smowton
merged 39 commits into
github:main
from
JLLeitschuh:feat/JLL/java/CWE-200_temp_directory_local_information_disclosure
Feb 14, 2022
Merged
Changes from 31 commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
3a15678
Java: CWE-200: Temp directory local information disclosure vulnerability
JLLeitschuh cf0ed81
Add TempDir taint tracking for Files.write
JLLeitschuh ecad753
Add mkdirs check
JLLeitschuh bc12e99
Add `java.nio.file.Files` API checks
JLLeitschuh 13fed0e
Temp Dir Info Disclosure: Final pass and add documentation
JLLeitschuh e4c017e
Apply suggestions from code review
JLLeitschuh f910fd4
Remove path flow tracking in 'TempDirLocalInformationDisclosureFromMe…
JLLeitschuh 7929fae
Apply suggestions from code review
JLLeitschuh 41b5011
Apply suggestions from code review
JLLeitschuh f6067d2
Fix file names and formatting from PR feedback
JLLeitschuh c19f52c
Add release notes for "Temporary Directory Local information disclosure"
JLLeitschuh 7e55c92
Apply suggestions from code review
JLLeitschuh 6683198
Add QLdoc to TempDirUtils
JLLeitschuh df716cb
Revert changes to MethodAccessSystemGetProperty
JLLeitschuh cb30385
Update java/ql/src/Security/CWE/CWE-200/TempDirUtils.qll
JLLeitschuh 7e514e9
Add QLdoc and fix Compiler Errors in Tests
JLLeitschuh e795823
Autoformat TempDirUtils.qll
smowton a8d25b6
Apply suggestions from code review
JLLeitschuh a4b5573
Apply suggestions from code review
JLLeitschuh f7a4aac
Apply suggestions from code review
JLLeitschuh d5c9af3
Fixup documentation/code from PR feedback
JLLeitschuh 79db76d
Fix test failures TempDirLocalInformationDisclosureFromSystemProperty
JLLeitschuh 0a621c2
Fix the formatting in TempDirLocalInformationDisclosureFromMethodCall
JLLeitschuh 9299c79
Add information disclosure test fix suggestions
JLLeitschuh 0268dd9
Add file creation sanitizer
JLLeitschuh 1f47ea5
Update to new change note format
JLLeitschuh de38638
Combine CWE-200 queries
smowton c4112e6
Post refactor fixiup
JLLeitschuh 7965459
Apply suggestions from code review
smowton a6596ea
Fix test requirements, formatting
smowton 7f46640
Consider calls to setReadable(false, false) then setReadable(true, tr…
JLLeitschuh 787e3da
Update java/ql/src/Security/CWE/CWE-200/TempDirLocalInformationDisclo…
JLLeitschuh 49a7367
Fix FP from mkdirs call on exact temp directory
JLLeitschuh bafcce1
Apply suggestions from code review
JLLeitschuh eee521e
Fix test failure for TempDirLocalInformationDisclosure
JLLeitschuh 7dee22a
Fix implicit 'this' usage
JLLeitschuh bb580dd
Apply suggestions from code review
JLLeitschuh 76964d5
Apply suggestions from code review
JLLeitschuh 2048aed
Review feedback and improve temp dir vulnerable/safe code sugestion
JLLeitschuh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
java/ql/src/Security/CWE/CWE-200/TempDirLocalInformationDisclosure.qhelp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<!DOCTYPE qhelp PUBLIC | ||
"-//Semmle//qhelp//EN" | ||
"qhelp.dtd"> | ||
<qhelp> | ||
<overview> | ||
<p>Local information disclosure can occur when files/directories are written into | ||
directories that are shared between all users on the system.</p> | ||
|
||
<p>On most <a href="https://en.wikipedia.org/wiki/Unix-like">unix-like</a> systems, | ||
the system temporary directory is shared between local users. | ||
If files/directories are created within the system temporary directory without using | ||
APIs that explicitly set the correct file permissions, local information disclosure | ||
can occur.</p> | ||
|
||
<p>Depending upon the particular file contents exposed, this vulnerability can have a | ||
<a href="https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?vector=AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N&version=3.1">CVSSv3.1 base score of 6.2/10</a>.</p> | ||
</overview> | ||
|
||
<recommendation> | ||
<p>Use JDK methods that specifically protect against this vulnerability:</p> | ||
<ul> | ||
<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> | ||
<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> | ||
</ul> | ||
<p>Otherwise, create the file/directory by manually specifying the expected posix file permissions. | ||
For example: <code>PosixFilePermissions.asFileAttribute(EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE))</code></p> | ||
JLLeitschuh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<ul> | ||
<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> | ||
<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> | ||
<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> | ||
</ul> | ||
</recommendation> | ||
|
||
<example> | ||
<p>In the following example, files and directories are created with file permissions that allow other local users to read their contents.</p> | ||
|
||
<sample src="TempDirUsageVulnerable.java"/> | ||
|
||
<p>In the following example, files and directories are created with file permissions that protect their contents.</p> | ||
|
||
<sample src="TempDirUsageSafe.java"/> | ||
JLLeitschuh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
</example> | ||
|
||
<references> | ||
<li>OSWAP: <a href="https://owasp.org/www-community/vulnerabilities/Insecure_Temporary_File">Insecure Temporary File</a>.</li> | ||
<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> | ||
JLLeitschuh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
</references> | ||
</qhelp> |
213 changes: 213 additions & 0 deletions
213
java/ql/src/Security/CWE/CWE-200/TempDirLocalInformationDisclosure.ql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,213 @@ | ||||||
/** | ||||||
* @name Temporary Directory Local information disclosure | ||||||
JLLeitschuh marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if this might be clearer. The query name should be in sentence case.
Suggested change
|
||||||
* @description Writing information without explicit permissions to a shared temporary directory may disclose it to other users. | ||||||
* @kind path-problem | ||||||
* @problem.severity warning | ||||||
* @precision very-high | ||||||
JLLeitschuh marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
* @id java/local-temp-file-or-directory-information-disclosure | ||||||
* @tags security | ||||||
* external/cwe/cwe-200 | ||||||
* external/cwe/cwe-732 | ||||||
*/ | ||||||
|
||||||
import java | ||||||
import TempDirUtils | ||||||
import DataFlow::PathGraph | ||||||
|
||||||
private class MethodFileSystemFileCreation extends Method { | ||||||
MethodFileSystemFileCreation() { | ||||||
this.getDeclaringType() instanceof TypeFile and | ||||||
this.hasName(["mkdir", "mkdirs", "createNewFile"]) | ||||||
} | ||||||
} | ||||||
|
||||||
abstract private class FileCreationSink extends DataFlow::Node { } | ||||||
|
||||||
/** | ||||||
* The qualifier of a call to one of `File`'s file-creating or directory-creating methods, | ||||||
* treated as a sink by `TempDirSystemGetPropertyToCreateConfig`. | ||||||
*/ | ||||||
private class FileFileCreationSink extends FileCreationSink { | ||||||
FileFileCreationSink() { | ||||||
exists(MethodAccess ma | | ||||||
ma.getMethod() instanceof MethodFileSystemFileCreation and | ||||||
ma.getQualifier() = this.asExpr() | ||||||
) | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* The argument to a call to one of `Files` file-creating or directory-creating methods, | ||||||
* treated as a sink by `TempDirSystemGetPropertyToCreateConfig`. | ||||||
*/ | ||||||
private class FilesFileCreationSink extends FileCreationSink { | ||||||
FilesFileCreationSink() { | ||||||
exists(FilesVulnerableCreationMethodAccess ma | ma.getArgument(0) = this.asExpr()) | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* A call to a `Files` method that create files/directories without explicitly | ||||||
* setting the newly-created file or directory's permissions. | ||||||
*/ | ||||||
private class FilesVulnerableCreationMethodAccess extends MethodAccess { | ||||||
FilesVulnerableCreationMethodAccess() { | ||||||
exists(Method m | | ||||||
m = this.getMethod() and | ||||||
m.getDeclaringType().hasQualifiedName("java.nio.file", "Files") | ||||||
| | ||||||
m.hasName(["write", "newBufferedWriter", "newOutputStream"]) | ||||||
or | ||||||
m.hasName(["createFile", "createDirectory", "createDirectories"]) and | ||||||
this.getNumArgument() = 1 | ||||||
or | ||||||
m.hasName("newByteChannel") and | ||||||
this.getNumArgument() = 2 | ||||||
) | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* A call to a `File` method that create files/directories with a specific set of permissions explicitly set. | ||||||
* We can safely assume that any calls to these methods with explicit `PosixFilePermissions.asFileAttribute` | ||||||
* contains a certain level of intentionality behind it. | ||||||
*/ | ||||||
private class FilesSanitizingCreationMethodAccess extends MethodAccess { | ||||||
FilesSanitizingCreationMethodAccess() { | ||||||
exists(Method m | | ||||||
m = this.getMethod() and | ||||||
m.getDeclaringType().hasQualifiedName("java.nio.file", "Files") | ||||||
| | ||||||
m.hasName(["createFile", "createDirectory", "createDirectories"]) and | ||||||
this.getNumArgument() = 2 | ||||||
) | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* The temp directory argument to a call to `java.io.File::createTempFile`, | ||||||
* treated as a sink by `TempDirSystemGetPropertyToCreateConfig`. | ||||||
*/ | ||||||
private class FileCreateTempFileSink extends FileCreationSink { | ||||||
FileCreateTempFileSink() { | ||||||
exists(MethodAccess ma | | ||||||
ma.getMethod() instanceof MethodFileCreateTempFile and ma.getArgument(2) = this.asExpr() | ||||||
) | ||||||
} | ||||||
} | ||||||
|
||||||
private class TempDirSystemGetPropertyToCreateConfig extends TaintTracking::Configuration { | ||||||
TempDirSystemGetPropertyToCreateConfig() { this = "TempDirSystemGetPropertyToCreateConfig" } | ||||||
|
||||||
override predicate isSource(DataFlow::Node source) { | ||||||
source.asExpr() instanceof MethodAccessSystemGetPropertyTempDirTainted | ||||||
} | ||||||
|
||||||
/** | ||||||
* Find dataflow from the temp directory system property to the `File` constructor. | ||||||
* Examples: | ||||||
* - `new File(System.getProperty("java.io.tmpdir"))` | ||||||
* - `new File(new File(System.getProperty("java.io.tmpdir")), "/child")` | ||||||
*/ | ||||||
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { | ||||||
isAdditionalFileTaintStep(node1, node2) | ||||||
} | ||||||
|
||||||
override predicate isSink(DataFlow::Node sink) { sink instanceof FileCreationSink } | ||||||
|
||||||
override predicate isSanitizer(DataFlow::Node sanitizer) { | ||||||
exists(FilesSanitizingCreationMethodAccess sanitisingMethodAccess | | ||||||
sanitizer.asExpr() = sanitisingMethodAccess.getArgument(0) | ||||||
) | ||||||
} | ||||||
} | ||||||
|
||||||
// | ||||||
// Begin configuration for tracking single-method calls that are vulnerable. | ||||||
// | ||||||
/** | ||||||
* A `MethodAccess` against a method that creates a temporary file or directory in a shared temporary directory. | ||||||
*/ | ||||||
abstract class MethodAccessInsecureFileCreation extends MethodAccess { | ||||||
/** | ||||||
* Gets the type of entity created (e.g. `file`, `directory`, ...). | ||||||
*/ | ||||||
abstract string getFileSystemEntityType(); | ||||||
} | ||||||
|
||||||
/** | ||||||
* An insecure call to `java.io.File.createTempFile`. | ||||||
*/ | ||||||
class MethodAccessInsecureFileCreateTempFile extends MethodAccessInsecureFileCreation { | ||||||
MethodAccessInsecureFileCreateTempFile() { | ||||||
this.getMethod() instanceof MethodFileCreateTempFile and | ||||||
( | ||||||
// `File.createTempFile(string, string)` always uses the default temporary directory | ||||||
this.getNumArgument() = 2 | ||||||
or | ||||||
// The default temporary directory is used when the last argument of `File.createTempFile(string, string, File)` is `null` | ||||||
DataFlow::localExprFlow(any(NullLiteral n), getArgument(2)) | ||||||
) | ||||||
} | ||||||
|
||||||
override string getFileSystemEntityType() { result = "file" } | ||||||
} | ||||||
|
||||||
/** | ||||||
* The `com.google.common.io.Files.createTempDir` method. | ||||||
*/ | ||||||
class MethodGuavaFilesCreateTempFile extends Method { | ||||||
MethodGuavaFilesCreateTempFile() { | ||||||
getDeclaringType().hasQualifiedName("com.google.common.io", "Files") and | ||||||
hasName("createTempDir") | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* A call to the `com.google.common.io.Files.createTempDir` method. | ||||||
*/ | ||||||
class MethodAccessInsecureGuavaFilesCreateTempFile extends MethodAccessInsecureFileCreation { | ||||||
MethodAccessInsecureGuavaFilesCreateTempFile() { | ||||||
getMethod() instanceof MethodGuavaFilesCreateTempFile | ||||||
} | ||||||
|
||||||
override string getFileSystemEntityType() { result = "directory" } | ||||||
} | ||||||
|
||||||
/** | ||||||
* This is a hack: we include use of inherently insecure methods, which don't have any associated | ||||||
* flow path, in with results describing a path from reading `java.io.tmpdir` or similar to use | ||||||
* in a file creation op. | ||||||
* | ||||||
* We achieve this by making inherently-insecure method invocations both a source and a sink in | ||||||
* this configuration, resulting in a zero-length path which is type-compatible with the actual | ||||||
* path-flow results. | ||||||
*/ | ||||||
class InsecureMethodPseudoConfiguration extends DataFlow::Configuration { | ||||||
InsecureMethodPseudoConfiguration() { this = "InsecureMethodPseudoConfiguration" } | ||||||
|
||||||
override predicate isSource(DataFlow::Node node) { | ||||||
node.asExpr() instanceof MethodAccessInsecureFileCreation | ||||||
} | ||||||
|
||||||
override predicate isSink(DataFlow::Node node) { | ||||||
node.asExpr() instanceof MethodAccessInsecureFileCreation | ||||||
} | ||||||
} | ||||||
|
||||||
from DataFlow::PathNode source, DataFlow::PathNode sink, string message | ||||||
where | ||||||
( | ||||||
any(TempDirSystemGetPropertyToCreateConfig conf).hasFlowPath(source, sink) and | ||||||
message = | ||||||
"Local information disclosure vulnerability from $@ due to use of file or directory readable by other local users." | ||||||
or | ||||||
any(InsecureMethodPseudoConfiguration conf).hasFlowPath(source, sink) and | ||||||
// Note this message has no "$@" placeholder, so the "system temp directory" template parameter below is not used. | ||||||
message = | ||||||
"Local information disclosure vulnerability due to use of " + | ||||||
source.getNode().asExpr().(MethodAccessInsecureFileCreation).getFileSystemEntityType() + | ||||||
" readable by other local users." | ||||||
) and | ||||||
not isPermissionsProtectedTempDirUse(sink.getNode()) | ||||||
select source.getNode(), source, sink, message, source.getNode(), "system temp directory" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import java.nio.file.Files; | ||
import java.nio.file.attribute.PosixFilePermission; | ||
import java.nio.file.attribute.PosixFilePermissions; | ||
import java.util.EnumSet; | ||
|
||
public class TempDirUsageSafe { | ||
void exampleSafe() throws IOException { | ||
Path temp1 = Files.createTempFile("random", ".txt"); // GOOD: File has permissions `-rw-------` | ||
|
||
Path temp2 = Files.createTempDirectory("random-directory"); // GOOD: File has permissions `drwx------` | ||
|
||
File tempDirChildFile = new File(System.getProperty("java.io.tmpdir"), "/child-create-file.txt"); | ||
Files.createFile( | ||
tempDirChildFile.toPath(), | ||
tempDirChild.toPath(), | ||
PosixFilePermissions.asFileAttribute(EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)) | ||
); // GOOD: Good has permissions `-rw-------` | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
java/ql/src/Security/CWE/CWE-200/TempDirUsageVulnerable.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import java.io.File; | ||
|
||
public class TempDirUsageVulnerable { | ||
void exampleVulnerable() { | ||
File temp1 = File.createTempFile("random", ".txt"); // BAD: File has permissions `-rw-r--r--` | ||
|
||
File temp2 = File.createTempFile("random", "file", null); // BAD: File has permissions `-rw-r--r--` | ||
|
||
File systemTempDir = new File(System.getProperty("java.io.tmpdir")); | ||
File temp3 = File.createTempFile("random", "file", systemTempDir); // BAD: File has permissions `-rw-r--r--` | ||
|
||
File tempDir = com.google.common.io.Files.createTempDir(); // BAD: CVE-2020-8908: Directory has permissions `drwxr-xr-x` | ||
|
||
new File(System.getProperty("java.io.tmpdir"), "/child").mkdir(); // BAD: Directory has permissions `-rw-r--r--` | ||
|
||
File tempDirChildFile = new File(System.getProperty("java.io.tmpdir"), "/child-create-file.txt"); | ||
Files.createFile(tempDirChildFile.toPath()); // BAD: File has permissions `-rw-r--r--` | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preview after fixing the errors in the qhelp document:
Temporary Directory Local information disclosure
Local information disclosure can occur when files/directories are written into directories that are shared between all users on the system.
On most unix-like systems, the system temporary directory is shared between local users. If files/directories are created within the system temporary directory without using APIs that explicitly set the correct file permissions, local information disclosure can occur.
Depending upon the particular file contents exposed, this vulnerability can have a CVSSv3.1 base score of 6.2/10.
Recommendation
Use JDK methods that specifically protect against this vulnerability:
java.nio.file.Files#createTempDirectory
java.nio.file.Files#createTempFile
Otherwise, create the file/directory by manually specificfying the expected posix file permissions. Eg.
PosixFilePermissions.asFileAttribute(EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE))
java.nio.file.Files#createFile
java.nio.file.Files#createDirectory
java.nio.file.Files#createDirectories
Example
In the following example, files and directories are created with file permissions allowing other local users to read their contents.
In the following example, files and directories are created with file permissions protecting their contents.
References