...
A directory is secure with respect to a particular user if only the user and the system administrator are allowed to create, move, or delete files inside the directory. Furthermore, each parent to the secure directory can be moved or deleted only by the user and the system administratordirectory must itself be a secure directory up to and including the root directory. On most systems, home or user directories are secure by default and only shared directories are insecure.
...
Three file link types are supported in Windows NTFS (New Technology File System): hard links, junctions, and symbolic links. Symbolic links are available in NTFS starting with Windows Vista.
...
File names on many operating systems may be used to access device files. Device files are used to access hardware and peripherals. Reserved MS-DOS device names include AUX
, CON
, PRN
, COM1
, and LPT1
. Character special files and block special files on POSIX systems are used to apply access rights and to are POSIX device files that direct operations on the files to the appropriate device drivers.
...
Wiki Markup |
---|
Device files in POSIX can be a security risk when an attacker can access them in an unauthorized way. For instance, if attackersmalicious programs can read or write to the {{/dev/kmem}} device, they may be able to alter their own priority, user ID, or other attributes of their process or they may simply crash the system. Similarly, access to disk devices, tape devices, network devices, and terminals being used by other processes can also lead to problems \[[Garfinkel 1996|AA. Bibliography#Garfinkel 96]\]. |
On Linux, it is possible to lock certain applications by attempting to open read or write data on devices rather than files. Consider the following device path names:
...
- mandatory locking is supported only by certain network file systems.
- file systems must be mounted with support for mandatory locking, and this which is disabled by default.
- locking relies on the group ID bit, which can be turned off by another process (thereby defeating the lock).
- the lock is implicitly dropped if the holding process closes any descriptor of the file.
...
Code Block | ||
---|---|---|
| ||
String file = /* provided by user */; InputStream in = null; try { in = new FileInputStream(file); // ... } finally { try { if (in !=null) { in.close();} } catch (IOException x) { // handle error } } |
...
Code Block | ||
---|---|---|
| ||
String filename = /* provided by user */;
Path path = new File(filename).toPath();
try (InputStream in = Files.newInputStream(path)) {
// read file
} catch (IOException x) {
// handle error
}
|
...
This noncompliant code example first checks that the file is a regular file (using the new Java SE 7 NIO2 APIs) before opening it.
Code Block | ||
---|---|---|
| ||
String filename = /* provided by user */;
Path path = new File(filename).toPath();
try {
BasicFileAttributes attr =
Files.readAttributes(path, BasicFileAttributes.class);
// Check
if (!attr.isRegularFile()) {
System.out.println("Not a regular file");
return;
}
// other necessary checks
// Use
try (InputStream in = Files.newInputStream(path)) {
// read file
}
} catch (IOException x) {
// handle error
}
|
...
Code Block | ||
---|---|---|
| ||
String filename = /* provided by user */;
Path path = new File(filename).toPath();
try {
BasicFileAttributes attr = Files.readAttributes(
path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
// Check
if (!attr.isRegularFile()) {
System.out.println("Not a regular file");
return;
}
// other necessary checks
// Use
try (InputStream in = Files.newInputStream(path)) {
// read file
};
} catch (IOException x) {
// handle error
}
|
...
This noncompliant code example performs the necessary checks and then opens the file. After opening the file, it performs a second check to make sure that the file has not been moved and that the file it opened is the same one it file that was checked. This reduces the chance that an attacker has changed the file between checking and then opening the file. In both checks, the file's fileKey
attribute is examined. This serves as a unique key for identifying files and is a more reliable indicator of the file's identity than its path name.
Wiki Markup |
---|
The SE 7 Documentation \[[J2SE 2011|AA. Bibliography#J2SE 11]\] describes the {{fileKey}} attribute as follows: |
Returns an object that uniquely identifies the given file, or null if a file key is not available. On some platforms or file systems it is possible to use an identifier, or a combination of identifiers to uniquely identify a file. Such identifiers are important for operations such as file tree traversal in file systems that support symbolic links or file systems that allow a file to be an entry in more than one directory. On UNIX file systems, for example, the device ID and inode are commonly used for such purposes.
The file key returned by this method can only be guaranteed to be unique if the file system and files remain static. Whether a file system re-uses identifiers after a file is deleted is implementation dependent and consequently unspecified.
File keys returned by this method can be compared for equality and are suitable for use in collections. If the file system and files remain static, and two files are the same with non-null file keys, then their file keys are equal.
Code Block | ||
---|---|---|
| ||
String filename = /* provided by user */;
Path path = new File(filename).toPath();
try {
BasicFileAttributes attr = Files.readAttributes(
path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
Object fileKey = attr.fileKey();
// Check
if (!attr.isRegularFile()) {
System.out.println("Not a regular file");
return;
}
// other necessary checks
// Use
try (InputStream in = Files.newInputStream(path)) {
// Check
BasicFileAttributes attr2 = Files.readAttributes(
path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS
);
Object fileKey2 = attr2.fileKey();
if (fileKey != fileKey2) {
System.out.println("File has been tampered with");
}
// read file
};
} catch (IOException x) {
// handle error
}
|
...
- The TOCTOU race condition still exists between the first check and open. During this race window, an attacker can replace the regular file with a symbolic link or other nonregular file. The second check detects this race condition but does not eliminate it; an attacker can still cause the system to block when opening the file.
- An attacker could subvert this code by letting the check operate on a regular file, substituting the nonregular file for the open, and then resubstituting the regular file to circumvent the second check. This vulnerability exists because Java lacks any mechanism to obtain file attributes from a file by any means other than the file name, and the binding of the file name to a file object is reasserted every time the file name is used in an operation. Consequently, an attacker can still switch out swap a file for a nefarious file, such as a symbolic link.
- A system with hard links allows an attacker to construct a malicious file that is a hard link to a sensitive protected file. Hard links cannot be reliably detected by a program and can foil canonicalization attempts, which are prescribed by rule IDS02-J. Canonicalize path names before validating them.
...
Because of the potential for race conditions and the inherent accessibility of shared directories, files must be operated on only in secure directories. Because programs may run with reduced privileges and lack the facilities to construct a secure directory, a program may need to throw an exception if it determines that a given path name is not in a secure directory.
Following is an a POSIX-specific implementation of an isInSecureDir()
method. This method ensures that the supplied file and all directories above it are owned by either the user or the system administrator, that each directory lacks write access for any other users, and that directories above the given file may not be deleted or renamed by any other users (except the system administrator).
Code Block | ||
---|---|---|
| ||
/** * Indicates whether file lives in a secure directory relative * to the program's user * @param file Path to test * @return true if file's directory is secure */ public static boolean isInSecureDir(Path file) { return isInSecureDir(file, null); } public static boolean isInSecureDir(Path file, UserPrincipal user) { return isInSecureDir(file, null, 5); } /** * Indicates whether file lives in a secure directory relative * to the program's user * @param file Path to test * @param user User to test. If null, defaults to current user * @param symlinkDepth Number of symbolic links allowed * @return true if file's directory is secure */ public static boolean isInSecureDir(Path file, UserPrincipal user) { if (!file.isAbsolute()) {, int symlinkDepth) { if (!file.isAbsolute()) { file = file.toAbsolutePath(); } if (symlinkDepth <=0) { // Too many levels of symbolic links file = file.toAbsolutePath()return false; } // Get UserPincipal for specified user and superuser FileSystem fileSystem = Paths.get(file.getRoot().toString()).getFileSystem(); UserPrincipalLookupService upls = fileSystem.getUserPrincipalLookupService(); UserPrincipal root = null; try { root = upls.lookupPrincipalByName("root"); if (user == null) { user = upls.lookupPrincipalByName(System.getProperty("user.name")); } if (root == null || user == null) { return false; } } catch (IOException x) { return false; } // 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 (!isInSecureDir(Files.readSymbolicLink(partialPath))) { (!isInSecureDir(Files.readSymbolicLink(partialPath),)) { user, symlinkDepth - 1) // Symbolic link, linked-to dir not secure return false; } } else { UserPrincipal owner = Files.getOwner(partialPath); if (!user.equals(owner) && !root.equals(owner)) { // 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) { return false; } } return true; } |
When checking directories, it is important to traverse from the root directory to the bottommost leaf directory to avoid a dangerous race condition whereby an attacker who has privileges to at least one of the directories can rename and re-create a directory after the privilege verification of subdirectories but before the verification of the tampered directory.
The file name passed to this method is first 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 method checks every directory in the path, ensuring that every directory is owned by the current user or the system administraor and that all directories in the path forbid prevent other users from creating, deleting, or renaming files.
On POSIX systems, disabling group and other world write access to a directory prevents modification by anyone other than the owner of the directory and the system administrator.
Note that this method is effective only on file systems that are fully compatible with POSIX file access permissions, and ; it may not behave in correctly for file systems with other permission mechanisms.
The following compliant solution uses the isInSecureDir()
method to ensure that an attacker cannot tamper with the file to be opened and subsequently removed. Note that once the path name of a directory has been checked using isInSecureDir()
, all further file operations on that directory must be performed using the same path. This compliant solution also performs the same checks performed by the previous examples, such as making sure the requested file is indeed a regular file, and not a symbolic link, device file, and so forthor other special file.
Code Block | ||
---|---|---|
| ||
String filename = /* provided by user */;
Path path = new File(filename).toPath();
try {
if (!isInSecureDir(path)) {
System.out.println("File not in secure directory");
return;
}
BasicFileAttributes attr = Files.readAttributes(
path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
// Check
if (!attr.isRegularFile()) {
System.out.println("Not a regular file");
return;
}
// other necessary checks
try (InputStream in = Files.newInputStream(path)) {
// read file
}
} catch (IOException x) {
// handle error
}
|
Programs with elevated privileges may need to write files to directories owned by unprivileged users. One example would be a mail daemon that reads a mail message from one user and places it in a directory owned by another user. In such cases, the proper course of action is to mail daemon should assume the privileges of a user when reading or writing files on behalf of that user, in which case all file access should occur in secure directories relative to that user. When a program with elevated privileges must write files on its own behalf, these files should be in secure directories relative to the privileges of the program (such as directories accessible only by the system administrator).
Exceptions
FIO00-EX0: Programs that operate on single-user systems or on systems that have no shared directories or no possibility of file system vulnerabilities do not need to ensure that files are maintained in secure directories before operating on them.
Risk Assessment
Allowing Performing operations to be performed on files in shared directories can result in (DoS) attacks. If the program has elevated privileges, then privilege escalation exploits become are possible.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
FIO00-J | medium | unlikely | medium | P4 | L3 |
...
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="777d8fa014260138-3cc54039-442740b6-9ed7954d-f3e02f293e825c1fd961e0cd"><ac:plain-text-body><![CDATA[ | [[API 2006 | AA. Bibliography#API 06]] | Class | ]]></ac:plain-text-body></ac:structured-macro> | |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="6b62eb991133ede0-d3e66f4e-44fe417b-a9948aec-aa0db35cc2839f57465a18b3"><ac:plain-text-body><![CDATA[ | [[CVE 2011 | AA. Bibliography#CVE 08]] | [CVE-2008-5354 | http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2008-5354] | ]]></ac:plain-text-body></ac:structured-macro> |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="8637c49802c343a5-9f8c8701-4e1b4ffc-ba719241-34b49ccddb2a3c110af076ff"><ac:plain-text-body><![CDATA[ | [[Darwin 2004 | AA. Bibliography#Darwin 04]] | 11.5, Creating a Transient File | ]]></ac:plain-text-body></ac:structured-macro> | |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="73457aade2fc4aaf-48cbad6d-4ceb4ba1-b2099c53-35dbd505ce4950befaadf0c0"><ac:plain-text-body><![CDATA[ | [[Garfinkel 1996 | AA. Bibliography#Garfinkel 96]] | Section 5.6, Device Files | ]]></ac:plain-text-body></ac:structured-macro> | |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="4d7b080c60d2ab11-c9e151af-46e842ce-b65cb475-81ace2907625ebbffda2b51d"><ac:plain-text-body><![CDATA[ | [[Howard 2002 | AA. Bibliography#Howard 02]] | Chapter 11, Canonical Representation Issues | ]]></ac:plain-text-body></ac:structured-macro> | |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="1d273ccd815b814d-1d2b4641-43354ff6-8c248e1d-6abbbd0235afa3cfa1edaecf"><ac:plain-text-body><![CDATA[ | [[J2SE 2011 | AA. Bibliography#J2SE 11]] | The try-with-resources Statement | ]]></ac:plain-text-body></ac:structured-macro> | |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="58a94b127dcda591-913a3e19-444a4459-8431b77c-a77cff92c262e35bc577e512"><ac:plain-text-body><![CDATA[ | [[Open Group 2004 | AA. Bibliography#Open Group 04]] | [ | http://www.opengroup.org/onlinepubs/009695399/functions/open.html] | ]]></ac:plain-text-body></ac:structured-macro> |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="ff33cdc9cd8c4788-21fed12c-4c894e50-92a9acb0-7810ce01c7224f9ce3a404ea"><ac:plain-text-body><![CDATA[ | [[SDN 2008 | AA. Bibliography#SDN 08]] | Bug IDs 4171239, 4405521, 4635827, 4631820 | ]]></ac:plain-text-body></ac:structured-macro> | |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="38614e896cd2899d-dae4a29a-4e484104-b678b2f0-10899c451c842f0475b526e0"><ac:plain-text-body><![CDATA[ | [[Secunia 2008 | AA. Bibliography#Secunia 08]] | [Secunia Advisory 20132 | http://secunia.com/advisories/20132/] | ]]></ac:plain-text-body></ac:structured-macro> |
...