代码审计系列题目CTFD部署(上)
阅读原文时间:2023年07月08日阅读:3

关于简单部署题目请参考:https://www.cnblogs.com/Cl0ud/p/13783325.html

如果需要进行较复杂部署,可参考本篇

PHP代码审计系列题目的部署,较之前的部署方案,改变的地方除了题目代码之外,还将题目权限进行了限制,题目结构更正规化,基础镜像没有进行修改,还是原来的 php:5.6-fpm-alpine,其不足在于该环境没有php.ini 文件,如果你出题不需要修改这个这一点可以直接忽略,优点在于该环境相比直接使用ubuntu的镜像环境占用的空间更小,这里当然就直接选择使用占用空间更小的php:5.6-fpm-alpine

目录结构如下:

-模板
    --dockerfile
    --docker-compose.yml
    --start.sh
    --www(这里面放题目的源代码)
        ---...
        ---...

这里以CTFTraining中0ctf_2016_unserialize题目的Dockerfile为例进行讲解:

其题目目录环境为:

FROM php:5.6-fpm-alpine

LABEL Author="Virink <virink@outlook.com>"
LABEL Blog="https://www.virzz.com"

COPY files /tmp/

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \
    && apk add --update --no-cache nginx mysql mysql-client \
    && docker-php-source extract \
    && docker-php-ext-install mysql \
    && docker-php-source delete \
    && mysql_install_db --user=mysql --datadir=/var/lib/mysql \
    && sh -c 'mysqld_safe &' \
    && sleep 5s \
    && mysqladmin -uroot password 'qwertyuiop' \
    && mysql -e "source /tmp/db.sql;" -uroot -pqwertyuiop \
    && mkdir /run/nginx \
    && mv /tmp/docker-php-entrypoint /usr/local/bin/docker-php-entrypoint \
    && mv /tmp/nginx.conf /etc/nginx/nginx.conf \
    && mv /tmp/vhost.nginx.conf /etc/nginx/conf.d/default.conf \
    && mv /tmp/src/* /var/www/html \
    && chmod -R -w /var/www/html \
    && chmod -R 777 /var/www/html/upload \
    && chown -R www-data:www-data /var/www/html \
    && rm -rf /tmp/* \
    && rm -rf /etc/apk

EXPOSE 80

VOLUME ["/var/log/nginx"]

CMD ["/bin/sh", "-c", "docker-php-entrypoint"]

简单查看这段dockerfile,很容易注意到这一点:(简单截取一部分

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \
    && apk add --update --no-cache nginx mysql mysql-client \

为什么这里要用两个&&而不是直接使用两个RUN

原因在于从 Docker 1.10 开始,COPY、ADD 和 RUN 语句会向镜像中添加新层。前面的示例创建了两个层而不是一个。

镜像的层就像 Git 的提交(commit)一样。

Docker 的层用于保存镜像的上一版本和当前版本之间的差异。就像 Git 的提交一样,如果你与其他存储库或镜像共享它们,就会很方便。

实际上,当你向注册表请求镜像时,只是下载你尚未拥有的层。这是一种非常高效地共享镜像的方式。

但额外的层并不是没有代价的。

层仍然会占用空间,你拥有的层越多,最终的镜像就越大。Git 存储库在这方面也是类似的,存储库的大小随着层数的增加而增加,因为 Git 必须保存提交之间的所有变更

所以在这里我们将其合并成一条命令,就是为了减少其层数而降低占用空间的大小。

现在从头开始阅读Dockerfile代码

FROM php:5.6-fpm-alpine

LABEL Author="Virink <virink@outlook.com>"
LABEL Blog="https://www.virzz.com"

FROM 命令指定基础镜像

LABEL指令用于为镜像添加元数据,多用于声明构建信息,作者、机构、组织等,每一个LABEL指令会生成一个新的镜像层,如果你使用多个label,将导致构建出一个低效的镜像,跟RUN命令类似,同时对我们几乎没啥帮助,可以略过了。

COPY files /tmp/

复制主机当前文件夹下的files文件夹到容器/tmp文件夹下

详细讲解一下RUN命令下的各条指令

sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories

在国内访问 apk 仓库较缓慢,修改docker容器下载源为中科大镜像,加速后面几步的更新和下载

apk add --update --no-cache nginx mysql mysql-client

Alpine 提供了自己的包管理工具 apk,可以通过 https://pkgs.alpinelinux.org/packages 网站上查询包信息,也可以直接通过 apk 命令直接查询和安装各种软件。

这里使用apk进行下载nginx,mysql,mysqli-client

接着是三条:

docker-php-source extract
docker-php-ext-install mysql
docker-php-source delete

关于docker-php-source , docker-php-ext-install ,docker-php-enable-docker-configure,详情参考:https://www.cnblogs.com/yinguohai/p/11329273.html

这里简单介绍:

docker-php-source extract

在php容器中创建一个/usr/src/php目录,里面放了一些自带的文件

docker-php-ext-install mysql

安装并启动PHP扩展,命令格式:

docker-php-ext-install “源码包目录名”

这里即安装并启动了mysql服务

docker-php-source delete

这里对 docker-php-source extract 初始化的 /usr/src/php目录进行了删除

接着是一段mysql的操作

mysql_install_db --user=mysql --datadir=/var/lib/mysql
sh -c 'mysqld_safe &'
sleep 5s
mysqladmin -uroot password 'qwertyuiop'
mysql -e "source /tmp/db.sql;" -uroot -pqwertyuiop

使用mysql_install_db命令初始化MySQL数据库目录

mysql_install_db --user=mysql --datadir=/var/lib/mysql

mysqld_safe是服务端工具,用于启动mysqld,并且是mysqld的守护进程,在后面加&符号令其在后台运行,而因为mysqld_safe是mysqld的守护进程,所以mysqld_safe会在启动MySQL服务器后继续监控其运行情况,并在其死机时重新启动它。

sh -c 'mysqld_safe &'

等待五秒

sleep 5s

一般dockerfile里面的sleep都是为了等待一些服务启动,防止运行到后面的命令时前面的服务还没启动,这样就会导致报错退出,sleep很大程度上降低了出现这种情况的概率。

mysqladmin -uroot password 'springbirdtcl11111'

mysqladmin命令是mysql服务器管理任务的客户端工具,它可以检查mytsql服务器的配置和当前工作状态,创建和删除数据库,创建用户和修改用户密码等操作。

这里用于修改密码了

mysql -e "source /tmp/db.sql;" -uroot -pqwertyuiop

导入并执行sql文件

mkdir /run/nginx
mv /tmp/docker-php-entrypoint /usr/local/bin/docker-php-entrypoint
mv /tmp/nginx.conf /etc/nginx/nginx.conf
mv /tmp/vhost.nginx.conf /etc/nginx/conf.d/default.conf

这一段是创建nginx文件夹以及导入我们自己写的配置文件

mv /tmp/src/* /var/www/html
chmod -R -w /var/www/html
chmod -R 777 /var/www/html/upload
chown -R www-data:www-data /var/www/html

移动源代码到html文件夹下,这样外部可访问

mv /tmp/src/* /var/www/html

进行权限限制

chmod -R -w /var/www/html
chmod -R 777 /var/www/html/upload
chown -R www-data:www-data /var/www/html

-R : 对目前目录下的所有文件与子目录进行相同的权限变更(即以递归的方式逐个变更)

+表示增加权限、- 表示取消权限、= 表示唯一设定权限。

r 表示可读取,w 表示可写入,x 表示可执行,X 表示只有当该文件是个子目录或者该文件已经被设定过为可执行。

这里递归取消了html目录下的可写入权限

因为7的二进制数为111,即递归增加了任意用户对于/upload目录下的读写执行权限

然后将/html目录下的权限限制为www-data权限

rm -rf /tmp/*
rm -rf /etc/apk

删除之前产生的源文件,以及清除apk安装包们

对外暴露80端口

EXPOSE 80

接着声明容器中/var/log/nginx为匿名卷

VOLUME ["/var/log/nginx"]

当Dockerfile中声明了匿名卷但是run的时候没有使用 -v绑定匿名卷的话那么docker就会在/var/lib/docker/volumes这个目录下创建一个目录来绑定匿名卷。

也就是容器内的nginx的log日志会被挂载到主机的/var/lib/docker/volumes的一个新的目录下

最后是

CMD ["/bin/sh", "-c", "docker-php-entrypoint"]

该docker-php-entrypoint文件在原目录的files下,其内容为:

#!/bin/sh

sed -i "s/flag{0ctf_2016_unserialize_is_very_good!}/$FLAG/" /var/www/html/config.php

export FLAG=not_flag
FLAG=not_flag

php-fpm &

nginx &

mysqld_safe &

tail -F /var/log/nginx/error.log /var/log/nginx/access.log

其实就是实现动态flag,并且启动相关服务,校内平台暂时还没有用到动态flag,所以暂时不需要考虑该问题。

下面的部分摘抄自v0n师傅的博客(写到这里的时候才发现v0n师傅写过相关的文章了,早知道分析另外一个dockerfile了呜呜呜

摘抄:

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

我们只需要src文件夹中放入源码、flag.sh、db.sql(flag.sh、db.sql文件名不能变),现在我们dockerfile只需要这么写:

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

至于python还是java环境的出题,我暂时还没尝试过,就不班门弄斧了。

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

参考链接: