Web 安全

# XSS

XSS (opens new window)(跨网站指令码 Cross-site scripting) 是一种网站应用程式的安全漏洞攻击,是代码注入的一种。它允许恶意使用者将程式码注入到网页上,其他使用者在观看网页时就会受到影响。这类攻击通常包含了 HTML 以及使用者端脚本语言。

XSS 分为三种:反射型存储型DOM-based

# 如何攻击

XSS 通过修改 HTML 节点或者执行 JS 代码来攻击网站。例如,通过 URL 获取某些参数:

<!-- http://www.domain.com?name=<script>alert(1)</script> -->
<div>{{name}}</div>

上述 URL 输入可能会将 HTML 改为 <div><script>alert(1)</script></div>,这样页面中就凭空多了一段可执行脚本。这种攻击类型是反射型攻击,也可以说是 DOM-based 攻击

也有另一种场景,比如写了一篇包含攻击代码 <script>alert(1)</script> 的文章,那么可能浏览文章的用户都会被攻击到。这种攻击类型是存储型攻击,也可以说是 DOM-based 攻击,并且这种攻击打击面更广。

# 如何防御

# 字符转义

最普遍的做法是转义输入输出的内容,对于引号,尖括号,斜杠进行转义:

function escape(str) {
    str = str.replace(/&/g, '&amp;')
    str = str.replace(/</g, '&lt;')
    str = str.replace(/>/g, '&gt;')
    str = str.replace(/"/g, '&quto;')
    str = str.replace(/'/g, '&#39;')
    str = str.replace(/`/g, '&#96;')
    str = str.replace(/\//g, '&#x2F;')
    return str
}

通过转义可以将攻击代码 <script>alert(1)</script> 变成:

// -> &lt;script&gt;alert(1)&lt;&#x2F;script&gt;
escape('<script>alert(1)</script>');

对于显示富文本来说,不能通过上面的办法来转义所有字符,因为这样会把需要的格式也过滤掉。这种情况通常采用白名单过滤的办法,当然也可以通过黑名单过滤,但是考虑到需要过滤的标签和标签属性实在太多,更加推荐使用白名单的方式。

var xss = require('xss');
var html = xss('<h1 id="title">XSS Demo</h1><script>alert("xss");</script>');
// -> <h1>XSS Demo</h1>&lt;script&gt;alert("xss");&lt;/script&gt;
console.log(html);

以上示例使用了 js-xss 来实现。可以看到在输出中保留了 h1 标签且过滤了 script 标签。

# CSP

另外,我们还可以通过 CSP 来尽量减少 XSS 攻击。CSP 本质上也是建立白名单,规定了浏览器只能够执行特定来源的代码。

CSP (opens new window)(内容安全策略 Content Security Policy) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段。

通常可以通过 HTTP Header 中的 Content-Security-Policy 来开启 CSP:

  • 只允许加载本站资源:

    Content-Security-Policy: default-src 'self'
    
  • 只允许加载 HTTPS 协议图片:

    Content-Security-Policy: img-src https://*
    
  • 允许加载任何来源框架:

    Content-Security-Policy: child-src 'none'
    

除此之外,<meta> 元素也可以被用来配置该策略,例如:

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">

更多属性可以查看这里 (opens new window)

# CSRF

CSRF (opens new window)(跨站请求伪造 Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF,是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。跟 XSS 相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。

简单点说,CSRF 就是利用用户的登录态发起恶意请求。

# 如何攻击

假设网站中有一个通过 GET 请求提交用户评论的接口,那么攻击者就可以在钓鱼网站中加入一个图片,图片的地址就是评论接口:

<img src="http://www.domain.com/xxx?comment='attack'" />

如果接口是 POST 提交的,就相对麻烦点,需要用表单来提交接口:

<form action="http://www.domain.com/xxx" id="CSRF" method="post">
    <input name="comment" value="attack" type="hidden" />
</form>

# 如何防御

防范 CSRF 可以遵循以下几种规则:

  • GET 请求不对数据进行修改
  • 阻止不明外域的访问
    • 同源检测,阻止第三方网站请求接口
    • Samesite Cookie,不让第三方网站访问到用户 Cookie
  • 提交时要求附加本域才能获取的信息
    • CSRF Token
    • 双重 Cookie 验证

SameSite

可以对 Cookie 设置 SameSite 属性。该属性设置 Cookie 不随着跨域请求发送,该属性可以很大程度减少 CSRF 的攻击,但是该属性目前并不是所有浏览器都兼容。

验证 Referer

对于需要防范 CSRF 的请求,我们可以通过验证 Referer 来判断该请求是否为第三方网站发起的。

Token

服务器下发一个随机 Token(算法不能复杂),每次发起请求时将 Token 携带上,服务器验证 Token 是否有效。

# 点击劫持

# 如何攻击

点击劫持是一种视觉欺骗的攻击手段。攻击者将需要攻击的网站通过 iframe 嵌入自己的网页中,并将 iframe 设置为透明,然后诱使用户在该页面上进行操作,此时用户将在不知情的情况下点击透明的 iframe 页面。

# 防御方法

还是让后端大佬解决,使用一个 HTTP 响应头 —— X-Frame-OptionsX-Frame-Options 可以说是为了解决点击劫持而生的,它有三个可选的值:

  1. DENY: 浏览器会拒绝当前页面加载任何 frame 页面
  2. SAMEORIGIN: frame 页面的地址只能为同源域名下的页面
  3. ALLOW-FROM origin: 允许 frame加载的页面地址(这是一个被弃用的指令,不再适用于现代浏览器 - MDN (opens new window)

# window.opener

# 如何攻击

window.opener 返回打开当前窗口的那个窗口的引用,例如:在 A 页面中打开了 B 页面,B 页面的 window.opener 返回 A 页面的 window

一般来说,打开同源的页面,不会有什么问题。但对于跨域的外部链接来说,存在一个被钓鱼的风险。比如你正在浏览购物网站,从当前网页打开了某个外部链接,在打开的外部页面,可以通过 window.opener.location 改写来源站点的地址。利用这一点,将来源站点改写到钓鱼站点页面上,例如跳转到伪造的高仿购物页面,当再回到购物页面的时候,是很难发现购物网站的地址已经被修改了的,这个时候你的账号就存在被钓鱼的可能了。

通过以下方式跳转链接,新页面的 window.opener 会保留原父页面 window 对象的引用:

  • 通过 window.open 打开新页面,并且该 API 第 3 个参数未设置 noopener参考文档 (opens new window)
  • 通过 a 标签打开页面,并且该 a 标签设置了 target="_blank"rel="opener" 属性

# 如何防御

  1. 通过 a 标签跳转链接,不同时设置 target="_blank"rel="opener" 属性
  2. 通过 window.open 跳转链接,第 3 个参数 windowFeatures (opens new window) 设置 noopener 属性
  3. HTTP 响应头 Cross-Origin-Opener-Policy (opens new window) 的值设置为 same-origin
  4. 将外链替换为内部的跳转连接服务,跳转时先跳到内部地址,再由服务器 redirect 到外链

# 中间人攻击

# 如何攻击

中间人攻击是攻击方同时与服务端和客户端建立起了连接,并让对方认为连接是安全的,但是实际上整个通信过程都被攻击者控制了。攻击者不仅能获得双方的通信信息,还能修改通信信息。中间人攻击的本质是客户端和服务端之间的认证和信任问题。

# 如何防御

对称加密、非对称加密、混合加密技术都没有有效防止中间人攻击,因为中间人可以截取首次传输的密钥并偷天换日,而客户端或服务端并无法得知。HTTPS 作为防止中间人攻击的终极手段,引入证书机制解决了客户端和服务端的信任问题,从而较为有效的防止了中间人攻击。

# 密码安全

密码安全虽然大多是后端的事情,但是作为一名优秀的前端程序员也需要熟悉这方面的知识。

加盐

对于密码存储来说,必然是不能明文存储在数据库中的,否则一旦数据库泄露,会对用户造成很大的损失。并且不建议只对密码单纯通过加密算法加密,因为存在彩虹表的关系。

通常需要对密码加盐,然后进行几次不同加密算法的加密。

// 加盐也就是给原密码添加字符串,增加原密码长度
sha256(sha1(md5(salt + password + salt)));

但是加盐并不能阻止别人盗取账号,只能确保即使数据库泄露,也不会暴露用户的真实密码。一旦攻击者得到了用户的账号,可以通过暴力破解的方式破解密码。对于这种情况,通常使用验证码增加延时或者限制尝试次数的方式。并且一旦用户输入了错误的密码,也不能直接提示用户输错密码,而应该提示账号或密码错误。

# SQL 注入

# 如何攻击

拼接 SQL 时未仔细过滤,黑客可提交畸形数据改变语义。比如查某个文章,提交了这样的数据 id=-1 or 1=1 等。1=1 永远是 true,导致 where 语句永远是 ture。那么查询的结果相当于整张表的内容,攻击者就达到了目的。或者,通过屏幕上的报错提示推测 SQL 语句等。

# 如何防御

  1. 禁止目标网站利用动态拼接字符串的方式访问数据库
  2. 减少不必要的数据库抛出的错误信息
  3. 对数据库的操作赋予严格的权限控制
  4. 净化和过滤掉不必要的 SQL 保留字,比如:where, or, exec

# 参考资料

上次更新: 2024/4/15 02:28:03