Sekiro环境基础搭建之WSS

一、服务端部署

项目地址:https://github.com/yint-tech/sekiro-open

官方文档地址:https://sekiro.iinti.cn/sekiro-doc/01_manual/4.server_install.html

所用环境是 Ubuntu20

安装步骤:

  1. 安装 docker,可以参考这篇文章 https://blog.csdn.net/qq_44732146/article/details/121207737
  2. 安装 docker-compose:sudo pip install docker-compose
  3. 安装 Sekiro:

启动 docker 之后,执行如下命令

curl https://oss.iinti.cn/sekiro/quickstart.sh | bash
  1. 打开网址 http://(服务器的IP):5612,注册一个账号(第一个注册账号是超级管理员)

img.png

二、客户端连接服务端

此处可参考官方文档

如果你的注入网站有 wss 的同源检测拦截,那么就算是可以创建 wss 连接,也可能因为同源问题被阻断.
假定访问网站:https://xxx.abc.com
使用 wss 连接 sekiro: wss://sekiro.iinti.cn/business/register?group=ws-group&clientId=1221
如果 www.abc.com , 存在同源策略拦截,则可能报错:refused to connect to 'wss://sekiro.iinti.cn/business/register?group=ws-group&clientId=1221' because of it violates the following Content Security Policy directive: xxxxxx

提前嗦嗦安装 CA 证书

点击这里 pem 格式 或者 crt 格式 下载证书,并跟随系统引导安装根证书

方法一:使用代理解决 CSP

Sekiro 的端口服务实现了 http/https 代理,你可以直接将 Sekiro 的端口设定为你的浏览器的代理

如果你的注入网站由 wss 的同源检测拦截,那么可以通过将浏览器的代理设定到 sekio 服务器上,此时由于流量都能经过 sekiro 服务器,并且 sekiro 拥有 CA 根证书可以解密和伪造流量。 那么此时 wss 连接可以直接使用对应主网页的域名构造 wss 连接,此时 sekiro 可以感知这个连接需要转发到 sekiro 服务器,将会全自动在内部处理。

此时可以完美解决 wss 同源拦截问题

请注意,当通过代理的方式访问 Sekiro,那么 Sekiro 必然开启 ssl 解密,请注意此时一定需要安装 Sekiro 的 CA 证书(上文已提及),否则 https 网页将会全部因为证书不被信任而被阻断

方法二:构造子域名解决 CSP

如果整体将 Sekiro 服务器设置为代理服务器,则会将所有的流量打入到 Sekiro 服务器,在考虑多浏览器集群控制的时候,多 IP 聚集可能带来风控问题,以及 Sekiro 服务器作为出口 ip 也可能带来风控问题。 此时可以使用 pac 脚本单独控制 Sekiro 的 WebSocket 连接进入 sekiro 服务器,其余的流量则正常通过。

  1. 下载代理插件:https://github.com/FelisCatus/SwitchyOmega
  2. 配置 PAC 脚本
function FindProxyForURL(url, host) {
  if (shExpMatch(url, "wss://*")) {
    // 只要是wss协议,则代理到sekiro.iinti.cn:5612
    // 这里实现比较粗暴,具体规则可根据网站实际调整
    return "PROXY sekiro.iinti.cn:5612";
  }
  return "DIRECT";
}
  1. 构造和目标网站完全一致的同域名 websocket 连接
var client = new SekiroClient(
  "wss://xxx.abc.com/business/register?group=test-ws&clientId=" + Math.random()
);
client.registerAction("testAction", function (request, resolve, reject) {
  resolve("ok");
});
  1. 构造子域名解决 CSP

    1. 假定网站 sekiro 部署在: 47.93.16.121:443
    2. 配置 hosts 文件: sekrio.xxx.abc.com 47.93.16.121 使得域名 sekiro.xxx.abc.com 解析到 47.93.16.121( 也即模拟申请一个域名,绑定在 sekiro 服务器上,并且让这个域名在目标网站的子域名)
    3. 安装 CA 证书,确保系统根证书完成安装,并且设置信任 sekirio 证书
    4. 使用构造子域名的 wss 连接: wss://sekiro.xxx.abc.com/business/register?group=ws-group&clientId=1221 创建连接

    条件:需要 CSP 规则中支持 wss: connect-src wss: ,否则只能使用代理的方式,这是因为 CSP 规则认为 http 的网页不应该被 websocket 加载:https://csplite.com/csp118/

wss 注入

复制官方的代码:

