#CTF

# 引言

放暑假了,想着搭建一个 ctf 靶场训练下新生,但遇到的问题比我想象中的多得多。。。写个 blog 记录一下。

# 参考文章

  • ctfd 使用 ctfd-whale 动态靶机插件搭建靶场指南

  • 手把手教你如何建立一个支持 ctf 动态独立靶机的靶场(ctfd+ctfd-whale)

  • CTFd-Whale 推荐部署实践

三篇文章所使用的方法基本上差不多,但是部署的 ctfd 版本和插件版本不尽相同,第一篇文章中使用的是 frankli0324 师傅修改后的 ctfd-whale,但是部署上之后测试发现,renew 功能无法使用,不知道是不是我部署错误的问题,而第二篇则是无法正确的映射题目的端口,所以最后还是选择了官方的部署教程。

经过更新后,frankli0324 师傅修改后的 ctfd-whale 已经可以正常使用。

# 部署

# 环境准备

  • 安装 docker

  • 安装 docker-compose

# 配置 swarm

docker swarm init
docker node update --label-add='name=linux-1' $(docker node ls -q)

# 安装

  1. 下载 ctfd
git clone https://github.com/CTFd/CTFd --depth=1
cd CTFd
  1. 修改 docker-compose.yml
version '2' -> version '3'

接着 docker-compose up -d ,对 CTFd 进行初始配置。

  1. 配置 frps,在 docker-compose-yml 中配置如下:
services:
    ...
    frps:
        image: glzjin/frp
        restart: always
        volumes:
            - ./conf/frp:/conf
        entrypoint:
            - /usr/local/bin/frps
            - -c
            - /conf/frps.ini
        ports:
            - 10000-10100:10000-10100  # 映射 direct 类型题目的端口
            - 8001:8001  # 映射 http 类型题目的端口
        networks:
            default:  # 需要将 frps 暴露到公网以正常访问题目容器
            frp_connect:
              ipv4_address: 172.1.0.3
  
networks:
    ...
    frp_connect:
        driver: overlay
        internal: true
        ipam:
            config:
                - subnet: 172.1.0.0/16
  1. 创建目录 ./conf/frp,并创建 frps.ini 文件,填写:
[common]
# 下面两个端口注意不要与 direct 类型题目端口范围重合
bind_port = 7987  # frpc 连接到 frps 的端口
vhost_http_port = 8001  # frps 映射http类型题目的端口
token = your_token
subdomain_host = node3.buuoj.cn  # 访问http题目容器的主机名,需要配置泛解析域名

之后部署时需要把注释内容全部删除,否则会报错。

  1. 配置 frpc,同样修改 docker-compose.yml
services:
    ...
    frpc:
        image: glzjin/frp:latest
        restart: always
        volumes:
          - ./conf/frp:/conf/
        entrypoint:
          - /usr/local/bin/frpc
          - -c
          - /conf/frpc.ini
        depends_on:
          - frps #frps 需要先成功运行
        networks:
            frp_containers:  # 供 frpc 访问题目容器
            frp_connect:  # 供 frpc 访问 frps, CTFd 访问 frpc
                ipv4_address: 172.1.0.4
  
networks:
    ...
    frp_containers:
        driver: overlay
        internal: true  # 如果允许题目容器访问外网,则可以去掉
        attachable: true
        ipam:
            config:
                - subnet: 172.2.0.0/16
  1. 创建./conf/frp/frpc.ini,填写:
[common]
token = your_token
server_addr = 172.1.0.3
server_port = 7987  # 对应 frps 的 bind_port
admin_addr = 172.1.0.4  # 请参考 “安全事项”
admin_port = 7400
  1. 检查 frp 配置是否正确
docker-compose up -d
docker-compose logs frpc

查看日志是否正确,正确情况如下:

[service.go:224] login to server success, get run id [******], server udp port [******]
[service.go:109] admin server listen on ******
  1. 配置 CTFd
services:
    ctfd:
        ...
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock
        depends_on:
            - frpc #frpc需要先运行
        networks:
            ...
            frp_connect:

照着修改 docker-compose.yml 即可。

  1. 下载 CTFd-Whale
git clone https://github.com/frankli0324/CTFd-Whale CTFd/plugins/ctfd-whale --depth=1
docker-compose build # 需要安装依赖
docker-compose up -d
  1. 配置 ctfd-whale

1. Auto Connect Network:ctfd_frp_containers

2. Http Domain Suffix:设置好的泛解析域名

3. Http Port:同 frps.ini 的 vhost_http_port

4. Direct IP Address:公网 ip

设置完成后理论上应该就成功了。

完整的 docker-compose.tml:

version: '3'
  
