网络安全之浏览器端的威胁要塞防御
浏览器端的威胁防御
1、用且仅用 HTTPS,防范网络攻击
众所周知,一个安全的应用需要对浏览器和 Web 服务器之间的所有连接进行加密。此外,建议禁用一些旧的密码套件和协议。使用 HTTPS 时,仅加密网站的“敏感”部分是不够的。如非这样,攻击者可以截获某个未加密的 HTTP 请求,然后伪造来自服务器的响应,返回恶意内容。幸运的是,HTTPS 目前是很容易做到的。我们可以通过 Let's Encrypt 免费获得证书,加上 CertBot 免费续期。
继续我们的清单,下一个是 HSTS 它与 HTTPS 密切相关。
2、使用 HSTS 和预加载来保护用户免受 SSL 剥离攻击
服务器可以用 HSTS 或 Strict Transport Security header 来强制进行加密连接。它表示需要一直使用 HTTPS 连接访问网站。
HSTS 可以防止 SSL 剥离攻击。所谓的 SSL 剥离攻击也就是:网络上的攻击者截获浏览器发出的第一个 HTTP 请求(通常是未加密的),并立即伪造对该请求的回复,假装是服务器并将连接降级为明文 HTTP。
值得注意的是,HSTS 仅在用户至少成功访问了一次应用程序的情况下才能生效。为了克服这个限制,可以把我们的网站提交到 https://hstspreload.org ,这样,各浏览器便可以将我们的域名通过硬编码写入到 HSTS 列表中。
如下:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
警告:
在实施 HSTS 时,将会强制进出该网站的所有网络请求均被加密,如果网站请求中仍然有纯文本,可能无法访问。所以,先设置一个小的 max-age 参数进行调试,如果一切正常工作,再加大这个值。调试成功后再加上预加载 (preload) ,把开启预加载保留在最后一步,因为关闭它是件很麻烦和痛苦的事情。
3、设置安全 Cookie,保护用户免受网络攻击
给 Cookie 加上 Secure 属性。此属性将防止 Cookie 在(意外或强制的)未加密的连接中泄漏。
Set-Cookie: foo=bar; ...other options... Secure
4、安全生成 HTML 以避免 XSS 漏洞
要避免 XSS(跨站点脚本)漏洞,可以采用下面两种方法:
-
完全静态的网站(例如 JavaScript SPA + 后端 API)。避免生成 HTML 问题的最有效方法是根本不生成 HTML,如前述方法,当然,也可以试试很酷的 NexJS。
-
模板引擎。针对传统的 Web 应用程序,其中的 HTML 大多是在后端服务器上根据提供参数动态生成的。这种情况下,不要通过字符串连接来创建 HTML 。推荐的做法是使用模板引擎,比如 PHP 语言的 Twig、Java 语言的 Thymeleaf、Python 语言的 Jinja2 等等。
此外,务必要正确配置模板引擎,从而可以自动对参数进行编码,并且不要使用任何可以绕过这种编码的“不安全”函数。不要把 HTML 放在回调函数、属性(不带引号)或 href/src 等诸如此类的地方。
① 2000 多本网络安全系列电子书
② 网络安全标准题库资料
③ 项目源码
④ 网络安全基础入门、Linux、web 安全、攻防方面的视频
⑤ 网络安全学习路线图
5、安全使用 JavaScript 以避免 XSS 漏洞
要避免 JavaScript 端的 XSS(跨站点脚本)漏洞,切忌将不受信任的数据传递到可执行代码的函数或属性中。这类常见的函数或属性包括:
-
eval、setTimeout、setInterval 等。
-
innerHTML,React's dangerouslySetInnerHTML 等。
-
onClick、onMouseEnter、onError 等。
-
href、src 等。
-
location, location.href 等。
6、沙箱处理不可信内容,避免 XSS 漏洞
最好是能避免不可信的内容,但往往又不能完全避免:例如需要从远程获取 HTML 进行展示,或者需要允许用户用所见即所得的编辑器写文章,等等。
要避免这些场景中的 XSS(跨站点脚本)漏洞,请首先使用 DOMPurify 清理内容,然后在沙箱中进行内容呈现。
即使所见即所得的编辑库声称从 HTML 中移除了恶意内容,仍然可以通过重新净化和沙箱来处理,进一步确保安全。
还有一种常见的情况是,我们想在网页展示广告等内容。这种情况下简单采用 IFrame 是不够的,因为 same-origin 策略会允许跨域的 frame 将父级 frame (也就是我们的网站)的 URL 修改为一个钓鱼网站。因此,要记住使用 IFrame 的沙箱属性来避免此种情况的发生。
7、采用内容安全策略,避免 XSS 漏洞
内容安全策略(CSP)可以很好地防御 XSS(跨站点脚本)攻击、点击劫持攻击等。所以,一定要用它!默认情况下,CSP 会阻止几乎所有的危险操作,所以额外的配置越少越好。如下:
Content-Security-Policy: default-src 'self'; form-action 'self'; object-src 'none'
它允许从 Web 应用程序的源代码加载脚本、样式、图像、字体等,但不允许加载其他内容。最值得注意的是,它将阻止内联脚本()的运行,从而更好地预防 XSS 漏洞。
此外,form-action:'self' 指令可防止在网站上创建恶意 HTML 表单(比如“您的会话已过期,请在此处输入密码”类似的表单),并将其提交到攻击者的服务器。
无论如何,都不要指定 script-src: unsafe inline ,一旦这样做,CSP 将形同虚设。
最后,如果你担心 CSP 会影响生产环境,可以先以 Report-Only 模式进行部署:
Content-Security-Policy-Report-Only: default-src 'self'; form-action 'self'
8、设置 HttpOnly 的 Cookie,保护用户免受 XSS 攻击
为 Cookie 设置 HttpOnly 属性,可以防止 Cookie 被 JavaScript 代码访问。 一旦跨脚本攻击发生,该设置也会让黑客更难窃取到 Cookie 信息。当然,有些需要被 JavaScript 代码访问的 Cookie,就不能做这个设置了。
Set-Cookie: foo=bar; ...other options... HttpOnly
9、针对下载功能,合理设置避免 XSS 漏洞
向用户提供下载功能时,在 header 中设置 Content-Disposition: attachment,从而避免 XSS 漏洞。该设置将禁止在用户浏览器直接渲染文件,从而避免 HTML 或 SVG 格式的下载文件可能引发的漏洞。如下:
Content-Disposition: attachment; filename="document.pdf"
假如我们想允许特定的文件(如 pdf)能在浏览器端打开,并且也确定这样是安全的,那么,可以针对该类型文件,将 header 省略掉或是将 attachment 换为 inline。
10、针对 API 响应,合理设置避免 XSS 漏洞
反射型文件下载(RFD)攻击往往通过构建一个 URL 从 API 下载一个恶意文件来实现。针对该类漏洞,可采用在 API HTTP 响应中返回带有安全文件名的 Content-Disposition header 来防御。
11、利用现有平台的反跨站请求伪造(CSRF)机制,避免 CSRF 漏洞
为避免反跨站请求伪造漏洞,务必确保我们所采用的平台开启了反跨站请求伪造功能,并确保该配置发挥了应有的作用。
12、验证 OAuth 身份认证的 state 参数,避免 CSRF 漏洞
有一类与 OAuth 身份认证相关的跨站请求伪造漏洞是黑客让用户不经意间采用其账户进行登录。因此,如果有使用 OAuth 身份认证,务必确保对状态(state)参数的验证。
13、正确使用 HTTP 协议,避免 CSRF 漏洞
除了 POST、PUT、PATCH、DELETE 以外,不要使用其它 HTTP 方法进行数据更改。GET 请求一般是不包含在反跨站请求伪造机制中的。
14、为 Cookie 设置同源属性,避免 CSRF、XS-leak、XSS 漏洞
为 Cookie 设置 SameSite 属性。SameSite 能防止大多数的跨站点请求伪造攻击,而且还可以防止许多跨站点泄漏的漏洞。
SameSite 属性有两种模式:宽松(lax)和严格(strict)。
宽松模式可以防止大多数跨站点计时和跨站点请求伪造攻击,但对基于 Get 请求的跨站点请求伪造漏洞无效。如下:
Set-Cookie: foo=bar; ...other options... SameSite=Lax严格模式则可以防止该类基于 Get 请求的漏洞,以及反射型的跨站点脚本漏洞。然而,严格模式不适合常规的应用程序,因为它会中断身份验证链接。如果用户已登录某个网站,现在要在新的页面打开指向该应用程序的链接,则打开的新页面将不会为该用户自动登录。由于严格模式的限制,会话 Cookie 也不会随请求一起发送。严格模式设置如下:
Set-Cookie: foo=bar; ...other options... SameSite=Strict
15、每次登录创建一个新的会话 ID,防止会话固定攻击
会话固定攻击一般是在以下情形发生:
-
攻击者将 Cookie(例如 JSESSIONID=ABC123)注入到用户的浏览器中。不用担心,攻击者有很多方法可以做到这一点。
-
用户使用其凭据登录,并在登录请求中提交攻击者设置的 JSESSIONID=ABC123 。
-
应用程序对 Cookie 和用户进行身份验证。
-
与此同时,拥有该 Cookie 的攻击者也就可以通过该用户的身份进行登录了。
为了防止出现这种情况,程序中需要在身份验证通过后,创建一个新的会话 ID 返回给用户,而不是验证可能被动了手脚的 Cookie。
16、合理命名 Cookie,防止会话固定攻击
难道 Cookie 命名也能影响到网络应用程序的安全性?确实如此!将 Cookie 采用 __Host-** 的形式来命名,浏览器将:
-
不能通过非加密的链接访问该项 Cookie, 从而避免会话固定攻击以及其它涉及到 Cookie 读取与写入的攻击;
-
不允许子域名重写该项 Cookie,从而避免来自子域名网站(抑或是被攻陷,抑或本身就是恶意的)的攻击。
该项设置示例如下:
Set-Cookie: __Host-foo=bar ...other options...
17、设置 Cache-Control header,防止用户信息被窃取
缓存是将访问过的网站、下载过的文件全部存储在硬盘的某个位置,直到有人手动删除它们。默认情况下,浏览器会对页面的一切内容进行缓存,从而加快访问速度、节约网络带宽。
要在公共网络环境保证信息安全,我们需要将所有 HTTP 响应设置一个合适的 Cache-Control header,特别是针对非公开的和动态的内容。
该项设置示例如下:
Cache-Control: no-store, max-age=0
18、设置 Clear-Site-Data header,防止用户信息被窃取
另外一个可以有效保证用户退出后记录即被清除的 header 是 Clear-Site-Data 。当用户退出登录时,可以在 HTTP 请求中携带该 header。浏览器会清除该域名下的缓存、Cookie、存储以及执行上下文。大部分浏览器都支持该 header。
该项设置示例如下:
Clear-Site-Data: "*"
19、妥当地处理“退出”,防止用户信息被窃取
用户退出登录后,务必要对访问令牌和会话识别码进行失效处理。这样,即使攻击者从访问历史/缓存/内存等地方获取到这些信息,它们也不再有效。
此外,如果有单点登录,切记要调用单点登录的退出端口。否则,因为单点登录会话仍处于活跃状态,此时的退出将会无效,只要用户再次点击“登录”,即可自动登录。
最后,清理掉你可能用到过的 Cookie、HTML5 存储等。上面提到的 Clear-Site-Data 还未被某些浏览器支持,所以最好还是手工清除一下。
20、针对 JavaScript 密码采用 SessionStorage,防止用户信息被后来者窃取
SessionStorage 类似于 LocalStorage,但对每个标签页都是独有的,而且在浏览器/标签页关闭以后将自动清除。
注意:如果要在系统内打开的多个标签页之间同步用户的授权信息,那就需要用事件来同步 sessionStorage 信息。
21、不要通过 URL 传输敏感信息
URL 设计的初衷就不是为了传输敏感信息。它会被显示在屏幕上,存储到浏览器历史记录,也容易随 referrer header 而泄漏,被记录在服务器日志等。所以,切忌在 URL 中传递敏感信息。
22、采用 Referrer 策略,防止 URL 地址泄露
默认情况下,当从系统中链接到一个外部网站时,浏览器会设置一个 Referrer 的 header 来告诉该网站此次访问的来源。这个 header 包含了整个 URL 地址,这可能就涉及到一点隐私。
可以在 HTTP 响应中设置一个 Referrer-Policy 的 header 来禁止该默认行为:
Referrer-Policy: no-referrer
23、为应用设置独立域名,防止同源应用相互干扰
因此,我们需要给每个应用一个独立的域名。所以,这种情况下应该设置为:https://app1.example.com/ 和 https://app2.example.com/
注意:位于同一个域名下的子域名是可以为整个域名设置 Cookie 的。例如 app1.example.com 可以为 example.com 设置 Cookie,而这个 Cookie 也将适用于 app2.example.com。允许为一个站点设置 Cookie 有时会给会话固定等类型的漏洞以可乘之机。公共后缀列表可以用来应对该问题。此外,也可以通过将 Cookie 命名为 __Host- 来防止其被子域名所覆盖。
24、谨慎采用 CORS(跨域资源共享)
浏览器的安全模型大部分是依赖于同源策略,它可以防止应用的跨域读取。而 CORS(跨域资源共享)则是一种允许网站进行跨域资源访问的手段。所以,决定使用它之前,最好先搞清楚自己是否真的需要。
25、限制请求来源
如果你在 api.example.com 的服务需要被来自 www.example.com 的 GET 请求访问,那么可以在 api.example.com 服务上指定如下 header:
Access-Control-Allow-Origin: https://www.example.com如果你有个公开的服务接口(比如说一个提供给互联网上 JavaScript 客户端使用的计算器服务),那么你可以指定一个随机的来源:
Access-Control-Allow-Origin: *
如果你只想让有限的几个域名访问它,那么可以在程序中读取请求的 Origin header,进行比对后处理。不过,建议使用现成的库来操作,不要徒手撸,很容易出错。
26、谨慎使用 allow credentials 选项
默认情况下,跨域资源共享是不带用户凭证的。但如果在 Web 服务器端指定如下 header,则将允许携带:
Access-Control-Allow-Origin: https://www.example.com Access-Control-Allow-Credentials: true
这对 header 组合相当危险。因为它会使跨域访问具备已登录用户的权限,并使用该权限来访问网站资源。所以,如果你不得不使用它,务必小心为上。
27、对 HTTP method 进行验证
Access-Control-Allow-Methods: GET
28、合理使用 WebSockets, 避免反跨站请求伪造等漏洞
WebSockets 迄今还是比较新的技术,技术文档较少使用它难免会有些风险。所以,采用时务必要做到以下几点:
-
对连接进行加密
就像我们应该用 https:// 而非 http:// 采用 WebSockets 时也要使用 wss:// 而非 ws://
HSTS 也会影响 WebSockets ,它会自动将非加密的 WebSocket 连接升级到 wss://
-
对连接进行鉴权
如果使用的是基于 Cookie 的鉴权机制,且 WebSocket 服务器与应用服务器在同一个域名下,那就可以在 WebSocket 中继续使用已有的会话。不过切记要对请求源进行验证!
如果不是基于 Cookie ,可以在系统中创建一个单次使用、有时间限制并与用户 IP 绑定的授权令牌,用该令牌对 WebSocket 进行授权。
-
对连接源进行确认
理解 WebSockets 的一个关键点在于要知道同源策略对其是无效的。任何一个能与你的系统建立 WebSocket 连接的网站,在使用 Cookie 鉴权的时候,都是可以直接获得用户信息的。因此,在 WebSocket 握手时,必须要确认连接源。可以通过验证请求头中的 Origin 参数来确认。
如果想要做到双重保险,可以采用反跨站请求伪造令牌作为 URL 参数。但针对每个任务则需要创建一次性的独立令牌,而不要直接使用反跨站请求伪造令牌,因为后者主要是用来为应用的其它部分提供安全保障的。
29、采用 U2F 令牌或客户端证书,保护系统关键用户免受钓鱼攻击
如果系统可能会面临钓鱼攻击的威胁,说人话也就是,“如果存在这样的可能性:攻击者创建一个假的网站,骗取管理员/CEO 或其它用户的信任,从而盗取其用户名、密码和验证码”,那么就应该使用 U2F 令牌或客户端证书来防止这种攻击,这样的话即使攻击者有了用户名、密码和验证码也无法得逞。
备注: 强调钓鱼防护对于一般用户而言往往会带来不必要的麻烦。然而,提供多一种可选项对终端用户而言也非坏事。此外,向用户提前告知钓鱼攻击的危险也是非常必要的。
30、针对跨站点泄露进行保护
跨站点泄露是一系列浏览器边信道攻击。这种攻击使恶意网站可以从其它 Web 应用程序的用户中推测出信息。
这种攻击存在已有时日,但是浏览器端却是最近才开始添加针对性的预防机制。可以在 这篇文章 中了解关于该类攻击的更多细节以及应该采取的安全控制措施。