安全永远是产品的最基础需求
这里总结几种常见攻击与防范
XSS,跨站脚本攻击,原理是恶意攻击者往 Web 页面里插入恶意可执行网页脚本代码,当用户浏览该页之时,嵌入其中 Web 里面的脚本代码会被执行,从而可以达到攻击者盗取用户信息或其他侵犯用户安全隐私的目的。
非持久型 XSS 漏洞,也叫反射型 XSS 漏洞,一般是通过给别人发送带有恶意脚本代码参数的 URL,当 URL 地址被打开时,特有的恶意代码参数被 HTML 解析、执行。
比如你的页面包含以下代码
<select>
<script>
document.write(''
+ '
+ location.href.substring(location.href.indexOf('default=') + 8)
+ ''
);
document.write('');
script>
select>
攻击者可以直接通过 URL (类似:https://xx.com/xx?default=alert(document.cookie)) 注入可执行的脚本代码。
特征:
非持久型 XSS 漏洞攻击有以下几点特征:
盗取用户敏感保密信息
防范
Web 页面渲染的所有内容或者渲染的数据都必须来自于服务端。
document.referrer
,document.forms
等这种 DOM API 中获取数据直接渲染。eval
, new Function()
,document.write()
,document.writeln()
,window.setInterval()
,window.setTimeout()
,innerHTML
,document.creteElement()
等可执行字符串的方法。escape 转义的目的是将一些构成 HTML 标签的元素转义,比如
<
,>
,空格
等,转义成<
,>
,等显示转义字符。有很多开源的工具可以协助我们做 escape 转义。
持久型 XSS 漏洞,也被称为存储型 XSS 漏洞,一般存在于 Form 表单提交等交互功能,如发帖留言,提交文本信息等,黑客利用的 XSS 漏洞,将内容经正常功能提交进入数据库持久保存,当前端页面获得后端从数据库中读出的注入代码时,恰好将其渲染执行。
主要注入页面方式和非持久型 XSS 漏洞类似,只不过持久型的不是来源于 URL,refferer,forms 等,而是来源于后端从数据库中读出来的数据。持久型 XSS 攻击不需要诱骗点击,黑客只需要在提交表单的地方完成注入即可,但是这种 XSS 攻击的成本相对还是很高。攻击成功需要同时满足以下几个条件:
特征
持久型 XSS 有以下几个特点:
防范
其实现在很多的浏览器以及各种开源的库都专门针对了 XSS 进行转义处理,尽量默认抵御绝大多数 XSS 攻击,但是还是有很多方式可以绕过转义规则,让人防不胜防。比如「基于字符集的 XSS 攻击」就是绕过这些转义处理的一种攻击方式,比如有些 Web 页面字符集不固定,用户输入非期望字符集的字符,有时会绕过转义过滤规则。
形成原因
由于浏览器在 meta 没有指定 charset 的时候有自动识别编码的机制,所以这类攻击通常就是发生在没有指定或者没来得及指定 meta 标签的 charset 的情况下
防范
形成原因
基于 Flash 的跨站 XSS 也是属于反射型 XSS 的一种,虽然现在开发 ActionScript 的产品线几乎没有了,但还是提一句吧,AS 脚本可以接受用户输入并操作 cookie,攻击者可以配合其他 XSS(持久型或者非持久型)方法将恶意 swf 文件嵌入页面中。主要是因为 AS 有时候需要和 JS 传参交互,攻击者会通过恶意的 XSS 注入篡改参数,窃取并操作cookie。
防范
形成原因
有一些场景是后端需要对一个传进来的待跳转的 URL 参数进行一个 302 跳转,可能其中会带有一些用户的敏感(cookie)信息。如果服务器端做302 跳转,跳转的地址来自用户的输入,攻击者可以输入一个恶意的跳转地址来执行脚本。
防范
CSRF,跨站请求伪造攻击
攻击者可以盗用你的登陆信息,以你的身份模拟发送各种请求。攻击者只要借助少许的社会工程学的诡计,例如通过 QQ 等聊天软件发送的链接(有些还伪装成短域名,用户无法分辨),攻击者就能迫使 Web 应用的用户去执行攻击者预设的操作。
例如
当用户登录网络银行去查看其存款余额,在他没有退出时,就点击了一个 QQ 好友发来的链接,那么该用户银行帐户中的资金就有可能被转移到攻击者指定的帐户中。
所以遇到 CSRF 攻击时,将对终端用户的数据和操作指令构成严重的威胁。当受攻击的终端用户具有管理员帐户的时候,CSRF 攻击将危及整个 Web 应用程序。
下图大概描述了 CSRF 攻击的原理,可以理解为有一个小偷在你配钥匙的地方得到了你家的钥匙,然后拿着要是去你家想偷什么偷什么。
完成 CSRF 攻击必须要有三个条件:
站点 A 没有做任何 CSRF 防御
你也许会问:「如果我不满足以上三个条件中的任意一个,就不会受到 CSRF 的攻击」。其实可以这么说的,但你不能保证以下情况不会发生:
CSRF 的防御可以从服务端和客户端两方面着手,防御效果是从服务端着手效果比较好,现在一般的 CSRF 防御也都在服务端进行。服务端的预防 CSRF 攻击的方式方法有多种,但思路上都是差不多的,主要从以下两个方面入手:
token
一般而言,普通的 Web 应用都是以 GET、POST 请求为主,还有一种请求是 cookie 方式。我们一般都是按照如下规则设计应用的请求:
当正确的使用了 GET 和 POST 请求之后,剩下的就是在非 GET 方式的请求中增加随机数,这个大概有三种方式来进行:
CSRF 的防御可以根据应用场景的不同自行选择。CSRF 的防御工作确实会在正常业务逻辑的基础上带来很多额外的开发量,但是这种工作量是值得的。
SQL 注入漏洞(SQL Injection)是 Web 开发中最常见的一种安全漏洞。可以用它来从数据库获取敏感信息,或者利用数据库的特性执行添加用户,导出文件等一系列恶意操作,甚至有可能获取数据库乃至系统用户最高权限。
而造成 SQL 注入的原因是因为程序没有有效的转义过滤用户的输入,使攻击者成功的向服务器提交恶意的 SQL 查询代码,程序在接收后错误的将攻击者的输入作为查询语句的一部分执行,导致原始的查询逻辑被改变,额外的执行了攻击者精心构造的恶意代码。
很多 Web 开发者没有意识到 SQL 查询是可以被篡改的,从而把 SQL 查询当作可信任的命令。殊不知,SQL 查询是可以绕开访问控制,从而绕过身份验证和权限检查的。更有甚者,有可能通过 SQL 查询去运行主机系统级的命令。
下面将通过一些真实的例子来详细讲解 SQL 注入的方式的原理。
考虑以下简单的管理员登录表单:
<form action="/login" method="POST">
<p>Username: <input type="text" name="username" />p>
<p>Password: <input type="password" name="password" />p>
<p><input type="submit" value="登陆" />p>
form>
后端的 SQL 语句可能是如下这样的:
let querySQL = `
SELECT *
FROM user
WHERE username='${username}'
AND psw='${password}'
`;
// 接下来就是执行 sql 语句...
目的就是来验证用户名和密码是不是正确,按理说乍一看上面的 SQL 语句也没什么毛病,确实是能够达到我们的目的,可是你只是站在用户会老老实实按照你的设计来输入的角度来看问题,如果有一个恶意攻击者输入的用户名是 zoumiaojiang' OR 1 = 1 --
,密码随意输入,就可以直接登入系统了。WFT!
冷静下来思考一下,我们之前预想的真实 SQL 语句是:
SELECT * FROM user WHERE username='zoumiaojiang' AND psw='mypassword'
可以恶意攻击者的奇怪用户名将你的 SQL 语句变成了如下形式:
SELECT * FROM user WHERE username='zoumiaojiang' OR 1 = 1 --' AND psw='xxxx'
在 SQL 中,--
是注释后面的内容的意思,所以查询语句就变成了:
SELECT * FROM user WHERE username='zoumiaojiang' OR 1 = 1
这条 SQL 语句的查询条件永远为真,所以意思就是恶意攻击者不用我的密码,就可以登录进我的账号,然后可以在里面为所欲为,然而这还只是最简单的注入,牛逼的 SQL 注入高手甚至可以通过 SQL 查询去运行主机系统级的命令,将你主机里的内容一览无余。
防范
防止 SQL 注入主要是不能允许用户输入的内容影响正常的 SQL 语句的逻辑,当用户的输入的信息将要用来拼接 SQL 语句的话,我们应该永远选择不相信,任何内容都必须进行转义过滤,当然做到这个还是不够的,下面列出防御 SQL 注入的几点注意事项:
命令行注入漏洞,指的是攻击者能够通过 HTTP 请求直接侵入主机,执行攻击者预设的 shell 命令。
假如现在需要实现一个需求:用户提交一些内容到服务器,然后在服务器执行一些系统命令去产出一个结果返回给用户,接口的部分实现如下:
// 以 Node.js 为例,假如在接口中需要从 github 下载用户指定的 repo
const exec = require('mz/child_process').exec;
let params = {/* 用户输入的参数 */};
exec(`git clone ${params.repo} /some/path`);
这段代码确实能够满足业务需求,正常的用户也确实能从指定的 git repo 上下载到想要的代码,可是和 SQL 注入一样,这段代码在恶意攻击者眼中,简直就是香饽饽。
如果 params.repo
传入的是 https://github.com/zoumiaojiang/zoumiaojiang.github.io.git当然没问题了。 可是如果params.repo
传入的是 https://github.com/xx/xx.git && rm -rf /* &&
恰好你的服务是用 root 权限起的就惨了。
防范
shell-escape
npm 包。还是前面的例子,我们可以做到如下:
const exec = require('mz/child_process').exec;
// 借助 shell-escape npm 包解决参数转义过滤问题
const shellescape = require('shell-escape');
let params = {/* 用户输入的参数 */};
// 先过滤一下参数,让参数符合预期
if (!/正确的表达式/.test(params.repo)) {
return;
}
let cmd = shellescape([
'git',
'clone',
params.repo,
'/some/path'
]);
// cmd 的值: git clone 'https://github.com/xx/xx.git && rm -rf / &&' /some/path
// 这样就不会被注入成功了。
exec(cmd);
无论是在何种后端语言环境中,凡是涉及到代码调用系统 shell 命令的时候都一定要谨慎。
利用大量的请求造成资源过载,导致服务不可用,
这个攻击应该不能算是安全问题,这应该算是一个另类的存在,因为这种攻击根本就是耍流氓的存在,「伤敌一千,自损八百」的行为。出于保护 Web App 不受攻击的攻防角度,还是介绍一下 DDoS 攻击吧,毕竟也是挺常见的。
DDoS 攻击可以理解为:「你开了一家店,隔壁家点看不惯,就雇了一大堆黑社会人员进你店里干坐着,也不消费,其他客人也进不来,导致你营业惨淡」。为啥说 DDoS 是个「伤敌一千,自损八百」的行为呢?毕竟隔壁店还是花了不少钱雇黑社会但是啥也没得到不是?DDoS 攻击的目的基本上就以下几个:
- 深仇大恨,就是要干死你
- 敲诈你,不给钱就干你
- 忽悠你,不买我防火墙服务就会有“人”继续干你
DDos 攻击从层次上可分为
网络层攻击
与应用层攻击
,从攻击手法上可分为快型流量攻击
与慢型流量攻击
,但其原理都是造成资源过载,导致服务不可用。
网络层的 DDoS 攻击究其本质其实是无法防御的,我们能做得就是不断优化服务本身部署的网络架构,以及提升网络带宽。当然,还是做好以下几件事也是有助于缓解网络层 DDoS 攻击的冲击:
应用层 DDoS 攻击不是发生在网络层,是发生在 TCP 建立握手成功之后,应用程序处理请求的时候,现在很多常见的 DDoS 攻击都是应用层攻击。应用层攻击千变万化,目的就是在网络应用层耗尽你的带宽,下面列出集中典型的攻击类型。
当时绿盟为了防御 DDoS 攻击研发了一款叫做 Collapasar
的产品,能够有效的防御 SYN Flood 攻击。黑客为了挑衅,研发了一款 Challenge Collapasar
攻击工具(简称 CC)。
CC 攻击的原理,就是针对消耗资源比较大的页面不断发起不正常的请求,导致资源耗尽。因此在发送 CC 攻击前,我们需要寻找加载比较慢,消耗资源比较多的网页,比如需要查询数据库的页面、读写硬盘文件的等。通过 CC 攻击,使用爬虫对某些加载需要消耗大量资源的页面发起 HTTP 请求。
DNS Flood 攻击采用的方法是向被攻击的服务器发送大量的域名解析请求,通常请求解析的域名是随机生成或者是网络世界上根本不存在的域名,被攻击的DNS 服务器在接收到域名解析请求的时候首先会在服务器上查找是否有对应的缓存,如果查找不到并且该域名无法直接由服务器解析的时候,DNS 服务器会向其上层 DNS 服务器递归查询域名信息。域名解析的过程给服务器带来了很大的负载,每秒钟域名解析请求超过一定的数量就会造成 DNS 服务器解析域名超时。
根据微软的统计数据,一台 DNS 服务器所能承受的动态域名查询的上限是每秒钟 9000 个请求。而我们知道,在一台 P3 的 PC 机上可以轻易地构造出每秒钟几万个域名解析请求,足以使一台硬件配置极高的 DNS 服务器瘫痪,由此可见 DNS 服务器的脆弱性。
针对 HTTP 协议,先建立起 HTTP 连接,设置一个较大的 Conetnt-Length,每次只发送很少的字节,让服务器一直以为 HTTP 头部没有传输完成,这样连接一多就很快会出现连接耗尽。
防范
应用层的防御有时比网络层的更难,因为导致应用层被 DDoS 攻击的因素非常多,有时往往是因为程序员的失误,导致某个页面加载需要消耗大量资源,有时是因为中间件配置不当等等。而应用层 DDoS 防御的核心就是区分人与机器(爬虫),因为大量的请求不可能是人为的,肯定是机器构造的。因此如果能有效的区分人与爬虫行为,则可以很好地防御此攻击。
利用 XSS
举个例子,如果 12306 页面有一个 XSS 持久型漏洞被恶意攻击者发现,只需在春节抢票期间在这个漏洞中执行脚本使得往某一个小站点随便发点什么请求,然后随着用户访问的增多,感染用户增多,被攻击的站点自然就会迅速瘫痪了。这种 DDoS 简直就是无本万利,不用惊讶,现在大站有 XSS 漏洞的不要太多。
来自 P2P 网络攻击
大家都知道,互联网上的 P2P 用户和流量都是一个极为庞大的数字。如果他们都去一个指定的地方下载数据,成千上万的真实 IP 地址连接过来,没有哪个设备能够支撑住。拿 BT 下载来说,伪造一些热门视频的种子,发布到搜索引擎,就足以骗到许多用户和流量了,但是这只是基础攻击。
高级的 P2P 攻击,是直接欺骗资源管理服务器。如迅雷客户端会把自己发现的资源上传到资源管理服务器,然后推送给其它需要下载相同资源的用户,这样,一个链接就发布出去。通过协议逆向,攻击者伪造出大批量的热门资源信息通过资源管理中心分发出去,瞬间就可以传遍整个 P2P 网络。更为恐怖的是,这种攻击是无法停止的,即使是攻击者自身也无法停止,攻击一直持续到 P2P 官方发现问题更新服务器且下载用户重启下载软件为止。
最后总结下,DDoS 不可能防的住,就好比你的店只能容纳 50 人,黑社会有 100 人,你就换一家大店,能容纳 500 人,然后黑社会又找来了 1000 人,这种堆人头的做法就是 DDoS 本质上的攻防之道,「道高一尺,魔高一丈,魔高一尺,道高一丈」,讲真,必要的时候就答应勒索你的人的条件吧,实在不行就报警吧。
流量劫持基本分两种:DNS 劫持
和 HTTP 劫持
,目的都是一样的,就是当用户访问 zoumiaojiang.com 的时候,给你展示的并不是或者不完全是 zoumiaojiang.com 提供的 “内容”。
DNS 劫持,也叫做域名劫持,可以这么理解,「你打了一辆车想去商场吃饭,结果你打的车是小作坊派来的,直接给你拉到小作坊去了」,DNS 的作用是把网络地址域名对应到真实的计算机能够识别的 IP 地址,以便计算机能够进一步通信,传递网址和内容等。如果当用户通过某一个域名访问一个站点的时候,被篡改的 DNS 服务器返回的是一个恶意的钓鱼站点的 IP,用户就被劫持到了恶意钓鱼站点,然后继而会被钓鱼输入各种账号密码信息,泄漏隐私。
这类劫持,要不就是网络运营商搞的鬼,一般小的网络运营商与黑产勾结会劫持 DNS,要不就是
电脑中毒,被恶意篡改了路由器的 DNS 配置,基本上做为开发者或站长却是很难察觉的,除非有用户反馈,现在升级版的 DNS 劫持还可以对特定用户、特定区域等使用了用户画像进行筛选用户劫持的办法,另外这类广告显示更加随机更小,一般站长除非用户投诉否则很难觉察到,就算觉察到了取证举报更难。无论如何,如果接到有 DNS 劫持的反馈,一定要做好以下几件事:
HTTP 劫持您可以这么理解,「你打了一辆车想去商场吃饭,结果司机跟你一路给你递小作坊的广告」,HTTP 劫持主要是当用户访问某个站点的时候会经过运营商网络,而不法运营商和黑产勾结能够截获 HTTP 请求返回内容,并且能够篡改内容,然后再返回给用户,从而实现劫持页面,轻则插入小广告,重则直接篡改成钓鱼网站页面骗用户隐私。能够实施流量劫持的根本原因,是 HTTP 协议没有办法对通信对方的身份进行校验以及对数据完整性进行校验。如果能解决这个问题,则流量劫持将无法轻易发生。所以防止 HTTP 劫持的方法只有将内容加密,让劫持者无法破解篡改,这样就可以防止 HTTP 劫持了。
HTTPS 协议就是一种基于 SSL 协议的安全加密网络应用层协议,可以很好的防止 HTTP 劫持。这里有篇 文章 讲的不错。HTTPS 在这就不深讲了,后面有机会我会单独好好讲讲 HTTPS。如果不想站点被 HTTP 劫持,赶紧将你的站点全站改造成 HTTPS 吧。
如果你的系统是有登录控制的,那就要格外小心了,因为很有可能你的系统越权操作漏洞,越权操作漏洞可以简单的总结为 「A 用户能看到或者操作 B 用户的隐私内容」,如果你的系统中还有权限控制就更加需要小心了。所以每一个请求都需要做 userid 的判断
以下是一段有漏洞的后端示意代码:
// ctx 为请求的 context 上下文
let msgId = ctx.params.msgId;
mysql.query(
'SELECT * FROM msg_table WHERE msg_id = ?',
[msgId]
);
以上代码是任何人都可以查询到任何用户的消息,只要有 msg_id 就可以,这就是比较典型的越权漏洞,需要如下这么改进一下:
// ctx 为请求的 context 上下文
let msgId = ctx.params.msgId;
let userId = ctx.session.userId; // 从会话中取出当前登陆的 userId
mysql.query(
'SELECT * FROM msg_table WHERE msg_id = ? AND user_id = ?',
[msgId, userId]
);
嗯,大概就是这个意思,如果有更严格的权限控制,那在每个请求中凡是涉及到数据库的操作都需要先进行严格的验证,并且在设计数据库表的时候需要考虑进 userId 的账号关联以及权限关联。
目录遍历漏洞指通过在 URL 或参数中构造 ../
,./
和类似的跨父目录字符串的 ASCII 编码、unicode 编码等,完成目录跳转,读取操作系统各个目录下的敏感文件,也可以称作「任意文件读取漏洞」。
目录遍历漏洞原理:程序没有充分过滤用户输入的 ../
之类的目录跳转符,导致用户可以通过提交目录跳转来遍历服务器上的任意文件。使用多个..
符号,不断向上跳转,最终停留在根 /
,通过绝对路径去读取任意文件。
目录遍历漏洞几个示例和测试,一般构造 URL 然后使用浏览器直接访问,或者使用 Web 漏洞扫描工具检测,当然也可以自写程序测试。
http://somehost.com/../../../../../../../../../etc/passwd
http://somehost.com/some/path?file=../../Windows/system.ini
# 借助 %00 空字符截断是一个比较经典的攻击手法
http://somehost.com/some/path?file=../../Windows/system.ini%00.js
# 使用了 IIS 的脚本目录来移动目录并执行指令
http://somehost.com/scripts/..%5c../Windows/System32/cmd.exe?/c+dir+c:\
防御 方法就是需要对 URL 或者参数进行 ../
,./
等字符的转义过滤。
物理路径泄露属于低风险等级缺陷,它的危害一般被描述为「攻击者可以利用此漏洞得到信息,来对系统进一步地攻击」,通常都是系统报错 500 的错误信息直接返回到页面可见导致的漏洞。得到物理路径有些时候它能给攻击者带来一些有用的信息,比如说:可以大致了解系统的文件目录结构;可以看出系统所使用的第三方软件;也说不定会得到一个合法的用户名(因为很多人把自己的用户名作为网站的目录名)。
防止这种泄漏的方法就是做好后端程序的出错处理,定制特殊的 500 报错页面。
和物理路径泄露类似,就是攻击者可以通过请求直接获取到你站点的后端源代码,然后就可以对系统进一步研究攻击。那么导致源代码暴露的原因是什么呢?基本上就是发生在服务器配置上了,服务器可以设置哪些路径的文件才可以被直接访问的,这里给一个 koa 服务起的例子,正常的 koa 服务器可以通过 koa-static 中间件去指定静态资源的目录,好让静态资源可以通过路径的路由访问。比如你的系统源代码目录是这样的:
|- project
|- src
|- static
|- ...
|- server.js
你想要将 static 的文件夹配成静态资源目录,你应该会在 server.js
做如下配置:
const Koa = require('koa');
const serve = require('koa-static');
const app = new Koa();
app.use(serve(__dirname + '/project/static'));
但是如果配错了静态资源的目录,可能就出大事了,比如:
// ...
app.use(serve(__dirname + '/project'));
这样所有的源代码都可以通过路由访问到了,所有的服务器都提供了静态资源机制,所以在通过服务器配置静态资源目录和路径的时候,一定要注意检验,不然很可能产生漏洞。
最后,希望 Web 开发者们能够管理好自己的代码隐私,注意代码安全问题,比如不要将产品的含有敏感信息的代码放到第三方外部站点或者暴露给外部用户,尤其是前端代码,私钥类似的保密性的东西不要直接输出在代码里或者页面中。也许还有很多值得注意的点,但是归根结底还是绷住安全那根弦,对待每一行代码都要多多推敲。
django的模板会保护你免受大部分xss攻击,
但不是完全安全的。例如对下面的就不保护:
django在X-Frame-Options 中间件的表单里包含有点击劫持保护功能,这种功能在支持的浏览器里可以保护站点不在frame里被渲染。可以按view的基础禁用这个保护功能,或配置发送明确的header。
强烈推荐任何一个不必要或是只允许小部分 使页面包装在第三方frame的网站 使用这个中间件。
尽量使用https
而非http
如果一定要用http 需要做到下面几点
设置SECURE_PROXY_SSL_HEADER, 确保你彻底理解了警告。若不这样做会导致CSRF漏洞,若没有正确实施同样会导致危险
建立跳转,基于HTTP的连接会跳转到HTTPS。
用安全的cookies
若浏览器用默认的HTTP连接,cookie会被泄露。所以需要你的 SESSION_COOKIE_SECURE
和CSRF_COOKIE_SECURE
设置为True。这会让浏览器只通过HTTPS传送cookies。注意着意味着session在HTTP下不会起作用,CSRF保护机制会阻止一切通过HTTP传送的POST数据。
妥善设置allowed_hosts参数
Django会在HttpRequest.get_host()
方法中确认host 头是否与ALLOWED_HOSTS设置的值冲突。
确认只会由get_host()方法应用。若你的代码通过request.META直接访问host头,你会绕过这个安全防护。
校验安全列表
运行 manage.py check –deploy
,确保每一项配置都正确。该操作会检查你可能遗漏的安全设置。
只要做开发,就一定会涉及到web安全问题
不管使用什么开发框架,都需要提前先去理解器内部的安全机制。然后针对这些机制去合理的使用框架, 如果框架内部或者扩展库没有与web攻击相对应的扩展的话,也有必要自己封装。
在django中,一些中间件,settings里的参数设置True或False,不要因为急于功能开发而去注释或删除他们, 你需要先去了解他们的作用,然后再去决定是否需要注释
flask框架中, 也有很多web安全拓展库 比如Flask-Security
总之一句话 把web安全放在第一位