function SekiroClient(e) {
  if (((this.wsURL = e), (this.handlers = {}), (this.socket = {}), !e))
    throw new Error("wsURL can not be empty!!");
  (this.webSocketFactory = this.resolveWebSocketFactory()), this.connect();
}
(SekiroClient.prototype.resolveWebSocketFactory = function () {
  if ("object" == typeof window) {
    var e = window.WebSocket ? window.WebSocket : window.MozWebSocket;
    return function (o) {
      function t(o) {
        this.mSocket = new e(o);
      }
      return (
        (t.prototype.close = function () {
          this.mSocket.close();
        }),
        (t.prototype.onmessage = function (e) {
          this.mSocket.onmessage = e;
        }),
        (t.prototype.onopen = function (e) {
          this.mSocket.onopen = e;
        }),
        (t.prototype.onclose = function (e) {
          this.mSocket.onclose = e;
        }),
        (t.prototype.send = function (e) {
          this.mSocket.send(e);
        }),
        new t(o)
      );
    };
  }
  if ("object" == typeof weex)
    try {
      console.log("test webSocket for weex");
      var o = weex.requireModule("webSocket");
      return (
        console.log("find webSocket for weex:" + o),
        function (e) {
          try {
            o.close();
          } catch (t) {}
          return o.WebSocket(e, ""), o;
        }
      );
    } catch (t) {
      console.log(t);
    }
  if ("object" == typeof WebSocket)
    return function (o) {
      return new e(o);
    };
  throw new Error("the js environment do not support websocket");
}),
  (SekiroClient.prototype.connect = function () {
    console.log("sekiro: begin of connect to wsURL: " + this.wsURL);
    var e = this;
    try {
      this.socket = this.webSocketFactory(this.wsURL);
    } catch (o) {
      console.log("sekiro: create connection failed,reconnect after 2s"),
        setTimeout(function () {
          e.connect();
        }, 2e3);
    }
    this.socket.onmessage(function (o) {
      e.handleSekiroRequest(o.data);
    }),
      this.socket.onopen(function (e) {
        console.log("sekiro: open a sekiro client connection");
      }),
      this.socket.onclose(function (o) {
        console.log("sekiro: disconnected ,reconnection after 2s"),
          setTimeout(function () {
            e.connect();
          }, 2e3);
      });
  }),
  (SekiroClient.prototype.handleSekiroRequest = function (e) {
    console.log("receive sekiro request: " + e);
    var o = JSON.parse(e),
      t = o.__sekiro_seq__;
    if (!o.action)
      return void this.sendFailed(t, "need request param {action}");
    var n = o.action;
    if (!this.handlers[n])
      return void this.sendFailed(t, "no action handler: " + n + " defined");
    var s = this.handlers[n],
      i = this;
    try {
      s(
        o,
        function (e) {
          try {
            i.sendSuccess(t, e);
          } catch (o) {
            i.sendFailed(t, "e:" + o);
          }
        },
        function (e) {
          i.sendFailed(t, e);
        }
      );
    } catch (r) {
      console.log("error: " + r), i.sendFailed(t, ":" + r);
    }
  }),
  (SekiroClient.prototype.sendSuccess = function (e, o) {
    var t;
    if ("string" == typeof o)
      try {
        t = JSON.parse(o);
      } catch (n) {
        (t = {}), (t.data = o);
      }
    else "object" == typeof o ? (t = o) : ((t = {}), (t.data = o));
    (Array.isArray(t) || "string" == typeof t) && (t = { data: t, code: 0 }),
      t.code ? (t.code = 0) : t.status ? (t.status = 0) : (t.status = 0),
      (t.__sekiro_seq__ = e);
    var s = JSON.stringify(t);
    console.log("response :" + s), this.socket.send(s);
  }),
  (SekiroClient.prototype.sendFailed = function (e, o) {
    "string" != typeof o && (o = JSON.stringify(o));
    var t = {};
    (t.message = o), (t.status = -1), (t.__sekiro_seq__ = e);
    var n = JSON.stringify(t);
    console.log("sekiro: response :" + n), this.socket.send(n);
  }),
  (SekiroClient.prototype.registerAction = function (e, o) {
    if ("string" != typeof e) throw new Error("an action must be string");
    if ("function" != typeof o) throw new Error("a handler must be function");
    return (
      console.log("sekiro: register action: " + e), (this.handlers[e] = o), this
    );
  });

var client = new SekiroClient(
  "wss://www.linkedin.cn/business/register?group=ws-group&clientId=" +
    Math.random()
);
client.registerAction("testAction", function (request, resolve, reject) {
  c = undefined;
  resolve(window.ja("d_incareer2_profile_homepage", c));
});

浏览器调用接口,成功

img_1.png