前端安全之跨域安全(下)

0x01 WebSocket

基本概念

  • 一般的,Web应用的交互过程通常是客户端通过浏览器发出一个请求,服务器端接收请求后进行处理并返回结果给客户端,客户端浏览器将信息呈现。这种机制对于信息变化不是特别频繁的应用尚可,但却不适用于高并发与用户实时响应的场景,比如股票的实时信息、地图导航等。

  • 于是,基于HTML5规范的、有Web TCP之称的WebSocket应运而生。

  • WebSocket是HTML5一种新的协议,它实现了浏览器和服务器全双工通信,更好地节省服务器资源和宽带并达到实时通讯,它建立在TCP之上,同HTTP一样通过TCP来传输数据,但和HTTP协议的不同点在于:

    • WebSocket是持久化的协议,而HTTP是非持久连接;
    • WebSocket是一种双向通信协议,在建立连接后,WebSocket服务器和浏览器/客户端代理都能主动地向对方发送或接收数据,就像Socket一样,而HTTP是单向通信协议;
    • WebSocket需要类似TCP的三次握手连接,但和TCP不同的是,WebSocket是基于HTTP协议进行的握手,连接成功后才能相互通信;
    • WebSocket具有功能强大、双向、低延迟等特征,特别是针对实时的、事件驱动的Web应用程序而言,不惜要的网络流量和延迟得以显著减少,通信效率和应用程序表现大大提升;
  • WebSocket定义了两种URI格式:ws://和wss://,类似于HTTP和HTTPS,ws://使用明文传输,默认端口为80,wss://使用TLS加密传输,默认端口为443。

  • 优点:

    • 支持双向通信,实时性比较强
    • 更好的二进制支持
    • 较少的开销。创建连接后,数据交换时候不用携带所有的数据头部信息
    • 支持拓展
  • 缺点:

    • 缺少认证机制
    • 存在跨站点劫持漏洞
  • 其他一些头字段解释如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    HTTP头	是否必须	解释
    Host 是 服务端主机名
    Upgrade 是 固定值,”websocket”
    Connection 是 固定值,”Upgrade”
    Sec-WebSocket-Key 是 客户端临时生成的16字节随机值, base64编码
    Sec-WebSocket-Version 是 WebSocket协议版本
    Origin 否 可选, 发起连接请求的源
    Sec-WebSocket-Accept 是(服务端) 服务端识别连接生成的随机值
    Sec-WebSocket-Protocol 否 可选,客户端支持的协议
    Sec-WebSocket-Extensions 否 可选, 扩展字段
  • 两个重要的安全头,Sec-WebSocket-Key与Sec-WebSocket-Accept:客户端负责生成一个Base64编码过的随机数字作为Sec-WebSocket-Key,服务器则会将一个GUID和这个客户端的随机数一起生成一个散列Key作为Sec-WebSocket-Accept返回给客户端。这个工作机制可以用来避免缓存代理(caching proxy),也可以用来避免请求重播(request replay)。

  • 出于安全考虑而设计的,以“Sec-”开头的头字段可以避免被浏览器脚本读取到,这样攻击者就不能利用XHR来伪造WebSocket请求来执行跨协议攻击,因为XHR接口不允许设置Sec-开头的Header。

  • WebSocket属性

    1
    2
    3
    属性	描述
    Socket.readyState 只读属性 readyState 表示连接状态,可以是以下值:0 - 表示连接尚未建立。1 - 表示连接已建立,可以进行通信。2 - 表示连接正在进行关闭。3 - 表示连接已经关闭或者连接不能打开。
    Socket.bufferedAmount 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。
  • WebSocket事件

1
2
3
4
5
事件	事件处理程序	描述
open Socket.onopen 连接建立时触发
message Socket.onmessage 客户端接收服务端数据时触发
error Socket.onerror 通信发生错误时触发
close Socket.onclose 连接关闭时触发
  • WebSocket方法

    1
    2
    3
    方法	描述
    Socket.send() 使用连接发送数据
    Socket.close() 关闭连接

0x02 CSWSH漏洞

  • CSWSH全称Cross-site WebSocket Hijacking,跨站点WebSocket劫持漏洞。

  • 漏洞场景

    • 支持WebSocket协议的Web站点如股票实时查询、地图导航等,并且未对请求的Origin头字段进行校验。
  • 漏洞原理

    • CSWSH漏洞类似于全能型的CSRF漏洞,可读可写。
    • 漏洞根源是WebSocket天生可跨域,不受同源策略的影响。在此基础上,若目标服务端未对WebSocket协议请求的Origin头字段进行校验,则会导致WebSocket协议请求可被攻击者劫持,从而窃取敏感信息。
  • 漏洞场景

    • 目标站点存在cookie校验机制的场景
      -w996

      • 1、用户首先登录stock.com实时查询股票信息,其中该站点支持WebSocket,需要用户携带cookie访问;
      • 2、接着用户被诱使在当前的浏览器访问beauty.com,其中加载了恶意JS代码到用户的浏览器中执行;
      • 3、恶意JS代码通过WebSocket协议向stock.com站点发起请求,此时请求是用户浏览器发起的、是自动带上cookie信息的;
      • 4、stock.com收到恶意JS发送的WebSocket请求,由于未校验ws://请求的Origin头字段,在检测cookie合法后,返回敏感信息到用户浏览器;
      • 5、用户浏览器中的恶意JS收到stock.com响应的WebSocket协议响应信息后,发往攻击者服务器,从而造成跨站点WebSocket劫持攻击;
  • 检测方法

    • 修改请求报文中的Origin头字段,重放该WebSocket协议升级请求,若服务器返回101响应则表示连接成功即未对源进行检测,则可能存在CSWSH漏洞。

    • 最好是进一步测试是否可以发送WebSocket消息,若这个WebSocket连接能够发送/接受消息的话,则完全证明CSWSH漏洞的存在。

    • 一般的漏洞挖掘步骤:

      • 找到支持WebSocket的站点;
      • 使用ZAP/BURP等代理工具重放切换WebSocket协议的报文,其中修改Origin头查看服务端是否校验Origin头;
      • 若未校验Origin头,则进一步发送WebSocket连接报文查看能否成功利用;
  • POC

    • 使用JavaScript创建Websocket请求:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
            <meta charset="utf-8">
      <script>
      function ws_attack(){//自定义函数ws_attack
      //定义函数功能
      //创建WebSocket并赋值给ws变量
      var ws = new WebSocket("ws://域名:端口/");//如果请求的Websocket服务仅支持HTTP就写成ws://,如果请求的Websocket服务支持HTTPs就写成wss://
      ws.onopen = function(evt) {
      //当ws(WebSocket)处于连接状态时执行
      ws.send("来了老弟");
      };
      ws.onmessage = function(evt) {
      //当ws(WebSocket)请求有响应信息时执行
      //注意:响应的信息可以通过evt.data获取!例如:alert(evt.data);
      ws.close();
      };
      }
      ws_attack();//执行ws_attact函数
      </script>
  • 漏洞挖掘

    • 进行CSWSH漏洞挖掘前需要准备好一款可以重放WebSocket协议报文的代理工具,Burp是做不到的,但是我们可以选择OWASP ZAP来实现。

    • 一般的漏洞挖掘步骤:

      • 找到支持WebSocket的站点;
      • 使用ZAP等代理工具重放切换WebSocket协议的报文,其中修改Origin头查看服务端是否校验Origin头;
      • 若未校验Origin头,则进一步发送WebSocket连接报文查看能否成功利用;
    • 当然,切换协议的请求报文依然是可以使用Burp来完成的,这里修改Origin头之后再重放报文,发现成功响应101报文,证明该站点未校验Origin,可能存在CSWSH漏洞:

  • 案例

    • 1、如下请求:

      1
      2
      3
      4
      5
      6
      7
      GET / HTTP/1.1
      Host: localhost:8080
      Origin: http://127.0.0.1:3000
      Connection: Upgrade
      Upgrade: websocket
      Sec-WebSocket-Version: 13
      Sec-WebSocket-Key:w4v7O6xFTi36lq3RNcgctw==
    • 2、篡改Origin,发现没有对Origin进行验证

    • 3、验证发现可以请求并成功进行重放,存在Websocket跨域劫持(这里只是简单的评论请求,危害就是:点我链接让你评论我想评论的,试想:如果是修改密码的WebSocket请求存在劫持那么问题就大了~)

  • 漏洞利用

    • 攻击流程跟以往的交互类漏洞没什么区别(点我链接读取你XXX、点我链接让你XXX):

0x03 防御

  • 防御方法

    • WebSocket 令牌机制

      • 服务器端为每个 WebSocket 客户端生成唯一的一次性 Token;客户端将 Token 作为 WebSocket 连接 URL 的参数(譬如 ws://http://echo.websocket.org/?token=randomOneTimeToken),发送到服务器端进行 WebSocket 握手连接;服务器端验证 Token 是否正确,一旦正确则将这个 Token 标示为废弃不再重用,同时确认 WebSocket 握手连接成功;如果 Token 验证失败或者身份认证失败,则返回 403 错误。
    • 使用白名单校验请求报文的Origin头字段;

      • 在服务器端的代码中增加 Origin 检查,如果客户端发来的 Origin 信息来自不同域,建议服务器端拒绝这个请求,发回 403 错误响应拒绝连接

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        实例代码:
        public class CustomConfigurator extends ServerEndpointConfig.Configurator {

        private static final String ORIGIN = "http://jeremy.laptop:8080";
        @Override
        public boolean checkOrigin(String originHeaderValue) {
        if(originHeaderValue==null || originHeaderValue.trim().length()==0)
        return true;
        return ORIGIN.equals(originHeaderValue);
        }
        }