飞牛NAS白嫖H5棋牌游戏:openinggame/qp 从安全审计到完整部署实录

写在前头:这套东西是 2021 年的闭源商业演示版,放出来主要是引流到作者 Telegram 群的。部署玩玩可以,当生产平台用就算了。本文记录完整过程,重点是各种坑和修补方案,给同样爱折腾的朋友一个参考。

一、项目是什么

GitHub 上的 openinggame/qp,一套商用 H5 网页版棋牌游戏平台(麻将/斗地主/百家乐/捕鱼/彩票等 30+ 游戏),Go 微服务架构 + Egret 引擎前端,支持千万级用户是宣传话术,实际就是个小集群。

关键点:仓库里没有服务端源码,只有 docker-compose.yml + 数据库备份(mysqldb.tar.gz / mongodb.tar.gz),游戏逻辑全在 Docker Hub 的闭源镜像里:

  • openinggame/web:v1 —— nginx + H5 前端
  • openinggame/server:v1 —— 游戏服务端(gs 游戏服 / gt 网关 / p 代理,三个 Go 进程挤在一个容器里)

二、先审计,再部署

部署前先把两个镜像从 Docker Hub 拉下来拆包做了静态分析(我部署的机器是飞牛 NAS,4 核 / 16G):

审计结论:没发现传统后门。

  • 三个 Go 二进制 strings 扫描:无公网 IP、无外部回连、无挖矿/反弹 shell 特征
  • 依赖全是正常库(sarama/zap/gokrb5 等)
  • 数据库备份是 2021 年空库初始数据,无隐藏管理员账号

但有几个真实风险点:

  1. kafka 容器挂载了宿主机 /var/run/docker.sock —— 容器能控制宿主机 Docker,等于给容器开了宿主机的门(已删)
  2. 镜像用可变 tag(:v1),作者随时能推送恶意新版本(已锁本地)
  3. 弱口令一堆:MySQL root/root、Redis 123456、MongoDB 123456,JWT 密钥硬编码
  4. 原版端口 80/81 直接暴露公网、无 TLS
另外这游戏有个"区块链开奖"功能(水果机/百人牛牛),开奖走 ginar.io 区块链服务、可跳以太坊浏览器验证——这就是它宣传的"公平公正",本质是赌场玩法背书,心里有数就行。

三、部署流程

3.1 环境

  • 飞牛 NAS(x86_64,4核/16G),Docker 29.x + Compose v5
  • 本机 80/81/443 被飞牛系统 nginx 占用,改用高位端口 8081/8082 只绑内网

3.2 第一个坑:镜像拉不下来

docker pull 走了代理环境变量完全无效——因为拉镜像是 dockerd 守护进程干的,客户端设的 https_proxy 它根本不理,直连 Docker Hub 基本卡死。

修补方案:手动从 registry 下载镜像层 + 构造 docker save 格式 + docker load 导入。

# 1. 拿 token
TOKEN=$(curl -s -x socks5h://旁路由:7893 \
  "https://auth.docker.io/token?service=registry.docker.io&scope=repository:openinggame/server:pull" \
  | jq -r .token)

# 2. 拿 manifest(注意 docker hub 返回的 digest 在响应头里)
curl -s -D headers.txt -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
  "https://registry-1.docker.io/v2/openinggame/server/manifests/v1"

# 3. 下载每层(⚠️ blob 下载返回 307 跳 CloudFront,curl 必须加 -L)
curl -sL -H "Authorization: Bearer $TOKEN" \
  "https://registry-1.docker.io/v2/openinggame/server/blobs/sha256:<layer-digest>"

# 4. 解压 gzip 层 → 按 config.json 的 rootfs.diff_ids 命名目录 → 写 manifest.json/repositories
#    → tar 打包 → docker load -i xxx.tar

要点:

  • 多架构镜像(mysql/mongo 等)返回的是 manifest list,要按 platform=linux/amd64 再取一次单架构 manifest
  • 层目录名用 config.jsonrootfs.diff_ids(解压后 digest),不是下载时的压缩 digest
  • docker load 需要 manifest.json(含 RepoTags/Layers)+ repositories 文件,缺一不可

3.3 加固版 docker-compose

原版 compose 直接改造成这样(完整文件我放在文末链接,关键修改):

services:
  web:
    image: openinggame/web:v1        # 已本地锁死
    ports:
      - "192.168.2.21:8081:80"      # 只绑内网,不用 80
    environment:
      - API_HOST=192.168.2.21
    volumes:
      - ./hall-config/HALL_config_53ef0ae.json:/HALL_config_53ef0ae.json:ro
  server:
    image: openinggame/server:v1
    ports:
      - "192.168.2.21:8082:81"
    volumes:
      - ./run.sh:/run.sh:ro         # 修补后的启动脚本
  mysql:
    image: mysql:8.0.23
    volumes:
      - qp-mysql:/var/lib/mysql     # named volume,不暴露端口
  mongodb:
    image: mongo:4.4.4
    command: --auth
    volumes:
      - qp-mongo:/data/db
  # redis0/1/2、kafka、zookeeper、etcd 全部不暴露端口,仅 game 网络内部

删除/修改的关键项:

  • kafka 去掉 - /var/run/docker.sock:/var/run/docker.sock
  • etcd 的 --data-dir 改到卷路径(原版写容器层,重启丢数据)
  • kafka 加 KAFKA_HEAP_OPTS=-Xmx256m -Xms256m 省内存
  • 前端配置 HALL_config 的 ipList 预填 127.0.0.1:8082,web 启动时 entrypoint 会自动把 127.0.0.1 替换成 API_HOST

数据库数据:把仓库里的 mysqldb/mongodb 备份解压,用临时 alpine 容器灌进 named volume,注意 chown -R 999:999(mysql/mongo 的 uid 都是 999)。

四、排坑实录(重点)

坑 3:gs/gt 端口竞争,游戏服起不来

启动后登录接口一直连不上,查日志发现:

listen tcp 0.0.0.0:6071: bind: address already in use

原因:gs(游戏服)和 gt(网关)跑在同一个容器里,两者都会绑定固定端口 6060/6071/6072。run.sh 里 gs 先启动,但 gs 二进制 55MB 加载慢,被后启动的 gt 抢先绑了 6071 → gs 的 RPC 模块初始化失败 → Web 服务(8080)不监听 → p 网关连不上游戏服 → 全链路挂。

修补:写自己的 run.sh 挂载覆盖容器里的 /run.sh,gs 启动后等 12 秒再启动 gt/p:

cd /server/gs && ./gs -profile=game >/dev/null 2>&1 &
sleep 12s   # 等 gs 完成固定端口绑定
cd /server/gt && ./gt -profile=gate >/dev/null 2>&1 &
cd /server/p && ./p -profile=proxy >/dev/null 2>&1 &

坑 4:浏览器报"所有线路不可用"

部署好了但浏览器打开一直提示线路不可用。排查过程非常曲折,结论是前端连的不是普通 WebSocket,而是定制 MQTT 协议

  1. 前端线路检测是 HTTP 请求 /1,响应必须是字符串 "2"(这个接口正常)
  2. 登录接口 POST /game/login/by/guest 正常,返回 JWT
  3. 真正的坑在登录后的 WS 连接:前端用 Paho MQTT 客户端,连接地址是 ws://IP:8082/ws(Paho 定制版默认 path),必须带正确的 Origin 头http://IP:8081),否则 403
  4. 握手成功后是定制 MQTT 握手流程

    • 客户端发标准 MQTT CONNECT(MQIsdp v3)
    • 服务器回 PRE_CONNECT(0xF0 类型,带 messageIdentifier)
    • 客户端计算 hash = CRC32(messageIdentifier + password + "!@#$%^&*IH") 再发一次 CONNECT
    • 服务器校验通过才回 CONNACK
  5. 密码必须是 exchange 后的 token:登录返回的初始 token 还要再调一次 POST /game/login/exchange/token(form 格式 token=xxx)换新 token,用初始 token 连 MQTT 必被拒

排查手段记录一下:headless Chrome CDP(--remote-debugging-port=9222)开页面抓 console + WebSocket 帧(Network.webSocketFrameSent/Received),把协议栈一层层扒出来的。附 Python 手工握手脚本:

import websocket, struct, zlib

ws = websocket.create_connection("ws://192.168.2.21:8082/ws",
    header={"Origin": "http://192.168.2.21:8081"},
    subprotocols=["mqttv3.1"])
# 第一次 CONNECT
ws.send_binary(mqtt_connect("test01", "user", token_exchanged))
pre = ws.recv()  # 0xF0 02 xx xx = PRE_CONNECT
msg_id = pre[2] << 8 | pre[3]
hashv = str(zlib.crc32(f"{msg_id}{token_exchanged}!@#$%^&*IH".encode()) & 0xFFFFFFFF)
ws.send_binary(mqtt_connect("test01", "user", token_exchanged, game_version="1.0.000", hashv=hashv))
resp = ws.recv()  # 0x20 02 00 00 = CONNACK 成功!
如果你也遇到"所有线路不可用":先确认上面 5 步,再检查浏览器缓存——我这边服务端一切正常,最后发现是浏览器缓存了旧配置,清缓存 + 无痕窗口就好了。

坑 5:游戏服每 10 分钟报 ginar 超时

CallInitialize [http://172.16.236.22:7090/rng/initialize/...] i/o timeout

原因:gs 硬编码了原作者内网 RNG 服务器地址(172.16.236.22:7090,区块链开奖服务),我们的网络里不存在,每 10 分钟重试超时一次。普通游戏(斗地主/麻将)不受影响,只影响"区块链开奖"类游戏初始化。闭源改不了,忽略即可。

坑 6:出牌假死、卡顿

排查发现是 NAS 负载高(4 核机器 load 5+):原有十几个容器 + 游戏集群 10 个容器 + 调试用的 headless Chrome 后台页面在抢 CPU。清理掉调试进程后负载从 5.7 降到 1.3,流畅多了。

另外这个游戏本身是 2021 年演示版,出牌逻辑 bug 多、体验不完善是常态,别指望生产级品质。

五、总结

  • 这套棋牌能跑、能玩,游客免注册,每个注册号送 100 万游戏币,当玩具/研究用可以
  • 闭源 + 演示版 = 别上线、别充钱、别当真
  • 最大收获其实是排坑过程:手动下载 Docker 镜像、定制 MQTT 协议逆向、CDP 调试前端——折腾本身就是乐趣

部署目录结构:

qp/
├── docker-compose.yml          # 加固版编排
├── run.sh                      # 修补版启动脚本(gs 先跑 12 秒)
├── hall-config/
│   └── HALL_config_53ef0ae.json  # 定制前端配置(ipList 指向 8082)
└── repo/                       # 原仓库(数据库备份等)

记录于折腾之夜。如果这篇文章对你有帮助,欢迎交流;如果发现哪步有更好的解法,也请告诉我。

飞牛NAS白嫖H5棋牌游戏:openinggame/qp 从安全审计到完整部署实录
https://ifmo.cn/index.php/archives/NAS-H5-openinggame-qp.html
本文作者 辣条
发布时间 2026-08-11
许可协议 CC BY-NC-SA 4.0
发表新评论