Yaklang websocket劫持教程

news2024/9/27 23:27:57

背景

随着Web应用的发展与动态网页的普及,越来越多的场景需要数据动态刷新功能。在早期时,我们通常使用轮询的方式(即客户端每隔一段时间询问一次服务器)来实现,但是这种实现方式缺点很明显:
大量请求实际上是无效的,这导致了大量带宽的浪费。

这时候我们急需一个新的技术来解决这一痛点,Websocket应运而生: WebSocket是一种网络传输协议,可在单个TCP连接上进行 全双工通信
,位于OSI模型的应用层。

Websocket的诞生也给我们带来了新的挑战,我们能否对websocket的请求与响应进行劫持与修改呢?要想做到这一点,我们首先得了解websocket协议。

websocket协议细节

等等,看到这个标题的时候先别急着划走,实际上websocket协议比我们想象中的要简单,他实际上 几乎 等同于原始的TCP
socket,只不过多出了额外的协议头以及一个升级的过程。

我们先来看websocket的升级过程,先是客户端发起协议升级请求,其采用标准的HTTP报文格式,且必须使用 GET 请求方法:

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==

这里我们需要关注的最后四行的特殊请求头:

  • Connection: Upgrade:表示要升级协议

  • Upgrade: websocket:表示要升级到websocket协议

  • Sec-WebSocket-Version: 13:表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号

  • Sec-WebSocket-Key:与后面服务端响应头Sec-WebSocket-Accept配套,提供基本的校验。其本身是一个 bas64编码过的随机16字节

服务器返回101状态码的响应,至此完成协议升级:

HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=

这里我们需要的关注的是最后的Sec-WebSocket-Accept请求头,其与前文的Sec-WebSocket-Key对应,主要有以下两个目的:

  • 确保服务器理解 WebSocket 协议

  • 防止客户端意外请求 WebSocket 升级

Sec-WebSocket-Accept请求头是由Sec-WebSocket-Key计算而成的,其伪代码如下:

toBase64(sha1(Sec-WebSocket-Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))

协议升级后,双方开始使用websocket协议进行通讯。我们来看看websocket的协议细节,一个经典的概览图如下:

0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+

如果看不懂无所谓,我们逐个字段进行讲解:

FIN :1 bit

如果是1,表示这是消息的最后一个分片,如果是0,表示不是是消息的最后一个分片。 通常为1

RSV1, RSV2, RSV3 :各占1 bit

一般情况下全为0
。当客户端、服务端协商采用WebSocket扩展时,这三个标志位可以非0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用WebSocket扩展,连接出错。

Opcode : 4 bit

操作代码,Opcode的值决定了应该如何解析后续的数据, 可以简单地理解为消息类型,一般通讯时为%x1或%x2 。可选值如下:

  • %x0:表示一个延续帧。当Opcode为0时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片
  • %x1:表示这是一个文本帧(frame)
  • %x2:表示这是一个二进制帧(frame)
  • %x3-7:保留的操作代码,用于后续定义的非控制帧
  • %x8:表示连接断开
  • %x9:表示这是一个ping操作
  • %xA:表示这是一个pong操作
  • %xB-F:保留的操作代码,用于后续定义的控制帧

Mask : 1 bit

表示是否要对数据进行掩码操作。 客户端向服务端发送数据时该bit为1,否则为0 。掩码算法在后续Masking key提到。

Payload length : 数据的长度,单位是字节。其可能为7/7+16/1+64 bit。

假设数据长度 = x,如果

  • 0<=x<=125:用这7个bit来代表数据长度。

126<=x<=65535:7个bit设置为126(1111110)。后续2个字节代表一个16位的无符号整数,该无符号整数的值为数据的长度(大端序)。

  • 65535<x:7个bit设置为127(1111111)。后续8个字节代表一个64位的无符号整数,该无符号整数的值为数据的长度(大端序)。

Masking-key :0/32 bit

假如前文所述Mask为1,则此Masking-key占32 bit(即四个字节),否则为0 bit。Masking-
key用于将客户端传输给服务器的数据进行掩码操作。前文的Payload length,不包括Masking-key的长度。

具体的掩码算法伪代码如下:

设原数据为bytes,Masking-key为key,则:

for i in range(len(bytes)):

bytes[i] ^= key[i&3]

Payload data :(x+y) byte

载荷数据包括了扩展数据、应用数据。其中,扩展数据x字节,应用数据y字节。

在前文的升级阶段没有协商使用扩展的话,扩展数据数据为0字节。 剩下的应用数据就是传输的原始socket内容
,因此也一般会结合其他压缩算法/协议使用,如protobuf。

websocket劫持实现

在了解了websocket协议之后,我们实现websocket劫持就变得很简单了,用一张流程图来展示:

其中重点主要是原始数据与websocket帧之间的转换。

解析原始数据

前面说过,websocket协议实际上几乎只是比原始socket多了一个头,那么我们解析原始数据可以分为以下几步:

  1. 设初始n=2,即抛弃前两个websocket头字节

  2. 判断第2个byte的后7个bit(payload length),如果为126,则n+2,如果其127,则n+8

  3. 判断第2个byte的第1个bit(mask位)是否为1,如果为1,则从n~n+4位取出masking-key,并将n+4,将n位后的数据进行掩码处理

  4. 返回n位后的数据,即为原始数据

重新封装成websocket帧

可以分为以下几步:

  1. 第1个byte照抄(也可以根据需要修改后4位bit及opcode,修改消息类型)

  2. 第2个byte第1个bit(mask位)照抄,后7位bit根据修改后的数据长度进行处理

  3. 如果数据长度大于125,则要写入uint16或uint64的数据长度字节(大端序)

  4. 如果mask位为1,则生成并写入32位的随机masking-key,再将数据进行掩码处理与写入,此时即封装好了的websocket帧

websocket劫持实现时遇到的坑点

这里讲下在websocket劫持实现时遇到的坑点,仅供参考

保持协议的完整性

实际上前文提到的劫持所使用的技术都是中间人技术,这里我遇到的坑点就是没保持协议的完整性,我在处理时从服务器端接收到了101状态码的响应,但却没有将其写入回客户端,导致客户端断开,整个websocket的升级也就失败了,所以需要提醒的就是在劫持时要保持协议的完整性,该发送或接收到的内容都要到位。

实现FrameReader而非简单的Read

我之前的一个错误实例如下:

这里实际上犯了几个错误:

  1. reader.Read()是非阻塞的,也就是说如果缓冲中没有数据的话,它会不断地返回0和EOF,但是我这里判断如果n<=0则会不断continue,这会导致不断创建新的4096字节的bytes,无法释放

  2. 后续我将b作为websocket帧来处理,但是b的大小只有4096,假如数据量超大,这样写毫无疑问是错误的

后来其他师傅发现了这个bug并指出这几点错误,我才意识到我应该抽象出一个FrameReader来去读取websocket帧,根据读取到的前几个字节来判断最终要读取的长度。

新版yak的websocket尝鲜

websocket劫持尝鲜

经过一番努力之后,终于实现了websocket劫持功能,在yak的mitm标准库中新增了wscallback与wsforcetext两个函数,我们来看一个简单的用例:

go fn{
    mitm.Start(8084, mitm.wsforcetext(true),mitm.wscallback(
    fn(data, isRequest){
        if isRequest {
            data = "Hijack request"
        } else {
            data = "Hijack Response"
        }
        return data
    }))
}

for {
    time.sleep(1)
}

wscallback参数接受一个函数作为参数,该函数拥有2个参数: data([]byte类型)和isRequest(bool类型)并
接收一个返回值(必须存在返回值) ,作为修改后的数据。

isRequest参数用于判断劫持到的是否为websocket请求(true即websocket请求,false为websocket响应),data参数则为劫持到的原始数据。

接下来我们使用go来启动一个websocket的测试服务器,这里需要安装依赖:“github.com/gorilla/websocket”:

package main

import (
        "fmt"
        "net/http"
        "os"
        "time"

        "github.com/gorilla/websocket"
)

func main() {
        var upgrader = websocket.Upgrader{}

        f, err := os.CreateTemp("", "test-*.html")
        if err != nil {
                panic(err)
        }
        f.Write([]byte(`<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>Sample of websocket with golang</title>
    <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script>
        $(function() {
            var ws = new WebSocket('ws://' + window.location.host + '/ws');
            ws.onmessage = function(e) {
                $('<li>').text(event.data).appendTo($ul);
            ws.send('{"message":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}');
            };
            var $ul = $('#msg-list');
        });
    </script>
</head>
<body>
<ul id="msg-list"></ul>
</body>
</html>`))
        index := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                http.ServeFile(w, r, f.Name())
        })
        http.Handle("/", index)
        http.Handle("/index.html", index)
        http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
                // msg := &RecvMessage{}

                ws, err := upgrader.Upgrade(w, r, nil)
                if err != nil {
                        panic(err)
                        return
                }
                defer ws.Close()

                go func() {
                        for {
                                _, msg, err := ws.ReadMessage()
                                if err != nil {
                                        panic(err)
                                        return
                                }
                                fmt.Printf("server recv from client: %s\n", msg)
                        }
                }()

                for {
                        time.Sleep(time.Second)
                        ws.WriteJSON(map[string]interface{}{
                                "message": fmt.Sprintf("Golang Websocket Message: %v", time.Now()),
                        })
                }
        })

        err = http.ListenAndServe(":8884", nil)
        if err != nil {
                panic(err)
        }
}

现在,我们访问http://127.0.0.1:8884,会发现屏幕会每秒输出一条json内容,例如:

{"message":"Golang Websocket Message: 2022-09-05 15:17:22.497926 +0800 CST m=+7.689153001"}

同时,在终端中会每秒输出一条以下内容:

server recv from client: {"message":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}

这时候我们挂上代理http://127.0.0.1:8084/,重启websocket服务器进行访问,然后会发现上述的内容都会发生改变,屏幕输出的内容变为:

Hijack Response

同时,终端输出的内容变为:

server recv from client: Hijack request

直接发起websocket请求

还是使用上述的websocket的测试服务器作为服务端,启动。

yak中编写如下代码,运行:

rsp, req, err = poc.Websocket(`GET /ws HTTP/1.1
Host: 127.0.0.1:8884
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: Upgrade
Sec-WebSocket-Key: LIb4U+i+y+phoP4B2y6uoA==
Sec-WebSocket-Version: 13
Upgrade: websocket

`, poc.websocketFromServer(func(data, cancel){
    dump(data)
}), poc.websocketOnClient(func(wsClient) {
    go fn {
        for {
                wsClient.WriteText(`{"message": "hello"}`)
                time.Sleep(1)
        }
    }
}))
die(err)

解释一下上述代码,poc.Websocket指定了这个请求需要去对websocket请求进行收发处理,其实际上是poc.Http(,poc.websocket(true))的简写。第一个参数是我们熟悉的websocket升级请求,后面跟着的是可选参数函数:

  1. poc.websocketFromServer,这个函数接受一个函数作为参数,其中data为从服务端接收到的数据,cancel是一个无参数函数,用于直接中断websocket连接。

  2. poc.websocketOnClient,这个函数接受一个函数作为参数,其中wsClient是一个结构体,可以直接使用其的一些方法,如:

1. `c.Stop()`,结束websocket连接

2. `c.Write([]byte)`,往websocket写入内容

3. `c.WriteText([]byte)`,同`c.Write([]byte)`

4. ...

通过程序输出可以看到我们正常建立了websocket连接并完成了收发。

新版yakit的websocket劫持尝鲜

Yak版本 1.1.2

Yakit版本 1.1.2

websocket劫持

正常启动yakit的MITM,然后也启动上文提到的websocket服务器:

挂载代理访问http://127.0.0.1:8884/,出现websocket升级的请求,手动放行:

等待websocket协议升级完成后,我们成功劫持到了websocket的请求,按下劫持响应并修改请求内容,最后按下提交数据:

可以看到服务器已经接收到修改过后的请求:

同时我们拦截到了服务器的响应,修改响应内容然后按下提交数据:‘

发现浏览器中显示我们修改过后的响应:

websocket fuzzer

在MITM中的HTTP History找到websocket的升级响应,按下FUZZ按钮:

跳转到websocket fuzzer页面,我们尝试建立连接:

建立websocket连接完成后可以在右侧看到实时的服务器请求与响应:

我们尝试在下方发送数据框发送websocket请求:

可以看到成功发送websocket请求:

websocket fuzzer

在MITM中的HTTP History找到websocket的升级响应,按下FUZZ按钮:

[外链图片转存中…(img-WKp0XoFM-1676620638602)]

跳转到websocket fuzzer页面,我们尝试建立连接:

[外链图片转存中…(img-AfHcTU1R-1676620638602)]

