泛谈 web 安全

188 阅读15分钟

随着互联网的发展,很多业务都依赖于互联网,例如说网上银行、网络购物、网游等,这也造成了很多恶意攻击者出于不良的目的对Web 服务器进行攻击,想方设法通过各种手段获取他人的个人账户信息谋取利益。所以 web 安全至关重要,本文就来谈一谈 Web 前端中常见的一些安全问题。

一、点击劫持(Clickjacking)

点击劫持(Clickjacking) 是一种在网页中将恶意代码等隐藏在看似无害的内容之下,并诱使用户点击的手段。该术语最早由雷米亚·格罗斯曼(Jeremiah Grossman)与罗伯特·汉森(Robert Hansen)于2008年提出。这种行为又被称为界面伪装(UI redressing)。

在点击劫持中,攻击者将他们的恶意链接嵌入到网站的按钮或合法页面中。在受感染的站点中,每当用户单击合法链接时,攻击者都会获取该用户的机密信息,最终损害用户在 Internet 上的隐私。

举例来说:如用户收到一封包含一段视频的电子邮件,但其中的“播放”按钮并不会真正播放视频,而是链入一购物网站。这样当用户试图“播放视频”时,实际是被诱骗而进入了一个购物网站。

防止点击劫持的方法

(1)X-Frame-Options

X-Frame-Options HTTP响应头是用来给浏览器指示允许一个页面可否在 <frame><iframe><embed> 或者 <object> 中展现的标记。站点可以通过确保网站没有被嵌入到别人的站点里面,从而避免点击劫持攻击。

X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
  • DENY

  • 表示该页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许。

  • SAMEORIGIN

  • 表示该页面可以在相同域名页面的 frame 中展示。规范让浏览器厂商决定此选项是否应用于顶层、父级或整个链,有人认为该选项不是很有用,除非所有的祖先页面都属于同一来源(origin)

(2)CSP: frame-ancestors

HTTP 头部 Content-Security-Policy(CSP) 中的frame-ancestors 指令指定了一个可以包含<frame><iframe><object><embed>,or <applet>等元素的有效父级。

当该指令设置为none时,其作用类似于X-Frame-Options: DENY 

Content-Security-Policy: frame-ancestors <source>;
Content-Security-Policy: frame-ancestors <source> <source>;
  • <host-source>:一个 Internet 主机的名称或 IP 地址,以及一个可选的URL scheme和/或端口号。这些站点的地址可以包含一个可选的引导通配符(星号, '*'),或者你可以使用通配符(同样还是, '*')作为端口地址,以示这个源的所有合法端口地址都是有效的。
    例子:

    • http://*.example.com: 匹配所有使用 http:URL scheme 并来对于 example.com 及其子域名的加载意图。
    • mail.example.com:443: 匹配所有对于 mail.example.com 在 443 端口的访问意图。
    • https://store.example.com: 匹配所有使用 https:访问 store.example.com 的意图。
  • <scheme-source>:一个 schema 配置,比如'http:'或'https:'。注意,冒号是必要的。你同样也可以指定一个 data schema(但并不推荐)。

    • 'data:' 允许 data: URIs 作为内容源。 这是不安全的,攻击者可以用此来注入恶意代码。请谨慎使用,并不要令其作用于脚本。
    • 'mediastream:' 允许 mediastream: URIs 作为内容源。
    • 'blob:' 允许 blob: URIs 作为内容源。
    • 'filesystem:' 允许 filesystem: URIs 作为内容源。
  • 'self':指向一个该受保护文档所在的源,包含同样的 URL schema 和端口号。必须用单引号设置。有些浏览器会从源指令中排除 blobfilesystem。需要允许这些内容类型的站点可以通过 Data 属性指定它们。

  • 'none':指向一个空集,意味着没有 URL 会被匹配。也需要单引号包裹设置。

二、跨站脚本(Cross-site scripting,XSS)

跨站脚本 (XSS) 攻击是一种安全漏洞,是代码注入的一种。攻击者可以利用这种漏洞在网站上注入恶意的客户端代码。若受害者运行这些恶意代码,攻击者就可以突破网站的访问限制并冒充受害者。

攻击者可以使用 XSS 向毫无戒心的用户发送恶意脚本。最终用户的浏览器无法知道该脚本不应被信任,并将执行该脚本。因为它认为脚本来自受信任的来源,所以恶意脚本可以访问浏览器保留并与该站点一起使用的任何 cookie、会话令牌或其他敏感信息。这些脚本甚至可以重写 HTML 页面的内容。

XSS 的本质:就是恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。

在以下 2 种情况下,容易发生 XSS 攻击:

  1. 数据从一个不可靠的链接进入到一个 Web 应用程序。
  2. 没有过滤掉恶意代码的动态内容被发送给 Web 用户。

恶意内容一般包括 JavaScript,但是,有时候也会包括 HTML,FLASH 或是其他浏览器可执行的代码。XSS 攻击的形式千差万别,但他们通常都会:将 cookies 或其他隐私信息发送给攻击者,将受害者重定向到由攻击者控制的网页,或是经由恶意网站在受害者的机器上进行其他恶意操作

2.1 XSS 的注入方法

  • 在 HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。
  • 内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。
  • 标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。
  • 在标签的 href、src 等属性中,包含 javascript: 等可执行代码。
  • 在 onload、onerror、onclick 等事件中,注入不受控制代码。
  • 在 style 属性和标签中,包含类似 background-image:url("javascript:..."); 的代码(新版本浏览器已经可以防范)。
  • 在 style 属性和标签中,包含类似 expression(...) 的 CSS 表达式代码(新版本浏览器已经可以防范)。

2.2 XSS 分类

(1)存储型 XSS

注入型脚本永久存储在目标服务器上。当浏览器请求数据时,脚本从服务器上传回并执行。

攻击步骤

  1. 攻击者将恶意代码提交到目标网站的数据库中。
  2. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。

(2)反射型 XSS

当用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。Web 服务器将注入脚本,比如一个错误信息,搜索结果等 返回到用户的浏览器上。由于浏览器认为这个响应来自"可信任"的服务器,所以会执行这段脚本。

攻击步骤

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。

反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等。

由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。

(3)基于 DOM 的 XSS

通过修改原始的客户端代码,受害者浏览器的 DOM 环境改变,导致有效载荷的执行。也就是说,页面本身并没有变化,但由于 DOM 环境被恶意修改,有客户端代码被包含进了页面,并且意外执行。

攻击步骤

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL。
  3. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。

2.3 XSS 预防

(1)预防存储型和反射型 XSS 攻击

存储型和反射型 XSS 都是在服务端取出恶意代码后,插入到响应 HTML 里的,攻击者刻意编写的“数据”被内嵌到“代码”中,被浏览器所执行。

预防这两种漏洞,有两种常见做法:

  • 改成纯前端渲染,把代码和数据分隔开。
  • 对 HTML 做充分转义。

纯前端渲染

纯前端渲染的过程:

  1. 浏览器先加载一个静态 HTML,此 HTML 中不包含任何跟业务相关的数据。
  2. 然后浏览器执行 HTML 中的 JavaScript。
  3. JavaScript 通过 Ajax 加载业务数据,调用 DOM API 更新到页面上。

在纯前端渲染中,我们会明确的告诉浏览器:下面要设置的内容是文本(.innerText),还是属性(.setAttribute),还是样式(.style)等等。浏览器不会被轻易的被欺骗,执行预期外的代码了。

但纯前端渲染还需注意避免 DOM 型 XSS 漏洞(例如 onload 事件和 href 中的 javascript:xxx 等,请参考下文”预防 DOM 型 XSS 攻击“部分)。

转义 HTML

如果拼接 HTML 是必要的,就需要采用合适的转义库,对 HTML 模板各处插入点进行充分的转义。

常用的模板引擎,如 doT.js、ejs、FreeMarker 等,对于 HTML 转义通常只有一个规则,就是把 & < > " ' / 这几个字符转义掉,确实能起到一定的 XSS 防护作用,但并不完善

(2)预防 DOM 型 XSS 攻击

DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。

在使用 .innerHTML.outerHTMLdocument.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent.setAttribute() 等。

(3)Content Security Policy

严格的 CSP 在 XSS 的防范中可以起到以下的作用:

  • 禁止加载外域代码,防止复杂的攻击逻辑。
  • 禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。
  • 禁止内联脚本执行(规则较严格,目前发现 GitHub 使用)。
  • 禁止未授权的脚本执行(新特性,Google Map 移动版在使用)。
  • 合理使用上报可以及时发现 XSS,利于尽快修复问题。

(4)HttpOnly

HTTP-only Cookie: 禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie。

具体预防策略可见:Cross Site Scripting Prevention Cheat Sheet

三、跨站请求伪造(Cross-site request forgery, CSRF)

CSRF(也称:XSRF)是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。

