Web 应用程序的十大安全漏洞
考虑您构建的应用程序的安全性非常重要。 解决安全问题的方法有很多,但一个有效的入门方法是解决 OWASP(开放 Web 应用程序安全项目)确定的十大安全问题。 在本文中,我们将介绍当前应用程序的十大安全漏洞。
OWASP 是一个致力于 Web 应用程序安全的国际组织,社区每四年发布一次 OWASP Top 10 报告,其中概述了 Web 应用程序最紧迫的安全问题。 我们将从 PHP 开发人员的角度来审视这些漏洞,但它们与使用任何编程语言构建应用程序都相关。
OWASP 安全漏洞:概述和比较
2021 年 OWASP Top 10 列表列出了 10 个最危险的 Web 应用程序安全漏洞。 如果我们将当前列表与 2017 年列表进行比较,我们可以看到列表中仍然存在一些安全漏洞,但位置不同,并且列表中还包含一些新的安全漏洞。
下表比较了 2017 年和 2021 年的列表。(2021 年列表中引入的安全漏洞以粗体标出,其余的只是重新排列。)
2017 OWASP Top 10 2021 OWASP Top 10 #1 – 注入 #1 – 破坏访问控制 #2 – 破坏身份验证 #2 – 加密失败 #3 – 敏感数据暴露 #3 – 注入 #4 – XML 外部实体 (XXE) #4 –不安全设计 #5 – 损坏的访问控制 #5 – 安全配置错误 #6 – 安全配置错误 #6 – 易受攻击和过时的组件 #7 – 跨站点脚本 (XSS) #7 – 识别和身份验证失败 #8 – 不安全的反序列化 #8 –软件和数据完整性故障 #9 – 使用具有已知漏洞的组件 #9 – 安全日志记录和监控故障 #10 – 日志记录和监控不足 #10 – 服务器端请求伪造 (SSRF)
该表表明大多数针对 Web 应用程序的安全漏洞都没有改变。 当开发人员试图修复这些缺陷时,他们的方法发生了变化。 与普遍的看法相反,避免这些安全漏洞很容易开始; 我们只需要知道适用于特定安全问题的一些基本规则。
让我们深入研究这些安全问题。
损坏的访问控制
根据 2021 年版的 OWASP,我们最应该关注的问题是访问控制被破坏。 损坏的访问控制就像它听起来的那样:当我们控制对应用程序的访问的方式存在缺陷时,就会发生这种情况。 损坏的访问控制示例如下图所示。
<form method="post" action="">
<input type="text" name="Username" placeholder="Your Username?">
<input type="text" name="Password" placeholder="Your Password?">
<input type="submit" name="Submit" value="Log In">
</form>
<?php
if(isset($_POST['Submit'])) {
$Username = $_POST['Username'];
$Password = $_POST['Password'];
if(!empty($Username)) {
if(!empty($Password)) {
header("loggedin_page.php");
exit;
}
}
}
?>
你看到问题了吗? 该代码只是检查用户名和密码字段是否不为空。 如何在数据库中运行几个查询以确保用户名和密码存在? 验证相关帐户? 那部分很容易被遗忘。 用户可以简单地在用户名和密码字段中输入任何内容以确保它们不为空,单击提交,用户将登录。
为避免破坏访问控制问题:在将用户标记为已登录之前,始终根据数据库验证用户名(电子邮件)和密码字段。
加密失败
加密失败以前被称为“敏感数据泄露”。 敏感数据暴露被重命名为“加密失败”,因为这解决了许多安全问题,而“敏感数据暴露”仅解决其中一个问题。
加密失败涵盖了加密数据的失败,这通常会导致敏感数据暴露。 PHP 中的密码失败主要与密码有关:使用设计缓慢的散列算法(想想 BCrypt 和 Blowfish)以外的任何东西来散列它们都是密码失败,因为其他类型的散列(MD5 和类似的)很容易且快速蛮力。
为避免加密失败:确保存储在数据库中的所有密码都使用慢于暴力破解的算法进行哈希处理。 我们建议您选择 Blowfish 或 BCrypt,因为这些算法可以长期安全使用,并经过安全专家的测试,并被证明可以抵御攻击。
如果您有很多用户使用您的应用程序,您可能还想研究加盐。 对于大量的哈希值,盐会减慢破解过程。
注入和不安全设计
注入是网络上讨论最频繁的安全问题。 每个人都听说过:将用户输入传递给数据库,你就有了注入漏洞。 注入攻击相对容易克服,但由于连接到数据库的应用程序数量庞大,它们仍然是一个问题。
下图描述了一个相关的代码示例。
<form method="post" action="">
<input type="text" name="Username" placeholder="Your Username?">
<input type="text" name="Password" placeholder="Your Password?">
<input type="submit" name="Submit" value="Log In">
</form>
<?php
if(isset($_POST['Submit'])) {
$Username = $_POST['Username'];
$Password = $_POST['Password'];
if(!empty($Username)) {
if(!empty($Password)) {
$Query = $DB->query("SELECT * FROM users WHERE username = $Username AND password = $Password");
} else {
echo "Password empty!";
exit;
}
} else {
echo "Username empty!";
exit;
}
}
?>
上面显示的缺陷是不言自明的:当任何用户输入被传递到数据库时,任何人都可以做任何想做的事。 此缺陷并非 PHP 独有。 如果您使用任何其他编程语言将用户输入直接传递到数据库中,您将遇到完全相同的问题。
成功安装的 SQL 注入攻击的后果范围很广,但在大多数情况下,它们包括以下内容:
- 攻击者可以备份用户表,对其他信息系统进行撞库攻击。 攻击者可以获得数据库内部的管理权限,然后修改或删除其中的表。
如果成功执行这两项操作,将对任何业务都不利。 用户表的数据库转储将导致它在暗网上出售,一旦出售并且攻击者获利,其他攻击者将使用该数据进行撞库攻击。 获得对存储用户数据的数据库的管理权限的攻击者也会造成严重破坏——不仅对网站用户,而且对网站所有者,他们将受到负面公众审查的风暴。
为避免 SQL 注入:将 PDO 与参数化查询一起使用。 这种方法可以保护应用程序免受 SQL 注入攻击,因为数据是与查询本身分开发送的。
前面显示的这种查询方法如下图所示(注意第 13 和 14 行的变化)。
<?php
if(isset($_POST['Submit'])) {
$Username = $_POST['Username'];
$Password = $_POST['Password'];
if(!empty($Username)) {
if(!empty($Password)) {
$Query = $DB->prepare("SELECT * FROM users WHERE username = :Username AND password = :Password");
$Query->execute(array(":Username" => $Username, ":Password" => $Password));
} else {
echo "Password empty!";
exit;
}
} else {
echo "Username empty!";
exit;
}
}
?>
另一方面,不安全设计与注入不同,有一个单独的类别。 注入是不安全设计的一部分,但不安全设计不是注入。 不安全的设计涵盖代码是如何通过设计编写的(即默认情况下)。 这意味着,如果默认情况下,您的代码将任何用户输入传递给数据库,或者如果它允许用户在不验证自己的情况下登录,或者如果它允许他们在不检查其扩展名的情况下上传文件,或者如果它返回用户输入而不验证它,你有一个不安全的设计缺陷。
为避免 SQL 注入、将用户输入传递到数据库以及不安全的设计缺陷,请确保您遵循 OWASP 或其他供应商概述的安全编码指南。 如果您遵循这些准则,您应该在这方面是安全的。
安全配置错误和过时的组件
在第五和第六位置,我们有安全配置错误和过时的组件。 这两个缺陷与前面提到的不同,但它们也是非常危险的。
在探测应用程序是否存在可能的安全配置错误漏洞时,攻击者会查看所有内容。 他们会尝试访问默认帐户、访问应受保护的页面、利用未修补的漏洞等。 在这种情况下,我们唯一的希望是针对各种漏洞更新和修补组件。 过时的组件通常带有严重的漏洞,如果被利用,可能会导致数据库泄露和敏感数据暴露、服务器宕机、声誉受损、罚款等。
这就是为什么始终执行以下操作至关重要:
- 确保您的应用程序使用的组件始终是最新的。 确保在一段时间不活动后强行注销用户。 (也就是说,确保会话在指定的时间段后过期。)如果可能,请考虑在尝试提交表单或登录网站的某个部分等一段时间后尝试实施验证码. 如果可能,请使用 Web 应用程序防火墙来保护您的 Web 应用程序免受针对它的攻击,并考虑使用类似 Cloudflare 提供的服务来同时保护您的应用程序免受 DoS 和 DDoS 攻击。
为避免错误配置和过时的组件缺陷:确保您使用的是更新的组件,并且您的代码遵循上述基本安全标准。
为了使您的应用程序更加安全,请特别注意让用户验证自己的组件。
识别和认证失败
识别和身份验证失败以前称为“身份验证失效”漏洞。 当应用程序没有充分保护自身允许用户进行身份验证的部分时,就会出现此类漏洞,这可能意味着以下一种或多种情况:
- 该应用程序不会通过使用验证码或其他措施来保护其表单免受暴力破解。 应用程序的注册页面允许使用弱密码。 (也就是说,应用程序没有定义最小密码长度。)注册表单缺少“重复密码”字段。 (也就是说,用户注册时没有仔细检查他们的密码是否正确。)密码更改表单不受 CSRF(跨站点请求伪造)的保护,让用户 B 代表用户 A 伪造请求。(即也就是说,用户 B 可以发送一个特制的 URL,打开后会更改用户 A 的密码。)可以枚举帐户:应用程序根据特定帐户是否存在于数据库中来提供不同类型的消息。 该应用程序以纯文本形式存储密码。 应用程序在输入参数中指定用户名后返回用户名,而不对其进行过滤。 (这种方法会引发 XSS 攻击,攻击者能够将恶意脚本注入网站。)
为避免识别和身份验证失败:确保安全地构建注册和登录表单。 当然,说起来容易做起来难,但是按照下面概述的步骤操作,您就可以开始了:
- 确保所有注册用户都使用安全密码。 (强制执行八个字符或更多字符的策略。)在登录尝试失败一定次数(例如五次或更多次)后,向用户显示验证码。 换句话说,强制执行速率限制。 确保用户提供的所有参数都是干净的。 (也就是说,不要在未经验证的情况下将用户输入返回给用户。这样做会引发 XSS 攻击。)确保允许更改密码的表单针对 CSRF 进行保护。 换句话说,生成一个令牌,该令牌在每次请求时都会发生变化,以使攻击者无法伪造请求并伪装成用户。 尽可能使用双因素身份验证,以避免针对您的登录表单的凭据填充攻击。
软件和完整性故障以及日志记录和监控问题
虽然与…相关的问题