...
Code Block | ||
---|---|---|
| ||
class TempFile { public static void main(String[] args) throws IOException{ File f = File.createTempFile("tempnam",".tmp"); FileOutputStream fop = new FileOutputStream(f); String str = "Data"; try { fop.write(str.getBytes()); fop.flush(); }finally { // Stream/file still open; file will // not be deleted on Windows systems f.deleteOnExit(); // Delete the file when the JVM terminates } } } |
Noncompliant Code Example (POSIX, Java 1.7
...
)
This noncompliant code example creates a temporary file using the newest features of Java 1.7's NIO facility. It uses the createTempFile()
method, which creates an unpredictable name. (The actual method by which the name is created is implementation-defined and undocumented.) The file is specifically created with POSIX file permissions denying access to the file to everyone except the file's creator, a similar permission set can be devised for Windows. Furthermore, the createTempFile()
will throw an exception if the file already existed. The file is opened using the try-with-resources construct, which automatically closes the file whether or not an exception occurs. And finally, the file is opened with the Java 1.7 DELETE_ON_CLOSE
option, which serves to remove the file automatically when it is closed. And finally, the file is opened using the try-with-resources construct, which automatically closes the file whether or not an exception occurs.
Code Block | ||
---|---|---|
| ||
class TempFile { public static void main(String[] args) { // POSIX file permissions for exclusive read/write Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-------"); FileAttribute att = PosixFilePermissions.asFileAttribute( perms); Path tempFile = null; try { tempFile = Files.createTempFile( "file", ".myapp", att); try (BufferedWriter writer = Files.newBufferedWriter(tempFile, Charset.forName("UTF8"), StandardOpenOption.DELETE_ON_CLOSE)) { System.out.println("Temporary file ready for writing: " + tempFile); // write // write to file } System.out.println("Temporary file write done, file erased"); } catch (FileAlreadyExistsException x) { System.err.println("File exists: " + tempFile); } catch (IOException x) { // Some other sort of failure, such as permissions. System.err.println("Error creating temporary file: " + x); } } } |
Despite the new Java 1.7 features, this example still has several vulnerabilities. There is no mechanism to open the file with exclusive access, a feature provided by standard POSIX. Consequently the temporary file, once created, is still accessible to the user or to the system root userand modifiable by anyone with access to its containing directory. Also, since because the creation of the file , and the opening of the file are distinct operations, this program is still vulnerable to a time-of-check-time-of-use (TOCTOU) race condition.
...
Wiki Markup |
---|
To work around the file/stream termination issue, always attempt to terminate the resource normally before invoking {{deleteOnExit()}}. Using {{File.io.delete()}} to immediately delete the file is good practice, when possible; this avoids improper JVM termination related issues. Moreover, although unreliable, {{System.gc()}} may be invoked to free up related resources. Sometimes, the resources to be deleted cannot be closed first; see, for example, \[[Bug ID: 4635827|http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4635827]\]. There is no known workaround for this case. Consequently, temporary files must be created only in secure directories. |
Compliant Solution (POSIX, Java 1.7, secure directory)
Because of the potential for race conditions, and the inherent accessability of a temporary file, temporary files must only be created in secure directories. This compliant solution depends on an isSecureDir()
method. This method ensures that file and all directories above it are owned by either the user or the superuser, that each directory does not have write access for any other users, and that directories above path may not be deleted or renamed by any other users. When checking directories, it is important to traverse from the root to the leaf to avoid a dangerous race condition whereby an attacker who has privileges to at least one of the directories can rename and recreate a directory after the privilege verification.
The file name passed to this function is first verified to be a directory, and rendered absolute if necessary. If the path contains any symbolic links, this routine will recursively invoke itself on the linked-to directory and ensure it is also secure. A symlinked directory may be secure if both its source and linked-to directory are secure. The function checks every directory in the path, ensuring that every directory is owned by the current user or the superuser and that all directories in the path forbid other users from deleting or renaming files.
POSIX maintains that a directory can prevent modifications to its files either by disabling group and other write access to the directory or by turning on the sticky bit. However, Java 1.7 provides no mechanism for accessing the sticky bit.
Most home directories are secure by default on standard POSIX installations, and only shared directories such as /tmp
are insecure. However, since any user can create an insecure directory, a program should verify that a particular directory is actually secure, as it may not have the permissions required to create a secure directory.
Note that this method is only effective on filesystems that are fully compatible with UNIX permissions, and it may not behave normally for filesystems with other permission mechanisms, such as AFS.
Code Block | ||
---|---|---|
| ||
public static boolean isSecureDir(Path file) {
if (!Files.isDirectory( file)) {
return false;
}
if (!file.isAbsolute()) {
file = file.toAbsolutePath();
}
// If any parent dirs (from root on down) are not secure, dir is not secure
for (int i = 1; i <= file.getNameCount(); i++) {
Path partialPath = Paths.get( file.getRoot().toString(), file.subpath( 0, i).toString());
try {
if (Files.isSymbolicLink( partialPath)) {
if (!isSecureDir( Files.readSymbolicLink( partialPath))) {
// Symbolic link, linked-to dir not secure
return false;
}
} else {
FileSystem fileSystem = partialPath.getFileSystem();
UserPrincipalLookupService upls = fileSystem.getUserPrincipalLookupService();
UserPrincipal root = upls.lookupPrincipalByName("root");
UserPrincipal user = upls.lookupPrincipalByName( System.getProperty("user.name"));
UserPrincipal owner = Files.getOwner( partialPath);
if (!owner.equals( user) && !owner.equals( root)) {
// dir owned by someone else, not secure
return false;
}
PosixFileAttributes attr = Files.readAttributes( partialPath, PosixFileAttributes.class);
Set<PosixFilePermission> perms = attr.permissions();
if (perms.contains( PosixFilePermission.GROUP_WRITE) ||
perms.contains( PosixFilePermission.OTHERS_WRITE)) {
// someone else can write files, not secure
return false;
}
}
} catch (IOException x) {
x.printStackTrace(System.out);
return false;
}
}
return true;
}
|
This compliant solution uses the isSecureDir()
method to ensure that an attacker may not tamper with the file to be opened and subsequently removed. Note that once the path name of a directory has been checked using isSecureDir()
, all further file operations on that directory must be performed using the same path.
Code Block | ||
---|---|---|
| ||
class TempFile {
public static boolean isSecureDir(Path file) {
// ...
}
public static void main(String[] args) {
Path tempDir = new File(args[0]).toPath();
if (!isSecureDir( tempDir)) {
System.out.println("Temporary Directory not secure");
return;
}
// POSIX file permissions for exclusive read/write
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-------");
FileAttribute att = PosixFilePermissions.asFileAttribute( perms);
Path tempFile = null;
try {
tempFile = Files.createTempFile( tempDir, "file", ".myapp", att);
try (BufferedWriter writer = Files.newBufferedWriter(tempFile, Charset.forName("UTF8"),
StandardOpenOption.DELETE_ON_CLOSE)) {
// write to file
}
System.out.println("Temporary file write done, file erased");
} catch (FileAlreadyExistsException x) {
System.err.println("File exists: " + tempFile);
} catch (IOException x) {
// Some other sort of failure, such as permissions.
System.err.println("Error creating temporary file: " + x);
}
}
}
|
Risk Assessment
Failure to follow best practices while creating, using and deleting temporary files can lead to denial of service vulnerabilities, misinterpretations and alterations in control flow.
...