导读:安全永远是产品的最基础需求,如果把攻防对抗比作一场战争,Web 渗透、后门技术、横向移动、隧道搭建、权限提升等技术就像排兵布阵,用战术优势不断攻城略地。前端工程师要面临的将是正面的安全战场,厉害的研发工程师从业务代码不断精进,写到框架层,再写到系统层,每个层次都要对Web安全具备深度的思考和设计,这种不断深入的过程即是不断探求原理和创新的过程。距离用户最近的前端工程师同样距离潜在危险也最近,稍不注意就可能在代码中留下安全隐患。
在项目研发的过程中,Web前端工程师不能只是完成任务了事,而是要从端到端的角度去思考会有哪些安全问题,项目评审、研发执行和测试总结都要具有安全意识。本文希望通过站在前端工程师的角度介绍相关的安全问题和常规解决办法,建立安全意识,减少安全知识薄弱导致的重大安全问题。
01
常见安全攻击
安全无小事,在用户完整的Web全链路访问过程中,每个环节都可能产生安全问题,比如用户本身的安全意识薄弱、操作系统和浏览器漏洞、页面安全漏洞、网络劫持、后端漏洞、恶意攻击都可能一石激起千层浪。
1.1 跨站脚本攻击
XSS(Cross Site Script)跨站脚本攻击也是最常见的攻击,主要原理是恶意攻击者尝试各种可能向 Web 页面里插入恶意可执行网页脚本代码,当用户浏览该页之时嵌入其中的脚本代码会被执行,从而达到攻击者盗取用户信息或其他侵犯用户安全隐私的目的。
非持久型 XSS
持久型 XSS
基于字符集的 XSS
基于 Flash 的跨站 XSS
未经验证的跳转 XSS
非持久型XSS,一般是通过给已登录的用户发送带有恶意脚本代码参数的 URL,当 URL 地址被打开时,特有的恶意代码参数被 HTML 解析、执行。这种攻击不经过服务器存储,直接通过 HTTP 的 GET 和 POST 请求就能完成一次攻击,拿到用户隐私数据。这种url类似于:
https://xx.com/xx?query=<script>alert(document.cookie)</script>
解决办法:
尽量不要使用eval 、innerHTML、setInterval等可执行字符串的方法;渲染的数据都必须来自于服务端;尽量不要从 URL等这种 DOM API 中获取数据直接渲染;前端渲染的时候对任何的字段都需要做escape[1]编码。
持久型 XSS 漏洞(存储型),一般存在于表单提交等交互功能,如发帖留言,提交文本信息等,攻击者利用XSS 漏洞将JS代码经正常功能提交进入数据库持久保存,当其他用户页面从数据库中读出到攻击者的注入代码时,恰好将恶意代码触发执行。
解决办法:
任何字段都需要做转义处理,在入库前应该选择不相信任何[2]数据;输出给前端数据统一进行转义处理。
基于字符集的 XSS,是在页面字符集不固定的前提下绕过转义处理的一种攻击方式,用户输入非期望字符集的字符,有时会绕过转义过滤规则。
<script>alert("xss")</script>
未经验证的跳转 XSS,如果服务器端做302 跳转,跳转的地址来自用户的输入,攻击者可以输入一个恶意的跳转地址来执行脚本。
解决办法:
不仅要指定字符集为 utf-8,而且标签要闭合。
1.2 跨站请求伪造攻击
你不能保证你登录了一个网站后,不再打开一个tab页面并访问另外的网站。你不能保证你关闭浏览器了后,你本地的Cookie立刻过期,你上次的会话已经结束。所以,跨站请求伪造攻击CSRF(Cross-Site Request Forgery)就有机可乘,遇到这种攻击时将对终端用户的数据和操作指令构成严重的威胁,当受攻击的终端用户具有管理员帐户的时候,将危及整个 Web 应用程序。完成CSRF攻击必须具备三个条件:
用户已经登录了站点 A,并在本地记录了 cookie
没有登出站点 A 的情况下,访问了恶意攻击者提供的引诱危险站点 B
站点 A 没有做任何 CSRF 防御
简单的理解:攻击者可以盗用你的登陆信息,以你的身份在你不知情的情况下模拟发送各种请求。
举个例子,银行网站A,它以GET请求来完成银行转账的操作,如:”http://bank.com/Transfer.p hp?toBankId=11&money=1000“,存在一个危险网站B,它里面有一段HTML的代码如下:
<img src=”bank.com/Transfer.php?toBankId=11&money=1000“ >
你登录了银行网站A,然后访问危险网站B,这时你会发现你的银行账户少了1000块。为什么会这样呢?原因是银行网站A违反了HTTP规范,使用GET请求更新资源。在访问危险网站B的之前,你已经登录了银行网站A,而B中的img以GET的方式请求第三方资源(这里的第三方就是指银行网站了,原本这是一个合法的请求,但这里被不法分子利用了),所以你的浏览器会带上你的银行网站A的Cookie发出Get请求,去获取资源”http://bank.com/Transfer.php?toBankId=11& money=1000“,结果银行网站服务器收到请求后,认为这是一个更新资源操作(转账操作),所以就立刻进行转账操作。
解决办法:
在非 GET 请求中增加 token,渲染表单的时候,为每一个表单包含一个 csrfToken,提交表单的时候带上 csrfToken,然后在后端做 csrfToken 验证;每个 POST 请求使用验证码,但是需要用户多次输入验证码,用户体验比较差,所以不适合在业务中大量运用。归根结底还是要正确使用 GET,POST 请求和 cookie,确保用户知情操作。
1.3 目录/源码暴露漏洞
很多情况下服务器端会使用各种框架去开发,目录组织都是相似的,如果没有做任何配置就将目录结构暴露出来是很危险的事情。虽然物理路径泄露属于低风险等级缺陷,攻击者可以利用此漏洞得到信息,来对系统进一步地攻击,通常都是系统报错的信息直接显示到页面可见导致的漏洞。防止这种泄漏的方法就是做好后端程序的出错处理,定制特殊的 500 报错页面。
前端代码都是要下载到端进行执行的,所以前端并没有秘密,我们说的源码暴露指的是后端代码的暴露。那么,导致后端源代码暴露的原因是什么呢?基本上就是发生在服务器配置上了,服务器可以设置哪些路径的文件才可以被直接访问的,如果发生配置疏忽就有可能导致所有的源代码都可以通过路由访问到,所有的服务器都提供了静态资源机制。
// 配置正确,仅可访问static
app.use(serve(__dirname + '/project/static'));
// 配置错误,范围过大,存在风险
app.use(serve(__dirname + '/project'));
1.4 SQL 注入
SQL 注入漏洞是 Web 开发中最常见的一种安全漏洞。如果攻击者有途径将SQL命令输入到你的程序并被触发执行,可以用它来从数据库获取敏感信息,或者利用数据库的特性执行一系列恶意操作。造成 SQL 注入的原因是主要是程序没有有效的转义过滤用户的输入,使攻击者成功的向服务器提交恶意的 SQL 代码,导致原始的逻辑被改变,额外的执行了攻击者精心构造的恶意代码,甚至删除你的数据库数据。
假如,存在用户输入的变量username,我们之前预想的 SQL 语句是:
SELECT * FROM user WHERE username='nomi' AND psw='mypassword'
结果,攻击者巧妙的利用这个漏洞使用的奇怪用户名username将 SQL 语句变成了如下形式:
SELECT * FROM user WHERE username='nomi' OR 1 = 1 --' AND psw='xxxx'
这条 SQL 语句的查询条件永远为真,所以意思就是恶意攻击者不用我的密码,就可以登录进我的账号,然后可以在里面为所欲为。
解决办法:
有效的转义过滤用户的输入。
1.5 命令行注入
命令行注入漏洞是指web应用程序中调用了系统可执行命令的函数,而且输入参数是可控的,如果黑客拼接了注入命令,就可以进行非法操作了。
比如,攻击者能够通过 HTTP 请求直接侵入主机,执行攻击者预设的 shell 命令。
// 存在命令行注入攻击漏洞
exec(`git clone ${repo} /project/path`);
这段代码确实能够满足业务需求从指定的 git repo 上下载到想要的代码,
如果 repo 传入的是一段可执行的命令片段,恰好你的服务是用 root 权限,那结果是十分可怕的,比如:
https://github.com/xx/xx.git && rm -rf /* &&
解决办法:
有效的转义过滤用户的输入。
1.6 DDoS 攻击
DDoS 又叫分布式拒绝服务,全称 Distributed Denial of Service,其原理就是利用大量的请求造成资源过载,导致服务不可用,网络层 DDos 攻击和应用层DDos攻击都比较常见。
解决办法:
其本质其实是无法防御的,我们能做的就是不断优化服务本身部署的网络架构,以及提升网络带宽,具体防御方法请详见专业书籍。
1.7 劫持篡改
流量劫持应该算是黑产行业的一大经济支柱,流量劫持基本分两种:DNS 劫持和HTTP 劫持,目的都是一样的,就是当用户访问南辕北辙。
HTTP 劫持主要是当用户访问某个站点的时候会经过运营商网络,而不法运营商和黑产勾结能够截获 HTTP 请求返回内容,并且能够篡改内容,然后再返回给用户,从而实现劫持页面,轻则插入小广告,重则直接篡改成钓鱼网站页面骗用户隐私。
如果当用户通过某一个域名访问一个站点的时候,被篡改的 DNS 服务器返回的是一个恶意的钓鱼站点的 IP,用户就被劫持到了恶意钓鱼站点,然后继而会被钓鱼输入各种账号密码信息,泄漏隐私。
解决办法:
HTTPS 协议就是一种基于 SSL 协议的安全加密网络应用层协议,可以很好的防止 HTTP 劫持。
02
Web开发安全原则
前端工程师在开发中,从需求拿到到研发部署线上运行,面临着一些开发的安全常识,注意到这些安全原则可以有效的减少Web安全问题。
前端无秘密,尽量不要考虑前端实现加密解密;
防止用户信息被JS获取,尽量使用http-only;
不要将用户敏感信息打印到HTML文档上;
用户输入的数据要转移;
渲染的数据要转移;
确保自身后端数据是安全的;
尽量不要从URL获取数据,建立参数白名单;
资源使用HTTPS;
确保第三方资源(库)安全再使用;
确保JSBridge API安全;
域白名单,确保接口在其他域下无法使用;
上传不能让用户定义路径、文件名,限制好可上传的文件类型;
避免旁注[3];
在设置密码之类的功能上应加入密码强度要求;
采用安全编码标准。
03
总结
千里之堤毁于蚁穴,如果网站在安全上除了问题绝对属于大问题,轻则影响用户使用,重则造成巨大的损失,所以安全永远是产品的最基础需求。
参考资料
Web安全新手小白入门必读?读《Web安全深度剖析》
彻底搞懂HTTPS的加密原理
常见 Web 安全攻防总结
互联网创业公司如何防御 DDoS 攻击?
浅谈网络劫持的原理及影响
HTTP劫持
如何通过 XSS 获取受 http-only 保护的 cookie ?
Free Frontend
参考
escape 转义的目的是将一些构成 HTML 标签的元素转义,比如 <,>,空格 等,转义成 <,>, 等显示转义字符。有很多开源的工具可以协助我们做 escape 转义;
POST 请求提交表单后端没做转义直接入库 ;
就是说在保证自己的程序没问题的同时,也要保证同台服务器的其他站点没问题。至少要设置好系统权限,即使别人的站点出问题也不能影响自己的站点 ;