CTF需要web环境,通过docker可以更好更快地创建一个独立的容器,作为选手的动态靶机。
Docker,是具备有简化配置、全平台、提高效率、方便共享、快速部署等特点的。
去年学了docker,一直想用它来创建一个本地web环境。
正好赶上了出题时机,顺便总结一下近期的docker笔记。

一、Docker基本命令

纯笔记,其实也已经够用了。 详细资料请移步下方链接🔗

拉取查看篇

1
2
3
4
5
6
7
8
docker pull ubuntu:16.04                # 拉取对应镜像

docker images # 查看当前系统中存在镜像

docker ps # 查看运行中的容器
docker ps -a # 查看所有容器(包括已结束运行的)

docker cp 主机文件名 容器id:容器指定目录 # 将主机文件复制到指定位置,反之则交换位置

运行篇

1
2
docker run -it 容器id		      # 以交互模式运行容器,为容器重新分配一个伪输入终端
docker run -d 容器id # 后台运行容器,并返回容器ID
docker run命令详情
1
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

OPTIONS说明:

  • -a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项;
  • -d: 后台运行容器,并返回容器ID;
  • -i: 以交互模式运行容器,通常与 -t 同时使用;
  • -P: 随机端口映射,容器内部端口随机映射到主机的端口
  • -p: 指定端口映射,格式为:主机(宿主)端口:容器端口
  • -t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
  • –name=“nginx-lb”: 为容器指定一个名称;
  • –dns 8.8.8.8: 指定容器使用的DNS服务器,默认和宿主一致;
  • –dns-search example.com: 指定容器DNS搜索域名,默认和宿主一致;
  • -h “mars”: 指定容器的hostname;
  • -e username=“ritchie”: 设置环境变量;
  • –env-file=[]: 从指定文件读入环境变量;
  • –cpuset=“0-2” or --cpuset=“0,1,2”: 绑定容器到指定CPU运行;
  • -m :设置容器使用内存最大值;
  • –net=“bridge”: 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型;
  • –link=[]: 添加链接到另一个容器;
  • –expose=[]: 开放一个端口或一组端口;
  • –volume , -v: 绑定一个卷

停止删除篇

1
2
3
4
5
6
7
docker stop 容器id 	            #停止指定容器
docker stop $(docker ps -a -q) #停止所有已经运行的容器

docker rm 容器id #删除指定容器,删除前先停止
docker rm $(docker ps -a -q) #删除所有已经停止的指令

docker rmi 镜像id # 删除指定镜像

进入退出容器篇

1
2
3
docker exec -it 容器id  /bin/bash  	# 进入容器的bash界面
docker attach 容器id # 进入容器内部
exit # 进入docker内部后退出容器

之前的docker exec -it ID /bin/bash其实并没有进入这个容器,而是弹了一个bash出来让我们能在容器里操作,而attach才算进入了容器内部。

查看相关配置

1
2
3
4
docker port 容器id   				# 查看容器映射端口
netstat -tlnp # 查看主机开放端口
systemctl status firewalld # 查看防火墙状态
systemctl stop firewalld # 暂时关闭防火墙

导出导入篇

1
2
3
4
docker export 容器id > ctf.tar			# 导出为tar文件
docker save 镜像name > ./ctf.tar # 将→指定镜像名←保存成 tar 归档文件
cat ctf.tar | docker import - ctf # 可以这样导入
docker load<ctf.tar # 载入镜像包

小Tip:

  • docker save 保存的是镜像(image),docker export 保存的是容器(container);

  • docker load 用来载入镜像包,docker import 用来载入容器包,但两者都会恢复为镜像;

  • docker load 不能对载入的镜像重命名,而 docker import 可以为镜像指定新名称。

若导入时出现这个问题open /var/lib/docker/tmp/docker-import-970689518/bin/json: no such file or directory,说明这个tar包缺少docker所需要的一些json文件,不能直接导入。

二、文件目录总览

目录文件夹如下:
文件目录总览

三、Dockerfile

Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。

👉 Dockerfile相关指令教程 👈

文件名:Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
FROM ubuntu:16.04

LABEL Author="Ztop"
LABEL Blog="https://www.zeker.top"
ENV REFRESHED_AT 2022-03-01

ENV LANG C.UTF-8

# 先写 修改源/更新 【如果必须的话】
# 替换源(这里可用sed或者直接COPY一个完整的sources.list来替换)
RUN sed -i s@/deb.debian.org/@/mirrors.aliyun.com/@g /etc/apt/sources.list && \
sed -i s@/security.debian.org/@/mirrors.aliyun.com/@g /etc/apt/sources.list && \
apt-get update -y


#防止Apache安装过程中地区的设置出错
ENV DEBIAN_FRONTEND noninteractive

#安装apache2
RUN apt-get install apache2 -y

#安装php
RUN apt-get install php -y
RUN apt-get install libapache2-mod-php -y --fix-missing
RUN apt-get install php7.0-mysql -y


# #安装mysql
# RUN apt-get install mysql-server -y
# RUN apt-get install mysql-client -y

# 然后才是复制文件,不推荐挂载卷
# ADD会自动解压压缩包,而COPY不会
# ADD html.tgz /var/www
#将题目源码放进去
RUN rm -rf /var/www/html/index.html
COPY ./src/ /var/www/html/

WORKDIR /var/www/html/

#启动脚本
COPY ./run.sh /root/run.sh
RUN chmod +x /root/run.sh
ENTRYPOINT ["/root/run.sh"]

#设置环境变量
#ENV FLAG=flag{test_flag}

#暴露端口
EXPOSE 80

在 Dockerfile 文件的存放目录下,执行构建动作。

1
2
3
4
5
6
7
8
docker build -t ctf:v1 .            # dockerfile目录下输入构建命令,用来构建基于ubuntu:16.04的自定义镜像。

docker run -d -p 5555:80 -e FLAG=flag{$(cat /proc/sys/kernel/random/uuid)} 镜像id # 添加系统uuid作为动态flag字段

docker ps # 查看是否运行

docker exec -it 容器id /bin/bash # 进入容器
exit # 在容器内退出

四、Shell文件

文件名:run.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#! /bin/bash

# 定义动态flag字段
uuid=$(cat /proc/sys/kernel/random/uuid)
FLAG="flag{$uuid}"
sed -i "s/flag_is_here/$FLAG/" /var/www/html/flag
# sed -i "s/flag_is_here/$FLAG/" /var/www/html/flag

# 覆盖掉环境变量,防止非预期
export FLAG=not_flag
FLAG=not_flag

#启动apache
service apache2 restart

# # never exit,此处是为了运行完上条应用服务后,有对应的前台进程
tail -f /dev/null

附上flag文件中的内容

1
flag_is_here

sed -i 就是直接对文本文件进行操作的
替换文件中的原字符串

1
2
3
sed -i 's/原字符串/新字符串/' /home/1.txt		# 替换每一行的第一个匹配项

sed -i 's/原字符串/新字符串/g' /home/1.txt # 替换每一行的所有匹配项

五、Docker-compose

Docker-compose 是一个用来把 docker 自动化的东西。
有了 docker-compose 你可以把所有繁杂重复的 docker 操作全都一条命令,自动化的完成。

Compose 是用于定义和运行多容器 Docker 应用程序的工具。
通过 Compose,你可以使用 YML 文件来配置应用程序需要的所有服务。
然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。

文件名:docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 指定该文件版本
version: '3'
# 把每个子目录视为一个镜像,开始构建
services:
web1:
# 此处仅允许 image, build, ports,禁止其他字段出现,如果有 volume,cmd 等设置需求,请在 Dockerfile 里进行文件拷贝或者申明。
image: ctf/test1 #镜像名字
build: ./web/ #build的位置,docker会去web1中的dockerfile开始搭建
# restart: always
ports:
- "5555:80" #设置映射的断口
environment:
- FLAG=flag{this_is_test_flag} # 这里定义了flag,但是不会覆盖sh里的$FLAG
# - FLAG=flag{$(cat /proc/sys/kernel/random/uuid)} # 获取容器的uuid作为flag 会报错
  • version
    说明了yml文件指明的版本号,一般我们使用2,3这两个版本。
  • services
    yml文件的主体,定义了服务了配置。里面的web标签是我们自己定义的。
    build表明了以dockerfile类型启动一个容器,后面跟的是dockerfile的路径,支持相对路径和绝对路径,在这个yml文件里面,表明dockerfile与yml处在同个目录下。

容器的启动可以根据已有的镜像,如果定义了image这个标签,就会从本地搜寻相关镜像构建容器,如果本地找不到相关的镜像,就会从网上数据库搜寻相关的镜像。

但大家可能会产生疑问了,这里我们定义了build还有image两个不同的标签来构建镜像,那么容器到底要用build还是image来构建呢,这种情况下将按照dockerfile的方式来构建镜像,并且把镜像的名称定义为image标签里面的名称。

  • environment
    构建了相关的环境变量,在这里我们是定义了一个FLAG环境变量,并且值为flag{this_is_test_flag}
    定义之前要删除shell文件中的定义变量,否则会报错
  • ports
    定义了映射的端口,5555:80表示映射到本机的5555端口,后面的80端口则要与Dockerfile文件中EXPOSE的端口保持一致。

编写完成,直接运行下面第一条compose命令

1
2
3
docker-compose up  -d   # 创建并启动所有服务(常用)
docker-compose build # 容器镜像创建(没启动)
docker-compose stop # 关闭所有服务

此时访问主机的5555端口,即http://your-ip:5555,即可看到题目👇
浏览器题目查看
查看一下是否实现本地动态flag👇
获取动态flag

六、好用的出题镜像

从上面的过程中,我们看到对于一道题目来说,除了源码以外,最大的不方便之处就是还要有相关的一些文件配置。
在这里我推荐virink写的base_image_nginx_mysql_php_56来辅助我们快速出题。
这个镜像主要好在不需要我们去配置其他的nginx设置,而且还支持自动导入db.sql文件,支持自动执行flag.sh文件。

这里以一道题为例,题目中主要是需要配置数据库.
我们只需要src文件夹中放入源码flag.shdb.sql(文件名不能变),现在我们dockerfile只需要这么写:

1
2
3
4
5
6
FROM ctftraining/base_image_nginx_mysql_php_56

COPY src /var/www/html

RUN mv /var/www/html/flag.sh / \
&& chmod +x /flag.sh

这个镜像就会自动为我们配置相关的nginx文件,同时自动导入要执行的db.sql文件来配置数据库(默认用户为root,密码也为root,执行后db.sql会被删掉)
同时镜像还会自动执行flash.sh来从环境变量中读取flag写入到flag.php中(需要注意的是,flag.sh必需在根目录下,也就是我们需要执行mv /var/www/html/flag.sh /这一步的原因所在)。
可以看到,这个镜像极大的方便了我们对Dockerfile的编写,把Dockerfile简化到只需要两三句话就能搞定。

如果是要在php7的环境下出题,可以采用base_image_nginx_mysql_php_73

更多docker模板可前往链接 👉 https://github.com/DASCTF-Test 👈 ,这个docker模板中囊括了很多比赛题目需要的环境,web方面拥有多个版本php mysql的环境,拥有java与python等题目环境,pwn也拥有多个版本的操作系统与题目环境。

如果还想通过更多的环境学习如何出题,可以在这个 👉 项目 👈 中查看更多的题目。