作者:Gilberto Najera-Gutierrez 译者:飞龙 协议:CC BY-NC-SA 4.0
每个渗透测试的目标都是识别应用、服务器或网络中的可能缺陷,它们能够让攻击者有机会获得敏感系统的信息或访问权限。检测这类漏洞的原因不仅仅是了解它们的存在以及推断出其中的漏洞,也是为了努力预防它们或者将它们降至最小。
这一章我们,我们会观察一些如何预防多数 Web 应用漏洞的例子和推荐,根据 OWASP:
https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project
根据 OWASP,Web 应用中发现的最关键的漏洞类型就是一些代码的注入攻击,例如 SQL 注入、OS 命令注入、HTML 注入(XSS)。
这些漏洞通常由应用的弱输入校验导致。这个秘籍中,我们会设计一些处理用户输入和构造所使用的请求的最佳实践。
为了防止注入攻击,首先需要合理校验输入。在服务端,这可以由编写我们自己的校验流程来实现,但是最佳选择是使用语言自己的校验流程,因为它们更加广泛使用并测试过。一个极好的激励就是 PHP 中的filter_var
,或者 ASP.NET 中的 校验助手。例如,PHP 中的邮箱校验类似于:
function isValidEmail($email){
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
在客户端,检验可以由创建 JavaScript 校验函数来完成,使用正则表达式。例如,邮箱检验流程是:
function isValidEmail (input) {
var result=false;
var email_regex = /^[a-zA-Z0-9._-]+@([a-zA-Z0-9.-]+\.)+[azA-Z0-9.-]{2,4}$/;
if ( email_regex.test(input) ) {
result = true;
}
return result;
}
对于 SQL 注入,避免拼接输入值为查询十分关键。反之,使用参数化查询。每个编程语言都有其自己的版本:
PHP MySQLLi:
$query = $dbConnection->prepare('SELECT * FROM table WHERE name = ?');
$query->bind_param('s', $name);
$query->execute();
C#:
string sql = "SELECT * FROM Customers WHERE CustomerId = @ CustomerId";
SqlCommand command = new SqlCommand(sql); command.Parameters.Add(new SqlParameter("@CustomerId", System. Data.SqlDbType.Int));
command.Parameters["@CustomerId"].Value = 1;
Java:
String custname = request.getParameter("customerName");
String query = "SELECT account_balance FROM user_data WHERE user_ name =? ";
PreparedStatement pstmt = connection.prepareStatement( query );
pstmt.setString( 1, custname);
ResultSet results = pstmt.executeQuery( );
考虑注入出现的时机,对减少可能的损失总量也有帮助。所以,使用低权限的系统用户来运行数据库和 Web 服务器。
确保输入用于连接数据库服务器的用户不是数据库管理员。
禁用甚至删除允许攻击者执行系统命令或提权的储存过程,例如 MSSQL 服务器中的xp_cmdshell
。
预防任何类型代码注入攻击的主要部分永远是合理的输入校验,位于服务端也位于客户端。
对于 SQL 注入,始终使用参数化或者预编译查询。而不是拼接 SQL 语句和输入。参数化查询将函数参数插入到 SQL 语句特定的位置,消除了程序员通过拼接构造查询的需求。
这个秘籍中,我们使用了语言内建的校验函数,但是如果你需要校验一些特殊类型的参数,你可以通过使用正则表达式创建自己的版本。
除了执行正确校验,我们也需要在一些人蓄意注入一些代码的情况下,降低沦陷的影响。这可以通过在操作系统的上下文中为 Web 服务器合理配置用户权限,以及在数据库服务器上下文中配置数据库和 OS 来实现。
对于数据校验来讲,最有用的工具就是正则表达式。在处理和过滤大量信息的时候,它们也能够让渗透测试变得更容易。所以好好了解它们很有必要。我推荐你查看一些站点:
带有缺陷的身份验证和会话管理是当今 Web 应用中的第二大关键的漏洞。
身份验证是用户证明它们是它们所说的人的过程。这通常通过用户名和密码来完成。一些该领域的常见缺陷是宽松的密码策略,以及隐藏式的安全(隐藏资源缺乏身份验证)。
会话管理是登录用户的会话标识符的处理。在 Web 服务器中,这可以通过实现会话 Cookie 和标识来完成。这些标识符可以植入、盗取,或者由攻击者使用社会工程、XSS 或 CSRF 来“劫持”。所以,开发者必须特别注意如何管理这些信息。
这个秘籍中,我们会设计到一些实现用户名/密码身份验证,以及管理登录用户的会话标识符的最佳实践。
!
、&
、#
、%
,以及其它)。http://
存在于 URL 的情况下会重定向到安全的选项,并防止“无效证书”信息的覆写。例如使用 Burp Suite 的时候会出现的情况。更多信息请见: https://www.owasp.org/index.php/HTTP_Strict_Transport_Security 。身份校验机制通常在 Web 应用中简化为用户名/密码登录页面。虽然并不是最安全的选择,但它对于用户和开发者最简单,以及当密码被盗取时,最重要的层面就是它们的强度。
我们可以从这本书看到,密码强度由破解难度决定,通过爆破、字典或猜测。这个秘籍的第一个提示是为了使密码更难以通过建立最小长度的混合字符集来破解,难以通过排除更直觉的方案(用户名、常见密码、公司名称)来猜测,并且通过使用强哈希或加密储存,难以在泄露之后破解。
对于会话管理来说,过期时间、唯一性和会话 ID 的强度(已经在语言内建机制中实现),以及 Cookie 设置中的安全都是关键的考虑因素。
谈论身份校验安全的最重要的层面是,如果消息可以通过中间人攻击拦截或者服务,没有任何安全配置、控制或强密码是足够安全的。所以,合理配置的加密通信频道的使用,例如 TLS,对保护我们的用户身份数据来说极其重要。
OWASP 拥有一些非常好的页面,关于身份校验和会话管理。我们推荐你在构建和配置 Web 应用时阅读并仔细考虑它们。
我们之前看到,跨站脚本,在展示给用户的数据没有正确编码,并且浏览器将其解释并执行为脚本代码时发生。这也存在输入校验因素,因为恶意代码通常由输入变量插入。
这个秘籍中,我们会涉及开发者所需的输入校验和输出编码,来防止应用中的 XSS 漏洞。
应用存在 XSS 漏洞的第一个标志是,页面准确反映了用户提供的输入。所以,尝试不要使用用户提供的信息来构建输出文本。
当你需要将用户提供的信息放在输出页面上时,校验这些数据来防止任何类型代码的插入。我们已经在 A1 中看到如何实现它。
出于一些原因,如果用户被允许输入特殊字符或者代码段,在它插入到输出之前,过滤或合理编码文本。
对于过滤,在 PHP 中,可以使用filter_var
。例如,如果你想让字符串为邮件地址:
$email = "john(.doe)@exa//mple.com";
$email = filter_var($email, FILTER_SANITIZE_EMAIL);
echo $email;
对于编码,你可以在 PHP 中使用htmlspecialchars
:
$str = "The JavaScript HTML tags are <script> for opening, and </ script> for closing.";
echo htmlspecialchars($str);
在 .NET 中,对于 4.5 及更高版本,System.Web.Security.AntiXss
命名空间提供了必要的工具。对于 .NET 框架 4 及之前的版本,你可以使用 Web 保护库:http://wpl.codeplex.com/
。
同样,为了防止储存型 XSS,在储存进数据库或从数据库获取之前,编码或过滤每个信息片段。
不要忽略头部、标题、CSS和页面的脚本区域,因为它们也可以被利用。
除了合理的输入校验,以及不要将用户输入用作输出信息,过滤和编码也是防止 XSS 的关键层面。
过滤意味着从字符串移除不允许的字符。这在输入字符串中存在特殊字符时很实用。
编码将特殊字符转换为 HTML 代码表示。例如,&
变为&
、<
变为<
。一些应用允许在输入字符串中使用特殊字符,对它们来说过滤不是个选择。所以应该在将输入插入页面,或者储存进数据库之前编码输入。
OWASP 拥有值得阅读的 XSS 预防速查表:
当应用允许攻击者(也是校验过的用户)仅仅修改请求中的,直接指向系统对象的参数值,来访问另一个未授权的对象时,就存在不安全对象的直接引用(IDOR)。我们已经在本地文件包含和目录遍历漏洞中看到了一些例子。
根据 OWASP,IDOR 是 Web 应用中第四大关键漏洞。这些漏洞通常由不良的访问控制实现,或者“隐藏式安全”策略(如果用户不能看到它,他们就不能知道它的存在)导致。这些在没有经验的开发者之中是个常见的做法。
这个秘籍中,我们会涉及在设计访问控制机制时应该考虑的关键层面,以便预防 IDOR 漏洞。
URL?page="restricted_page"
),而是要创建索引,并在内部处理它(URL?page=2
)。不安全对象的直接引用在 Web 应用中的表现形式有所不同,从目录遍历到敏感的 PDF 文档的引用。但是它们的大多数都依赖于一个假设,即用户永远不会找到方法来访问不能显式访问的东西。
为了防止这种漏洞,需要在设计和开发期间执行一些积极操作。设计可靠授权机制,来验证尝试访问一些信息的用户的关键是,是否用户真正允许访问它。
将引用对象映射为下标来避免对象名称直接用于参数值(就像 LFI 中的那样)是第一步。攻击者也可以修改下标,这很正常,就像对对象名称所做的那样。但是数据库中存在下标-对象的表的话,添加字段来规定访问所需的权限级别,比起没有任何表并且直接通过名称来访问资源,要容易得多。
之前说过,下标的表可能包含访问对象所需的权限级别,更加严格的话还有拥有者的 ID。所以,它只能够在请求用户是拥有者的情况下访问。
最后,输入校验必须存在于 Web 应用安全的每个层面。
系统的默认配置,包括操作系统和Web 服务器,多数用于演示和强调他们的基本或多数有关特性,并不能保护它们不被攻击。
一些常见的可能使系统沦陷的默认配置,是数据库、Web 服务器或CMS 安装时创建的默认管理员账户,以及默认管理员页面、默认栈回溯错误信息,以及其它。
这个秘籍中,我们会涉及 OWASP Top 10 中第五大关键漏洞,错误的安全配置。
可能的话,删除所有管理员应用,例如 Joomla 的 admin,WordPress 的 admin,PhpMyAdmin,或者 Tomcat Manager。如果不能这样,使它们只能从本地网络访问,例如,在 Apache 服务器中禁止来自外部网络的 PhpMyAdmin 访问,修改httd.conf
文件(或者相应的站点配置文件)。
<Directory /var/www/phpmyadmin>
Order Deny,Allow
Deny from all
Allow from 127.0.0.1 ::1
Allow from localhost
Allow from 192.168
Satisfy Any
</Directory>
这会首先禁止所有地址到phpmyadmin
目录的访问,之后它允许任何来自 locaohost 和以192.168
开头的地址的请求,这是本地网络的地址。
修改所有 CMS、应用、数据库、服务器和框架的所有管理员密码,使其强度足够。一些应用的例子是:
禁用所有不必要或未使用的服务器和应用特性。从日常或每周来看,新的漏洞都出现在 CMS 的可选模块和插件中。如果你的应用不需要它们,就不要激活它们。
始终执行最新的安全补丁和更新。在生成环境,建立测试环境来预防使站点不工作的缺陷十分重要,因为新版本存在一些兼容性及其它问题。
建立不会泄露跟踪信息、软件版本、程序组件名称,或任何其它调试信息的自定义的错误页面。如果开发者需要跟踪错误记录或者一些一些标识符对于技术支持非常必要,创建带有简单 ID 和错误描述的索引,并只展示 ID 给用户。所以当错误报告给相关人士的时候,它们会检查下标并且知道发生了什么错误。
采取“最小权限原则”。每个用户在每个层面(操作系统、数据库、或应用)上都应该只能够严格访问正确操作所需的信息。
使用上一个要点来考虑账户,构建安全配置的原则,并且将其应用到每个新的实现、更新或发布以及当前系统中。
强制定期的安全测试或审计,来帮助检测错误配置或遗漏的补丁。
谈论安全和配置问题时,“细节决定成败”十分恰当。web 服务器、数据库服务器、CMS、或者应用配置应该在完全可用和实用、以及保护用户和拥有者之间取得平衡。
Web 应用的一个常见错误配置就是一些 Web 管理站点对整个互联网都可见。这看起来并不是个大问题,但是我们应该知道,管理员登录页面更容易吸引攻击者,因为它可以用于获得高级权限等级,并且任何 CMS、数据或者站点管理工具都存在已知的常用默认密码列表。所以,我们强烈推荐不要把这些管理站点暴露给外部,并且尽可能移除它们。
此外,强密码的使用,以及修改默认密码(即使它们是强密码),在发布应用到公司内部网络,以及互联网的时候需要强制执行。当今,当我们将服务器开放给外部的时候,它收到的第一个流量就是端口扫描,登录页面请求,以及登录尝试,甚至在第一个用户知道该应用之前。
自定义错误页面的使用有助于安全准备,因为 Web 服务器和应用中的默认的错误信息展示太多的信息(从攻击者角度),它们关于错误、所使用的编程语言、栈回溯、所使用的数据库、操作系统以及其它。这些信息不应该暴露,因为它会帮助我们理解应用如何构建,并且提供所使用软件的版本和名称。攻击者通过这些信息就可以搜索已知漏洞,并构造更加有效的攻击过程。
一旦我们的服务器上的部署应用和所有服务都正确配置,我们就可以制订安全原则并且将其应用于所有要配置的新服务器或者已更新的服务器,或者当前带有合理规划的生产服务器。
这个配置原则需要持续测试,以便改进它以及持续保护新发现的漏洞。
当应用储存或使用敏感信息(信用卡号码、社会安全号码、健康记录,以及其它)时,必须采取特殊的手段来保护它们,因为它可能为负责保护它们的组织带来严重的信用、经济或者法律损失,以及被攻破。
OWASP Top 10 的第六名是敏感数据泄露,它发生在应该保护的数据以纯文本泄露,或者带有弱安全措施的时候。
这个秘籍中,我们会涉及一些处理、传递和储存这种数据类型的最佳实践。
如果你使用的敏感数据可以在使用之后删除,那么删除它。最好每次使用信用卡的时候询问用户,避免被盗取。
在处理支付的时候,始终使用支付网关,而不是在你的服务器中储存数据。查看:http://ecommerce-platforms.com/ ecommerce-selling-advice/choose-payment-gateway-ecommerce-store
。
如果我们需要储存敏感数据,我们要采取的第一个保护就是使用强密码算法和相应的强密钥来加密。推荐 Twofish、AES、RSA 和三重 DES。
密码储存在数据库的时候,应该以单项哈希函数的哈希形式存储,例如,bcypt、scrypt 或 SHA-2。
确保所有敏感文档只能被授权用户访问。不要在 Web 服务器的文档根目录储存它们,而是在外部目录储存,并通过程序来访问。如果出于某种原因必须在服务器的文档根目录储存敏感文件,使用.htaccess
文件来防止直接访问:
Order deny,allow
Deny from all
禁用包含敏感数据的页面缓存。例如,在 Apache 中我们可以禁用 PDF 和 PNG 的缓存,通过httpd.conf
中的下列设置:
<FilesMatch "\.(pdf|png)>
FileETag None
Header unset ETag
Header set Cache-Control "max-age=0, no-cache, no-store, mustrevalidate"
Header set Pragma "no-cache"
Header set Expires "Wed, 11 Jan 1984 05:00:00 GMT"
</FilesMatch>
如果你允许文件上传,始终使用安全的通信频道来传输敏感数据,也就是带有 TLS 的 HTTPS,或者 FTPS(SSH 上的 FTP)。
对于保护敏感数据,我们需要最小化数据泄露或交易的风险。这就是正确加密储存敏感数据,以及保护加密密钥是所做的第一件事情的原因。如果可能不需要储存这类数据,这只是个理想选择。
密码应该使用单向哈希算法,在将它们储存到数据之前计算哈希。所以,即使它们被盗取,攻击者也不能立即使用它们,并且如果密码强度足够,哈希也是足够强的算法,它就不会在短时间内被破解。
如果我们在 Apache 服务器的文档根目录(/var/ www/html/
)储存敏感文档或数据,我们就通过 URL 将这些信息暴露用于下载。所以,最好将它储存到别的地方,并编写特殊的服务端代码来在必要时获取它们,并带有预先的授权检查。
此外,例如 Archive.org、WayBackMachine 或者 Google 缓存页面,可能在缓存含有敏感信息的文件时,以及我们没能在应用的上一个版本有效保护它们时产生安全问题。所以,不允许缓存此类文档非常重要。
功能级别的访问控制是访问控制的一种,用于防止匿名者或未授权用户的功能调用。根据 OWASP,缺乏这种控制是 Web 应用中第七大严重的安全问题。
这个秘籍中,我们会看到一些推荐来提升我们的应用在功能级别上的访问控制。
开发者只在工作流的开始检查授权,并假设下面的步骤都已经对用户授权,这是常见的现象。攻击者可能会尝试调用某个功能,它是工作流的中间步骤,并由于控制缺失而能够访问它。
对于权限,默认禁止所有用户是个最佳实践。如果我们不知道一些用户是否有权访问一些功能,那么它们就不应该执行。将你的权限表转化为授权表。如果某些用户在某些功能上没有显式的授权,则禁止它们的访问。
在为你的应用功能构建或实现访问控制机制的时候,将所有授权储存在数据库中,或者在配置文件中(数据库是最好的选项)。如果用户角色和权限被硬编码,它们就会难以维护、修改或更新。
当 Web 应用没有使用会话层面或者操作层面的标识,或者标识没有正确实现的时候,它们就可能存在跨站请求伪造漏洞,并且攻击者可以强迫授权用户执行非预期的操作。
CSRF 是当今 Web 应用的第八大严重漏洞,根据 OWASP, 并且我们在这个秘籍中会看到如何在应用中防止它。
防止 CSRF 完全是确保验证过的用户是请求操作的人。由于浏览器和 Web 应用的工作方式,最佳实践是使用标识来验证操作,或者可能的情况下使用验证码来控制。
由于攻击者打算尝试破解标识的生成,或者验证系统,以一种攻击者不能猜测的方式,安全地生成它们非常重要。而且要使它们对每个用户和每个操作都唯一,因为复用它们会偏离它们的目的。
验证码控制和重新授权有时候会非常麻烦,使用户反感。但是如果操作的重要性值得这么做,用户可能愿意接受它们来换取额外的安全级别。
有一些编程库有助于实现 CSRF 防护,节省开发者的大量工作。例子之一就是 OWASP 的 CSRF Guard:https://www.owasp.org/index.php/CSRFGuard
。
现在的 Wbe 应用不再是单个开发者,也不是单个开发团队的作品。开发功能性、用户友好、外观吸引人的 Web 应用涉及到三方组件的使用,例如编程库,外部服务的 API(Fackbook、Google、Twitter),开发框架,以及许多其它的组件,其中编程、测试和打补丁的工作量很少,甚至没有。
有时候这些三方组件被发现存在漏洞,并且它们将这些漏洞转移到了我们的应用中。许多带有漏洞组件的应用很长时间都没有打补丁,使整个组织的安全体系中出现缺陷。这就是 OWASP 将使用带有已知漏洞的三方组件划分为 Web 应用安全的第九大威胁的原因。
这个秘籍中,我们会了解,如果一些我们所使用的组件拥有已知漏洞,应该到哪里寻找,以及我们会查看一些这种漏洞组件的例子。
https://technet.microsoft.com/library/security/
,Joomla:https:// developer.joomla.org/security-centre.html
,和Oracle:http://www. oracle.com/technetwork/topics/security/alerts-086861.html
。我们可以使用它们来保持我们用于应用的软件的更新。http://www.cvedetails.com/
)。这里我们可以搜索多数厂商或产品,或者列出所有已知漏洞(至少是拥有 CVE 号码的漏洞),并且按照年份、版本和 CVSS 分数排列。https://www.exploit-db.com/
)。 Full disclosure 邮件列表(http://seclists.org/fulldisclosure/
),以及 Packet Storm 的文件部分(https://packetstormsecurity.com/files/
)。考虑在我们的应用中使用三方软件组件之前,我们需要查看它的安全信息,并了解,我们所使用的组件是否有更稳定更安全的版本或替代。
一旦我们选择了某个,并且将其包含到我们的应用中,我们需要使其保持更新。有时它可能涉及到版本改动以及没有后向兼容,但是这是我们想要维持安全的代价。如果我们不能更新或为高危漏洞打补丁,我们还可以使用 WAF(Web 应用防火墙)和IPS(入侵检测系统)来防止攻击。
除了在执行渗透测试的时候比较实用,下载和漏洞发布站点可以被系统管理员利用,用于了解可能出现什么攻击,它们的原理,以及如何保护应用避免它们。
根据 OWASP,未验证的重定向和转发是 Web 应用的第十大严重安全问题。它发生在应用接受 URL 或内部页面作为参数来执行重定向或转发操作的时候。如果参数没有正确验证,攻击者就能够滥用它来使其重定向到恶意网站。
这个秘籍中,我们会了解如何验证我们接受的用于重定向或转发的参数,我们需要在开发应用的时候实现它。
重定向和转发是钓鱼者和其它社会工程师最喜欢用的工具,并且有时候我们对目标没有任何安全控制。所以,即使它不是我们的应用,它的安全问题也会影响我们的信誉。这就是最好不要使用它们的原因。
如果这种重定向的目标是已知站点,例如 Fackbook 或 Google,我们就可以在配置文件或数据表中建立目标目录,并且不需要使用客户端提供的参数来实现。
如果我们构建包含所有允许的重定向和转发 URL 的数据表,每个都带有 ID,我们可以将 ID 用于参数,而不是目标本身。这是一种白名单的形式,可以防止无效目标的插入。
最后同样是校验。我们始终要校验每个来自客户端的输入,这非常重要,因为我们不知道用户要输入什么。如果我们校验了重定向目标的正确性,除了恶意转发或重定向之外,我们还可以防止可能的 SQL 注入、XSS或者目录遍历。所以,它们都是相关的。