建立websocket连接完成后可以在右侧看到实时的服务器请求与响应:

[外链图片转存中…(img-hTxQJ96B-1676620638603)]

我们尝试在下方发送数据框发送websocket请求:

[外链图片转存中…(img-xogbIIIJ-1676620638603)]

可以看到成功发送websocket请求:

[外链图片转存中…(img-Ky0DLWRZ-1676620638603)]

[外链图片转存中…(img-glbr0Rp5-1676620638603)]

##最后
对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

同时每个成长路线对应的板块都有配套的视频提供:


当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料&工具,并且已经帮大家分好类了。

因篇幅有限,仅展示部分资料,有需要的小伙伴,可以【扫下方二维码】免费领取:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/354216.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

matlab离散系统仿真分析——电机

目录 1.电机模型 2.数字PID控制 3.MATLAB数字仿真分析 3.1matlab程序 3.2 仿真结果 4. SIMULINK仿真分析 4.1simulink模型 4.2仿真结果 1.电机模型 即&#xff1a; 其中&#xff1a;J 0.0067&#xff1b;B 0.10 2.数字PID控制 首先我们来看一下连续PID&#xff1…

[一键CV] Blazor 拖放上传文件转换格式并推送到浏览器下载

前言 昨天有个小伙伴发了一个老外java编写的小工具给我,功能是转换西班牙邮局快递Coreeos express的单据格式成Amazon格式,他的需求是改一下程序为匹配转换另一个快递公司MRW格式到Amazon格式,然而我堂堂一个Blazor发烧友,怎么可能去反编译人家的java修改呢?必须直接撸一个Bl…

Docker 快速上手学习入门教程

目录 1、docker 的基础概念 2、怎样打包和运行一个应用程序&#xff1f; 3、如何对 docker 中的应用程序进行修改&#xff1f; 4、如何对创建的镜像进行共享&#xff1f; 5、如何使用 volumes 名称对容器中的数据进行存储&#xff1f;// 数据挂载 6、另一种挂载方式&…

Mongodb WT_PANIC: WiredTiger library panic

文章目录故障现象排查过程1.查看Log2.同步恢复数据故障现象 周五突然收到Mongo实例莫名奇妙挂了告警&#xff0c;一般都是RS复制集架构模式&#xff08;5节点&#xff09;&#xff0c;查看此实例角色为SECONDAR&#xff0c;挂了暂时不影响线上业务&#xff0c;但还是需要尽快修…

前端智能化在淘宝的2022实践总结

过去十年是智能化蓬勃发展的十年&#xff0c;但未来十年会是智能化渗入各领域彻底改变我们生活和工作的十年。阿里前端智能化方向小组历经 4 年的实践和演变&#xff0c;在前端融入业务技术团队和终端融合的背景之下&#xff0c;前端智能化小组在2022年更多以优化拓展基础业务工…

【计算机网络】因特网概述

文章目录因特网概述网络、互联网和因特网互联网历史与ISP标准化与RFC因特网的组成三种交换方式电路交换分组交换和报文交换三种交换方式的对比与总结计算机网络的定义和分类计算机网络的定义计算机网络的分类计算机网络的性能指标速率带宽吞吐量时延时延带宽积往返时间利用率丢…

球员分析-前锋

1、球员位置 1.1柱式中锋 球员&#xff1a;吉鲁、奥斯梅恩、米特罗维奇 1.2防守型前锋 球员&#xff1a;劳塔罗、瓦尔迪、维尔纳 1.3抢点前锋 球员&#xff1a;伊卡尔迪、曼联c罗、因扎吉 1.4组织型前锋 球员&#xff1a;凯恩、本泽马、迪巴拉 2、战术职责 2.1柱式中锋&#xf…

设计模式-状态机模式

参考 什么是状态机&#xff1f; 设计模式-状态机模式 什么是状态机(有限状态自动机) 可以把状态机比作方程式, 你输入当前信息, 就能得到下一个信息 举个例子, 按钮门有两个状态, 关闭状态和打开状态, 如果按下开门按钮, 门的状态就从关闭到打开 状态机就是接受门当前状态…

极兔一面:10亿级ES海量搜索狂飙10倍,该怎么办?

背景说明&#xff1a; ES高性能全文索引&#xff0c;如果不会用&#xff0c;或者没有用过&#xff0c;在面试中&#xff0c;会非常吃亏。 所以ES的实操和底层原理&#xff0c;大家要好好准备。 另外&#xff0c;ES调优是一个非常、非常核心的面试知识点&#xff0c;大家要非…

就业大山之下的网络安全:安逸的安服仔

从去年开始&#xff0c;各个互联网大厂就接二连三的放出了裁员消息&#xff0c;整个互联网行业好像都处于寒冬状态。微博、小米、滴滴、知乎、拼多多等在内的一大批互联网知名企业&#xff0c;也相继传出“人员优化”的消息。 除了国内市场的萧条&#xff0c;国外市场也是不容…

kubernetes教程 --Pod调度

Pod调度 在默认情况下&#xff0c;一个Pod在哪个Node节点上运行&#xff0c;是由Scheduler组件采用相应的算法计算出来的&#xff0c;这个过程是不受人工控制的。但是在实际使用中&#xff0c;这并不满足的需求&#xff0c;因为很多情况下&#xff0c;我们想控制某些Pod到达某…

springboot simple (13) springboot Elasticsearch(Elasticsearch8.5.1)

这里首先简单的介绍了Elasticsearch&#xff0c;然后实现了springboot集成Elasticsearch。 版本&#xff1a; Elasticsearch&#xff1a;v8.5.1 Kibana&#xff1a;v8.5.1 springboot集成elasticsearch有两种方式。 1&#xff09;rest客户端RestHingLevelClient&#xff1b; …

2.2 BeautifulSoup 装载HTML文档

HTML文档结点的查找工具很多&#xff0c;其中 BeautifulSoup 是功能强大且十分流行的查找工具之一。1. BeautifulSoup 的安装安装&#xff1a;pip install bs4导包&#xff1a;from bs4 import BeautifulSoup2. BeautifulSoup 装载HTML文档如果 doc 是一个 HTML 文档&#xff0…

fast planner总结

一、前端 kinodynamic A*算法动力学路径搜索 1.1 路径搜索的主要函数为kinodynamicAstar类的search函数 int KinodynamicAstar::search(Eigen::Vector3d start_pt, Eigen::Vector3d start_v, Eigen::Vector3d start_a,Eigen::Vector3d end_pt, Eigen::Vector3d end_v, bool ini…

DPDK — Userspace PMD 源码分析

目录 文章目录目录PMD driver 通过 IGB_UIO 与 UIO 进行交互注册一个 UIO 设备PMD 的应用层实现PMD 同样支持中断处理方式PMD driver 通过 IGB_UIO 与 UIO 进行交互 IGB_UIO 内核模块的另一个主要功能就是让用于态的 PMD 网卡驱动程序得以与 UIO 进行交互。对于 PMD 的实现来说…

松下PLC通过fpwin上传写入MRTC模块方法

目录 PLC程序上传方法 加密模块使用 PLC程序上传方法 手动将PLC模式设置为prog模式查看PLC是否设置为禁止上传查询指示灯是否变蓝&#xff0c;变蓝则需要将PLC禁止上传功能取消。 3.当上述动作操作完成后&#xff0c;将PLC程序导入到PLC中。为了配合加密程序使用&#xff0c;…

进程通信方式

无名管道( pipe )&#xff1a; 管道是一种半双工的通信方式&#xff0c;数据只能单向流动&#xff0c;而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。高级管道&#xff08;popen&#xff09;&#xff1a; 将另一个程序当做一个新的进程在当前程序进…

主成分分析(PCA)原理详解

1. 相关背景 在许多领域的研究与应用中&#xff0c;通常需要对含有多个变量的数据进行观测&#xff0c;收集大量数据后进行分析寻找规律。多变量大数据集无疑会为研究和应用提供丰富的信息&#xff0c;但是也在一定程度上增加了数据采集的工作量。更重要的是在很多情形下&…

Windows server——部署web服务

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 本章重点 一.web讲解 1.WWW概述 &#xff08;1&#xff09;WWW服务概述 &…

Linux应用编程下连接本地数据库进行增删改查系列操作

文章目录前言一、常用SQL操作语句二、相关函数解析三、连接本地数据库四、编译运行五、程序源码前言 本篇为C语言应用编程下连接Linux本地数据库进行增删改查系列操作。 在此之前&#xff0c;首先当然是你需要具备一定的数据库基础&#xff0c;所以下面我先列出部分常用的SQL…