《Java安全编码标准》一2.3 IDS02-J在验证之前标准化路径名

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管理员控制台中,它允许远程攻击者向任意目录上传文件。

2.3.9 相关规范

2.3.10 参考书目

时间: 2024-10-04 10:51:21

《Java安全编码标准》一2.3 IDS02-J在验证之前标准化路径名的相关文章

《Java安全编码标准》一第 2 章 输入验证和数据净化(IDS)

第 2 章 输入验证和数据净化(IDS) 规 则 风险评估概要

《Java安全编码标准》一导读

前 言 在Java编程语言中,关键的安全编码要素是采用良好的文档和强制的编码规范.本书提供了在Java语言中的一系列安全编码规则.这些规则的目标是消除不安全的编码实践,因为这些不安全的因素会导致可利用的漏洞.如果应用这些安全编码标准,可以帮助设计出安全的.可靠的.健壮的.有弹性的.可用性和可维护性高的高质量系统,并且这些安全编码规范还可作为评估源代码质量特性的一个指标(不管使用的是手动的还是自动化的过程).对于使用Java编程语言开发的软件系统,这个编码规范具有广泛的影响. 目 录 第1章 概述

《Java安全编码标准》一1.11 小结

1.11 小结 尽管作为一种相对安全的语言,Java语言及其类库还是在很大程度上存在着一些编程问题,从而造成系统安全漏洞.如果假设Java本身提供的功能特性可以减少一般的软件问题,并足够Java程序安全得不需要进行进一步检测,那么我们就大错特错了.因为任何在软件实现中出现的缺陷都会产生严重的安全影响,绷紧安全性这根弦是非常关键的,这样,当我们进行系统开发和部署时,就可以避免出现软件的安全漏洞问题.为了减少因为编程错误所带来的安全漏洞的可能性,Java开发人员应当遵循本编码标准中的安全编码规则,并

《Java安全编码标准》一1.5 拒绝服务

1.5 拒绝服务 拒绝服务攻击试图使计算机的资源不可获得,或者对需要使用该计算机资源的用户来说,会造成资源不足的情况.通常这种攻击是持续服务的服务器系统需要重点关注的,它与桌面应用程序有很大区别:然而,拒绝服务的问题可以出现在所有类别的应用上. 1.5.1 资源耗尽型的拒绝服务 拒绝服务可能出现在,相对于输入数据需要的资源消耗来说,使用比例上更为巨大的资源.通过客户端软件检查资源是否被过度消耗,并希望用户来处理与资源相关的问题是不合理的.但是,存在这样的客户端软件,它们可以检查那些可能会导致持久

《Java安全编码标准》一2.6 IDS05-J使用ASCII字符集的子集作为文件名和路径名

2.6 IDS05-J使用ASCII字符集的子集作为文件名和路径名 如果文件名和路径名中包含了特殊字符,就会有问题,并且它会引起不可预期的系统行为,从而导致安全漏洞.在构建文件名或者路径名的时候,下面的字符和模式是有问题的. 以破折号开头:当调用一个程序时,使用的是它的文件名,若文件名以破折号开头,那么会导致问题,因为这样的文件名的第一个字符会被解析为选项标志. 控制字符,例如换行符.回车与Esc:在执行shell脚本和记录日志的时候,如果在文件名中采用控制字符,那么可能会导致意想不到的问题.

《Java安全编码标准》一2.11 IDS10-J不要拆分两种数据结构中的字符串

2.11 IDS10-J不要拆分两种数据结构中的字符串 在历史遗留系统中,常常假设字符串中的每一个字符使用8位(一个字节,Java中的byte).而Java语言使用16位表示一个字符(Java中的Char类型).遗憾的是,不管是Java的byte类型还是char类型数据,都不能表示所有的Unicode字符.许多字符串使用例如UTF-8编码的方式存储和通信,而在这种编码中,字符长度是可变的. 当Java字符串以字符数组的方式存储时,它可以用一个字节数组来表示,字符串里的一个字符可以用两个连续的或更

《Java安全编码标准》一2.1 IDS00-J净化穿越受信边界的非受信数据

2.1 IDS00-J净化穿越受信边界的非受信数据 许多程序会接受来自未经验证的用户数据.网络连接,以及其他来自于非受信域的不可信数据,然后它们会将这些数据(经过修改或不经修改)穿过受信边界送到另一个受信域中.通常,这些数据是以字符串的形式出现的,并且它们会有既定的内部数据结构,这种数据结构必须能够被子系统所解析.同时,必须净化这些数据,因为子系统可能并不具备处理恶意输入信息的能力,这些未经净化数据输入很有可能包含注入攻击. 值得注意的是,程序必须对传递给命令行解释器或者解析器的所有字符串数据进

《Java安全编码标准》一2.13 IDS12-J在不同的字符编码中无损转换字符串数据

2.13 IDS12-J在不同的字符编码中无损转换字符串数据 在String对象之间进行转换时,如果涉及不同的编码类型,可能会导致数据丢失. 根据Java API[API 2006] 对?String.getBytes(Charset)方法的描述: 该方法总会替代那些错误格式的输入和不可映射的字符序列,把它们替换成这些字符的字节数组. 当必须将一个String转化为字节数组时,例如写入一个文件,并且在这个字符串中含有不可映射的字符序列的时候,就必须进行正确的字符编码. 2.13.1 不符合规则的

《Java安全编码标准》一2.7 IDS06-J从格式字符串中排除用户输入

2.7 IDS06-J从格式字符串中排除用户输入 对Java格式字符串的解释要比对在像C语言这样的语言中更严格[Seacord 2005].当任何转换参数不能匹配相应的格式符时,标准类库实现会抛出一个相应的异常.这种方法降低了被恶意利用的可能性.然而,恶意用户输入可以利用格式字符串,并且造成信息泄露或者拒绝服务.因此,不能在格式字符串中使用非受信来源的字符串. 2.7.1 不符合规则的代码示例 这个不符合规则的代码示例展示了可能出现信息泄露的问题.它将信用卡的失效日期作为输入参数并将其用在格式字