Saturday, December 19, 2009

FilePermission class leaks sensitive information

Somebody might consider it ironic that the security class which is responsible mapping access permission to files (java.io.FilePermission) is actually leaking information about the filesystem.

FilePermission uses a doPrivileged block to obtain a canonical path to the file/folder given as a parameter to the constructor. The canonical path is then stored in a private and transient field (called cpath) which has no accessor method.

The canonical path is security-sensitive, because if you give it the input of "." it will become the full (canonical) path to the current execution directory.

Also, in windows, if I give it a path, such as "c:\windows" it becomes "C:\WINDOWS" on my machine, as both the drive letter and windows folder are uppercase. If I give it a path that does not exist, such as "C:\whatever" it does not get altered. Thus I can test the existence of files and folders.

The cpath is not directly accessible, but the FilePermission class has a hashCode method, and the implementation is:

384  public int hashCode() {
385     return this.cpath.hashCode();
386  }


So the hashcode of the String of the canonical path is available. I looked into the possibility of reversing the string hash, but it's not really practical. The simple algorith (which is explained here) is easy to reverse, but as it's extremely lossy, the number of strings that have any given hash is very big.

File or folder existence on Windows can be easily tested by giving a toLowerCase and toUpperCase versions of any path to FilePermission and then comparing the hashcodes. If the hashcodes are equal, the file/folder exists, if they're unequal, it doesn't exist.

For example, on my machine, the following:

001 import java.io.FilePermission;
002 
003 public class FP {
004     public static void main(String[] args) throws Exception {
005         System.out.println(fileExists("C:/windows"));
006         System.out.println(fileExists("C:/filedoesnotexist"));
007     }
008     
009     static boolean fileExists(String name) {
010         return new FilePermission(name.toLowerCase(), "read").hashCode() == new FilePermission(name.toUpperCase(), "read").hashCode();
011     }
012 }


Yields the output:
true
false

In similar fashion, you could compare the hashes of FilePermissions for ".", "..", "../..", "../../.." until the hashcode stops changing, which means you've hit the root (Drive-Letter:\ on windows or / on linux/unix/etc). The depth of the execution folder can thus be determined and it is possible to try to guess each of the folders of the path individually.

It's not a very serious problem at all, but it's one I found to be amusing both for the simplicity of it and the fact that it's in the very class that is used to map access to files.

No comments: