• 如何利用curl仿造websocket请求?


    之前两次signalr、 websocket实时推送相关:

    tag: 浏览器--->nginx--> server

    其中提到nginx默认不会为客户端转发UpgradeConnection标头, 因为为了让被代理的后端服务器知道客户端要升级协议,故要在nginx上显式转发标头:

    # 以下为 /realtime/路径请求添加  Connection、Upgrade标头
    
    location /realtime/ {     
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
    

    事情本该就就这么简单, 但devops总会有各种奇怪的姿势。

    小动作引起的头脑风暴

    但是运维在给nginx配置的时候,给/根路径配置了webcoket协议升级标头。

    按照字面理解,导致所有的客户端转发请求都在要求切换到websocket协议,但是除了/realtime路径, 服务器其他http路径并没有做websocket协议的逻辑,那其他http请求是不是都该报错了。

    是实际看,所有的请求(websocket、http)都没有报错,都按照指定预期返回。

    刨一下

    利用asp.netcore默认脚手架项目:

    已知http://localhost:5000/WeatherForecast是http请求,返回一大坨json数据;
    WeatherForecast添加断言日志:

    模拟ops的错配效果,我们给这个请求添加websocket协议升级标头。

    第一次:curl 'http://localhost:5000/WeatherForecast' -H 'Upgrade: websocket' -H 'Connection: Upgrade' --verbose ======》 200ok、大坨json数据。

    日志记录:

     该请求是不是webcocket请求:False,headers:[Accept, */*], [Connection, Upgrade], [Host, localhost:5000], [User-Agent, curl/7.79.1], [Upgrade, websocket]
    

    以上说明,服务端并不认为是websocket请求,但是按照http业务处理返回了200ok+大坨json数据,这演示了ops虽然错配,但对于常规的http请求没造成影响。

    那服务端到底是怎么认定websocket请求?

    服务端认定websocket请求的源码

    依次判断;

    • HttpMethod: GET
    • Sec-WebSocket-Version标头==13
    • Connection标头==Upgrade
    • Upgrade标头==websocket
    • 有效的Sec-WebSocket-Key标头

    这样我们就明白了,虽然websocket协议基于http,添加了httpConnectionUpgrade协商标头,但是浏览器实际会给我们带上Sec-WebSocket-KeySec-WebSocket-Version等标头,以向服务器证明这是一个有效的websocket握手。

    于是我们可以使用
    curl 'http://localhost:5000/WeatherForecast' -H 'Upgrade: websocket' -H 'Connection: Upgrade' -H 'Sec-WebSocket-Version: 13' -H 'Sec-webSocket-Key: eeZn6lg/rOu8QbKwltqHDA==' --verbose 仿造客户端websocket请求。 =====》 200ok、 大坨json数据

    这里提示:浏览器websocket会自动帮我们加上这些标头; 在前端编程 let ss = new WebSocket("ws://localhost:5000/WeatherForecast") 也会自动帮助我们带上这些标头。

    image.png

    日志记录:

    该请求是不是webcocket请求:True,headers:[Accept, */*], [Connection, Upgrade], [Host, localhost:5000], [User-Agent, curl/7.79.1], [Upgrade, websocket], [Sec-WebSocket-Version, 13], [Sec-WebSocket-Key, eeZn6lg/rOu8QbKwltqHDA==]
    

    服务器认可这是websocket请求,服务端处理逻辑没改,故按照http代码逻辑返回200ok和JSON数据。

    真正要让服务端按照websocket姿势, 要使用HttpContext.WebSockets.AcceptWebSocketAsync()告知客户端开始切换协议,返回101响应码,并在原tcp上发起全双工通信。

    协商切换

    以上行为完美诠释了协商切换 的理念。

    客户端仅携带 Connection、Upgrade标头,被服务端当成一般的http标头。

    • sec-websocket-verison、sec-websocket-key,服务器认可是websocket请求, 但是依旧可以拒绝切换,使用http协议返回

    就坡下驴,将脚手架项目改成一个同时支持http和websocket协议的Action 吧:

    // 服务端对于websocket请求,使用服务端单向推送
        [HttpGet(Name = "GetWeatherForecast")]
      public async Task Get()
        {
            _logger.LogInformation("该请求是不是webcocket请求:"+ HttpContext.WebSockets.IsWebSocketRequest+",headers:{0}", Request.Headers);
    
            if (HttpContext.WebSockets.IsWebSocketRequest)
            {
                var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
    
                Enumerable.Range(1, 5).ToList().ForEach(async x =>
                {
                    var wf = new WeatherForecast
                    {
                        Date = DateTime.Now.AddDays(x),
                        TemperatureC = Random.Shared.Next(-20, 55),
                        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
                    };
                    var ss = JsonConvert.SerializeObject(wf);
                    var serverMsg = Encoding.UTF8.GetBytes(ss);
                    // 下面参数3: true 表示结束此次消息。
                    await webSocket.SendAsync(new ArraySegment<byte>(serverMsg, 0, serverMsg.Length), WebSocketMessageType.Text, true, CancellationToken.None);
                });
              
            }
            else
            {
                var arr = Enumerable.Range(1, 5).Select(index => new WeatherForecast
                {
                    Date = DateTime.Now.AddDays(index),
                    TemperatureC = Random.Shared.Next(-20, 55),
                    Summary = Summaries[Random.Shared.Next(Summaries.Length)]
                })
                .ToArray();
                await  Response.WriteAsJsonAsync(arr);
            }
        }   
    

    curl 'http://localhost:5000/WeatherForecast' -H 'Upgrade: websocket' -H 'Connection: Upgrade' -H 'Sec-WebSocket-Version: 13' -H 'Sec-webSocket-Key: eeZn6lg/rOu8QbKwltqHDA==' --verbose

    curl 'http://localhost:5000/WeatherForecast' -H 'Upgrade: websocket' -H 'Connection: Upgrade' --verbose

    回顾

    1. 本文记录了nginx在转发websocket请求时要添加的配置。
    2. 虽然ops错配了nginx for websocket url: nginx为http请求转发了ConnectionUpgrade标头, 但是服务器并不认可这是websocket升级协议,仅认为是携带了特殊标头的http请求,走原来的http业务处理逻辑是没有问题的。
    3. 在curl指令添加了 sec-websocket-version、sec-websocket-key标头,从客户端仿造了真实的websocket请求。

    浏览器websocket会自动帮我们加上这些标头; 在前端编程 let ss = new WebSocket("ws://localhost:5000/WeatherForecast") 也会自动帮助我们带上这些标头

    1. websocket是基于http为蓝本为建立,这是一个协商切换的行为,既然是协商, 服务端可以拒绝协商切换,依旧可以采用原http协议来处理。
  • 相关阅读:
    软件企业需要每年年审吗?
    03- 目标检测数据集和标注工具介绍 (目标检测)
    Vue2项目练手——通用后台管理项目第七节
    【Java基础】什么是Java的注释?—21天学习计划打卡第一天
    leetcode 35. 搜索插入位置
    JDK1.8新特性--->stream流
    vite-react修改antd得prefix
    yum vi/vim -- Linux
    matlab矩阵的输入
    TCP/IP网络协议通信函数接口
  • 原文地址:https://www.cnblogs.com/JulianHuang/p/16558322.html