"我的心田本空无一物,你来之后万物生长,你走之后,一片荒芜" --《隐入尘烟》
一、前言
最近有个朋友准备搭建一个CTF比赛靶场在学校搞一个CTF比赛,让我帮他参考参考。说实话,自己也没有整过,自己就先实验呗,搜集了大量的资料最终是搞成了,就差朋友买个服务器,和域名挂上了,这期间经历过了大量的报错,因此,想记录下来,为兄弟们指一条明道,避免出错。另外本篇文章大部分是依赖网上这位兄弟的文章,因此我把原文章链接挂出来,
感谢这位兄弟:
https://www.yuque.com/docs/share/364ef08c-9405-45fe-b269-e9236de57242?#pBjVN,
自己这样写主要是整理出来好查询,在加上自己遇到的问题和解决方案,以及汉化,题目发放的疑惑,值得写一下。
二、环境准备
- 主机:服务器版本的CentOs7
- 链接:http://centos.nethub.com.hk/7.9.2009/isos/x86_64/CentOS-7-x86_64-Minimal-2009.iso
- Docker版本:20.10.2
- Docker-compose版本:1.25.0
- IP地址:公网地址或虚拟机地址
三、环境搭建
1.系统环境搭建配置:如果是阿里云服务器,直接更新yum源:
yum update
2.如果是虚拟机不能直接更新yum源的话就换阿里源
centos 7 换阿里源方法:
- 先下载 wget :一定要确保有wget
yum install wget
- 阿里源:
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo yum clean all yum makecache yum update
注:一定要运行yum update 确保软件的更新和下载
3.换源之后把openssh-server 进行安装,主要是为了方便命令的输入,和文件的传输
- 运行:
yum install openssh-server
- 下载vim 编辑工具:
yum install vim
- 配置/etc/sshd/sshd_config 不然xshell等工具不能连接,改过之后如图所示:
- 用xshell连接就能复制命令,方便下面的操作:
4.安装系统环境所需服务,主要是python,数据库 运行完就ok了。
- 运行:
yum install -y git nginx mariadb mariadb-server Mysql-python python-pip gcc python-devel yum- utils device-mapper-persistent-data lvm2 epel-release
- 安装docker之前换源
- 命令:
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
- 选择合适的版本并安装
- 安装17.12.1.ce版本,可能很慢,耐心等待。
- 运行:
yum -y install docker-ce-17.12.1.ce
5.DaoCloud配置docker镜像源加速
- 运行:
curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io
- 查看是否安装成功并启动
- 运行:
docker --version
6.安装docker-compose
# 下载docker compose curl -L https://get.daocloud.io/docker/compose/releases/download/1.25.4/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose # 添加可执行权限 chmod +x /usr/local/bin/docker-compose # 将文件copy到 /usr/bin/目录下 ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose # 查看版本 docker-compose --version
2.靶场环境
1.下载CTFd
- 运行:
git clone https://github.com/glzjin/CTFd.git
- 下载frp
- 运行:
wget https://github.com/fatedier/frp/releases/download/v0.29.0/frp_0.29.0_linux_amd64.tar.gz
- 下载完成后解压备用:
tar -zxvf frp_0.29.0_linux_amd64.tar.gz
2.下载ctfd-whale
- 运行:
git clone https://github.com/glzjin/CTFd-Whale.git
- 将下载的CTFd-Whale文件夹重命名为小写:
mv CTFd-Whale/ ctfd-whale
3.下载docker版本的frps
- 运行:
git clone https://github.com/glzjin/Frp-Docker-For-CTFd-Whale
- 将下载后的Frp-Docker-For-CTFd-Whale也重命名为小写:
- 运行:
mv Frp-Docker-For-CTFd-Whale/ frp-docker-for-ctfd-whale
- 下载之后截图:
注:如果不能下载,或者下载缓慢的话,可以挂代理,也可以下载下来在传到服务器上
3.CTFd环境配置
1.Docker集群设置 初始化docker集群
- 运行:
docker swarm init
如果出现这种情况Cannot connect to the Docker daemon at unix:
- 运行:
systemctl start docker
- 然后再运行初始化命令:
docker swarm init
2.将刚刚初始化的这个集群加入到节点当中
命令执行后,返回的就是节点ID了,暂时不用管这个节点ID
- 运行:
docker node update --label-add='name=linux-1' $(docker node ls -q)
3.ctfd-whale放入CTFd的插件配置
- 将ctfd-whale放入CTFd的插件目录中
- 运行:
mv ctfd-whale/ CTFd/CTFd/plugins/
- 进入目录并配置frps.ini文件
- 运行:
cd frp-docker-for-ctfd-whale/frp vim frps.ini
改成图片所示:一般token不用更改,端口根据自己喜好更改,一般采用默认
4.修改完成后返回目录启动
注意:是 frp-docker-for-ctfd-whale目录
- 运行:
cd .. docker-compose up -d
耐心等待
- 构建完成以后查看是否正在运行
docker ps
5.将frpc文件配置到CTFd中
首先进入CTFd目录中,新建frpc文件夹
运行:
(1)cd CTFd/ (2) mkdir frpc
- 进入frpc的目录(frp_0.29.0_linux_amd64)将里面的frpc,frpc.ini,frpc_full.ini,LICENSE这四个文件放在CTFd/frpc文件夹中
cd ../frp_0.29.0_linux_amd64 mv frpc.ini ../CTFd/frpc/ mv frpc_full.ini ../CTFd/frpc/ mv frpc ../CTFd/frpc/ mv LICENSE ../CTFd/frpc/
- 进入刚刚新建的CTFd/fprc目录,配置frpc.ini文件,直接复制粘贴
[common] token = randomme server_addr = 172.1.0.4 server_port = 6490 pool_count = 200 tls_enable = true admin_addr = 172.1.0.3 admin_port = 7400
注意:这里的token要和frp-docker-for-ctfd-whale/frp/frps.ini的token一样,前面没改,这里就是randomme
- 进入CTFd目录,将下方内容复制到Dockerfile中,直接复制粘贴
FROM python:3.6-alpine RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories &&\ apk update && \ apk add python3 python3-dev linux-headers libffi-dev gcc make musl-dev py-pip mysql-client git openssl-dev g++ RUN adduser -D -u 1001 -s /bin/bash ctfd WORKDIR /opt/CTFd RUN mkdir -p /opt/CTFd /var/log/CTFd /var/uploads RUN pip3 config set global.index-url https://pypi.doubanio.com/simple RUN pip3 config set install.trusted-host pypi.doubanio.com COPY requirements.txt . RUN pip install -r requirements.txt -i https://pypi.doubanio.com/simple COPY . /opt/CTFd RUN for d in CTFd/plugins/*; do \ if [ -f "$d/requirements.txt" ]; then \ pip install -r $d/requirements.txt -i https://pypi.doubanio.com/simple; \ fi; \ done; RUN chmod +x /opt/CTFd/docker-entrypoint.sh RUN chown -R 1001:1001 /opt/CTFd RUN chown -R 1001:1001 /var/log/CTFd /var/uploads USER 1001 EXPOSE 8000 ENTRYPOINT ["/opt/CTFd/docker-entrypoint.sh"]
- 在CTFd目录配置docker-compose.yml文件,直接复制粘贴
version: '2.2' services: ctfd-nginx: image: nginx:1.17 volumes: - ./nginx/http.conf:/etc/nginx/nginx.conf user: root restart: always ports: - "443:443" networks: default: internal: depends_on: - ctfd cpus: '1.00' mem_limit: 150M ctfd: build: . user: root restart: always ports: - "8000:8000" environment: - UPLOAD_FOLDER=/var/uploads - DATABASE_URL=mysql+pymysql://root: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 networks: default: internal: frp: ipv4_address: 172.1.0.2 cpus: '1.00' mem_limit: 450M db: image: mariadb:10.4 restart: always environment: - MYSQL_ROOT_PASSWORD=ctfd - MYSQL_USER=ctfd - MYSQL_PASSWORD=ctfd volumes: - .data/mysql:/var/lib/mysql networks: internal: command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --wait_timeout=28800, --log-warnings=0] cpus: '1.00' mem_limit: 750M cache: image: redis:4 restart: always volumes: - .data/redis:/data networks: internal: cpus: '1.00' mem_limit: 450M frpc: image: glzjin/frp:latest restart: always volumes: - ./frpc:/conf/ entrypoint: - /usr/local/bin/frpc - -c - /conf/frpc.ini networks: frp: ipv4_address: 172.1.0.3 frp-containers: cpus: '1.00' mem_limit: 250M networks: default: internal: internal: true frp: driver: bridge ipam: config: - subnet: 172.1.0.0/16 frp-containers: driver: overlay internal: true ipam: config: - subnet: 172.2.0.0/16
- 配置requirements.txt 这里主要是修改gevent版本号,gevent原本是1.4.0的,这边经过多次尝试,不指定版本号才能拉取,直接复制粘贴
Flask==1.1.1 Werkzeug==0.16.0 Flask-SQLAlchemy==2.4.1 Flask-Caching==1.4.0 Flask-Migrate==2.5.2 Flask-Script==2.0.6 SQLAlchemy==1.3.11 SQLAlchemy-Utils==0.36.0 passlib==1.7.2 bcrypt==3.1.7 six==1.13.0 itsdangerous==1.1.0 requests>=2.20.0 PyMySQL==0.9.3 gunicorn==19.9.0 normality==2.0.0 dataset==1.1.2 mistune==0.8.4 netaddr==0.7.19 redis==3.3.11 datafreeze==0.1.0 python-dotenv==0.10.3 flask-restplus==0.13.0 pathlib2==2.3.5 flask-marshmallow==0.10.1 marshmallow-sqlalchemy==0.17.0 boto3==1.10.39 marshmallow==2.20.2 gevent tzlocal==2.1
- 配置nginx,在CTFd的目录下,新建一个nginx文件夹并进入
mkdir nginx cd nginx
- 在上面创建的nginx目录下, 创建一个文件http.conf,输入以下内容,直接复制粘贴
worker_processes 4; events { worker_connections 1024; } http { # Configuration containing list of application servers upstream app_servers { server ctfd:8000; } server { listen 80; client_max_body_size 4G; # Handle Server Sent Events for Notifications location /events { proxy_pass http://app_servers; proxy_set_header Connection ''; proxy_http_version 1.1; chunked_transfer_encoding off; proxy_buffering off; proxy_cache off; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $server_name; } # Proxy connections to the application servers location / { proxy_pass http://app_servers; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $server_name; } } }
6.开始构建,在CTFd目录下开始构建,运行下面的代码,耐心等待
docker-compose up -d
拉取完镜像之后
- 拉取完成后配置docker网络,先看一下现在的容器状态
docker ps -a
看到ctfd_frpc_1这个容器的状态是退出状态。
- 查看docker的网络
docker network ls
7.需要将ctfd_frpc_1,frp-docker-for-ctfd-whale_frps_1,ctfd_ctfd_1这三个容器加入到ctfd_frp网络中
并且这三个容器的IP如下:
ctfd_ctfd_1:172.1.0.2
ctfd_frpc_1:172.1.0.3
frp-docker-for-ctfd-whale_frps_1:172.1.0.4
- 查看一下ctfd_frp网络
docker network inspect ctfd_frp
我们需要把其他两个加进去
- 只有ctfd_ctfd_1这个容器是在ctfd_frp网络里的,此时我们指定IP将其他两个容器加入ctfd_frp网络里
docker network connect --ip 172.1.0.3 ctfd_frp ctfd_frpc_1 docker network connect --ip 172.1.0.4 ctfd_frp frp-docker-for-ctfd-whale_frps_1
- 再次查看一下ctfd_frp网络
- 重启一下这两个容器
docker restart ctfd_frpc_1 frp-docker-for-ctfd-whale_frps_1
- 再次查看一下ctfd_frp网络
docker network inspect ctfd_frp
发现已经可以看到,这个时候,ctfd_frpc_1,frp-docker-for-ctfd-whale_frps_1,ctfd_ctfd_1这三个容器已经加入到ctfd_frp网络中了
- 再查看一下ctfd_frpc_1容器如果没有一直重启就算配好了
- 直接访问http://你的ip:8000
至此,靶场已经搭建好了
4.网站基础以及ctfd-whale配置
注:这个主要是配置题目分发时端口开启情况,因为你做一道题肯定是访问网站的一个端口
- CTF配置
- 管理员配置
- 样式配置
- 配置比赛开始时间
- 直接finish 后
- 进入管理员模板配置进行ctfd-whale配置
- 设置方面,现给出下面的参数配置详情
属性配置Docker API URLunix://var/run/docker.sockFrp API IPfrpc的ip配置 172.1.0.3Frp API Portfrpc的端口配置 7400Frp Http Domain SuffixDocker API URL to connect(可填None)Frp Http Port80Frp Direct IP Address你的公网ip,本机即为127.0.0.1Frp Direct Minimum Port与之前frps最小端口呼应Frp Direct Minimum Port与之前frps最大端口呼应Max Container Count不超过最大-最小Max Renewal Times最大实例延时次数Frp config template填入frps的配置,只需填[common]Docker Auto Connect Containersctfd_frpc_1Docker Dns Setting可填机器内DNS,没有可填个外网DNS114.114.114.114Docker Swarm Nodeslinux-1 与前面swarm集群呼应Docker Multi-Container Network Subnet内网题大子网ip配置/CIDRDocker Multi-Container Network Subnet New Prefix每个内网题实例的CIDRDocker Container Timeout单位为秒Docker Auto Connect Networkctfd_frp-containers
- 其中Frp config template配置内容如下,一会直接复制粘贴就行
[common] token = randomme server_addr = 172.1.0.4 server_port = 6490 pool_count = 200 tls_enable = true admin_addr = 172.1.0.3 admin_port = 7400
- 我的配置
5.汉化
- 网上按照教程一顿操作,弄一个,网站瘫痪一次。最后发现汉化版本不一样,本次CTFd 的版本是2.3.1 因此,直接去github搜一下,果然找到了一个,只是一个半汉化。先凑合着用呗,其实你也可以自己汉化,就是把里面的样式自己改一下,比如在标题那里英文改成中文。
1.汉化链接
https://github.com/cxaqhq/ctfd-2.3.1
汉化包:ctfd-2.3.1-master.zip
下载下来之后,直接把
这两个替换为
这两个
2.汉化效果
6.发放题目
注:因为是swam模式本次仅仅测试了dynamic_ docker来添加题目 ,其他形式,请自测
1.添加一道web题目,如图这样配置
2.完成之后测试如下
因为拉取镜像需要时间,请耐性等待,多刷新浏览器
4.测试 网址
5.有的人可能就会问了,那我如何获取其他的题目,接下来就讲一讲。
- 其实也没有啥,比如ctftraining/qwb_2019_supersqli 就是拉取了ctftraining的一道题目。我在CTFd源码里面没有找到,去github找了一下,发现了这个。
https://github.com/CTFTraining/CTFTraining
- 然后我尝试拉取其他的题目,比如 qwb_2019_upload 按照格式ctftraining/qwb_2019_upload进行拉取,果然成功了。大约6-8分钟拉取完一个题目,请耐心等待。
至于其他方式拉取题目我也测试了下,因为是swam模式,只有docker-dynamic这种方式比较简单。
- standard 模式拉取ctftraining的试题也是可行的
比如我在~目录下创建一个CTFTraining目录,git 一道题目
mkdir CTFTraining cd CTFTraining/ git clone https://github.com/CTFTraining/0ctf_2016_unserialize.git cd cd 0ctf_2016_unserialize/
- 然后修改docker-compose.yml
- 修改之后,启动
- 这个时候你访问靶机地址:http://你的ip:8302 就能访问
- 同样在CTFd中加入这道题目
[是兄弟就来打我](http://192.168.52.164:8302/)
- 按照你自己的需求进行更改
flag 就是docker-compose.yml里面的flag
但是这种方式不太好,你打完这道题之后提交flag ,这道题目依然开着,只能手动关闭
6.附录:近年ctf writeup大全
https://github.com/ctfs/write-ups-2016 https://github.com/ctfs/write-ups-2015 https://github.com/ctfs/write-ups-2014 fbctf竞赛平台Demo https://github.com/facebook/fbctf ctf Resources https://github.com/ctfs/resources https://github.com/ctfwiki/ctf_game_history https://github.com/SecWiki/ctf-hub https://github.com/le31ei/ctf_challenges
7.问题报错以及解决办法
注释:本人遇到的问题还算比较少,因此报错以及解决办法多是搬运他人写的。
1.frpc日志报错:
解决方案:
仔细配置网络,网络配置的要求如下:
- 将ctfd_frpc_1,frp-docker-for-ctfd-whale_frps_1,ctfd_ctfd_1这三个容器加入到ctfd_frp网络中
- 这三个容器对应的IP如下:
- ctfd_ctfd_1:172.1.0.2
- ctfd_frpc_1:172.1.0.3
- frp-docker-for-ctfd-whale_frps_1:172.1.0.4
docker network connect --ip 172.1.0.3 ctfd_frp ctfd_frpc_1 docker network connect --ip 172.1.0.4 ctfd_frp frp-docker-for-ctfd-whale_frps_1 docker restart ctfd_frpc_1 frp-docker-for-ctfd-whale_frps_1
加入ctfd_frp网络之后,重启完成之后再查看一下ctfd_frp网络,运行
docker network inspect ctfd_frp
如果还是不行,灵活使用docker 的logs功能,查看是什么问题报错
docker logs <容器ID或名称>
2.Dockerfile的gevent报错
如果是以下报错,可以通过修改版本号解决
解决方案:
其他Dockerfile在构建的时候可能会出现gevent构建不成功的问题,有以下几种解决办法:
- 将镜像源替换为清华源
- 替换gevent版本
- 不指定gevent版本号
在CTFd目录下,修改requirements.txt的gevent
然后 docker-compose down 再启动 docker-compose up -d 不出意外应该就能解决问题
3.Dockerfile的world模块报错
解决方案:
将Dockerfile中的python和python-dev删掉,或者修改成python2或python3,python2-dev或python3-dev都可以
4.构建的时候,ctfd镜像无法启动
解决方案:
将docker-entrypoint.sh第一行修改为
#!/bin/bash
修改后重新docker-compose up -d即可
5.ctfd_ctfd_1容器一直在重启:
ctfd_ctfd_1日志报错如下
解决方法:
出现时区问题,因此在requirements.txt中添加如下:
tzlocal==2.1
6.ctf_frpc_1容器一直重启
解决方案:
仔细配置网络,网络配置的要求如下:
- 将ctfd_frpc_1,frp-docker-for-ctfd-whale_frps_1,ctfd_ctfd_1这三个容器加入到ctfd_frp网络中
- 这三个容器对应的IP如下:
- ctfd_ctfd_1:172.1.0.2
- ctfd_frpc_1:172.1.0.3
- frp-docker-for-ctfd-whale_frps_1:172.1.0.4
docker network connect --ip 172.1.0.3 ctfd_frp ctfd_frpc_1 docker network connect --ip 172.1.0.4 ctfd_frp frp-docker-for-ctfd-whale_frps_1 docker restart ctfd_frpc_1 frp-docker-for-ctfd-whale_frps_1
重启完成之后再查看一下ctfd_frp网络
docker network inspect ctfd_frp
如果还是不行,灵活使用docker 的logs功能,查看是什么问题报错
docker logs <容器ID或名称>
如果是以下报错
则重新配置frpc,编辑/CTFd/frpc/frpc.ini
[common] token = randomme server_addr = 172.1.0.4 server_port = 6490 pool_count = 200 tls_enable = true admin_addr = 172.1.0.3 #这里千万千万别忘加,之前要被搞气死 admin_port = 7400
然后运行
docker restart ctfd_frpc_1
7.MySQL容器反复重启
解决方法:
因为当前目录下的.data文件中的MySQL日志与容器版本不匹配
解决方法是将.data文件删除,重新构建镜像
7.docker容器无法启动或者frp端口无法映射
如果docker容器无法启动或者frp端口无法映射可以进容器检查,确保docker api填写正确,如docker-compose.yml中写的unix:///var/run/docker.sock你也可以使用端口形式的api如官方示例:可以用IP:端口指定API
docker容器无法启动问题
进入容器检查:
docker exec -it <ctfd容器id> sh /opt/CTFd# python >>>import docker >>>client=docker.DockerClient(base_url="unix:///var/run/docker.sock") >>>client.images.list()
如果api正确会列出所有镜像
frp端口无法映射
其实检查可以顺便检查一下上面的,因为都在ctfd容器内 docker exec -it <ctfd容器id> sh
docker exec -it <ctfd容器id> sh /opt/CTFd# python >>>import requests >>>requests.get("http://172.1.0.3:7400/api/reload")//即frp api的地址 返回 <Response [200]> #表示成功
如果frpc还是出现如下问题
requests.exceptions.ConnectionError: HTTPConnectionPool(host='172.1.0.3', port=7400): Max retries exceeded with url: /api/reload (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f8df919f850>: Failed to establish a new connection: [Errno 111] Connection refused'))
则重新配置frpc,编辑/CTFd/frpc/frpc.ini
[common] token = randomme server_addr = 172.1.0.4 server_port = 6490 pool_count = 200 tls_enable = true admin_addr = 172.1.0.3 #这里千万千万别忘加,之前要被搞气死 admin_port = 7400
然后运行docker restart ctfd_frpc_1(这里再看frpc.ini会发现内容更新了,admin配置没了,不用担心)
再进容器检查requests.get("http://172.1.0.3:7400/api/reload")应该就可以