本文不是一个大而全的课程,只是我们日常中常见的问题,因为网络安全是一个很大的话题,我们这里只介绍前端工程师应知应会的东西。大概包括 XSS, CSRF, 点击劫持,SQL注入,OS注入,请求劫持,DDOS,以及简单的防范策略。

1.XSS

XSS的英文是Cross Site Scripting也就是常说的跨站脚本攻击,因为缩写和CSS重叠,所以只能叫XSS,跨站脚本攻击是指通过存在安全漏洞的Web网站注册用户的浏览器内运行非法的HTML标签或JavaScript进行的一种攻击。

那么XSS一般是如何进行攻击的呢?假设我们页面中存在一个input或者textarea用来收集用户输入的数据,正常情况下不会有什么问题,假设用户输入的内容为 <script>alert(1)</script>。当我们将用户输入的这段内容通过innnerHTML添加到页面中时,就会运行该段代码,弹出alert。

document.body.innerHTML = inputValue;

可以发现,用户输入的js脚本是可以被执行的,这样的话就形成了一个安全漏洞,很多黑客都是先通过alert的方式先去试验网站是否可以被XSS,这也就意味着可以运行js里面的任何脚本。用户在不知情的情况下输入的账号密码会被黑客记录发送给自己,也可以通过js改写页面显示非法图片,将用户的登录状态复制到黑客的电脑上黑客可以使用用户的身份进行操作等等。

一般XSS的攻击方式有两种,一种是上面介绍的通过input输入的方式进行攻击叫做存储型,就是用户输入的内容会存储到数据库,每次打开页面都会执行,另一种是通过url参数攻击叫做反射型,假设我们网站url中携带的内容会渲染到页面。

http://localhost:8080/index.html?name=yd

黑客可以发送如下的链接给用户,用户一旦打开就会执行脚本。

http://localhost:8080/index.html?name=<script>alert(123)</script>

XSS的危害有哪些,简单来说javascript能做什么,他就可以做什么。

1.获取页面数据

2.获取Cookies

3.修改前端逻辑

4.发送请求

5.获取用户的信息和登录态

6.欺骗用户

如何防止XSS攻击呢?

  1. 可以在header中设置响应头 X-XSS-Protection,默认情况下禁止XSS攻击的,如果检测到url中存在XSS攻击,页面是拒绝访问的。但是他对存储型的攻击是无效的,只能拦截url中存在注入攻击的情况。
ctx.set('X-XSS-Protection', 0); // 允许XSS攻击

值有如下4种:

通常情况下浏览器会默认设置为1,禁止XSS攻击。

  1. CSP

内容安全策略(CSP Content Security Policy) 是一个附加的安全层,用于帮助检测和缓解某些类型的攻击,包括XSS和数据注入等攻击。这些攻击可用于实现从数据窃取到网站破坏或作为恶意软件分发版本等用途。

CSP本质上就是建立白名单,开发者明确告诉浏览器哪些外部资源可以加载和执行,我们只需要配置规则,如何拦截是浏览器自己实现的,我们可以通过这种方式来尽量减少XSS攻击。

这个策略他有如下几种方式:

// 只允许加载本站资源
Content-Security-Policy: default-src 'self'

// 只允许加载HTTPS协议图片
Content-Security-Policy: img-src https://*

// 不允许加载任何来源框架
Content-Security-Policy: child-src 'none'

一般被攻击是我们的网站执行了其他网站的js脚本,注入了黑客的js代码。假设我们的网站设置了只允许加载自己网站的代码,那么注入的js脚本就没办法执行了。

// 设置只允许执行自己网站的js脚本,
ctx.set('Content-Security-Policy', "default-src 'self'")

// 浏览器打开连接时4000端口的外部资源不能被加载
https://127.0.0.1:3000?from=<script src="http://127.0.0.1:4000/hack.js"></script>
  1. 转译字符

用户输入永远不可信任的,最普遍的做法就是转译输出的内容,对于引号,尖括号,斜杠进行转译,比如通过如下的函数,对用户输入的内容进行转译。

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;
}

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

这种转译叫做黑名单注意,就是把不安全的东西进行转译,比如说<\>, 但是有一种情况是不能进行黑名单转译的。

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

白名单的方式就是允许一部分安全的字符通过,其他的字符全部转译,这里推荐使用xss的npm包来处理。

// 引入xss
const xss = require('xss');

let html = xss('<h1 id="title">XSS Demo</h1><script>alert("xss")</script>');

// <h1 id="title">XSS Demo</h1>&lt;script&gt;alert("xss")&lt;/script&gt;

console.log(html);

可以看到这里xss保留了h1标签,因为他是安全的,对script标签进行了转译,因为他是不全安的。

一般情况用户输入的数据或者从url中获取的参数我们不建议直接使用innnerHTML插入到页面中,除了xss模块和escape方法还可以引入html模板,常见的是ejs模板。react,vue,angular等框架默认已经帮我们处理了xss。

// 转译 inputValue 内容
<%= inputValue %>

// 不转移 inputValue 内容
<%- inputValue %>
  1. HttpOnly Cookie

为什么用户的cookie可以被调取,原因是js是可以获取cookie的,我们可以通过禁止js访问cookie的方式防范这种攻击。

这是预防XSS攻击窃取用户cookie最有效的防御手段,Web应用程序在设置cookie时,将其属性设为HttpOnly, 就可以避免该网页的cookie被客户端恶意javaScript窃取,保护用户cookie信息。也就是服务在设置cookie的时候跟上HttpOnly即可。

response.addHeader('Set-Cookie', 'uid=112; path/; HttpOnly')

这样设置的cookie,js就没办法访问到了。

以上说的就是一些基本的防御XSS攻击的手段。CSP,字符转译,HttpOnly Cookie。

2.CSRF

CSRF( Cross Site Request Forgery),即跨站请求伪造,是一种常见的web攻击,它利用用户已登录的身份,在用户毫不知情的情况下,以用户的名义来完成非法操作。简单来说攻击步骤也很简单。

用户已经登录了站点A, 并且在A站点记录了登录状态(cookie),再次进来不需要登录了。在用户没有登出站点A的情况下,也就是登录态还有效时,访问了恶意攻击者提供的引诱危险站点B, B站点调用A站点的某个接口,比如说提交接口。如果A站点没有做任何的CSRF防御,就会被攻击。

原理也很简单,因为B站点调用了A站点的提交接口,根据cookie匹配原则,调用哪个站点的接口就会携带哪个站点的cookie,携带的就是用户存在A站点的cookie,这个时候提交接口传递的参数实际上是B站点提供的。在用户无意识的情况下以用户的身份调用了接口。

很多人可能会觉得,B站点调用A站点的接口跨域了啊,那怎么行。这没什么,跨域只是一种说法而已一般的跨域是前端拿不到接口的返回值,但不代表请求发不出去,这种攻击只要请求发出去了就达到攻击的目的了,返回值什么的都无所谓了。

防御CSRF的手段有三种。

第一个是禁止第三方网站携带Cookie,但是有兼容性问题,第二个方式是验证请求传递过来的referrer,判断是不是一个合法的referrer。其实很多的防盗链都是验证referrer的方式。

referrer就是发送请求的那个前端页面地址,可以通过referrer的方法进行屏蔽和过滤,但是他也有一个问题https是不发送referrer的,所以也算是兼容性的问题。

目前最有效的方式还是验证码的方式或者人机交互的方式,以前可以通过CSRF调取用户资金,因为转账比较简单,但现在基本转账都会发送验证码之类的验证。

3.点击劫持

点击劫持是一种视觉欺骗的攻击手段,攻击者将需要攻击的网站通过iframe嵌套的方式嵌入自己的网页中,并将iframe设置为透明,在页面中透出一个按钮诱导用户点击。

当你点击这个按钮的时候实际上是点击到了iframe中的某个按钮上,触发iframe嵌入网站的功能,比如想要给一个页面点赞,就可以把这个页面通过iframe伪装。

要防御这个其实很简单,只需要设置X-FRAME-OPTIONS响应头,X-FRAME-OPTIONS是一个http响应头,在现在浏览器有一个很好的支持,这个http响应头就是为了防御用iframe嵌套的点击劫持攻击。

该响应头有三个值可选分别是

DENY 表示页面不允许通过iframe的方式展示

SAMEORIGIN 表示页面可以在相同域名下通过iframe的方式展示

