作者:Gilberto Najera-Gutierrez
译者:飞龙
协议:CC BY-NC-SA 4.0
简介
每个渗透测试的目标都是识别应用、服务器或网络中的可能缺陷,它们能够让攻击者有机会获得敏感系统的信息或访问权限。检测这类漏洞的原因不仅仅是了解它们的存在以及推断出其中的漏洞,也是为了努力预防它们或者将它们降至最小。
这一章我们,我们会观察一些如何预防多数 Web 应用漏洞的例子和推荐,根据 OWASP:
https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project
10.1 预防注入攻击
根据 OWASP,Web 应用中发现的最关键的漏洞类型就是一些代码的注入攻击,例如 SQL 注入、OS 命令注入、HTML 注入(XSS)。
这些漏洞通常由应用的弱输入校验导致。这个秘籍中,我们会设计一些处理用户输入和构造所使用的请求的最佳实践。
操作步骤
- 为了防止注入攻击,首先需要合理校验输入。在服务端,这可以由编写我们自己的校验流程来实现,但是最佳选择是使用语言自己的校验流程,因为它们更加广泛使用并测试过。一个极好的激励就是 PHP 中的
filter_var
,或者 ASP.NET 中的 校验助手。例如,PHP 中的邮箱校验类似于:
1 function isValidEmail($email){
2 return filter_var($email, FILTER_VALIDATE_EMAIL);
3 }
- 在客户端,检验可以由创建 JavaScript 校验函数来完成,使用正则表达式。例如,邮箱检验流程是:
1 function isValidEmail (input) {
2 var result=false;
3 var email_regex = /^[a-zA-Z0-9._-]+@([a-zA-Z0-9.-]+\.)+[azA-Z0-9.-]{2,4}$/;
4 if ( email_regex.test(input) ) {
5 result = true;
6 }
7 return result;
8 }
- 对于 SQL 注入,避免拼接输入值为查询十分关键。反之,使用参数化查询。每个编程语言都有其自己的版本:
PHP MySQLLi:
1 $query = $dbConnection->prepare('SELECT * FROM table WHERE name = ?');
2 $query->bind_param('s', $name);
3 $query->execute();
C#:
1 string sql = "SELECT * FROM Customers WHERE CustomerId = @ CustomerId";
2 SqlCommand command = new SqlCommand(sql); command.Parameters.Add(new SqlParameter("@CustomerId", System. Data.SqlDbType.Int));
3 command.Parameters["@CustomerId"].Value = 1;
Java:
1 String custname = request.getParameter("customerName");
2 String query = "SELECT account_balance FROM user_data WHERE user_ name =? ";
3 PreparedStatement pstmt = connection.prepareStatement( query );
4 pstmt.setString( 1, custname);
5 ResultSet results = pstmt.executeQuery( );
考虑注入出现的时机,对减少可能的损失总量也有帮助。所以,使用低权限的系统用户来运行数据库和 Web 服务器。
确保输入用于连接数据库服务器的用户不是数据库管理员。
禁用甚至删除允许攻击者执行系统命令或提权的储存过程,例如 MSSQL 服务器中的
xp_cmdshell
。
工作原理
预防任何类型代码注入攻击的主要部分永远是合理的输入校验,位于服务端也位于客户端。
对于 SQL 注入,始终使用参数化或者预编译查询。而不是拼接 SQL 语句和输入。参数化查询将函数参数插入到 SQL 语句特定的位置,消除了程序员通过拼接构造查询的需求。
这个秘籍中,我们使用了语言内建的校验函数,但是如果你需要校验一些特殊类型的参数,你可以通过使用正则表达式创建自己的版本。
除了执行正确校验,我们也需要在一些人蓄意注入一些代码的情况下,降低沦陷的影响。这可以通过在操作系统的上下文中为 Web 服务器合理配置用户权限,以及在数据库服务器上下文中配置数据库和 OS 来实现。
另见
对于数据校验来讲,最有用的工具就是正则表达式。在处理和过滤大量信息的时候,它们也能够让渗透测试变得更容易。所以好好了解它们很有必要。我推荐你查看一些站点:
http://www.regexr.com/ 一个很好的站点,其中我们可以获得示例和参数并测试我们自己的表达式来查看是否有字符串匹配。
http://www.regular-expressions.info 它包含教程和实例来了解如何使用正则表达式。它也有一份实用的参考,关于主流语言和工具的特定实现。
http://www.princeton.edu/~mlovett/reference/Regular-Expressions.pdf (Jan Goyvaerts 编写的《Regular Expressions, The Complete Tutorial》)就像它的标题所说,它是个正则表达式的非常完备的脚本,包含许多语言的示例。
10.2 构建合理的身份验证和会话管理
带有缺陷的身份验证和会话管理是当今 Web 应用中的第二大关键的漏洞。
身份验证是用户证明它们是它们所说的人的过程。这通常通过用户名和密码来完成。一些该领域的常见缺陷是宽松的密码策略,以及隐藏式的安全(隐藏资源缺乏身份验证)。
会话管理是登录用户的会话标识符的处理。在 Web 服务器中,这可以通过实现会话 Cookie 和标识来完成。这些标识符可以植入、盗取,或者由攻击者使用社会工程、XSS 或 CSRF 来“劫持”。所以,开发者必须特别注意如何管理这些信息。
这个秘籍中,我们会设计到一些实现用户名/密码身份验证,以及管理登录用户的会话标识符的最佳实践。
操作步骤
如果应用中存在只能由授权用户查看的页面、表单或者任何信息片段,确保在展示它们之前存在合理的身份验证。
确保用户名、ID、密码和所有其它身份验证数据是大小写敏感的,并且对每个用户唯一。
建立强密码策略,强迫用户创建至少满足下列条件的密码:
- 对于 8 个字符,推荐 10 个。
- 使用大写和小写字母。
- 至少使用一个数字。
- 至少使用一个特殊字符(空格、
!
、&
、#
、%
,以及其它)。 - 禁止用户名、站点名称、公司名称或者它们的变体(大小写转换、l33t、它们的片段)用于密码。
- 禁止使用“常见密码”列表中的密码:https://www.teamsid.com/worst-passwords-2015/ 。
- 永远不要显示用户是否存在或者信息格式是否正确的错误信息。对不正确的登录请求、不存在的用户、名称或密码不匹配模式、以及所有可能的登录错误使用相同的泛化信息。这种信息类似于:
- 登录数据不正确。
- 用户名或密码无效。
- 访问禁止。
密码不能以纯文本格式储存在数据库中。使用强哈希算法,例如 SHA-2、scrypt、或者 bcrypt,它们特别为难以使用 GPU 破解而设计。
在对比用户输入和密码时,计算输入的哈希之后比较哈希之后的字符串。永远不要解密密码来使用纯文本用户输入来比较。
避免基本的 HTML 身份验证。
-
可能的话,使用多因素验证(MFA),这意味着使用不止一个身份验证因素来登录:
- 一些你知道的(账户信息或密码)
- 一些你拥有的(标识或手机号)
- 一些你的特征(生物计量)
如果可能的话,实现证书、预共享密钥、或其它无需密码的身份校验协议(OAuth2、OpenID、SAML、或者 FIDO)。
对于会话管理,推荐使用语言内建的会话管理系统,Java、ASP.NET和 PHP。它们并不完美,但是能够确保提供设计良好和广泛测试的机制,而且比起开发团队在时间紧迫情况下的自制版本,它们更易于实现。
始终为登录和登录后的页面使用 HTTPS – 显然,要防止只接受 SSL 和 TLS v1.1 连接。
为了确保 HTTPS 能够生效,可以使用 HSTS。它是由 Web 应用指定的双向选择的特性。通过 Strict-Transport-Security 协议头,它在 http://
存在于 URL 的情况下会重定向到安全的选项,并防止“无效证书”信息的覆写。例如使用 Burp Suite 的时候会出现的情况。更多信息请见:https://www.owasp.org/index.php/HTTP_Strict_Transport_Security 。始终设置 HTTPOnly 和安全的 Cookie 属性。
设置最少但实际的会话过期时间。确保正常用户离开之后,攻击者不能复用会话,并且用户能够执行应用打算执行的操作。
工作原理
身份校验机制通常在 Web 应用中简化为用户名/密码登录页面。虽然并不是最安全的选择,但它对于用户和开发者最简单,以及当密码被盗取时,最重要的层面就是它们的强度。
我们可以从这本书看到,密码强度由破解难度决定,通过爆破、字典或猜测。这个秘籍的第一个提示是为了使密码更难以通过建立最小长度的混合字符集来破解,难以通过排除更直觉的方案(用户名、常见密码、公司名称)来猜测,并且通过使用强哈希或加密储存,难以在泄露之后破解。
对于会话管理来说,过期时间、唯一性和会话 ID 的强度(已经在语言内建机制中实现),以及 Cookie 设置中的安全都是关键的考虑因素。
谈论身份校验安全的最重要的层面是,如果消息可以通过中间人攻击拦截或者服务,没有任何安全配置、控制或强密码是足够安全的。所以,合理配置的加密通信频道的使用,例如 TLS,对保护我们的用户身份数据来说极其重要。
另见
OWASP 拥有一些非常好的页面,关于身份校验和会话管理。我们推荐你在构建和配置 Web 应用时阅读并仔细考虑它们。
- https://www.owasp.org/index.php/Authentication_Cheat_Sheet
- https://www.owasp.org/index.php/Session_Management_Cheat_Sheet
10.3 预防跨站脚本
我们之前看到,跨站脚本,在展示给用户的数据没有正确编码,并且浏览器将其解释并执行为脚本代码时发生。这也存在输入校验因素,因为恶意代码通常由输入变量插入。
这个秘籍中,我们会涉及开发者所需的输入校验和输出编码,来防止应用中的 XSS 漏洞。
工作原理
应用存在 XSS 漏洞的第一个标志是,页面准确反映了用户提供的输入。所以,尝试不要使用用户提供的信息来构建输出文本。
当你需要将用户提供的信息放在输出页面上时,校验这些数据来防止任何类型代码的插入。我们已经在 A1 中看到如何实现它。
出于一些原因,如果用户被允许输入特殊字符或者代码段,在它插入到输出之前,过滤或合理编码文本。
对于过滤,在 PHP 中,可以使用
filter_var
。例如,如果你想让字符串为邮件地址:
1 $email = "john(.doe)@exa//mple.com";
2 $email = filter_var($email, FILTER_SANITIZE_EMAIL);
3 echo $email;
对于编码,你可以在 PHP 中使用htmlspecialchars
:
1 $str = "The JavaScript HTML tags are