Held-Hintergrund ohne Trennlinie
Leitlinien

注入-路径遍历

路径遍历是另一种非常常见的注入漏洞类型。它们往往发生在 URI 的构造(无论是 URL、文件路径还是其他)无法正确确保完全解析的路径不指向根目录之外时 预期的 路径。

重要的是要指出,路径遍历实际上也可以被视为路径*注入*漏洞。

路径遍历漏洞的影响在很大程度上取决于遍历发生的环境以及所做的整体强化。但是在我们讨论这个问题之前,让我们简单举一个关于这个漏洞的实际示例,看看我们在说什么:

快速分解

考虑在您的应用程序中使用一个端点来提供文档,例如合同或工作机会的模板。这些都可能是应用程序中的静态文件,如 PDF。

在这种情况下,你可能有一段这样的代码来应要求提取文件:

让 baseFolder = “/var/www/api/documents/”;
let 路径 = 基本文件夹 + request.params.filename;

返回文件.read(路径);

为了演示漏洞是如何产生的,我们还必须知道应用程序的根目录在哪里,因此,在本示例中,假设应用程序的根目录位于 '/var/www/api/'。

我们知道应用程序需要一个 “文件名” 参数,让我们来看几个输入示例,结果是什么:

Dateiname Ungelöster Pfad Aufgelöster Pfad
Datenschutz.pdf /var/www/api/documents/Privacy.pdf /var/www/api/documents/Privacy.pdf
../config/prod.config /var/www/api/documents/../config/prod.config /var/www/api/config/prod.config
../../../../etc/shadow /var/www/api/documents/../../../../etc/shadow /etc/shadow

注意我们如何能够使用 '.. /' 遍历文件系统。我们可以移出 PDF 通常存放的 “文档” 文件夹,进入包含 “影子” 文件的 “/etc/” 文件夹,在 Linux 上,该文件包含密码哈希。你可以想象,这真的不理想。

查看 Url 中的遍历

在构造旨在与 API 交互的 URL 时,可能会出现路径遍历的另一种变体。假设我们有一个包含以下方法的 API:

URL-Muster Beschreibung
/api/v1/order/get/{id} Liefert Details über die Bestellung mit der angegebenen ID
/api/v1/order/delete/{id} Löscht einen Auftrag mit einer bestimmten ID

该 API 由另一个应用程序进行交互,例如,在尝试获取订单信息时,该应用程序可能会调用它:

让 apiBase = “https://my.api/api/v1”;
let orderAPI = apiBase + “/order/get”;

let apiUrl = orderAPI + request.params.orderId;

让响应 = http.get (apiUrl);

根据用户提供的订单编号,现在会发生什么?在下面,您可以看到根据提供的输入调用的有效 URL。

规范化通常不在客户端完成(尽管可以),但是 Web 服务器会将请求规范化为如下所示的格式。

ID-Nummer der Bestellung Tatsächliche aufgerufene URL
1 /api/v1/bestellung/get/1
1/../../Löschen/1 /api/v1/bestellung/löschen/1

根据第二个示例的输入,我们实际上不是提取编号为 '1 的订单,而是调用了 delete 方法,这当然会导致删除订单。

缓解措施

在讨论路径遍历时,既有直接的缓解措施,也有可以而且应该尽可能频繁地应用的间接/防御技术。首先,让我们来看看如何处理路径。

直接缓解

在处理路径时,我们必须了解路径解析或路径规范化的过程及其重要性。

当你有像 '/var/www/api/documents/../../../../../... 这样的路径时/etc/shadow ',它位于非规范路径中。如果您从文件系统请求此路径,它会将其规范化为 “/etc/shadow”。切勿尝试打开非规范路径,这一点至关重要。相反,你应该先规范路径,验证它们是否仅指向预期的文件或文件夹,然后再读取。

让 baseFolder = “/var/www/api/documents/”;
let 路径 = 基本文件夹 + request.params.filename;

let resolvedPath = path.resolve(路径);