ALLOW-FROM 表示页面可以在指定来源的iframe中展示

ctx.set('X-FRAME-OPTIONS', 'DENY')

也可直接通过js判断是否在iframe中,不过该方法也有问题,跨域情况下内层的页面是无法操作外层的location的。

if (self !== top) {
    top.location.href = self.location.href; // 将外层的location修改为内层的location
    document.body.innnerHTML = ''; // 清除页面内容
}

4.SQL注入

SQL注入是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编写时的疏忽,通过SQL语句,实现无账号登录,甚至篡改数据库。SQL注入攻击比较偏向后端,前端同学了解即可。

假设我们的sql是下面这个样子的,查询数据库用户表中是否存在用户名为userName变量,密码为password变量的用户。

const sql = 'select * from user_table where username= "'+ userName +'" and password = "' + password + '"';

当用户输入了正确的用户名和密码的时候不会有什么问题

const userName = 'yd';
const password = '123456';

const sql = 'select * from user_table where username= "'+ userName +'" and password = "' + password + '"';

// select * from user_table where username= "yd" and password = "123456"

但是如果黑客输入的密码是1"or"1"="1 就会出现问题, 这是一条永远可以执行成功的sql。username等于yd ,password等于或者1=1;恒成立的sql。

const userName = 'yd';
const password = '1"or"1"="1';

const sql = 'select * from user_table where username= "'+ userName +'" and password = "' + password + '"';

// select * from user_table where username= "yd" and password = "1" or"1"="1"

一般情况下我们是不允许拼接sql的,所有的查询语句建议使用数据库提供的参数化查询接口,参数化的语句使用参数而不是将用户输入变量嵌入到SQL语句中,既不要直接拼接SQL语句,例如Node.js中的mysqljs库中的query方法。

const sql = ` SELECT * user_table WHERE username = ? AND password = ?`
res = await mysql.query(sql, [ctx.request.body.username, ctx.request.body.password]);