services:
  ctfd:
    build: .
    user: root
    restart: always
    ports:
      - "8000:8000"
    environment:
      - UPLOAD_FOLDER=/var/uploads
      - DATABASE_URL=mysql+pymysql://ctfd:ctfd@db/ctfd
      - REDIS_URL=redis://cache:6379
      - WORKERS=1
      - LOG_FOLDER=/var/log/CTFd
      - ACCESS_LOG=-
      - ERROR_LOG=-
      - REVERSE_PROXY=true
    volumes:
      - .data/CTFd/logs:/var/log/CTFd
      - .data/CTFd/uploads:/var/uploads
      - .:/opt/CTFd:ro
      - /var/run/docker.sock:/var/run/docker.sock
    depends_on:
      - db
      - frpc
    networks:
        default:
        internal:
        frp_connect:
          ipv4_address: 172.1.0.5
  
  nginx:
    image: nginx:1.17
    restart: always
    volumes:
      - ./conf/nginx/http.conf:/etc/nginx/nginx.conf
    ports:
      - 80:80
    depends_on:
      - ctfd
  
  db:
    image: mariadb:10.4.12
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=ctfd
      - MYSQL_USER=ctfd
      - MYSQL_PASSWORD=ctfd
      - MYSQL_DATABASE=ctfd
    volumes:
      - .data/mysql:/var/lib/mysql
    networks:
        internal:
    # This command is required to set important mariadb defaults
    command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --wait_timeout=28800, --log-warnings=0]
  
  cache:
    image: redis:4
    restart: always
    volumes:
    - .data/redis:/data
    networks:
        internal:
  frps:
        image: glzjin/frp
        restart: always
        volumes:
            - ./conf/frp:/conf
        entrypoint:
            - /usr/local/bin/frps
            - -c
            - /conf/frps.ini
        ports:
            - 10000-10100:10000-10100  # 映射direct类型题目的端口
            - 8001:8001  # 映射http类型题目的端口
        networks:
            default:  # 需要将frps暴露到公网以正常访问题目容器
            frp_connect:
              ipv4_address: 172.1.0.3
  frpc:
        image: glzjin/frp:latest
        restart: always
        volumes:
          - ./conf/frp:/conf/
        entrypoint:
          - /usr/local/bin/frpc
          - -c
          - /conf/frpc.ini
        depends_on:
          - frps #frps需要先成功运行
        networks:
            frp_containers:  # 供frpc访问题目容器
            frp_connect:  # 供frpc访问frps, CTFd访问frpc
                ipv4_address: 172.1.0.4
  
networks:
    default:
    internal:
        internal: true
    frp_connect:
        driver: overlay
        internal: true
        ipam:
            config:
                - subnet: 172.1.0.0/16
    frp_containers:
        driver: overlay
        internal: true  # 如果允许题目容器访问外网,则可以去掉
        attachable: true
        ipam:
            config:
                - subnet: 172.2.0.0/16

# 遇到的问题

# 部署时提示找不到 python 和 python-dev

ERROR: unable to select packages:
    required by: world[python]
  python-dev (no such package):
    required by: world[python-dev]

修改 python 和 python-dev 为 python3 和 python3-dev

# 编译时提示 cannot execute ‘cc1plus’

Dockerfile 中添加安装 g++

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
    apk update && \
    apk add \
        ...
        g++ \
        ...

# docker ps 正常但无法进入网站

docker logs 容器 id 显示

/usr/local/lib/python3.7/site-packages/tzlocal/unix.py:158: UserWarning: Can not find any timezone configuration, defaulting to UTC.
  warnings.warn('Can not find any timezone configuration, defaulting to UTC.')
Starting CTFd
/usr/local/lib/python3.7/importlib/_bootstrap.py:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
  return f(*args, **kwds)
/usr/local/lib/python3.7/importlib/_bootstrap.py:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
  return f(*args, **kwds)
/usr/local/lib/python3.7/importlib/_bootstrap.py:219: RuntimeWarning: greenlet.greenlet size changed, may indicate binary incompatibility. Expected 144 from C header, got 152 from PyObject
  return f(*args, **kwds)
[2020-10-11 12:31:30 +0000] [1] [INFO] Starting gunicorn 19.9.0
[2020-10-11 12:31:30 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
[2020-10-11 12:31:30 +0000] [1] [INFO] Using worker: gevent
[2020-10-11 12:31:30 +0000] [21] [INFO] Booting worker with pid: 21
[2020-10-11 12:31:31 +0000] [23] [INFO] Booting worker with pid: 23
[2020-10-11 12:31:32 +0000] [25] [INFO] Booting worker with pid: 25
[2020-10-11 12:31:34 +0000] [27] [INFO] Booting worker with pid: 27
[2020-10-11 12:31:35 +0000] [29] [INFO] Booting worker with pid: 29
[2020-10-11 12:31:36 +0000] [31] [INFO] Booting worker with pid: 31
[2020-10-11 12:31:37 +0000] [33] [INFO] Booting worker with pid: 33
[2020-10-11 12:31:39 +0000] [35] [INFO] Booting worker with pid: 35
[2020-10-11 12:31:40 +0000] [37] [INFO] Booting worker with pid: 37(一直在加)

删除 requirements.txt 中 gevent 的版本号。

# 无法更新 ctfd-whale 配置

这个问题很奇怪,我只遇到过一次,然后放了一晚上之后他就好了

所以,解决方法是,建议重启 docker