118

使用Docker构建安全的虚拟空间

 5 years ago
source link: http://www.freebuf.com/articles/rookie/185860.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

*本文作者:Li4n06,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。

前言

最近上的某水课的作业是出 ctf web题目,然而大多数同学连 php 都没学过,(滑稽)更别说配置服务器了,于是我想能不能趁机赚一波外快 造福一下同学,(其实就是想折腾了)。所以打算把我自己的 vps 分成虚拟空间给大家用。但是一般的虚拟空间安全性难以得到保证,一个空间出问题,其他的用户可能都跟着遭殃,也就是旁站攻击。更何况我们这个虚拟空间的用处是 ctf web 题目,总不能让人做出一道题目就能顺手拿到所有题目的 flag 吧。于是想到了使用 docker 来构建安全的虚拟空间,其间遇到了不少问题,下面就是折腾的过程了。

nA7buq2.jpg!web

实现思路

大体的思路是,在我的 vps 上为每个用户创建一个文件目录,然后将目录挂载到 docker 容器的默认网站目录,也就是/var/www/html,,用户可以通过 FTP 将网站源码上传到自己的文件目录,文件也会同步到容器内。这样就实现了各个空间的环境隔离,避免旁站攻击。

而数据库则可以单独构建一个 mysql 容器,为每个用户分配一个 user&database,让用户和空间容器来远程连接。

前期准备

选择镜像:

空间使用的镜像为: mattrayner/lamp:latest-1604 (ubuntu 16.04 + apachd2 + mysql,其实只要有mysql-client 就可以了)

数据库所使用的镜像为: mysql:5 (mysql 官方镜像)

配置FTP:

和配置常规的 FTP 没什么区别,这里特别强调3点:

一定要开启 ch_root,防止不同用户之间可以互相查看文件;

如果使用被动模式,那么 云主机的安全组 或者iptables 不要忘了放行端口;

将 umask 设置为 022 (保证用户上传的文件默认权限为755。

选择一个位置存放用户文件夹:

我这里新建一个 ~/rooms/ 来存放用户的文件夹。

配置数据库

1. 网络:

要让虚拟空间的容器能够远程连接数据库,首先要使容器之间在一个网段,那么我们就需要设置一个桥接模式的 docker network,我这里使用 172.22.0.0/16 这个网段。

$ docker network create --driver = bridge --subnet = 172 .22.0.0/16 room_net

2.创建 MySQL 容器:

我们的数据库需要满足:

允许用户远程连接;

允许空间容器连接。

第一点要求,我们通过将数据库容器的 3306 端口映射到 VPS 的开放端口即可,我这里映射到 3307。

第二点要求,只要通过我们刚刚设置的 docker network 即可实现。

所以启动创建容器的命令是的命令是:

$ docker run -d --name room-mysql --network room_net --ip 172 .22.0.1 -p 3307 :3306 -e MYSQL_ROOT_PASSWORD = your_password mysql:5

值得注意的一点是,root 用户是不需要远程登录的,出于安全考虑,我们应该 禁止其通过localhost意外的host登录

执行:

$ docker exec -it room-mysql /bin/bash -c "mysql -u root -p -e\"use mysql;update user set host='localhost' where user='root';drop user where user='root' and host='%';flush privileges;\""

创建空间过程

做好前期的准备工作,我们就可以开始构建空间了,出于方便我们将整个过程编写成 shell 脚本,这样以后要新建空间的时候,只需要运行一下就可以了。

我们创建空间需要以下几个步骤:

1. 创建新的 FTP 用户

这个用户应该满足这样的要求:

可以上传文件到虚拟空间用户文件夹 (废话);

不能访问除虚拟空间用户文件夹之外的位置 (在配置 FTP 时通过ch_root 实现);

创建的时候设置一个随机密码;

不能通过 ssh 登陆 (其实这也是用户能通过 ftp 连接 的必须条件。如果不限制的话,ftp登录时会出现 530 错误。

那么对应的 shell 脚本就是:

#/home/ubuntu/rooms/ 即你的vps上用来存放用户文件夹的位置  
# $1 参数为要设置的用户名,也是虚拟空间容器&数据库用户&数据库&用户文件夹的名字
useradd -g ftp -d /home/ubuntu/rooms/$1 -m $1 
​
pass=`cat /dev/urandom | head -n 10 | md5sum | head -c 10`  #生成随即密码
echo $1:$pass | chpasswd                                    #为用户设置密码
​
#限制用户通过 ssh 登录(如/etc/shells 里没有/usr/sbin/nologin 需要自己加进去
usermod -s /usr/sbin/nologin $1             
echo "create ftp user:$1 indentified by $pass"              #输出用户名和密码

2. 新建数据库用户&数据库,并为用户赋权

这部分操作比较简单,我们就只需要为用户新建一个 MySQL 账户和一个专属数据库就好了。

shell 脚本:

# 让用户输入 mysql 容器的 root 密码
read -sp "请输入 MySQL 容器的 root 账户密码:" mysql_pass
# 创建数据库
docker exec -it mysql-docker /bin/bash -c "mysql -u root -p$mysql_pass -e \"create database $1;\""
# 生成密码
pass=`cat /dev/urandom | head -n 10 | md5sum | head -c 10`
# 创建 MySQL 用户
docker exec -it mysql-docker /bin/bash -c "mysql -u root -p$mysql_pass -e \"CREATE USER '$1'@'%' IDENTIFIED BY '$pass';\""
# 为用户赋予权限
docker exec -it mysql-docker /bin/bash -c "mysql -u root -p$mysql_pass -e \"grant all privileges on $1.* to '$1'@'%';flush privileges;\""
# 输出账户信息
echo "create database user:$1@'%' indentified by $pass"

3. 新建空间

到现在我们已经可以创建空间容器了,想一想这个空间要满足什么基本要求呢?

能够外网访问;

能够连接数据库;

挂载用户文件夹内的文件到网站根目录。

那么命令就是:

$ docker run -d --name $1 --network room_net -p $2 :80 -v /home/ubuntu/rooms/$1 /www:/var/www/html mattrayner/lamp:latest-1604

但是作为一个用做虚拟空间的容器,我们还需要考虑 内存 的问题,如果不加限制,docker默认使用的最大内存就是 VPS 本身的内存,很容易被人恶意耗尽主机资源。

所以我们还要限制一下容器的最大使用内存。

关于 docker 容器内存使用的有趣的现象:

在最初,我把容器的内存限制到了 128m,然后访问网站发现 apache 服务没有正常启动,于是我把内存限制上调到了 256m,然后执行 docker stats 发现容器内存使用率接近100%;

有趣的是,当我尝试限制内存为 128m ,然后手动开启 apache 服务时,发现服务完全可以被正常启动,查看内存占用率,发现只占用了 30m 左右的内存。

为什么会出现这种情况呢?我大概猜想是因为容器内还有一些其他服务,当限制内存小于 256m 的时候,这些服务无法被同时启用,但是我们可以只启用 apache 啊!

于是命令变成了下面这样:

docker run -d --name $1 --cpus 0 .25 -m 64m --network room_net -p $2 :80 -v /home/ubuntu/rooms/$1 /www:/var/www/html mattrayner/lamp:latest-1604 docker exec -it $1 /bin/bash -c "service apach2 start;"

最后一步,修改挂载文件夹的所有者:

到这时,理论上我们的空间已经可以正常使用了,可是我用 FTP 连接上去发现,并没有权限上传文件。

经过漫长的 debug 后发现,在容器启动一段时间后,我们挂载到容器内部的文件夹的所有者发生了改变,于是我查看了容器内部的 run.sh 脚本,发现了这样的内容:

if [ -n "$VAGRANT_OSX_MODE" ];then
    usermod -u $DOCKER_USER_ID www-data
    groupmod -g $(($DOCKER_USER_GID + 10000)) $(getent group $DOCKER_USER_GID | cut -d: -f1)
    groupmod -g ${DOCKER_USER_GID} staff
    chmod -R 770 /var/lib/mysql
    chmod -R 770 /var/run/mysqld
    chown -R www-data:staff /var/lib/mysql
    chown -R www-data:staff /var/run/mysqld
else
    # Tweaks to give Apache/PHP write permissions to the app
    chown -R www-data:staff /var/www
    chown -R www-data:staff /app
    chown -R www-data:staff /var/lib/mysql
    chown -R www-data:staff /var/run/mysqld
    chmod -R 770 /var/lib/mysql
    chmod -R 770 /var/run/mysqld
fi

可以看到,当没有设置 $VAGRANT_OSX_MODE 这个环境变量时,容器会修改 /app(/var/www/html 的软链接)文件夹的所有者为 www-data ,那么我们就需要在启动容器时,设置这个环境变量值为真。

而 /app 文件夹 的默认所有者是 root 用户,我们将本地文件夹挂载到容器内的/app,后,本地文件夹的所有者也会变为 root 。所以我们还需要修改本地文件夹的所有者。

于是创建容器的 shell 脚本又变成了:

# 启动容器
docker run -d --name $1 --cpus 0.25 -m 64m --network room_net -p $2:80 -eVAGRANT_OSX_MODE=1 -v /home/ubuntu/rooms/$1/www:/var/www/html mattrayner/lamp:latest-1604
# 启动apache2
docker exec -it $1 /bin/bash -c "service apache2 start;"
# 修改挂载文件夹的所有者
chown $1:ftp -R /home/ubuntu/rooms/$1/www

最后的脚本:

到现在创建空间的过程就结束了,那么贴上最后的脚本

创建空间脚本:

#!/bin/bash
# The shell to create new room
# Last modified by Li4n0 on 2018.9.25
# Usage: 
#   option 1: database/dbuser/room/ftpuser/ name
#   option 2: port
​
# create new ftp user
useradd -g ftp -d /home/ubuntu/rooms/$1 -m $1
pass=`cat /dev/urandom | head -n 10 | md5sum | head -c 10`
echo $1:$pass | chpasswd
usermod -s /usr/sbin/nologin $1
echo "create ftp user:$1 indentified by $pass"
​
# create new database
read -sp "请输入 MySQL 容器的 root 账户密码:" mysql_pass
docker exec -it mysql-docker /bin/bash -c "mysql -u root -p$mysql_pass -e \"create database $1;\""
pass=`cat /dev/urandom | head -n 10 | md5sum | head -c 10`
docker exec -it mysql-docker /bin/bash -c "mysql -u root -p$mysql_pass -e \"CREATE USER '$1'@'%' IDENTIFIED BY '$pass';\""
docker exec -it mysql-docker /bin/bash -c "mysql -u root -p$mysql_pass -e \"grant all privileges on $1.* to '$1'@'%';flush privileges;\""
echo "create database user:$1@'%' indentified by $pass"
​
#create new room
docker run -d --name $1 --cpus 0.25 -m 64m --network room_net -p $2:80 -eVAGRANT_OSX_MODE=1 -v /home/ubuntu/rooms/$1/www:/var/www/html mattrayner/lamp:latest-1604
docker exec -it $1 /bin/bash -c "service apache2 start;"
chown $1:ftp -R /home/ubuntu/rooms/$1/www

删除空间脚本:

#!/bin/bash
read -sp "请输入 MySQL 容器的 root 账户密码:" mysql_pass
​
#drop the database
docker exec -it mysql-docker /bin/bash -c "mysql -u root -p$mysql_pass -e \"drop database $1\""
​
#delete dbuser
docker exec -it mysql-docker /bin/bash -c "mysql -u root -p$mysql_pass -e \"use mysql;drop user '$1'@'%';flush privileges;\""
​
#delete the container
docker stop $1
docker rm $1
​
#delete ftp user
userdel $1
rm -rf /home/ubuntu/rooms/$1

用法:

# 创建
sudo create_room.sh room1 10080  # 用户名 映射到 VPS 的端口
# 删除
sudo del_room.sh room1

总结

到这里我们就实现通过 docker 搭建较安全的虚拟空间了,当然,如果真的想上线运营,还有很多需要完善的地方,比如 空间大小的限制、用户文件和数据库的定时备份等等,有兴趣的朋友可以去自己完善。

那么到这里我的折腾就结束了,现在去卖空间给同学发福利了!

*本文作者:Li4n06,本文属 FreeBuf 原创奖励计划,未经许可禁止转载。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK