需求

在某些情况下,需要隐藏 GZCTF 的排行榜和排名。

原理

1.Dockerfile 开一个 OpenResty 做反代。

FROM openresty/openresty:centos

COPY hide.js /usr/local/openresty/nginx/conf/
COPY nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
RUN /usr/local/openresty/luajit/bin/luarocks install lua-resty-http

2.OpenResty 里隐藏API的返回。

        location ~ ^/api/game/(\d+)/details$ {
            gzip off;  

            content_by_lua_block {
                local http = require('resty.http')
                local cjson = require "cjson"

                -- 获取客户端请求的 Cookie
                local cookie = ngx.var.http_cookie

                -- 获取游戏 ID
                local game_id = ngx.var[1]

                -- 创建 HTTP 客户端实例
                local httpc = http.new()

                -- 发起 HTTP 请求
                local res, err = httpc:request_uri("http://gzctf:8080/api/game/" .. game_id .. "/details", {
                    method = "GET",
                    headers = {
                        ["Cookie"] = cookie,
                        ["X-Forwarded-For"] = ngx.var.remote_addr,
                    },
                    query = ngx.var.args  -- 转发查询参数
                })

                if not res then
                    ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
                    ngx.say("failed to request: ", err)
                    return
                end

                if res.status ~= ngx.HTTP_OK then
                    ngx.status = res.status
                    ngx.say("failed to request: ", res.body)
                    return
                end

                -- 解析 JSON 响应
                local data, err = cjson.decode(res.body)
                if not data then
                    ngx.say("failed to decode JSON: ", err)
                    return
                end

                -- 修改 rank 字段的正则表达式替换
                local modified_body = ngx.re.gsub(res.body, '"rank":\\s*\\d+', '"rank": -1')
                modified_body = ngx.re.gsub(modified_body, '"solved":\\s*\\d+', '"solved": -1')
                modified_body = ngx.re.gsub(modified_body, '"bloods":\\s*\\[[^\\]]*\\]', '"bloods": []')


                -- 发送修改后的 JSON 响应
                ngx.header.content_type = "application/json"
                ngx.say(modified_body)
            }
        }

        location ~ ^/api/game/(\d+)/scoreboard$ {
            return 200 '{
                "updateTimeUtc": "2024-05-13T08:54:29.6383344+00:00",
                "bloodBonus": 52459530,
                "timeLines": {
                    "all": []
                },
                "items": [],
                "challenges": {}
            }';
            add_header Content-Type application/json;
        }

排名直接改为 -1,积分榜直接返回空。

3. 配置中注入 JS,添加 CSS,把积分展示对应的区域和积分榜按钮隐藏。

        location /static/index.CNx9s2ZO.js {
            content_by_lua_block {
                local http = require("resty.http")
                local httpc = http.new()

                -- 发起 HTTP 请求
                local res, err = httpc:request_uri("http://gzctf:8080/static/index.CNx9s2ZO.js", {
                    method = "GET",
                    headers = {
                        ["X-Forwarded-For"] = ngx.var.remote_addr,
                    }
                })

                if not res then
                    ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
                    ngx.say("failed to request: ", err)
                    return
                end

                if res.status ~= ngx.HTTP_OK then
                    ngx.status = res.status
                    ngx.say("failed to request: ", res.body)
                    return
                end

                -- 获取响应体
                local body = res.body

                -- 读取并追加外部 JavaScript 文件内容
                local file = io.open("/usr/local/openresty/nginx/conf/hide.js", "r")
                local js_code = file:read("*all")
                file:close()

                -- 在响应体末尾追加 JavaScript 代码
                body = body .. js_code

                -- 设置响应头的内容类型
                ngx.header.content_type = "application/javascript"
                -- 发送修改后的响应体
                ngx.say(body)
            }
        }

hide.js和整个容器构建文件可以在 https://github.com/glzjin/GZCTF-Scoreboard-Hide 找到。

部署

1.克隆项目。

git clone https://github.com/glzjin/GZCTF-Scoreboard-Hide.git

2.构建

cd GZCTF-Scoreboard-Hide
docker build -t glzjin/gzctf_scoreboard_hide ./

3.修改docker-compose.yml

version: "3.0"
services:
  gzctf:
    image: gztime/gzctf:latest
    restart: always
    environment:
      - "GZCTF_ADMIN_PASSWORD=密码"
      # choose your backend language `en_US` / `zh_CN` / `ja_JP`
      - "LC_ALL=zh_CN.UTF-8"
    #ports:
    #  - "82:8080"
    volumes:
      - "./data/files:/app/files"
      - "./appsettings.json:/app/appsettings.json:ro"
      # - "./kube-config.yaml:/app/kube-config.yaml:ro" # this is required for k8s deployment
      - "/var/run/docker.sock:/var/run/docker.sock" # this is required for docker deployment
    depends_on:
      - db
    networks:
      - default
      - challenges

  proxy:
    image: glzjin/gzctf_scoreboard_hide
    restart: always
    ports:
      - "80:80"
    depends_on:
      - gzctf
    networks:
      - default
 
  db:
    image: postgres:alpine
    restart: always
    environment:
      - "POSTGRES_PASSWORD=密码"
    volumes:
      - "./data/db:/var/lib/postgresql/data"
networks:
  challenges:
    external: true

gzctf端口注释掉,并且把代理加上。

4.重启 GZCTF。

docker-compose up -d

最终效果