使用Docker構建安全的虛擬空間
*本文作者:Li4n06,本文屬 FreeBuf 原創獎勵計劃,未經許可禁止轉載。
前言
最近上的某水課的作業是出 ctf web題目,然而大多數同學連 php 都沒學過,(滑稽)更別說配置伺服器了,於是我想能不能趁機賺一波外快 造福一下同學,(其實就是想折騰了)。所以打算把我自己的 vps 分成虛擬空間給大家用。但是一般的虛擬空間安全性難以得到保證,一個空間出問題,其他的用戶可能都跟著遭殃,也就是旁站攻擊。更何況我們這個虛擬空間的用處是 ctf web 題目,總不能讓人做出一道題目就能順手拿到所有題目的 flag 吧。於是想到了使用 docker 來構建安全的虛擬空間,其間遇到了不少問題,下面就是折騰的過程了。
實現思路
大體的思路是,在我的 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/mysqldelse # 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/mysqldfi
可以看到,當沒有設置 $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# 啟動apache2docker 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 useruseradd -g ftp -d /home/ubuntu/rooms/$1 -m $1pass=`cat /dev/urandom | head -n 10 | md5sum | head -c 10`echo $1:$pass | chpasswdusermod -s /usr/sbin/nologin $1echo "create ftp user:$1 indentified by $pass"# create new databaseread -sp "請輸入 MySQL 容器的 root 賬戶密碼:" mysql_passdocker 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 roomdocker 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-1604docker exec -it $1 /bin/bash -c "service apache2 start;"chown $1:ftp -R /home/ubuntu/rooms/$1/www
刪除空間腳本:
#!/bin/bashread -sp "請輸入 MySQL 容器的 root 賬戶密碼:" mysql_pass#drop the databasedocker exec -it mysql-docker /bin/bash -c "mysql -u root -p$mysql_pass -e "drop database $1""#delete dbuserdocker exec -it mysql-docker /bin/bash -c "mysql -u root -p$mysql_pass -e "use mysql;drop user "$1"@"%";flush privileges;""#delete the containerdocker stop $1docker rm $1#delete ftp useruserdel $1rm -rf /home/ubuntu/rooms/$1
用法:
# 創建sudo create_room.sh room1 10080 # 用戶名 映射到 VPS 的埠# 刪除sudo del_room.sh room1
總結
到這裡我們就實現通過 docker 搭建較安全的虛擬空間了,當然,如果真的想上線運營,還有很多需要完善的地方,比如 空間大小的限制、用戶文件和資料庫的定時備份等等,有興趣的朋友可以去自己完善。
那麼到這裡我的折騰就結束了,現在去賣空間給同學發福利了!
*本文作者:Li4n06,本文屬 FreeBuf 原創獎勵計劃,未經許可禁止轉載。
推薦閱讀: