CTFd动态靶机搭建

基于docker搭建支持动态靶机的靶场

每做一道题,就会自动生成一个虚拟题目环境,每一个环境刚刚生成的时候都是崭新的。

我的环境

  • 主机:腾讯云的轻量服务器CentOS7.6-Docker20
  • Docker版本:20.10.17
  • Docker-compose版本:20.10.17
  • IP地址:公网地址

然后就完全按照这个链接上的做🧎🧎🧎:

成果:

ctfd

系统环境搭建配置

更新yum源,和安装系统环境所需服务:

yum update
yum install -y git nginx mariadb mariadb-server Mysql-python python-pip gcc  python-devel yum-utils device-mapper-persistent-data lvm2 epel-release

因为我的vps自带了docker,就不安装了。

靶场环境

cd ~
git clone https://github.com/glzjin/CTFd.git
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
git clone https://github.com/glzjin/CTFd-Whale.git
mv CTFd-Whale/ ctfd-whale
git clone https://github.com/glzjin/Frp-Docker-For-CTFd-Whale
mv Frp-Docker-For-CTFd-Whale/ frp-docker-for-ctfd-whale
docker swarm init
docker node update --label-add='name=linux-1' $(docker node ls -q)
mv ctfd-whale/ CTFd/CTFd/plugins/
cd frp-docker-for-ctfd-whale
docker-compose up -d
cd ~/CTFd
mkdir 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/
cd ~/CTFd/frpc
vim frpc.ini

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
cd ~/CTFd
vim Dockerfile

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"]
vim docker-compose.yml

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


vim requirements.txt

requirements.txt的内容如下:

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==1.4.0
tzlocal==2.1
mkdir nginx
cd nginx
vim http.conf

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;
    }
  }
}
cd ~/CTFd
docker-compose up -d
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 ps

查看容器,应该有6个容器,且查看状态STATUS是否是UP。是UP就OK。

在浏览器访问:http://ip:8000 即可打开。

Docker

我尝试了自己写web题目,这就要用到docker的知识。docker里有2个很重要的概念:容器和镜像。容器和镜像类似于对象和类。每道题目实际上是实例化或销毁一个docker镜像。

https://www.yuque.com/docs/share/364ef08c-9405-45fe-b269-e9236de57242?#OE1rs 里发放web题目时有一个Docker Images填写的是ctftraining/qwb_2019_supersqil。docker images就是镜像,我们填写的镜像名字是ctftraining/qwb_2019_supersqil,但是我们本地没有这个镜像啊,怎么还是创建成功了呢?是因为:

当运行容器时,使用的镜像如果在本地中不存在,docker 就会自动从 docker 镜像仓库中下载,默认是从 Docker Hub 公共镜像源下载。

我们可以从Docker Hub网站上查找镜像:Docker Hub Container Image Library | App Containerization

那如何自己出题目,自己创建镜像捏。我查阅了菜鸟教程上docker的知识,懂了大概。

可以利用本地有的镜像,实例化成容器后,使用docker exec -it id /bin/sh命令进入容器,进行web环境的搭建。搭建好后exit退出容器。再使用docker commit id image从已经创建的容器中更新镜像,并且提交这个镜像。最后在发放web题目时docker images填写我们生成的image名字就行了。

一些常用命令:

容器:

命令 功能
docker ps -a 列出所有docker进程,可以查看id、状态……
docker stop id 关闭指定id的docker进程
docker start id 开启指定id的docker进程
docker rm -f id 删除指定id的docker容器
docker exec -it id /bin/sh 进入指定id的docker容器
docker commit id image 从已经创建的容器中更新镜像,并且提交这个镜像

镜像:

命令 功能
docker images 查看所有镜像
docker run -it name /bin/sh 实例化镜像
docker rmi name 删除镜像