前端必备安全知识手记

安全这种东西就是不发生则已,一发生则惊人。作为前端,平时对这方面的知识没啥研究,最近了解了下,特此沉淀。文章内容包括以下几个典型的 web 安全知识点:XSS、CSRF、点击劫持、SQL 注入和上传问题等(下文以小王代指攻击者),话不多说,我们直接开车:car:。

XSS(Cross-site scripting)

XSS 中文叫做跨站脚本攻击。

一句话解释:小王在网页中注入恶意代码,当用户访问页面时,代码会自动运行。

具体点说:凡是在页面中可输入的地方(如地址栏、搜索框和评论区等等)都有隐藏的风险,小王可以在这些地方输入一些特殊的代码(形如 <script>alert(1)</script> ),于是乎本来只是单纯的文本变成了可执行的代码,从而造成了攻击。你可能会觉得 alert(1) 能有多大点事,但把它换成别的威力就不一样了,它可以重定向到一个由小王控制的页面、冒充用户发送请求、窃取用户信息发送到小王的服务器上等等。当然了,这还不够具体,例子才是最直白的:point_down:。

举个栗子

因为 XSS 攻击有反射型、存储型和 DOM 型三种类型,所以我们每种都举个简单的栗子:chestnut::

反射型

反射型 XSS 主要有以下两个步骤:

  1. 假设我们访问一个 www.a.com/?name=hh 页面,后端会把其中的 name 参数取出来并拼接到 html 中(形如 <p>你好啊,hh</p> )返回给用户。
  2. 这时如果我们访问 www.a.com/?name=hh<script>alert(1)</script> 页面,此时后端会返回形如 <p>你好啊,hh<script>alert(1)</script></p> 这样的一个 html,其中的 script 脚本就会在我们的浏览器上加载执行,从而造成攻击。

你可能会觉得这种 url 看起来就有嫌疑,我们一般不访问,但如果把这个 url 转成一个短网址(所谓短网址就是把 url 转成一个 看起来简约又正常的链接),并在网上散播(通常伴随一些美女图片、外挂和金钱:moneybag:等一些具有诱惑力的内容),从而骗取用户点击这个 url,造成攻击。所以日常生活中不要乱扫二维码、乱点链接。

当然了,拼接的形式多种多样:

拼接前 拼接字符串 拼接后
<div>#{msg}</div> <script>alert(1)</script> <div><script>alert(1)</script></div>
<img src="#{image}" /> 1" onerror="alert(1) <img src="1" onerror="alert(1)" />
var data = "#{data}" hello";alert(1);" var data = "hello";alert(1);""

存储型

这种类型一般出现在评论区、论坛、留言等类似的地方,基本流程是小王提交了一个恶意评论(就是包含一些恶意代码,和上面类似,形如这样的评论 <img src="1" onerror="alert(1)" /> ),只不过该评论被保存到了数据库中,而评论对大家又都是可见的,所以任何用户访问该页面时,恶意代码就会从数据库中取出,拼接在 html 中返回给用户,页面加载评论的同时也执行了其中的恶意代码。与反射型相比,存储型的辐射范围更加广泛,处理起来也较为麻烦,有时还需要查好多库删好几张表。

DOM 型

典型的例子就是你连接了一个公共 wifi,浏览页面的时候在底部或者四周有时会有一些小广告的出现,因为这种网络劫持会动态修改页面的内容,比如在 html 中追加一些广告或暗链(暗链就是偷偷加入几个 a 标签,一般可用来增加搜索排名),要注意的是这种攻击是不涉及到服务器的,它的核心是动态修改 dom。

小小总结

总的来说就是页面可输入或拼接显示的地方就可能会有潜在的风险。我们演示个小的 demo,虽然实际上不是这样的:joy:,但意思到了:

当我们第一次输入 <script>alert(1)</script> 的时候, alert 不生效是因为 H5 中规定用 innerHTML 插入的 script 将不被执行,但是我们可以写别的,比如 <img src='x' onerror="alert(1)">

,此时 alert 就被执行了,这个在 MDN 上有明确的说明,这里给大家截图看一下:eyes::

作为前端平时要注意 innerHTML 等一些有字符串拼接并展示的地方。

防御措施

转义和过滤:

一般来可以使用编码或转义的方式来防御 XSS 攻击,就像下面这样:

