
随着开发框架和平台的不断成熟,需要开发者考虑的安全问题越来越少,但并不是开发者就不需要关心项目的安全问题。Linux、Tomcat 等大型项目时不时爆出安全漏洞,把网络安全话题重新拉回大众视野。现代前端开发依赖 node 作为打包、构建和依赖管理平台,最近一次安全问题就是 npm 仓库中的 event-stream 包多了一个 flatmap-stream 依赖,而这个依赖项正在窃取开发者的数字货币。
就前端项目来说,需要考虑的安全问题相对较少,受到攻击后的损失也不及服务器被攻击后那么大,前端主要需要考虑的安全问题有:
npm 生态下依赖的安全性。 npm 非常年轻,和 Java 相比包的质量参差不齐,也缺乏良好的审查机制,不过好在 npm 提供了一些安全工具帮我们审查依赖的安全问题。
XSS 跨站脚本攻击。 XSS 攻击是非常常见的攻击方式,前端开发需要日常注意,我们后面会主要讨论这类攻击。
CSRF 跨站请求伪造攻击。 CSRF 不是非常流行,目前来说容易将此类攻击的破坏性降低到可以接受的程度。
代码自查
开发者和攻击者最大的不同之处在于,开发者完全拥有代码,因此占据了主动权。一般来说攻击者的扫描都只能对线上产品进行,如果开发者在上线之前对代码进行审查和扫描,可以事半功倍。
另外内部的渗透测试也类似于模拟攻击者来进行扫描业界已知漏洞,而代码层面的审查则需要开发团队完成。
npm audit

npm audit tool/npm 安全扫描工具
为了提高 npm 依赖的安全,npm 6.1 后添加了 npm audit 工具,这个工具可以搜索当前项目中使用的依赖是否存在安全问题,并提供了 npm audit fix 工具修复。
它的工作原理是维护了一个已知不良依赖的名单,如果代码中使用了直接从 GitHub 而不是 npm 仓库中获取依赖,或不知名的依赖。npm audit 也是无法发现威胁。总的来说在加入第三方依赖时,需要谨慎考虑,不滥用依赖在前端开发也是非常重要的。
Sonarqube

sonarqube dashboard/sonarqube 管理面板
Sonarqube 应该是今年业界最为流行的代码审查工具了,Sonarqube 使用了和其他开源软件一样,软件开源服务收费的策略。我们可以自己搭建公司内部的代码审查平台,也可以直接使用 sonarqube 在线的扫描服务。公开服务的 Sonarqube,可以直接和 GitHub 和 Bitbucket 集成,但是需要提供相应的授权, 因此使用 Sonarqube 需要提供仓库权限 。
Sonarqube 中发现威胁只是它的功能之一。它提供了发现 Code Smells、Bugs、Vulnerabilities 三大特性,并且支持 Java、JavaScript 和 C#等大量语言。如果我们仅仅需要检查前端项目中代码的安全缺陷,我们可以使用另外更加轻量级能集成到构建脚本中的工具。
snyk
如果不想暴露仓库权限,并且本地扫描,可以使用 snyk 这类轻量级的扫描工具。其实 snyk 也提供类似 Sonarqube 一样的平台,但是也提供了轻量级的本地扫描。
snyk 提供了 npm 安装,可以参考以下命令,简单的集成到 CI 中:
npm install -g snyk
snyk auth
snyk test
应对 XSS 攻击
XSS 攻击通过向页面中注入可以执行的 JavaScript 代码,因为可以通过 JavaScript 在已经登陆的用户页面上执行,可以使用已经信任用户的身份来进行攻击甚至盗走用户身份信息。 XSS 分为 DOM 型、反射型、存储型三种攻击类型,反射型和存储型服务器端可以通过过滤输出处理,对前端项目来说主要针对 DOM 型攻击采取安全措施。

上面这个动图是我假设的一个漏洞,前端代码直接接收外部输入,并添加到页面上。演示攻击者使用了一段代码:
通过图片触发 onerror 事件的方式执行一段 JavaScript 代码片段,再读取 LocalStorage 中的 token,最后通过图片 Ping 的方式发送到外部网站。这只是一段作为演示用的攻击方式,这种注入 XSS 的代码叫做 XSS payload,很多强大的 payload 在网络上传播。下面我们来讨论下在开发过程中如何应对这些攻击。
使用 HTTP 头启用浏览器安全行为
浏览器有很多内置的安全行为,可以防范 XSS 攻击,第一步需要做的是在上线时合理配置服务器环境,这是一种性价比很高的方式。使用 Nginx 或者 Apache 输入相应头信息不是一件特别难的事,这里有一个 checklist,分别说明了一些必要的 HTTP 头和用途。

checklist
下面演示一个 HTTP only 的例子,即使 XSS 攻击成功,也无法盗走 token:

避免框架中的危险特性
现代前端开发中使用了一般基于常用的框架开发,框架提供了很多安全特性在输入内容到 DOM 避免了 XSS 注入,但是如果不当使用,也会有一些风险。框架为了提供更大的灵活性往往允许原生的 HTML 内容被添加到 DOM 中并提供了对应 API,但基本上也会在文档中说明。
Vue 的 v-html 指令 。 Vue 的明确提示使用该指令的前提是信任输入内容,但是大量项目使用了此指令,甚至从 URL 上获取的部分内容。下面图片中的使用方式在项目中很常见,但是如果使用 xss payload 很容易像上面演示的那样被 xss 注入攻击。

v-html brings security issue/ v-html 属性会引发安全问题
dangerouslySetInnerHTML 。React 中提供了类似的机制,不过在 API 的名称上非常醒目,原理上和 Vue 类似,不再赘述。
另外一种不当使用框架的例子是,读取原生 DOM 并添加内容的行为,这种行为不仅对项目架构造成破坏,带来维护性的困难,而且会存在 XSS 注入的风险。
启用 CSP 浏览器安全策略
在银行和金融类项目,对安全要求非常重视。大家都知道的一个例子是银行项目都实现了自己的键盘输入控件,目的是防止操作系统的键盘 Hook,这个超出前端开发需要考虑的内容。另外一个方法是启用 CSP 浏览器内容安全策略,对加载到页面上的内容进一步限制,并且 CSP 还提供了异常报告的机制。
Mozilla 的 CSP 定义
“内容安全策略 (CSP) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击等”
通俗的来说,CSP 就是通过 HTTP 头部 Content-Security-Policy 或者 HTML meta 标签定义一系列白名单规则,限制页面上脚本的执行和资源的加载来源,例如不允许执行内联代码(<script>
块内容,内联事件,内联样式),禁止执行 eval() , newFunction() , setTimeout([string], …) 和 setInterval([string], …) ,达到进一步限制页面脚本的目的。例如:
CSP 策略包括多个指令和指令值组成:

csp 指令

CSP 策略
CSP 策略中有一个特别的指令 report-uri 可以配置页面上违规后的报告,一旦浏览器检测到违规的资源加载,浏览器会发送一个 JSON 数据包到指定服务器。
应对 CSRF 攻击
CSRF 攻击者在用户已经登陆目标网站后,诱导用户访问一个攻击页面,利用用户已经获取的权限,发起伪造的请求。
举个例子:
假设 GitHub 提供了一个给仓库加星的接口,htttps://github.com/{repositoryName}/star 而且这个接口使用了 GET 方法,当然 GitHub 不会这样做。
这样攻击者就可以在 GitHub 上 Readme 文件中中增加一张图片:
当用户浏览到这个仓库的时候就会给该仓库增加一个星。
因此业界通常的做法是 避免使用 GET 操作对数据资源的修改,使用 POST 时增加一个一次性的 token 。
如果 Spring boot 中使用 Spring security,会有默认的 CsrfFilter,只需要注册 CsrfFilter 即可启用 CSRF 机制。客户端对相同的 Restful 资源发出 POST 请求之前需要首先从 GET 方法得到一个一次性的 token,否则会得到一个 403 错误。
其他
实际项目中我们还有其他的一些安全措施:
例如加密代码中的密码等敏感信息,加密本身对前端来说意义不大,但是可以增加攻击者反向分析代码的难度,屏蔽代码中可以直接搜索到的关键字,例如 Login、Password 等字符串。
另外也要防止程序报错后意外暴露一些信息给用户,面对各种各样的异常,用户不可能主动也没有能力报告错误,我们可以通过使用sentry.io之类的一些工具收集控制台报错信息 。
最后这个世界上没有绝对的安全,即使 CSP 这类极其严格的策略都有可能被绕过,前端开发中安全也需要考虑成本,应该选用性价比高的安全策略。安全也不是独立的,应该和服务器、甚至操作系统层面联合考虑,例如后端提供的资源应该是通过 ID 不可枚举的,上传文件的时候也应该嗅探内容和 MIME 信息决定文件类型。
参考链接:
https://github.com/jshanson7/npm-audit/blob/master/blacklist.json
业界安全组织 OSSTM, OWASP, TCSEC
作者介绍:
少个分号(林宁),ThoughtWorks 咨询师,长期从事国内外企业软件开发,在 React、RESTful API 等新兴技术有大量经验,喜欢开发基本组件和工具来提升效率。目前专注于富前端和 API 开发,热衷于研究前后端的集成和解耦。
本文转载自 ThoughtWorks 洞见
原文链接:
https://insights.thoughtworks.cn/web-frontend-security/
评论