前言

上学期新生赛前夕(2024.7.2~2024.10.1),我一直很苦恼怎样用docker出ctf-web题,我那时候有一个很错误的思维:我要先把docker学明白了,再去动手实践弄这些。

再加上我暑假以及开学后很长一段时间受一些事情干扰很重,那段时间有点摆大烂(别学我…)。然后我的题在新生赛上闹了个笑话:原本是一个文件包含题,直接在url中输入flag即可拿到。。

这次辅导员突然叫我们给自家专业搞个CTF小比赛,刚好练下我的docker技术。

这道题目出得很简单了,勿喷。。

About Docker

Docker到底是个啥,我从我个人的理解出发简单说说:

假设一个程序需要在A操作系统下配置B环境再加上C的依赖,如果没有docker,而且情况又不那么理想下:

我是B系统,我要现在虚拟机中整个A系统,再配环境配依赖七七八八的,特别特别麻烦。

但是docker的出现让我们能够把构建环境的一整个过程写在一个配置文件中(dockerfile),让docker把这个单独的环境给还原。这样就不必大炮打蚊子。而且方便传输。

几个重要的基础概念

dockerfile

Dockerfile 是一个文本文件,包含了构建 Docker 镜像所需的所有命令和指令。它是一个自动化的脚本,用来描述如何从基础镜像创建一个定制化的 Docker 镜像,并且执行一系列操作(例如安装软件、配置环境、复制文件等)。

常见的 Dockerfile 指令

  • FROM:指定基础镜像,例如 FROM ubuntu:latest
  • RUN:在镜像中执行命令(例如安装软件包)。
  • COPY:将本地文件复制到镜像中。
  • WORKDIR:设置工作目录。
  • CMDENTRYPOINT:指定容器启动时执行的命令。
  • EXPOSE:声明容器的端口。

示例 Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
dockerfile复制编辑# 使用官方的 Node.js 镜像作为基础镜像
FROM node:14

# 设置工作目录
WORKDIR /app

# 复制本地文件到镜像中
COPY . .

# 安装依赖
RUN npm install

# 暴露应用程序端口
EXPOSE 3000

# 容器启动时执行的命令
CMD ["npm", "start"]

docker-compose

Docker Compose 是一个用于定义和运行多容器 Docker 应用的工具。通过一个名为 docker-compose.yml 的配置文件,你可以使用 YAML 语法描述多个容器的服务、网络、卷等配置,并且一键启动和管理它们。

主要功能

  • 定义多个服务:你可以在同一个 Compose 文件中定义多个容器服务。
  • 网络管理:自动创建和管理容器之间的网络连接。
  • 数据卷:管理容器的数据卷,方便数据共享和持久化。
  • 容器编排:通过一个命令启动或停止所有服务。

**示例 Docker Compose 文件 (docker-compose.yml)**:

1
2
3
4
5
6
7
8
9
10
yaml复制编辑version: '3'
services:
web:
image: nginx:latest
ports:
- "8080:80"
db:
image: mysql:latest
environment:
MYSQL_ROOT_PASSWORD: example
  • 这个例子定义了两个服务:
    • web 使用 nginx 镜像,暴露端口 8080。
    • db 使用 mysql 镜像,设置了数据库的 root 密码。

常用的 Docker Compose 命令

  • docker-compose up:根据配置文件启动所有服务。
  • docker-compose down:停止并移除服务。
  • docker-compose build:根据 Dockerfile 构建镜像。

实战

我想出一个文件包含+文件上传的简单CTF题。

首先新建一个目录,看看结构:

image-20250329131801872

dockerfile

看看这些具体内容,就知道它在从上到下顺序构建docker环境。

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
# 指定基础镜像为:
FROM php:7.3-fpm-alpine3.11

# 把default.conf放到根目录(这个是nginx的配置文件)
ADD default.conf /
ADD init.sh /

# 当前目录的src源码拷贝到html目录
COPY ./src /var/www/html

# 创建flag文件,内容inis.sh中有写入
RUN touch /flag \
&& chmod 666 /flag

# !!给apk进行换源,不然配置的时候等到明年,然后给你爆个红XD
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories

# 跑一些七七八八的基础配置,具体自己搜吧
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/ &&\
chmod 777 /var/www/html/uploads &&\
apk del m4 autoconf make gcc g++ linux-headers

# 暴露端口
EXPOSE 80
EXPOSE 9000

# 指定容器启动时运行的命令为根目录下的/init.sh
ENTRYPOINT ["/init.sh"]

在 Docker 中,镜像(image)是基于现有的操作系统构建的。对于你给出的 Dockerfile,虽然它没有明确说明创建一个新的 Linux 系统,但 FROM php:7.3-fpm-alpine3.11 指定了基础镜像,而这个基础镜像就是包含了一个操作系统(在这个案例中是基于 Alpine Linux 的 PHP 镜像)。

docker-compose

这个就没什么好看的了:

1
2
3
4
5
6
7
8
9
10
11
12
13
name: "upload"  # 设置应用程序的名称

services:
web: # 定义 web 服务
build: . # 使用当前目录的 Dockerfile 来构建镜像
container_name: upload # 设置容器的名称为 "upload"
ports:
- "8080:80" # 映射宿主机的 8080 端口到容器内的 80 端口
# volumes:
# - ./src:/var/www/html/uploads # 映射本地的 ./src 目录到容器内的 /var/www/html/uploads 目录
environment:
- APACHE_RUN_USER=www-data # 设置 Apache 进程运行的用户为 www-data
- APACHE_RUN_GROUP=www-data # 设置 Apache 进程运行的组为 www-data

default.conf

nginx配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
listen 80;
server_name localhost;

root /var/www/html;
index index.php;

location / {
try_files $uri $uri/ =404;
}

location /uploads/ {
alias /var/www/html/uploads/;
autoindex on;
}

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;
}
}

基本内容看完了之后,直接在当前目录开命令行:

1
docker-compose build

记住每次修改docker配置文件,都要重新build

后台启动!

1
docker-compose up -d

调试

1
docker exec -it upload Linux命令

这个命令可以查看当前docker-compose的运行情况。

image-20250329133739127

push至dockerhub

这里我用的是命令行。

首先docker login登录上dockerhub,注意这里如果登录不上的话,clash开全局代理。

然后docker images查看所有镜像,关注到目标即可。

docker tag (image_id) userid/name:latest

注意这里的name在你的docker-compose里头看:image-20250402201534260

docker push user_id/name:latest即可