docker详解(关于CTF的出题和搭建CTF赛场)
1417199074240269 发表于 浙江 CTF 3145浏览 · 2024-07-18 07:56

前言

实验环境:Ubuntu22.04

这里将自己用docker搭建题目、CTF平台的网上搜集的资料、碰到的问题之类的全部整理下来,并且将常见问题解决,做到一文教会docker搭建CTF题目环境的效果。保姆级别一文解。

提示:

本实验都是内网操作,旨在让大家知道怎么搭建本地的CTF平台进行CTF赛题的测试。

如果大家想搭建一个所有人都能访问的外网CTF平台,可以将实验环境换成云服务器,ip之类的都用服务器的,这里也会提一嘴。

实验环境:

这里用的是Ubuntu22.04,并且创建了一个全新的Ubuntu22.04,环境就是Ubuntu官网下载的iso磁盘文件,这里是官网:

https://ubuntu.com/download/desktop

进入官网后,点击Download Ubuntu Desktops。然后进入之后点击这个:

然后选择22.04版本。(24.04版本刚出,有些操作还不熟悉,用22的经典版本来实验)

然后下载[Ubuntu 22.04.4 Desktop (64-bit)]就可以了。懒人可以尝试一下这个网址,releases的网址(可能无法使用):

https://releases.ubuntu.com/22.04/ubuntu-22.04.4-desktop-amd64.iso.torrent?_ga=2.150362738.551212074.1718608062-1847051901.1718608062&_gl=1*czkv8p*_gcl_au*MTgzNzQ1MjM0NS4xNzE3MDY5NjI1

下载完之后就直接安装就行了,这些操作就不说了。全都点下一步,只有这里不同:

这里选择光盘映像就是自己下的那个iso文件。然后进入之后选择中文英文都可以,然后就登录就可以了。

原理解释:

这是我对于docker一点个人理解,其实这个和git是可以类比来看的。git是用来储存信息、分享资源的。而docker是一个用来搭建虚拟网站、虚拟服务器专用工具。

就像是git命令有一个可以用公网访问的github,可以在上面找到大家上传的源码、exe文件,docker也有一个dockerhub,大家可以在上面找到自己需求搭建的虚拟服务器的应用和中间件。比如可以选择php+nginx、选择Ubuntu虚拟服务器等等。

这就给CTF赛题提供了一个很好的平台,我们用docker可以直接搭建一个题目,不管是web还是pwn都是很好搭建的。

然后再解释一下docker中的一些常用命令,所有的[]是需要自己修改的内容:

docker pull []
docker images
docker rmi []
docker container ls
docker stop []
docker rm []
docker build -t [] . (这个点是有意义的,是说明利用当前文件夹)
docker run -d -p []:[] test 
docker ps -a

逐条解释:

docker pull 是用来拉去dockerhub里面的镜像,如果拉取不到,dockerhub有时候确实会出bug,建议找一个可以拉到的国内镜像源。

然后用docker images可以查看本地已有镜像源,储存在本地的。docker pull或者docker build出来的都会变成docker images被储存在本地。

docker rmi就是删除镜像(remove images)的意思不过要确保没有容器正在使用这个镜像

然后docker container ls就是查看docker容器的操作。在进行docker run的操作之后,就会产生一个docker 容器。

PS:这里来解释一下containerimages的区别。images就是一个储存在本地的变量,但是他是不会占用运行内存的。个人理解就是,比如在windows系统中有一个exe文件,当你没有运行他的时候,他并不会占用运行内存,但它会占用机器内存。但是运行的时候,它是会占用运行内存的。就是images进行docker run之后,就会变成docker container。这里画了一张图片:

不管是dockerhub pull来的资源,或者是dockerfile build的资源,都会先变成images储存在本地,然后用run变成container。如果端口可以被公网访问,那么这时候就可以访问了。

docker stop就是用来停止container占用的端口,并且服务直接停止。

docker rm就是用来移除这个container,但是在这之前必须先stop停止服务。

然后就是build -t了。命令是:docker build -t (该容器名字,可随意自取,但是不能重复) . (这个点是一定要加的,就是用来指定当前文件夹的),即:

docker build -t test .

这就可以build出一个image镜像了。

然后用docker run -id --name testweb -p []:[] test 。从前到后是
题目名字testweb
第一个端口是访问端口,比如开放的是5000,那么访问就是http://ip:5000这样的形式
第二个端口是映射端口,这是dockerfile里面定义的,比如EXPOSE 80开放80端口作为映射端口。那么就是5000:80这样的形式

映像名字test,这个test是需要指定的images,就是刚刚自己用docker build 创建的,比如刚刚创建的test。

然后是docker ps -a,用来查询docker进程,可以看到container是否在运行的进程。

这些docker常见命令掌握了,就能正常地搭建一个题目了。

前期准备:

前期准备我们需要下载一些之后要用到的工具。

直接在桌面打开terminal先,然后输入代码:

sudo apt install docker.io docker-compose

这个docker-compose用来搭建CTF平台,平台选择的是GZ::CTF,(因为我认为这个前端比较好看),网上也可以找到别的CTF平台。

遇到Y/n直接输入回车就行,如果sudo输入password就输入开机密码。

然后就安装好了docker和docker-compose,然后用以下代码验证安装:

docker -v
docker-compose -v

这样就是安装好了,然后去下个vim。

sudo apt install vim

这个vim是一个Linux系统下的编译器,作用就是编译文件。由于Linux没有图形系统,所以所有文件都可以进行编译,而vim是现在比较好用的一款编译器,这里有几个常见的操作方法:

键盘按下Insert或者按下i或者按下a进入插入模式,这个模式下就和编辑正常的txt文件一样编辑文件内容即可,普通模式是不能更改的。

用键盘上的Esc可以退出插入模式。

普通模式下,在文件末尾输入:q。一定要 冒号+Q 。冒号也是有意义的。这个是不保存直接退出。

还有:wq是保存加退出。

问题:

如果有时候碰到了readonly file不能更改,那就用sudo vim 文件名然后就能更改内容了。如果遇到这个页面:

输入E加回车就可以正常编辑了。

然后就要创建CTF平台的配置文件了,要把它放在一个文件夹里,我直接放在桌面的GZCTF文件夹了,然后再创建配置文件:

mkdir GZCTF
cd GZCTF
sudo touch appsettings.json
sudo touch docker-compose.yml
ls

然后就回显这两个创建好的文件,我们要进行编辑的,这两个文件网上都有,我这里就用GZCTF的配置文件,文件内容套用的是

https://blog.csdn.net/a00221aa/article/details/138073077这篇师傅的模板,写的很清晰。

首先是appsettings.json:

{
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "Database": "Host=db:5432;Database=gzctf;Username=postgres;Password=<Your POSTGRES_PASSWORD>"
  },
  "EmailConfig": {
    "SendMailAddress": "a@a.com",
    "UserName": "",
    "Password": "",
    "Smtp": {
      "Host": "localhost",
      "Port": 587
    }
  },
  "XorKey": "<Your XOR_KEY>",
  "ContainerProvider": {
    "Type": "Docker", // or "Kubernetes"
    "PortMappingType": "Default", // or "PlatformProxy"
    "EnableTrafficCapture": false,
    "PublicEntry": "<Your PUBLIC_ENTRY>", // or "xxx.xxx.xxx.xxx"
    // optional
    "DockerConfig": {
      "SwarmMode": false,
      "Uri": "unix:///var/run/docker.sock"
    }
  },
  "RequestLogging": false,
  "DisableRateLimit": true,
  "RegistryConfig": {
    "UserName": "",
    "Password": "",
    "ServerAddress": ""
  },
  "CaptchaConfig": {
    "Provider": "None", // or "CloudflareTurnstile" or "GoogleRecaptcha"
    "SiteKey": "<Your SITE_KEY>",
    "SecretKey": "<Your SECRET_KEY>",
    // optional
    "GoogleRecaptcha": {
      "VerifyAPIAddress": "https://www.recaptcha.net/recaptcha/api/siteverify",
      "RecaptchaThreshold": "0.5"
    }
  },
  "ForwardedOptions": {
    "ForwardedHeaders": 5,
    "ForwardLimit": 1,
    "TrustedNetworks": ["192.168.12.0/8"]
  }
}

这里要改的是:

POSTGRES_PASSWORD: 数据库密码

XOR_KEY: 用于加密比赛私钥的随机字符串

PUBLIC_ENTRY: 外部访问地址,可以是IP或域名

TrustedNetworks: 修改成自己的对应IP,防止网段冲突问题

然后这里有几点要说的是,XOR_KEY可以随意填写,
public_entry如果要写域名,必须是开放使用端口的自己的域名,这样才能成功访问到。而且如果用的是内网的话,这里的ip就用ifconfig直接查看就可以了:

sudo apt install net-tools
ifconfig

找到自己的如:192.168.XX.XXX的ip,放到那个TrustedNetworks里面,然后那个public_entry调整成127.0.0.1就可以了。密码这些自己设置把。

注意:

如果是云服务器,这里就将127.0.0.1调整成服务器的ip就可以了。

然后是docker-compose.yml了。这里是配置文件内容:

version: "3.0"
services:
  gzctf:
    image: gztime/gzctf:latest
    restart: always
    environment:
      - "GZCTF_ADMIN_PASSWORD=<Your GZCTF_ADMIN_PASSWORD>"
      # choose your backend language `en_US` / `zh_CN` / `ja_JP`
      - "LC_ALL=zh_CN.UTF-8"
    ports:
      - "80: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

  db:
    image: postgres:alpine
    restart: always
    environment:
      - "POSTGRES_PASSWORD=<Your POSTGRES_PASSWORD>"
    volumes:
      - "./data/db:/var/lib/postgresql/data"

这里要修改的有:

GZCTF_ADMIN_PASSWORD:初始管理员密码,在数据库未初始化时生效,需要在第一次启动时进行设置

POSTGRES_PASSWORD: 数据库密码

这里要注意的是,初始管理员密码一定要和平台相符,GZ的是大于8位并且有大小写字母和数字。然后这个数据库密码,和刚刚appsettings里面的数据库密码必须保持一致。

这些准备工作,除了ip以外全部都是一致的,所以就算是云服务器也可以这么操作。

然后就是平台创建了,接下来使用这个命令来搭建平台:

sudo docker-compose up -d

首次创建比较慢,要稍等一会。如果dockerhub坏了,就用国内镜像源,或者网上找一个镜像源:腾讯、网易都有。

换源:

换源操作如下:

先是:

sudo vim /etc/docker/daemon.json

这个是docker镜像源的配置文件,如果没有,直接用vim会自动创建。然后在文件内输入这个:

{
  "registry-mirrors": ["https://registry.docker-cn.com"]
}

括号里的内容也可以换成自己喜欢的镜像源,哪个快用哪个就行。我用的是自己阿里云的镜像站

然后一定要重启进程,不然成功不了:

sudo systemctl daemon-reload        #重启daemon进程
sudo systemctl restart docker       #重启docker

运行完之后,重复compose up的步骤:

慢慢等他启动成功就行:

CTF平台:

然后启动成功之后可以用自己的127.0.0.1访问了,如果是云服务器,就用服务器ip直接访问就可以了。

进去之后是一个这样的页面,接下里的操作就是要登录管理员。

GZ::CTF平台里面默认的管理员账号是Admin,然后密码就是刚刚的配置文件docker-compose.yml里面设置的密码,然后进入管理登录,然后新建比赛

这个页面建议时间调得长一些,这样下次进行docker测试的时候就可以直接用,然后取一个标题,新建比赛就行了。进入之后,题目管理然后新建题目就可以上传题目了。

接下来也说一下题目上传方法,以及如何用docker部署一个题目。

部署题目:

首先如果要部署题目,建议可以先从别的资源处获取题目的dockerfile,网上可以找到,这里有个很经典的题库,里面有很多经典的题:

https://github.com/W4terDr0p/W4terCTF-2023

这是WaterDr0p师傅上传到github的一个自己收集的题库,这里面就可以很简单地进行题目的部署。

这里就以部署web题目环境为例子部署一下这个环境。

首先下过来的文件夹放入虚拟机,即W4ter这个文件夹,然后进入之后在challenges文件夹找到web,我用的是one-sql的题目。直接进入该文件夹,然后打开terminal。

输入:

docker build -t onesql .

其中onesql可以自己取名,后面那个点是当前目录的意思。

build完成之后就是回显这样的东西。

我们接下来使用docker images来看看刚刚build出来的东西。

可以看到onesql标签(tag)latest。这个是可以调整的,比如刚刚的命令

docker build -t onesql:tag1 .

这样这个tag也会随之变化,在之后如果出现重名或者软件更新的时候,latest标签会使用不了,这里用latest演示的时候可以方便一些。

运行题目:

运行题目有两种方法,这里先解释一下:

第一种是docker run,docker run就是正常的docker镜像运行的方法,如果我docker run,需要当前ip开放一个可以映射的端口,还有一个空的端口,这样在空的端口映射才可以。举个反例,比如我刚刚用了8080端口部署的GZ::CTF平台或者是别的环境,那么当我用run再次利用此端口时,会回显端口被占用而不能运行。所以docker run在自己的ip的端口利用率较高的时候不太好用。

第二种就是直接用CTF平台进行题目运行,个人理解,其实原理和docker run是一样的,但是这个CTF平台会用本身占用的空端口给题目开放一个端口,举个例子,比如我的CTF平台除了占用了8080之外,其实它还占用了30000-40000的空闲端口,这些端口在平时不会冲突,但是当需要使用题目测试功能时,就会空出来给题目用,然后再占用此端口。减少了端口判断的功夫,并且也不会因为端口而导致环境崩溃的结果。

第一种:

那么接下来将会逐一展示,首先我们都需要用到这个onesql的环境。

用的指令是:

docker run -d -p 5137:80 onesql

这个docker run前面已经解释过了,-p就是端口。5137是可访问端口,然后80是映射端口。最后的onsql是镜像名称。

这里5137只要是空闲端口就可以,但是80必须是Dockerfile中写到的暴露端口,就是这个意思:

这里的EXPOSE 80中的80就是暴露端口,如果没有这个EXPOSE就是默认80端口。然后我们访问127.0.0.1:5137试试。如果是云服务器就访问 服务器ip:端口。

可以看到题目可以正常运行了。

接下来我们先看看容器运行情况,有两种方法:

docker ps -a
docker container ls

然后查看发现这个容器会一直运行,为了减少端口占用,结束一个题目测试之后建议把它先down了,不然以后忘了就不好了。

上面是题目,下面两个是GZ::CTF的容器。

然后执行:

docker stop 016(container ID前几位就可以)
docker rm 016

就可以把容器给down了。

第二种:

接下来就是平台直接部署,这个非常方便:

进到管理员界面的题目管理,然后点击新建题目进入题目后台,这里以web题目为例。所以我用的是动态容器选项:

然后就在这个位置输入onesql,就是自己刚刚用的题目的镜像名字,然后创建实例就可以了

然后实例入口会给一个端口,拿去直接访问就可以了:

这就是题目部署的简单实现了。

编写题目:

接下来是编写题目的环节,这个环节有两种方法。

直接拿现成的题目环境镜像进行改编,比如刚刚的one-sql的题目环境:

可以改它的app.py就可以改变源码,或者init.py和init.sh就可以修改配置信息。但是改编之前一定要经过作者同意,所以这里不进行赘述。

第二种方法是在网上找开源的可以直接改的环境,比如这么搜索:

nginx + php docker环境

这里直接用网上的集成环境就可以了,但是也可以直接docker pull php:7.4,然后docker pull nginx,有些麻烦。

webdevops/php-nginx

这个镜像就直接包含了php和nginx的环境。

指令就是:

docker pull webdevops/php-nginx

直接拉取环境,拉完之后docker images就可以直接查看到这个镜像名称了:

不过这里教学就只是说这个好用,所以还是直接从零开始写吧:

Dockerfile编写:

首先先新建一个新的文件夹,里面先放上三个文件:

Dockerfile、src(文件夹)、default.conf(因为有些不太好用)

在桌面打开terminal,指令:

mkdir mydocker
cd mydocker
touch Dockerfile
mkdir src
touch default.conf

然后点开文件夹就是:

如果觉得网速慢,可以加一个:

touch repositories

然后这个repositories的镜像源设置,可以直接用国内的aliyun的镜像源,即该文件内容:

https://mirrors.aliyun.com/alpine/v3.11/main/
https://mirrors.aliyun.com/alpine/v3.11/community/

其中default.conf可以直接用我这个:

server {
  listen 80;
  server_name localhost;

  root /var/www/html;
  index ***index.php*** index.htm index.nginx-debian.html;

  location / {
    try_files $uri $uri/ =404;
  }
  location ~ \.php$ {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
  }
}

这里面被我用三个*框起来的index.php可以换,换成index.html也可以,就是一开始访问该网址,直接定向到哪个文件。

我们直接写一个web最常见的php题目,所以先pull一下php:7.3-fpm-alpine3.11(这里的tag是版本,所以不能改)

docker pull php:7.3-fpm-alpine3.11

然后dockerfile编写题目其实就是先拉取一个镜像,即

FROM php:7.3-fpm-alpine3.11

然后把该文件夹下需要拉到dockerfile构建的虚拟服务端的文件都送到服务端去再说:

ADD repositories /etc/apk/repositories
ADD default.conf /

第一条命令不能改,这个是镜像源的配置文件,第二条先放到根目录。然后我们写一个题目出来放到src里面:

cd src
touch index.php

这里我直接给个一句话木马题目就好(新的系统没装Hackbar,就用GET来假装一句话木马好了):

<?php
highlight_file('index.php');
# 你知道一句话木马吗?
eval($_GET[J]);
?>

然后去dockerfile里面写:

ADD ./src/* /

将src目录里面所有文件都放到根目录。

这时候写一个sh文件,让flag能到我们预期位置,并且可以将php服务挂起,不能关闭。

在dockerfile同目录下写一个init.sh文件:

touch init.sh

由于GZCTF的动态flag生成在环境变量中,将他移到根目录,并且环境变量清除,需要init.sh文件这么写:

#!/bin/sh

echo $GZCTF_FLAG > /flag
export GZCTF_FLAG="not_flag" 
GZCTF_FLAG="not_flag"

# 启动 php-fpm,后台运行
php-fpm -D
# 启动 nginx,不要在后台运行,以保持容器活跃
nginx -g 'daemon off;'

注意:

这些命令都是最常见的,包括:

RUN #运行
FROM #拉取
ADD #添加到
EXPOSE #暴露端口

还有.sh文件中就是执行shell命令。既然已经直到怎么将flag从环境变量移到根目录,那么拆分、其他操作都可以举一反三了。

记得要创建/flag,并且给/flag权限,否则不能写入:

RUN touch /flag \
    && chmod 666 /flag

然后这一段可以直接照抄,是nginx环境的配置:

RUN apk update && apk add nginx && \
    apk add m4 autoconf make gcc g++ linux-headers && \
    docker-php-ext-install pdo_mysql opcache mysqli && \
    mkdir /run/nginx && \
    mv /default.conf /etc/nginx/conf.d && \
    touch /run/nginx/nginx.pid && \
    chmod 755 /init.sh && \
    chmod 755 /var/www/html/ &&\
    apk del m4 autoconf make gcc g++ linux-headers

要给/var/www/html/目录(这是web题目的默认路径)权限,否则容易在题目出现问题。然后移入文件:

mv /index.php /var/www/html && \

最后是开放端口

EXPOSE 80
EXPOSE 9000 #备用端口

最后用init.sh进入,则整个文件是这样的:

FROM php:7.3-fpm-alpine3.11

ADD repositories /etc/apk/repositories
ADD default.conf /
ADD init.sh /
ADD ./src/index.php /

RUN touch /flag \
    && chmod 666 /flag
RUN apk update && apk add nginx && \
    apk add m4 autoconf make gcc g++ linux-headers && \
    docker-php-ext-install pdo_mysql opcache mysqli && \
    mkdir /run/nginx && \
    mv /default.conf /etc/nginx/conf.d && \
    mv /index.php /var/www/html && \
    touch /run/nginx/nginx.pid && \
    chmod 755 /init.sh && \
    chmod 755 /var/www/html/ &&\
    apk del m4 autoconf make gcc g++ linux-headers

EXPOSE 80
EXPOSE 9000

ENTRYPOINT ["/init.sh"]

然后就可以直接docker build了。虽然用了镜像,但还是要一会时间。因为这个环境是带有mysql数据库的,更好出题。指令:

docker build -t test2 .

这个test2可以自取,点不能忘记,是当前目录。

然后回显这个就说明已经搭建好了:

名字是test2,我们拿去测试一下:

题目测试:

直接进入题目,发现回显都是正常,看看flag有没有被清除,以及根目录下flag是否存在:

payload:

?J=system('env');

环境变量清除没问题。

payload:

?J=system('cat /flag');

根目录下flag也没有问题。

总结:

docker不论是搭建CTF平台,还是单纯用docker来部署题目环境都是很好用的。而且目前情况来看,许多CTF平台的题目都是用docker来出的,所以多学习docker的各种命令是肯定没错的。有的时候源码泄露也能看出flag的位置,对CTF解题也有帮助。

1 条评论
某人
表情
可输入 255