2.3 IDS02-J在验证之前标准化路径名
根据Java API[API 2006]文档对java.io.File类的描述:
一个路径名,不管是抽象的还是字符串形式的,可以是相对路径也可以是绝对路径。使用绝对路径名,因为在定位一个路径表示的文件时,已经不需要其他信息了,因而可以认为是完整的。相比之下,一个相对路径名必须要增加其他的路径信息才能进行解释。
绝对路径名或者相对路径名会包含文件链接,比如符号(软)链接、硬链接、快捷方式、影子、别名和联名。这些文件链接在文件验证操作进行之前必须完全解析。例如,一个命名为trace的符号链接最终可以是指向路径名/home/system/trace。该路径名可能同时会包含特殊的文件名,这些文件名会让验证变得困难。
1)“.”指目录本身。
2)在目录内,一个特殊的文件名“..”指该目录的上一级目录。
除了这些特殊的问题之外,还有很多是和操作系统有关的,并且是和文件系统有关的命名转换,从而让验证变得困难。
标准化文件名比验证路径名要容易得多。很多路径名会指向一个目录或文件。此外,考虑到路径名指向的目录或文件,一个路径名的文本表示会产生很少或没有信息。结果是,所有的路径名必须被完整解析或者在验证之前进行标准化(canonicalized)。
验证可能是必要的,例如,当需要限定用户访问在某个特定目录下的文件,或者需要基于文件名或者路径名的时候,需要做出对应的安全决策。通常,这些限制被攻击者利用目录遍历(directory traversal)或者等价路径(path equivalence)这样的方式规避而产生漏洞。一个目录遍历漏洞会让I/O操作转到另一个特定的操作目录中去。当攻击者提供一个不同但是有等价名称的资源,并绕过安全检查的时候,就会由此产生一个路径等价漏洞。
标准化包括内在的竞态窗口,这个窗口在程序获得标准路径名的时间和打开文件的时间之间产生。在验证标准化的路径名的时候,文件系统可能已经被修改,并且标准化的路径名不再指向原有的合法文件。幸运的是,可以很容易消除这些竞态。标准化的路径名可以用来确定引用的文件名是否在一个安全的目录中(参考FIO00-J可以得到更多的信息)。如果引用的文件在一个安全的目录之内,根据定义,攻击者就不能篡改它也不能利用这些竞态条件。
该规则是规则IDS01-J的一个实例。
2.3.1 不符合规则的代码示例
以下不符合规则的代码示例可以从命令行参数接收文件路径,并使用File.getAbsolutePath()方法来获得绝对路径。它同时会使用?isInSecureDir()方法,这个方法在规则FIO00-J中进行定义,它可以用来保证文件在一个安全的目录中。然而,它并不能解析文件链接,也不能消除这些同类的错误。
public static void main(String[] args) {
??File f = new File(System.getProperty("user.home") +
??System.getProperty("file.separator") + args[0]);
??String absPath = f.getAbsolutePath();
??if (!isInSecureDir(Paths.get(absPath))) {
????throw new IllegalArgumentException();
??}
??if (!validate(absPath)) { // Validation
????throw new IllegalArgumentException();
??}
}
应用需要限制用户操作那些位于home目录之外的文件。validate()方法可以保证路径名在这个目录当中,但这个方法可以很容易被绕过。例如,用户可以在他的home目录中创建一个链接,这个链接指向位于home目录之外的目录或文件。当validate()方法处理这个链接的路径名的时候,仍然还认为它是在home目录中,结果是可以顺利通过验证,但在实际的操作中,它会操作,位于home目录之外的最终路径指向。
注意,在Windows和Macintosh平台中,File.getAbsolutePath()?方法可以解析符号链接、别名和快捷方式。尽管如此,在Java语言规范中却不能保证这样的行为在所有的平台上都有效,或者在未来的实现中均会如此。
2.3.2 符合规则的方案(getCanonicalPath())
该符合规则的解决方案使用?getCanonicalPath()?方法,这个方法是在Java 2中引入的,因为它能在所有的平台上对所有别名、快捷方式以及符号链接进行一致解析。那些特殊文件名如..同样被去除了,所以在执行验证之前,输入就被缩减成一种标准化形式。当存在validate()方法时,攻击者无法通过使用../序列进入特定的目录。
public static void main(String[] args) throws IOException {
??File f = new File(System.getProperty("user.home") +
??System.getProperty("file.separator")+ args[0]);
??String canonicalPath = f.getCanonicalPath();
??if (!isInSecureDir(Paths.get(canonicalPath))) {
????throw new IllegalArgumentException();
??}
??if (!validate(canonicalPath)) { // Validation
???throw new IllegalArgumentException();
??}
}
当在applet中使用时,getCanonicalPath()方法会抛出安全异常,因而会泄露太多关于宿主机的信息。getCanonicalFile()方法和getCanonicalPath()类似,但它会返回一个新的File对象而不是一个String。
2.3.3 符合规则的方案(安全管理器)
处理这类问题的一个综合方法是,给应用赋予相应的权限,而这些权限只对特定目录下的文件有效 ,比如用户的home目录。该方案只需要在程序的安全管理策略文件中指明程序的绝对路径,并且将java.io.FilePermission?以及读写权限赋予目录${user.home}/*?的路径。
grant codeBase "file:/home/programpath/" {
??permission java.io.FilePermission "${user.home}/*", "read, write";
};
该方案要求用户的home目录是一个安全目录,这会在规则FIO00-J中描述。
2.3.4 不符合规则的代码示例一
这段代码示例允许用户指定所操作的文件名的绝对路径,改用包含../序列的参数来指定位于特定目录之外的文件(在这个示例中是/img路径),从而违反了该程序的安全策略。
FileOutputStream fis =
??new FileOutputStream(new File("/img/" + args[0]));
// ...
2.3.5 不符合规则的代码示例二
该代码示例希望解决使用File.getCanonicalPath()?方法的问题,该方法完全解析参数并构造出标准化路径。例如,路径/img/../etc/passwd可以解析为/etc/passwd。没有经过验证的标准化是不够的,因为攻击者可以使用指定目录之外的特定文件。
File f = new File("/img/" + args[0]);
String canonicalPath = f.getCanonicalPath();
FileOutputStream fis = new FileOutputStream(f);
// ...
2.3.6 符合规则的方案
该方案从非受信的用户输入中获取文件名,对其进行标准化,然后基于起始路径名列表对其进行验证。当验证成功时,才会操作指定文件,也就是说,仅当该文件是在/img/java目录下的file1.txt文件或者file2.txt文件中的一个时才行。
File f = new File("/img/" + args[0]);
String canonicalPath = f.getCanonicalPath();
if (!canonicalPath.equals("/img/java/file1.txt") &&
????!canonicalPath.equals("/img/java/file2.txt")) {
???// Invalid file; handle error
}
FileInputStream fis = new FileInputStream(f);
/img/java目录必须是一个安全目录,不存在任何竞态条件。
2.3.7 符合规则的方案(安全管理器)
该方案赋予应用相应的权限读取指定目录或文件。比如,读权限的赋予,可以通过在安全策略文件中为该程序指定一个绝对路径名,并设置java.io.FilePermission为一个文件或目录的标准化绝对路径,然后将动作设为read。
// All files in /img/java can be read
grant codeBase "file:/home/programpath/" {
??permission java.io.FilePermission "/img/java", "read";
};
2.3.8 风险评估
当使用来自非受信源的路径名时,如果不首先进行标准化,然后进行验证,那么会导致目录遍历和路径等价漏洞。
相关漏洞 CVE-2005-0789 描述了在LimeWire 3.9.6~4.6.0节中的目录遍历漏洞,它的存在让远程攻击者可以通过在请求中通过..路径名读取任意文件。
CVE-2008-5518 描述了多种目录遍历漏洞,在Apache Geronimo应用服务器2.1到2.1.3 的Windows版本的Web管理员控制台中,它允许远程攻击者向任意目录上传文件。