拾碎Web安全

Posted by Ray on 2018-04-01

拾碎Web安全

XSS 跨站脚本攻击

实质:我们想在我们的程序中引用用户输入的数据,但是用户输入的数据被当成可执行的脚本代码来执行了

  1. 非持久XSS

利用系统的反馈行为漏洞,诱导用户主动点击,从而发起攻击,即把攻击代码直接写在URL中 系统通过url直接拿到内容进行无过滤渲染。

常见的当你的前端代码中有类似如下的:

location.href.substring(location.href.indexOf('default='))

即会根据直接获取用户发起输入查询请求的 url 的内容进行页面直接渲染在关闭了现代浏览器的默认开启的反射性XSS过滤(ctx.set(X-Xss-Protection:0))之后你就会看到当你输入:

http://xxx.com?default=<script>alert(document.cookie)</script>

或者,当用户的输入直接出现在了HTML属性中也会造成这种情况,即:

1
2
3
4
<!-- content 直接取自用户输入 -->
<img src = "#{content}">
<img src = "1" onload = "alert(1)">
<!-- 1" onload = "alert(1) 是用户输入-->
  1. 存储型XSS

最典型的场景就是黑客通过发表一篇博客,该博客里面含有恶意脚本,被后端无过滤存储到数据库中,每一个浏览该篇文章的用户都会中招。

防御策略:

  • 前端渲染的时候对任何的字段都需要做 escape 转义编码。(<,>,双引号,单引号转义)
  • 尽可能的选择对渲染数据都来自于后端,别从URL中取数据直接渲染
  • 设置HttpOnly的 cookie 即document.cookie 获取不到了
  • CSP 内容安全策略指定什么资源是可以执行的。
    • ctx.set('Content-Security-Prolicy','default-src:'self'')

关于富文本

  • 黑名单
    • 过滤Script标签
    • 过滤javascript:
    • 过滤onerrer
  • 白名单
    • 只允许特定标签和属性
    • 遍历富文本中的dom节点的每一个标签名,标签的属性

CSRF 跨站请求伪造

用户在登录了网站A的前提下,访问了一个危险网站B,危险网站B带着网站A的用户登录的cookies开始向网站A发起构造好的请求。

特点:

  • 危险网站B并不需要访问网站A的前端
  • 请求的 referer 是B网站

策略:

  • 禁止第三方网站带上本网站的cookie
    • 在cookie里面新增了一个same-site属性(strict,lax),规定只有来自同一个站点的请求才会带有 same-site属性的cookie。
    • 针对网站B不访问A的前端,可以设置关键信息的提交设置验证码
      • ccap Node端生成验证码图片的第三方模块
    • 针对网站B不访问A的前端,可以使用Token的方法
      • 在服务端生成一个随机数(token),并且在服务端需要放到两个地方cookies中,和页面表单中,二者缺一不可,只有表单中的 token那么B可以随便捏造一个假的发送,只有cookie那么无法比较。
      • 前端的表单一般使用 type=hiden 的 input标签来存放这个后端来的token值
      • 提交表单的时候进行校验,token值是否是空校验表单中的token和cookie中的token是否一样

Token的问题

将token放在表单中是可行的,但是如果一个页面要进行ajax请求的话,需要使用另外一种做法就是将 token 放到页面的meta标签中,在ajax请求的时候要从meta标签中获取那个随机数。

Cookie

特性:

  • Cookie 遵守同源策略
  • Cookie 在前端可以通过 document.cookie=来进行追加,但是不能覆盖(未使用http-only)但是可以篡改以前的同名的cookie
  • Cookie 只能通过有效期设置为过期的时间才能删除

作用:

  • 存储个性化设置(比如个性化皮肤)
  • 存储未登录的用户的一些唯一标识
  • 存储区已登录用户的凭证
  • 存储其他业务数据

登录相关

防止用户篡改Cookie值

