使用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.建立 SQL/">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 原創獎勵計劃,未經許可禁止轉載。