NodeJS路径遍历:示例及预防
让我们来看看什么是路径遍历攻击,以及在Node.js中可以采用哪些方法来阻止这种攻击。
构建一个安全而健壮的应用程序需要考虑的因素很多,并非一件容易的事情。要确保覆盖所有潜在的漏洞是一项十分艰巨的任务,这需要大量的经验和指导。在这些漏洞中,有一个和系统目录访问安全相关的漏洞,它通常会在程序进行路径遍历时受到攻击。
理解这一点有助于你找到解决问题的办法。网络上有大量相关的资源可以帮助你解决该问题,本文只是诸多资源中的一个。
本文旨在介绍路径遍历攻击的原理,以及使用Node.js可以采取哪些方法来阻止这类攻击。首先,我们将简要介绍什么是路径遍历攻击,然后将探讨一些常见的示例,最后介绍如何修复这些漏洞。通过本文的介绍,你应该对路径访问遍历有一个基本的了解,并能够在你的程序中阻止相关的漏洞。
请注意,本文是专门针对Node.js开发人员的。因此,我们希望你对Node.js开发栈有一个基本的了解。如果你还没有尝试过,请查看Node.js指南以获得更多的信息。
路径遍历介绍
什么是路径遍历攻击?它是一种利用服务器端糟糕的访问控制漏洞而实现的攻击,特别是对文件的访问。在这类攻击中,攻击者试图通过向应用程序中注入无效或恶意的内容来访问服务器上受限制的文件。可以把它看作是SQL注入,但针对的是系统目录而不是数据库。
很明显,能够访问服务器上未经授权的文件,其后果可想而知。攻击者一旦获取了这种权限,他就可以在我们的系统中造成非常严重的破坏,从而危及用户信息。
下面让我们更加深入地研究导致此漏洞的原因,以及你的服务器运行什么样的系统是何等重要。现在,让我们先看看几个路径遍历攻击的示例。
路径遍历攻击示例
你可能会问,一个典型的路径遍历攻击是什么样的?
其实很简单:
../../etc/passwd
出乎你的预料,对吗?
路径遍历攻击的核心思想就是找到一种方法,访问开发人员和应用程序不希望你访问的文件夹。因此,如果你对如何访问路径和Linux命令行有所了解的话,你就可以在不受保护的应用程序中进行相应的操作,从而实现路径遍历攻击。
下面是一些具体的例子。
相对路径攻击
相对路径攻击本质上就和我们上面所描述的一样。如果应用程序缺少有效的用户输入验证,攻击者可以利用该漏洞试图访问服务器中受限制的文件。这样情况下,passwd文件中包含我们在服务器上的密钥就有可能被泄露。
当然,你可以增强用户输入验证,以减轻这种漏洞所带来的风险。一种简单的方法是使用path.normalize()并对用户输入进行特殊字符过滤——顺便说一下,你应该始终对用户输入的内容进行过滤和验证——这可以避免许多问题。
有毒的Null字节攻击
在HTTP请求中,通过在字符串的末尾添加NULL字节和\0,攻击者可以绕开用户输入验证和过滤,从而访问未经授权的文件和目录。
那会是怎样的呢?就像这样:
/../../../../../../../../../../../../../../../../etc/passwd%00
请注意末尾的%00。我们的验证程序最终可能会将其翻译为\0.txt\0,从而允许访问passwd文件。
为了阻止此类攻击,你只需要添加下面的代码对用户输入的内容进行验证:
if (user_input.indexOf('\0') !== -1) { return respond('Access denied'); }
还算简单,对吧?
路径遍历攻击其实并不复杂。正如我们前面所提到的,它依赖于糟糕的访问控制逻辑的实现或者一些边界值判断。不过这些漏洞可能非常危险,我们应该尽可能地阻止或减少这些漏洞的发生,好消息是要做到这一点并不难。
其它阻止Node.js中路径遍历攻击的方法
当然,还有许多方法可以用来增强我们应用程序的安全性。JavaScript已经足够成熟,它提供了大量的文档用以指导我们采用不同的方法来减少攻击,这里我将列出其中一部分。
路径前缀验证
在应用程序中只允许某种级别的遍历会怎么样?在某些情况下,你可能希望应用程序允许用户在不同的文件夹中查找文件——例如,个人资料图片和文章都存放在不同的文件夹中。你可以在代码中进行硬编码来验证路径字符串,就像请求特定资源时使用的变量一样,但是这样做可能导致前缀路径遍历攻击。
如果用户被允许在应用程序中输入点和斜线等字符,那么攻击者就可以自由地遍历目录。为了阻止这种情况的发生,我们需要验证用户的输入并过滤掉这些字符,或者直接显示错误信息。
白名单
白名单是一种直接有效的方法,它可以减少漏洞被利用的可能性。当然,你不能指望白名单一定能派上用场,但在需要的时候总是能起到一定的作用。
一个简单的例子是验证用户输入的内容是否符合某个预定义的标准。例如,如果你规定应用程序只允许创建和处理路径中包含小写字母和数字字符的文件,那么你就可以验证用户输入的内容中只包含这类字符。
if (!/^[a-z0-9]+$/.test(user_input)) { return respond('Access denied'); }
通过添加以上验证方法,你可以针对恶意攻击添加一层额外的防护。
路径连接
最后,我们将实现一个通用的验证方案,为我们可能面临的所有这些漏洞提供一个较为健壮的方法,最终生成一个安全的路径字符串。
这个解决方案的代码看起来是这样的:
var root = '/var/www/'; exports.validatePath = (user_input) => { if (user_input.indexOf('\0') !== -1) { return 'Access denied'; } if (!/^[a-z0-9]+$/.test(user_input)) { return 'Access denied'; } var path = require('path'); var safe_input = path.normalize(user_input).replace(/^(\.\.(\/|\\|$))+/, ''); var path_string = path.join(root, safe_input); if (path_string.indexOf(root) !== 0) { return 'Access denied'; } return path_string; }
正如你所看到的,我们将前面讨论过的所有检查和验证方法合并到一起,包括可能对系统造成的任何风险。
写在最后
JavaScript是一种成熟而健壮的语言,因此有无数种方法可以解决这个问题。然而这些方法都不是最完美的,只有满足你的需求并进行了充分测试的方法才是最好的。
虽然解决方法看起来很简单,但要确保执行良好的路径遍历安全策略十分重要。我们需要尽可能地覆盖应用程序中的潜在缺口。当然,技术在不断更新,将来会出现更强大、更全面的解决方案来防止此类问题的发生。只要我们的方法有效且具备创造性,我们总会堵住系统中存在的漏洞。