function escape(str) {
    if (!str) return ''
    str = str.replace(/&/g, '&')
    str = str.replace(/</g, '<')
    str = str.replace(/>/g, '>')
    str = str.replace(/"/g, '"')
    str = str.replace(/'/g, ' ')
    return str
}
复制代码

对于富文本内容,因为其需要保留 html,所以不好直接使用转义的方法,通常使用白名单过滤(就是允许特定的 html 标签和属性),以抵御 XSS 攻击。当然了更不要相信用户的任何输入,对用户的输入进行特殊字符(如尖括号)的检查也是有必要的。

CSP(Content Security Policy)

这个其实就是设置个响应头信息( Content-Security-Policy ),告诉浏览器哪些资源是可加载执行的(实质就是白名单制度),为对应的内容指定相应的策略。具体设置大家可以查阅 MDN,这里截个 github 的图作为示例:

其他

  • HttpOnly:禁止 js 读取 cookies
  • 限制输入内容长度:提高门槛
  • 敏感操作使用验证码

CSRF(Cross-site request forgery)

CSRF 中文叫做跨站请求伪造。 一句话解释:小王盗用你的身份,以你的名义发送恶意请求(发邮件、转账之类的)。

举个栗子

这个没有分类,所以我们直接上栗子:chestnut::

https://www.a.com/index.php?money=100&to=xx
<img src="https://www.a.com/index.php?money=100&to=wo">

防御措施

CSRF 攻击通常有以下两个特点:

  • 通常发生在第三方网站。
  • 小王并不能获取到 cookies,他只是借用请求,有点借刀杀人的意思。

针对以上两点我们可以有以下几种预防措施:

referer

因为请求头中的 referer 记录了请求的来源地址,referer 会指向 b 网站,所以我们可以对 referer 进行检测,以此来禁止来自第三方网站的请求。 但每个浏览器对于 referer 的具体实现可能有差别,也不能保证浏览器自身没有安全漏洞。在有些情况下,攻击者甚至可以隐藏、修改自己请求的 referer。

token

由于 CSRF 是利用了浏览器自动携带 cookies 的特性,所以我们可以多加一个校验的字段(不通过 cookies 传递),简单点说就是:

  1. 登录的时候服务端会返回一个 token 值给前端。
  2. 前端在发送请求的时候再把这个 token 当作请求数据传递或者放在请求头里。
  3. 后端会对这个 token 值进行校验。而由 CSRF 的请求是没有携带 token 的,所以不会成功。

至于 token 是啥,这里简单说明以下,其实它是个字符串,你可以当做个临时 id,一般由 userId、随机数、时间戳通过散列算法得到(形如: let token = md5('userId' + 'time' + 'Math.random() + '...') ,其实就是调用一个函数)。

其他:

  • 加验证码
  • 可以通过设置 SameSite 的值来禁止第三方网站携带 cookies

点击劫持

这个比较好理解,我们直接看例子:

其实就是通过覆盖不可见的页面,诱导用户点击而造成的攻击行为。稍微高级一点的伪装可以怎么样呢,就是做成一个游戏界面,让你在页面上狂点,而实际上呢,则会触发一些攻击事件,比如打开摄像头、发送邮件之类的。至于我们为什么会点击,因为骗子通常知道你想要什么:sunglasses:,好奇心也驱使你这样做,并且我们当时并不觉得会有什么危险,所以我们都愿意试试看。

防御措施

禁止内嵌 iframe

让自己的网站不要被目标网站内嵌,可以通过 js 和设置头部 X-Frame-Options 实现:

if (top.location !== window.location) top.location = window.location
setHeader('X-Frame-Options', 'DENY')

验证码

增加攻击成本。

SQL 注入

因为这个和 XSS 有点小像,所以在这里稍微提及一下:hushed:。

一句话解释:小王利用潜在的数据库漏洞访问或修改数据。

举个栗子

  1. 用户填写了用户名和密码,点击登录发送了一个请求。
  2. 后端接收到请求并解析参数,将其拼装成一个 SQL 语句执行,形如 select * from user where username = '${data.username}' and pwd='${data.pwd}' ,并返回登录成功。
  3. 结果小王在填写密码的时候写上了 1' or '1'='1 ,结果后端一拼接 SQL 语句就变成了 select * from user where username = 'xiaowang' and pwd = '1' or '1'='1' ,显然这是成立的,也会返回成功。

是不是和 XSS 的拼接漏洞很像呢,本来是数据的东西变成程序执行了。

防御措施

以下内容可跳过,仅作为了解:

  • 不要给出具体的错误信息,越具体越给了小王方向
  • 检查数据类型
  • 对数据进行转义
  • 使用参数化查询:相当于分成两条语句,第一步明确目的,不能再被修改;第二步不管传啥只当作数据处理
  • 使用 ORM(对象关系映射):就是可以不用拼 SQL 语句的意思

上传问题

一句话解释:上传的文件被当做程序解析执行。

举个栗子

  1. 小王上传了一个恶意文件 xx.php,内容如下:
<?php
    phpinfo();
?>
复制代码
  1. 如果我们点击该文件的下载链接,就会执行该 php 文件(前提是服务器可以解析 php 文件,并且文件所在目录要有执行的权限)。

防御措施

if (ext === 'js') throw new Error('xxx')
if (file.type !== 'images/png') throw new Error('xxx')
我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章