如果 (!resolvedPath.startsWith(基础文件夹)
返回 “尝试在基础文件夹之外读取”;
其他
返回 file.read(已解析路径);

反模式-尝试清理文件名

做这样的事情可能很诱人:


让 baseFolder = “/var/www/api/documents/”;
let path = baseFolder + request.params.filename.replace (“../”, “”);
...

但是,这种方法应该 可以使用。处理路径的关键是始终查看规范路径。

只要规范路径没有违反任何规则,路径的最终构造方式就不会有任何区别。尝试对这样的路径进行清理非常容易出错,而且很少是安全的。

限制访问权限

在前面的示例中,我们使用了对 “/etc/shadow” 文件的读取,该文件在 Linux 上带有密码哈希值。但是,应用程序实际上没有理由能够在其根目录之外读取该文件或其他文件。

如果你使用容器,你可能已经缓解了很多风险。采取措施强化容器(不要以 root 身份运行等)至关重要。强烈建议删除 Web 进程的所有权限,并将其对文件系统的读取权限限制为仅限其严格需要的文件。

例子

现在,我们将分享一些不同语言的示例,以帮助在实际操作中更好地演示事物。

C#-不安全

如果不解析完整路径,或者确保仅使用路径的文件名部分,则会使代码容易受到路径遍历的影响。

var baseFolder = “/var/www/app/documents/”;
var 文件名 = “../../../../../../etc/passwd”;

//不安全:读取 /etc/passwd
var fileContents = file.readallText(路径组合(基本文件夹、文件名));

C#-安全-权威

在此示例中,我们通过解析完整(绝对)路径并确保文件解析路径位于我们的基本文件夹内来防止路径遍历。

var baseFolder = “/var/www/app/documents/”;
var 文件名 = “../../../../../../etc/passwd”;

var canonicalPath = path.getFullPath(Path.Combine(基本文件夹、文件名));

//安全:拒绝任何在指定基础之外进行读取的尝试。
如果 (!canonicalPath.startswith(基础文件夹)
返回 “正在尝试读取基本文件夹之外的文件”;

var fileContents = file.readallText (canonicalPath);

C#-安全-文件名

在此示例中,我们通过仅使用路径的文件名部分来防止路径遍历,从而确保无法遍历指定的文件夹。

var baseFolder = “/var/www/app/documents/”;

//仅当你不允许导航到其他子文件夹时才使用它
var 文件名 = path.getFileName (“../../../../../etc/passwd “);

//安全:读取 /var/www/app/documents/passwd
var fileContents = file.readallText(路径组合(基本文件夹、文件名));

Java-不安全

如果不解析完整路径,或者确保仅使用路径的文件名部分,则会使代码容易受到路径遍历的影响。

字符串 baseFolder = “/var/www/app/documents/”;
字符串文件名 = “../../../../../../etc/passwd”;

//不安全:读取 /etc/passwd
路径 filePath = paths.GET(基本文件夹 + 文件名);
列表<String>行 = files.readallLines (FilePath);

Java-安全-规范

在此示例中,我们通过解析完整(绝对)路径并确保文件解析路径位于我们的基本文件夹内来防止路径遍历。

字符串 baseFolder = “/var/www/app/documents/”;
字符串文件名 = “../../../../../../etc/passwd”;

//不安全:读取 /etc/passwd
路径 normalizedPath = paths.GET(基础文件夹 + 文件名).normalize ();
如果 (!normalizedPath.toString () .startsWith(基本文件夹))
{
返回 “正在尝试读取根目录以外的路径”;
}
其他
{
列表<String>行 = files.readAllLines(标准化路径);
}

Java-安全-文件名

在此示例中,我们通过仅使用路径的文件名部分来防止路径遍历,从而确保无法遍历指定的文件夹。

字符串 baseFolder = “/var/www/app/documents/”;

//仅当你不允许导航到其他子文件夹时才使用它
字符串文件名 = paths.GET (“../../../../../etc/passwd “) .getFileName () .toString ();

//安全:读取 /var/www/app/documents/passwd
路径 filePath = paths.GET(基本文件夹 + 文件名);
列表<String>行 = files.readallLines (FilePath);

Javascript-不

如果不解析完整路径,或者确保仅使用路径的文件名部分,则会使代码容易受到路径遍历的影响。

const fs = require ('fs');

const baseFolder = “/var/www/app/documents/”;
const fileName = “../../../../etc/passwd”;

//不安全:读取 /etc/passwd
const data = fs.readFileSync(基础文件夹 + 文件名,'utf8');

Javascript-安全-规范

在此示例中,我们通过解析完整(绝对)路径并确保文件解析路径位于我们的基本文件夹内来防止路径遍历。

const fs = 需要(“fs”);
常量路径 = 需要(“路径”);

const baseFolder = “/var/www/app/documents/”;
const fileName = “../../../../etc/passwd”;

const normalizedPath = path.normalize(path.join(基本文件夹,文件名));

//安全:读取 /var/www/app/documents/passwd
const data = fs.readFileSync(NormalizedPath,'utf8');

Javascrip-安全-文件名

在此示例中,我们通过仅使用路径的文件名部分来防止路径遍历,从而确保无法遍历指定的文件夹。

const fs = 需要(“fs”);
常量路径 = 需要(“路径”);

const baseFolder = “/var/www/app/documents/”;
const fileName = path.basename (“../../../../../../etc/passwd “);

//安全:读取 /var/www/app/documents/passwd
const data = fs.readFileSync(路径.join(基本文件夹,文件名),'utf8');

Python-不安全

如果不解析完整路径,或者确保仅使用路径的文件名部分,则会使代码容易受到路径遍历的影响。

baseFolder = “/var/www/app/documents/”
文件名 = “../../../../../../etc/passwd”

# 不安全:读取 /etc/passwd
FileContents = 打开(基本文件夹 + 文件名).read ()

Python-安全-规范

在此示例中,我们通过解析完整(绝对)路径并确保文件解析路径位于我们的基本文件夹内来防止路径遍历。

导入 os.path

baseFolder = “/var/www/app/documents/”
文件名 = “../../../../../../etc/passwd”

normalizedPath = os.path.normpath(基础文件夹 + 文件名)

# 安全:拒绝任何试图读取指定基础文件夹之外的文件的尝试
如果不是 normalizedPath.startsWith(基本文件夹):
返回 “正在尝试读出基本文件夹”

# 安全:读取 /var/www/app/documents/passwd
文件内容 = 打开(标准化路径).read ()

Python-安全-文件名

在此示例中,我们通过仅使用路径的文件名部分来防止路径遍历,从而确保无法遍历指定的文件夹。

导入 os.path

baseFolder = “/var/www/app/documents/”
文件名 = os.path.basename (“../../../../../../../etc/passwd “)

# 安全:读取 /var/www/app/documents/passwd
FileContents = 打开 (os.path.join(基本文件夹,文件名)) .read ()