路径表达式中使用的不受控制的数据¶
ID: cpp/path-injection
Kind: path-problem
Security severity: 7.5
Severity: warning
Precision: medium
Tags:
- security
- external/cwe/cwe-022
- external/cwe/cwe-023
- external/cwe/cwe-036
- external/cwe/cwe-073
Query suites:
- cpp-security-extended.qls
- cpp-security-and-quality.qls
访问用户控制的路径可能允许攻击者访问意外的资源。这可能导致敏感信息被泄露或删除,或者攻击者能够通过修改意外文件来影响行为。
从用户控制的数据简单构建的路径可能是绝对路径,或者可能包含意外的特殊字符,例如“..”。这样的路径可能指向文件系统上的任何位置。
建议¶
在使用用户输入构造文件路径之前先对其进行验证。
常见的验证方法包括检查规范化后的路径是否是相对路径且不包含任何“..”组件,或者检查路径是否包含在安全文件夹中。应该使用哪种方法取决于路径在应用程序中的使用方式,以及路径是否应该是单个路径组件。
如果路径应该是单个路径组件(例如文件名),则可以检查输入中是否存在任何路径分隔符(“/”或“\”)或“..”序列,如果找到则拒绝输入。
请注意,删除“../”序列并不够,因为输入可能仍然包含后跟“..”的路径分隔符。例如,如果仅删除“../”序列,则输入“…/…//”仍将导致字符串“../”。
最后,最简单(但限制性最强)的选择是使用安全模式允许列表,并确保用户输入与其中一种模式匹配。
示例¶
在此示例中,从用户读取文件名,然后使用该文件名访问文件。但是,恶意用户可以输入文件系统上任何位置的文件名,例如“/etc/passwd”或“../../../etc/passwd”。
int main(int argc, char** argv) {
char *userAndFile = argv[2];
{
char fileBuffer[PATH_MAX];
snprintf(fileBuffer, sizeof(fileBuffer), "/home/%s", userAndFile);
// BAD: a string from the user is used in a filename
fopen(fileBuffer, "wb+");
}
}
如果输入应该只是一个文件名,则可以检查它是否包含任何路径分隔符或“..”序列。
#include <stdio.h>
#include <string.h>
int main(int argc, char** argv) {
char *fileName = argv[2];
// Check for invalid sequences in the user input
if (strstr(fileName , "..") || strchr(fileName , '/') || strchr(fileName , '\\')) {
printf("Invalid filename.\n");
return 1;
}
char fileBuffer[PATH_MAX];
snprintf(fileBuffer, sizeof(fileBuffer), "/home/user/files/%s", fileName);
// GOOD: We know that the filename is safe and stays within the public folder
FILE *file = fopen(fileBuffer, "wb+");
}
如果输入应该位于特定目录中,则可以检查解析后的路径是否仍然包含在该目录中。
#include <stdio.h>
#include <string.h>
int main(int argc, char** argv) {
char *userAndFile = argv[2];
const char *baseDir = "/home/user/public/";
char fullPath[PATH_MAX];
// Attempt to concatenate the base directory and the user-supplied path
snprintf(fullPath, sizeof(fullPath), "%s%s", baseDir, userAndFile);
// Resolve the absolute path, normalizing any ".." or "."
char *resolvedPath = realpath(fullPath, NULL);
if (resolvedPath == NULL) {
perror("Error resolving path");
return 1;
}
// Check if the resolved path starts with the base directory
if (strncmp(baseDir, resolvedPath, strlen(baseDir)) != 0) {
free(resolvedPath);
return 1;
}
// GOOD: Path is within the intended directory
FILE *file = fopen(resolvedPath, "wb+");
free(resolvedPath);
}