除此之外,要严格限制web应用的数据库的操作权限,给此用户提供仅仅能够满足其工作的最低权限,从而最大限度的减少注入攻击对数据库的危害,后端代码检查输入的数据是否符合预期,严格限制变量的类型,例如使用正则表达式进行一些匹配处理。对进入数据库的特殊字符(',",\,<,>,&,,;)等,进行转译处理,或编码转换*。基本上所有的后端语言都有对字符串进行转译处理的方法,比如lodash的lodash._escapehtmlchar库。

5.OS命令注入

OS命令注入和SQL注入差不多,只不过SQL注入的是针对数据库的,而OS命令注入是针对操作系统的,OS命令注入攻击指通过web应用,执行非法的操作系统命令达到攻击的目的,只要在能调用shell函数的地方就有存在被攻击的风险,倘若调用shell时存在疏漏,就可以执行插入的非法命令。

以Node.js为例,加入在接口中需要从 github 下载用户指定的项目

const exec = require('mz/child_process').exec;
const params = { /* 用户输入的参数 */};
exec(`git clone ${params.repo}/some/path`);

如果传入的参数如下会怎样

https://github.com/xx/xx.git && rm -rf /* &&

这个时候如果用户的权限很大的话,就会执行rm -rf /*, 删掉服务器中所有内容。

6.请求劫持

下面这种不是我们前端的概念,但是很常用,这里也说一下,了解一下即可。

请求劫持分为两种,一种是DNS劫持,一种是HTTP劫持。

DNS服务器也就是域名服务器,他会把域名转换为ip地址,如果这个被篡改了,那跳转的网站就不是意向中的网站了。我们电脑中以一个host文件,那就是本地DNS,如果遇到DNS劫持可以查看本地host文件是否被篡改了。

HTTP劫持比较常见,HTTP本身是明文传输,并且传输的工程中很可能中间的某一环节被篡改。比如我们经常遇到这样的情况,我们再火车站链接了火车站的wifi,这个时候我们无论打开什么页面出现的都是登录wifi的页面。这个其实就是在路由器层对你访问的站点做了篡改,都没到运营商那一环。

HTTP劫持只能升级HTTPS了,因为他本身就是明文传输。

7.DDOS

这里参考了阮一峰老师的博客[DDOS 攻击的防范教程]。

DDOS不是一种攻击,而是一大类攻击的总称,他有几十种类型,新的攻击方法还在不断发明出来,网站运行的各个环节,都可以是攻击目标。只要把一个环节攻破,使得整个流程跑不起来,就达到了瘫痪服务的目的。

其中比较常见的一种攻击是CC攻击。他就是简单粗暴的送来大量正常的请求,超出服务器的最大承受量,导致宕机。

我遭遇的就是CC攻击,最多的时候全世界大概20多个IP地址轮流发出请求,每个地址的请求在每秒200 ~ 300次,我看访问日志的时候,就觉得那些请求像洪水一样涌来,一眨眼就是一大堆,几分钟的时间,日志文件的体积就大了100MB。

说实话,这是能算小攻击,但是我的个人网站没有任何防护,服务器还是跟其他人共享的,这种流量一来立刻就下线了。

现在的这种DDOS攻击方式一般是不会到达你的服务器本身的,因为大量的请求来了以后很可能在机房路由器中就全部占满了,请求已经到不了服务器层了,一般这种情况运营商直接就会把服务器下线掉了。所以服务器层基本不容易做控制。

我们可以备份网站,就是你要有一个备份网站,或者最低限度有一个临时主页。生产服务器万一下线了,可以立刻切换到备份网站,不至于毫无办法。

备份网站不一定是全功能的,如果能做到全静态浏览,就能满足需求。最低限度应该可以显示公告,告诉用户,网站出了问题,正在全力抢修。这种临时主页建议放到带宽大,可以应对攻击的网站上。

还可以通过HTTP请求的拦截,如果恶意请求有特征,对付起来很简单:直接拦截它就行了,可以在硬件,服务器,防火墙中进行拦截。

HTTP 拦截有一个前提,就是请求必须有特征。但是,真正的 DDOS 攻击是没有特征的,它的请求看上去跟正常请求一样,而且来自不同的 IP 地址,所以没法拦截。这就是为什么 DDOS 特别难防的原因。

DDOS 攻击的成本还是比较高的,我们可以通过宽带扩容 + CDN的方式来提高攻击成本。

8 .爬虫

爬虫可以爬取网站中的内容,Node中可以使用cheerio和https模块进行演示。

const cheerio = require('cheerio');
const https = require('https');

let html = '';

const $ = '';

https.get('url', res => {
    res.on('data', data => {
        html += data; // 保存返回的数据
    });
    res.on('finish', () => {
        $ = cheerio.load(html); // cheerio解析数据
        // $就是拿到的dom树, 想jq一样。
    })
})

cheerio用法类似于jquery,https可以发送https请求。在finish方法中表示获取到了页面的html。

防御爬虫的方式比较多样。比如说验证浏览器的UA,referrer 或者验证码。还有比如查看单位时间的访问次数,访问量。

还可以进行关键信息使用图片混淆,比如说有些文字我们直接让接口返回图片进行渲染。本身SPA单页面就是一种反爬取的手段,不过他最大的一个缺点就是对搜索引擎不友好。搜索引擎用的就是爬虫技术。所以后面又推出了SSR渲染来解决这个问题。

还有一些比较高级的防御手段,就是前端的一些技术限制。

字体乱序法,服务返回给前端的html中,文字和用户实际看到的不一致,比如说服务返回的div总内容是4998,而页面真正展示的是1995,他的做法也很简单,使用一个特殊的字体库,因为字体渲染的时候会去查找字体库,以字体库的样子渲染。在这个字体库中,4对应1,8对应5,就可以了。

还可以将网站重要的字体,生成图片,通过iconfont的方式来渲染。

还有一种canvas指纹反爬方法,canvas指纹的含义是,因为不同硬件对canvas支持不同,因此你只要画一个很复杂的canvas,那么得出的image,总是存在像素级别的误差。考虑到爬虫代码都是统一的,就算起selenium,也是ghost的,因此指纹一般都是一致的,因此绕过几率非常低。但事实上并不是如此,国内公司通常是IT统一装机,无论是软件还是硬件都惊人的一致。所以canvas指纹相似度特别高。

最后大家可以自行了解一下无头浏览器, 这东西真的是个神器。


欢迎关注,更多内容持续更新