路径表达式中使用不受控制的数据¶
ID: java/path-injection
Kind: path-problem
Security severity: 7.5
Severity: error
Precision: high
Tags:
- security
- external/cwe/cwe-022
- external/cwe/cwe-023
- external/cwe/cwe-036
- external/cwe/cwe-073
Query suites:
- java-code-scanning.qls
- java-security-extended.qls
- java-security-and-quality.qls
访问用户控制的路径可能允许攻击者访问意外资源。这可能导致敏感信息被泄露或删除,或者攻击者能够通过修改意外文件来影响行为。
从用户控制的数据中简单构建的路径可能是绝对路径,或者可能包含意外的特殊字符,例如“..”。这样的路径可能指向文件系统上的任何位置。
建议¶
在使用用户输入构建文件路径之前验证用户输入。
常见的验证方法包括检查标准化的路径是否为相对路径并且不包含任何“..”组件,或者检查路径是否包含在安全文件夹中。您应该使用的方法取决于路径在应用程序中的使用方式,以及路径是否应该是一个单独的路径组件。
如果路径应该是一个单独的路径组件(例如文件名),您可以检查输入中是否存在任何路径分隔符(“/”或“\”),或“..”序列,如果存在,则拒绝输入。
请注意,删除“../”序列不足,因为输入仍然可能包含路径分隔符后跟“..”。例如,如果只删除“../”序列,则输入“…/…//”将仍然导致字符串“../”。
最后,最简单(但最严格)的选择是使用安全模式的白名单,并确保用户输入与其中一个模式匹配。
示例¶
在本例中,文件名从 java.net.Socket
读取,然后用于访问文件并将其通过套接字发送回。但是,恶意用户可以在文件系统中的任何位置输入文件名,例如“/etc/passwd”或“../../../etc/passwd”。
public void sendUserFile(Socket sock, String user) {
BufferedReader filenameReader = new BufferedReader(
new InputStreamReader(sock.getInputStream(), "UTF-8"));
String filename = filenameReader.readLine();
// BAD: read from a file without checking its path
BufferedReader fileReader = new BufferedReader(new FileReader(filename));
String fileLine = fileReader.readLine();
while(fileLine != null) {
sock.getOutputStream().write(fileLine.getBytes());
fileLine = fileReader.readLine();
}
}
如果输入应该只是一个文件名,则可以检查它是否不包含任何路径分隔符或“..”序列。
public void sendUserFileGood(Socket sock, String user) {
BufferedReader filenameReader = new BufferedReader(
new InputStreamReader(sock.getInputStream(), "UTF-8"));
String filename = filenameReader.readLine();
// GOOD: ensure that the filename has no path separators or parent directory references
if (filename.contains("..") || filename.contains("/") || filename.contains("\\")) {
throw new IllegalArgumentException("Invalid filename");
}
BufferedReader fileReader = new BufferedReader(new FileReader(filename));
String fileLine = fileReader.readLine();
while(fileLine != null) {
sock.getOutputStream().write(fileLine.getBytes());
fileLine = fileReader.readLine();
}
}
如果输入应该位于特定目录中,则可以检查解析后的路径是否仍包含在该目录内。
public void sendUserFileGood(Socket sock, String user) {
BufferedReader filenameReader = new BufferedReader(
new InputStreamReader(sock.getInputStream(), "UTF-8"));
String filename = filenameReader.readLine();
Path publicFolder = Paths.get("/home/" + user + "/public").normalize().toAbsolutePath();
Path filePath = publicFolder.resolve(filename).normalize().toAbsolutePath();
// GOOD: ensure that the path stays within the public folder
if (!filePath.startsWith(publicFolder + File.separator)) {
throw new IllegalArgumentException("Invalid filename");
}
BufferedReader fileReader = new BufferedReader(new FileReader(filePath.toString()));
String fileLine = fileReader.readLine();
while(fileLine != null) {
sock.getOutputStream().write(fileLine.getBytes());
fileLine = fileReader.readLine();
}
}