Handling files is security-sensitive. It has led in the past to the following vulnerabilities:

Any access to the file system can create a vulnerability. Exposing a file's content, path or even its existence or absence is dangerous. It is also extremely risky to create or write files without making sure that their permission and content is safe and controlled. Using a file path or reading a file content must be always done with caution as they could have been tampered with.

The file system is a resource which can be easily exhausted. Opening too many files will use up all file descriptors, preventing other software from opening files. Filling the storage space will also prevent any additional write from happening.

This rule flags code that initiates the use of files. It does not highlight how the files are used as this is often done in external libraries or via abstractions like InputStream. It focuses instead on the creation of java.io.File or equivalent from a String. This action indicates that one or multiple files will be processed just after this code. The goal is to guide manual security code reviews.

Ask Yourself Whether

You are at risk if you answered yes to any of those questions.

Recommended Secure Coding Practices

Avoid using paths provided by users or other untrusted sources if possible. If this is required, check that the path does not reference an unauthorized directory or file. See OWASP recommendations as to how to test for directory traversal. Note that the paths length should be validated too.

No File and directory names should be exposed. They can contain sensitive information. This means that a user should not be able to list the content of unauthorized directories.

Make sure that no attackers can test for the existence or absence of sensitive files. Knowing that a specific file exists can reveal a vulnerability or at least expose file and directory names.

Files and directories should be created with restricted permissions and ownership. Only authorized users and applications should be able to access the files, and they should have as little permissions as needed. Modifying a file's permissions is not good enough. The permissions should be restricted from the very beginning.

Writing user input into files should be done with caution. It could fill the storage space if the amount of data written is not controlled. It could also write dangerous data which will later be used by an application or returned to another user. This is why the user input should be validated before being written.

Reading a file can lead to other vulnerabilities. Any file could have been modified by an attacker. Thus the same validation as for any user input should be performed on file content.

Once a file is read, its content should only be exposed to authorized users.

Add limits to the number of files your application access simultaneously or create because of a user action. It is possible to perform a Denial of Service attack by opening too many files, and thus exhausting available file descriptors, or by filling the file system with new files. Release file descriptors by closing files as soon as possible.

We also recommended to have tools monitoring your system and alerting you whenever resources are nearly exhausted.

Do not allow untrusted code to access the filesystem. For some programming languages, child-processes may have access to file descriptors opened by the parent process before the creation of the child process. This creates a vulnerability when a child process doesn't have the permission to access a file but is still able to modify it via the inherited file descriptor. Check your language documentation for "file descriptor leak" or the use of the flags O_CLOEXEC, FD_CLOEXEC, or bInheritHandles. File descriptors can be inherited in the following languages: C, C++, C#, Objective-C, Swift, Go (but disabled by default), some JVM versions, Javascript and TypeScript in Nodejs, Some PHP versions, Python, Ruby, Rust, VB6 and VB.NET.

Questionable Code Example

// === java.io.File ===
import java.io.File;

class A {
    void foo(String strPath, String StrParent, String StrChild, String prefix, String suffix, java.net.URI uri) throws Exception {

        // Questionable: check what is done with this file
        new File(strPath);
        new File(StrParent, StrChild);
        new File(uri);
        File.createTempFile(prefix, suffix);
    }
}
// === java.nio.file ===
import java.nio.file.attribute.FileAttribute;
import java.nio.file.*;

class A {
    void foo(FileSystem fileSystem, java.net.URI uri, String part1, String part2, String prefix, FileAttribute<?> attrs,
            String suffix) throws Exception {
        Path path = Paths.get(part1, part2); // Questionable
        Path path2 = Paths.get(uri); // Questionable

        Iterable<Path> paths = fileSystem.getRootDirectories(); // Questionable
        Path path3 = fileSystem.getPath(part1, part2); // Questionable

        Path path4 = Files.createTempDirectory(prefix, attrs); // Questionable
        Path path5 = Files.createTempFile(prefix, suffix, attrs); // Questionable
    }
}
// === Opening file from a string path ===
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.RandomAccessFile;

class A {
    void foo(String mode) throws Exception {
        FileReader reader = new FileReader("test.txt"); // Questionable
        FileInputStream instream = new FileInputStream("test.txt"); // Questionable
        FileWriter writer = new FileWriter("out.txt"); // Questionable
        FileOutputStream outstream = new FileOutputStream("out2.txt"); // Questionable
        RandomAccessFile file = new RandomAccessFile("test.txt", mode); // Questionable
    }
}
// ===  org.apache.commons.io.FileUtils ===
import org.apache.commons.io.FileUtils;

class A {
    void foo() {
        FileUtils.getFile("test.txt"); // Questionable
        FileUtils.getTempDirectory(); // Questionable
        FileUtils.getUserDirectory(); // Questionable
    }
}
// === Guava ===
import java.nio.charset.Charset;

import com.google.common.io.FileBackedOutputStream;
import com.google.common.io.MoreFiles;
import com.google.common.io.Resources;
import com.google.common.io.Files;
import com.google.common.io.LineProcessor;

class M {
    void foo(java.net.URL url, Charset charset, java.io.OutputStream stream, String resourceName, Class<?> contextClass,
            LineProcessor<Object> callback, int fileThreshold, boolean resetOnFinalize) throws Exception {

        Files.createTempDir(); // Questionable
        Files.fileTreeTraverser(); // Questionable (removed from public API in Guava 25.0)
        Files.fileTraverser(); // Questionable
        MoreFiles.directoryTreeTraverser(); // Questionable (removed from public API in Guava 25.0)
        MoreFiles.fileTraverser(); // Questionable
        Resources.asByteSource(url); // Questionable
        Resources.asCharSource(url, charset); // Questionable
        Resources.copy(url, stream); // Questionable
        Resources.getResource(contextClass, resourceName); // Questionable
        Resources.getResource(resourceName); // Questionable
        Resources.readLines(url, charset); // Questionable
        Resources.readLines(url, charset, callback); // Questionable
        Resources.toByteArray(url); // Questionable
        Resources.toString(url, charset); // Questionable

        // these OutputStreams creates files
        new FileBackedOutputStream(fileThreshold); // Questionable
        new FileBackedOutputStream(fileThreshold, resetOnFinalize); // Questionable
    }
}

Exceptions

This rule doesn't highlight any function call receiving a Path or File arguments as the arguments themselves have been highlighted before.

For example we highlight new File(String parent, String child) but not new File(File parent, String child) as the parent File should have been flagged earlier.

See