前言
实验环境: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:这里来解释一下container和images的区别。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解题也有帮助。