比如如果网站是使用给客户端使用 “userId=1” 的方式来进行登录态凭证的设置,那么当前端调用 document.cookie = 'userId = 2' 那么该用户就得到了用户2的身份。

解决办法1,在服务端再一次生成一个加密的签名,即使用 用户ID+签名 的Cookie组合

  1. 第一步生成加密签名序列号
1
2
3
4
5
6
7
8
9
10
// 加密工具模块
const crypto = require('crypto')
const Key = 'ray#@1234'//设置一个签名使用的密钥
const cry = {}
cry.cryptoUserId = function(userId){
var sign = crypto.createHmac('sha256',Key)
sign.update(userId+'')//参数只接受字符串,所以把用户id转换为字符串
return sign.digest('hex')
}
module.exports = cry
  1. 因为签名是一个不可逆的算法,不可能根据签名的值倒推出原来的明文是多少。所以解决的办法是,将签名过的字符串和用户登录Id一起发去客户端,根据客户端带回来的 userId 和 sign 值在服务端算 userId 的加密签名是否等于用户带来的 sign 从而判断是不是已经被篡改之后的 userId 。

方法二:Session 认证

即在Cookie中只set进一个存在服务端中的 seesionID(随机字符串) ,用户请求带来sessionId之后再服务端判断用户身份。

点击劫持问题

利用一个透明度被设为 0 的 Iframe 引用目标网站,诱导用户真实点击,用户其实点击的操作是在操作目标网站的某些操作。

防御点击劫持的办法:

  1. 利用页面被 iframe 引用的时候他的 top 对象是不等于 window 对象的特点(一个正常页面的top === window)可以在网站的源 html 中使用 javascript 做判断。但是当 iframe 设置 sand-box属性时 是可以做到禁止原页面的JS脚本的功能的。
  2. 在服务端设置 X-Frame-Options 来禁止或者允许部分的网站对我的页面进行 iframe 内嵌

HTTP 传输窃听

“traceroute www.baidu.com” 查看经过多少节点

在一个浏览器到服务器中间会经过很多的链路以及代理服务器,他们都有机会去窃听你的请求替换掉本来的响应

如果把用户登录请求时的用户名和密码都设置为明文….

如果你想获得的响应中被中间商在响应中增加个大量的广告….

最常见的例子:当你连接了某一个Wifi之后去访问百度,最后你却得到的页面是 wifi 运营商让你输入用户名和密码,这就是运营商的http劫持

解决策略:HTTPS

密码安全

密码泄露渠道

  • 网站数据库被盗
  • 服务器被入侵,得到密码就转发
  • 通讯过程被窃听
  • 撞库(人们习惯不同网站注册的时候使用同一套密码)

应对策略:

  • 不用明文对密码进行存储,对密码进行单向变换
    • 加强复杂度的变换测试,md5(sha256(xxx+盐+花样字符串)) ,去应对现在出现的“彩虹表”(根据md5值反推明文的表)。
  • 重视密码传输过程的安全性
    • https 传输
    • 频率限制(不允许连续多次的去试探密码登录)
    • 前端加密(意义有限),但是可以有效防止撞库的情况出现。

SQL注入

select * from user where userId = ${inputId} and password = ${inputPasswod}

上述sql语句想根据获取的用户输入id直接准备去查数据库,用户精明的输入了 1' or '1'='1

select * from user where userId = 1 and password = '1' or '1'='1'

sql注入的原理非常想XSS攻击的原理,执行了不该执行的代码。

防御:

  • 关闭错误输出(不要把后端的错误日志暴露给前端)
  • 转义
  • mysql提供的参数化查询(?数组的形式
  • ORM(对象关系映射)

DOS(Denial of Service)拒绝服务攻击 & DDOS大规模分布式拒绝服务攻击

种类:

  • TCP半连接
    • 只进行两次握手就不理人家了
  • http连接
  • 攻击DNS服务器
本文为原创文章作为学习交流笔记,如有错误请您评论指教
转载请注明来源:https://isliulei.com/article/web-security/