当恶意网站、电子邮件、博客、即时消息或程序导致用户的 Web 浏览器在用户通过身份验证时在受信任的站点上执行不需要的操作时,就会发生这种攻击。CSRF 攻击之所以有效,是因为浏览器请求会自动包含所有 cookie,包括会话 cookie。因此,如果用户通过了站点的身份验证,则站点无法区分合法的授权请求和伪造的经过身份验证的请求。当使用适当的授权时,这种攻击会被阻止,这意味着需要一个挑战-响应机制来验证请求者的身份和权限。

成功的 CSRF 攻击的影响仅限于易受攻击的应用程序暴露的能力和用户的权限。例如,这种攻击可能导致资金转移、更改密码或使用用户凭证进行购买。实际上,攻击者使用 CSRF 攻击使目标系统通过受害者的浏览器执行一项功能,在受害者不知情的情况下,至少在提交未经授权的事务之前。

具体预防策略可见:Cross-Site Request Forgery Prevention Cheat Sheet

四、中间人攻击(Man-in-the-middle attack,MITM)

密码学计算机安全领域中是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容。

image.png

4.1 攻击步骤

成功的 MITM 执行有两个不同的阶段:拦截和解密。

拦截

第一步是在用户流量到达预期目的地之前通过攻击者的网络拦截用户流量。

最常见(也是最简单)的方法是被动攻击,其中攻击者向公众提供免费的恶意 WiFi 热点。通常以与其位置相对应的方式命名,它们不受密码保护。一旦受害者连接到这样的热点,攻击者就可以完全了解任何在线数据交换。

希望采取更积极的拦截方法的攻击者可能会发起以下攻击之一:

  • IP 欺骗 涉及攻击者通过更改 IP 地址中的数据包标头将自己伪装成应用程序。结果,试图访问连接到应用程序的 URL 的用户被发送到攻击者的网站。
  • ARP 欺骗 是使用伪造的 ARP 消息将攻击者的 MAC 地址与局域网上合法用户的 IP 地址链接起来的过程。结果,用户发送到主机 IP 地址的数据反而被传输给了攻击者。
  • DNS 欺骗,也称为 DNS 缓存中毒,涉及渗透 DNS 服务器并更改网站的地址记录。结果,试图访问该站点的用户被更改后的 DNS 记录发送到攻击者的站点。

解密

拦截后,任何双向 SSL 流量都需要在不提醒用户或应用程序的情况下进行解密。有许多方法可以实现这一点:

  • **** 一旦向安全站点发出初始连接请求, HTTPS 欺骗就会向受害者的浏览器发送虚假证书。 它拥有与受感染应用程序相关的数字指纹,浏览器根据现有的受信任站点列表对其进行验证。然后,攻击者能够在受害者输入的任何数据被传递给应用程序之前访问它。
  • SSL BEAST  (针对 SSL/TLS 的浏览器漏洞利用)针对 SSL 中的 TLS 1.0 版漏洞。在这里,受害者的计算机感染了恶意 JavaScript,该 JavaScript 会拦截 Web 应用程序发送的加密 cookie。然后应用程序的密码块链接 (CBC) 被破坏,以便解密其 cookie 和身份验证令牌。
  • **** 当攻击者在 TCP 握手期间将伪造的身份验证密钥传递给用户和应用程序时,就会发生SSL 劫持。 这建立了看似安全的连接,而实际上,中间人控制着整个会话。
  • SSL 剥离 通过拦截从应用程序发送给用户的 TLS 身份验证,将 HTTPS 连接降级为 HTTP。攻击者向用户发送应用程序站点的未加密版本,同时保持与应用程序的安全会话。同时,攻击者可以看到用户的整个会话。

4.2 预防中间人攻击

阻止 MITM 攻击需要用户的几个实际步骤,以及应用程序的加密和验证方法的组合。

对于用户来说,这意味着:

  • 避免没有密码保护的 WiFi 连接。
  • 注意报告网站不安全的浏览器通知。
  • 不使用时立即退出安全应用程序。
  • 进行敏感交易时不使用公共网络(例如咖啡店、酒店)。

对于网站运营商而言,包括 TLS 和 HTTPS 在内的安全通信协议通过对传输的数据进行稳健的加密和身份验证来帮助减轻欺骗攻击。这样做可以防止拦截站点流量并阻止敏感数据(例如身份验证令牌)的解密。

应用程序使用 SSL/TLS 来保护其站点的每个页面而不仅仅是需要用户登录的页面被认为是最佳实践。这样做有助于降低攻击者从浏览不安全设备的用户那里窃取会话 cookie 的机会登录时网站的部分。