Slide 1

Slide 1 text

コンテナ上シェル悪用の話と
 Pure Bashでcurlが作れた話
 Katagaitai勉強会 2025/03/16 Ryoto (@systemctl_ryoto) 1 #katagaitai

Slide 2

Slide 2 text

$ whoami
 ● Ryoto (@systemctl_ryoto) ○ MSS(マネージドセキュリティサービス)の仕事をしています ● CTF歴 ○ 2017年〜(ほぼWeb⼀筋) ○ 所属:Katagaitai(2020〜) ● シェル芸 ○ 2019年〜 シェル芸勉強会(旧USP友の会)に参加 ○ 好きな領域:Bash、POSIX、sed ○ 記号難読化シェル芸を知っている⼈がいるかも 2 #katagaitai

Slide 3

Slide 3 text

今回のトピック CTFはあまり関係ありません(主にシェルに特化した内容になります) 第1部 コンテナ上でシェルを取れた場合にできること 主要イメージにプリインストールされたツールキットを⾒る ※シェルの取り⽅は扱いません 第2部 コンテナ上でbashを取れた場合にできること bashしか動かせなくてもcurlはだいぶ実装できる 第3部 コンテナハードニングを考える 第1部〜第2部を踏まえ、万⼀にも耐えうるコンテナイメージを検討 3 #katagaitai

Slide 4

Slide 4 text

第1部 コンテナでシェルを取れた場合にできること 4

Slide 5

Slide 5 text

コンテナのメリットと限界 コンテナとは cgroupやNamespaceといったLinuxの機能により、プロセス実⾏空間がホストから隔離されたもの 最悪コンテナ内で制御を取られたとしても、コンテナ外に影響が波及しにくい コンテナの限界 正しくハードニングしていれば、シェルを取られても⼤丈夫とは当然ならない ● 環境変数やファイルに保存されたシークレットが奪われる ● 外部へのアクセスが⾃由なら、新たにツールをインストールされる ● ネットワーク経由で横展開ができるかもしれない ● 特権コンテナのシェルが取れるとホストに影響する 5 第1部 コンテナでシェルを取れた場合にできること

Slide 6

Slide 6 text

最も単純な悪⽤⽅法 forkbomb :(){ :|:& };: 雑翻訳すると↓ async function colon() { colon(); colon(); } colon(); このオーケストレーション時代に、わざわざリソース飽和させるメリットは薄い (リソース制限を付けられることが多いのと、コンテナは破棄されて再起動されるだけなので) →もう少し⽬的を持った悪⽤ができないか 6 第1部 コンテナでシェルを取れた場合にできること

Slide 7

Slide 7 text

コンテナベースイメージ 7 第1部 コンテナでシェルを取れた場合にできること

Slide 8

Slide 8 text

現在の主要なコンテナベースイメージ Debian:Ubuntuの元になっているので、apt, dpkgがそのまま使える Debian-slim:Debianイメージから不要なものを取っ払ったもの  →doc, locale, manなど。詳細なリストは https://github.com/debuerreotype/debuerreotype/blob/master/scripts/.slimify-excludes Alpine Linux:軽量が売りということで、イメージサイズを⼩さくしたいときに⽤いられがち  →ライブラリがmuslであることに注意が必要 8 パッケージ管理 ライブラリ ローダ シェル Debian apt, dpkg glibc ld-linux-{arch} bash, dash Alpine apk musl ld-musl-{arch} busybox(ash互換) 第1部 コンテナでシェルを取れた場合にできること | コンテナベースイメージ

Slide 9

Slide 9 text

各⾔語公式イメージのベースイメージ DockerHubにある各種公式イメージも、ほとんどDebian and/or Alpineがベースになっている https://hub.docker.com/u/library ※インタプリタ⾔語はシェル取っても、⾔語処理系のほうがネイティブで⾼機能なことが多いので⾯⽩くない 9 ⾔語公式イメージ Debianベース Alpineベース OpenJDK ○ × PHP ○ × Golang ○ ○ NodeJS ○ ○ Python ○ ○ 第1部 コンテナでシェルを取れた場合にできること | コンテナベースイメージ

Slide 10

Slide 10 text

Alpine Linux 10 第1部 コンテナでシェルを取れた場合にできること | コンテナベースイメージ

Slide 11

Slide 11 text

Alpine Linuxイメージ内の実⾏ファイル⼀覧 / # find /usr/local/bin /usr/sbin /usr/bin /sbin /bin -type f -executable /usr/bin/scanelf /usr/bin/getconf /usr/bin/ldd /usr/bin/iconv /usr/bin/ssl_client /usr/bin/getent /sbin/apk # パッケージ管理ツール /sbin/ldconfig /bin/busybox # ほぼbusyboxが担っている / # ls -l $0 lrwxrwxrwx 1 root root 12 Feb 13 23:04 /bin/sh -> /bin/busybox 11 第1部 コンテナでシェルを取れた場合にできること | コンテナベースイメージ | Alpine Linux

Slide 12

Slide 12 text

Busybox BusyBox combines tiny versions of many common UNIX utilities into a single small executable. The utilities in BusyBox generally have fewer options than their full-featured GNU cousins; however, the options that are included provide the expected functionality and behave very much like their GNU counterparts. ● 主要なコマンドが詰め込まれたシングルバイナリ ● Alpine Linuxイメージに同梱されている ● busybox ls と引数でコマンド指定したり、 ln -s /bin/busybox /bin/ls のようにsymlinkを作る ● ⼀部コマンドでは頻出オプションのみの実装になっている ● Busyboxのシェル(sh/ash)はash互換のように⾒えるが、若⼲の独⾃拡張実装(Bash寄せ)がある [おまけ] シェル実装の超ざっくり系譜 (参考:https://qiita.com/ko1nksm/items/e7f43428352c0b4c78f9) Bourne shell -- ash(original) -- dash -- busybox ash \_ ksh -- bash \_ zsh 12 第1部 コンテナでシェルを取れた場合にできること | コンテナベースイメージ | Alpine Linux

Slide 13

Slide 13 text

Busyboxにはどんなコマンドがある? Busyboxマニュアル https://www.busybox.net/downloads/BusyBox.html コマンドは結構ある パッケージ:rpm, dpkg 編集:ed, sed, awk, vi 通信:wget, nc, telnet, (t)ftp, sendmail アーカイブ: cpio, b(un)zip2, g(un)zip, unzip, (un)lzma, tar, uncompress エンコード系:uuencode/uudecodeのみ coreutilsも多数あるが、完備はしていない 13 [, [[, acpid, addgroup, adduser, adjtimex, ar, arp, arping, ash, awk, basename, beep, blkid, brctl, bunzip2, bzcat, bzip2, cal, cat, catv, chat, chattr, chgrp, chmod, chown, chpasswd, chpst, chroot, chrt, chvt, cksum, clear, cmp, comm, cp, cpio, crond, crontab, cryptpw, cut, date, dc, dd, deallocvt, delgroup, deluser, depmod, devmem, df, dhcprelay, diff, dirname, dmesg, dnsd, dnsdomainname, dos2unix, dpkg, du, dumpkmap, dumpleases, echo, ed, egrep, eject, env, envdir, envuidgid, expand, expr, fakeidentd, false, fbset, fbsplash, fdflush, fdformat, fdisk, fgrep, find, findfs, flash_lock, flash_unlock, fold, free, freeramdisk, fsck, fsck.minix, fsync, ftpd, ftpget, ftpput, fuser, getopt, getty, grep, gunzip, gzip, hd, hdparm, head, hexdump, hostid, hostname, httpd, hush, hwclock, id, ifconfig, ifdown, ifenslave, ifplugd, ifup, inetd, init, inotifyd, insmod, install, ionice, ip, ipaddr, ipcalc, ipcrm, ipcs, iplink, iproute, iprule, iptunnel, kbd_mode, kill, killall, killall5, klogd, last, length, less, linux32, linux64, linuxrc, ln, loadfont, loadkmap, logger, login, logname, logread, losetup, lpd, lpq, lpr, ls, lsattr, lsmod, lzmacat, lzop, lzopcat, makemime, man, md5sum, mdev, mesg, microcom, mkdir, mkdosfs, mkfifo, mkfs.minix, mkfs.vfat, mknod, mkpasswd, mkswap, mktemp, modprobe, more, mount, mountpoint, mt, mv, nameif, nc, netstat, nice, nmeter, nohup, nslookup, od, openvt, passwd, patch, pgrep, pidof, ping, ping6, pipe_progress, pivot_root, pkill, popmaildir, printenv, printf, ps, pscan, pwd, raidautorun, rdate, rdev, readlink, readprofile, realpath, reformime, renice, reset, resize, rm, rmdir, rmmod, route, rpm, rpm2cpio, rtcwake, run-parts, runlevel, runsv, runsvdir, rx, script, scriptreplay, sed, sendmail, seq, setarch, setconsole, setfont, setkeycodes, setlogcons, setsid, setuidgid, sh, sha1sum, sha256sum, sha512sum, showkey, slattach, sleep, softlimit, sort, split, start-stop-daemon, stat, strings, stty, su, sulogin, sum, sv, svlogd, swapoff, swapon, switch_root, sync, sysctl, syslogd, tac, tail, tar, taskset, tcpsvd, tee, telnet, telnetd, test, tftp, tftpd, time, timeout, top, touch, tr, traceroute, true, tty, ttysize, udhcpc, udhcpd, udpsvd, umount, uname, uncompress, unexpand, uniq, unix2dos, unlzma, unlzop, unzip, uptime, usleep, uudecode, uuencode, vconfig, vi, vlock, volname, watch, watchdog, wc, wget, which, who, whoami, xargs, yes, zcat, zcip 第1部 コンテナでシェルを取れた場合にできること | コンテナベースイメージ | Alpine Linux

Slide 14

Slide 14 text

[おまけ] Coreutils = Fileutils + Shellutils + Textutils Busybox公式ページ (https://www.busybox.net/about.html) より It provides replacements for most of the utilities you usually find in GNU fileutils, shellutils, etc. Wikipedia (https://ja.wikipedia.org/wiki/GNU_Core_Utilities) より GNU Core UtilitiesまたはCoreutilsはUnix系のOSで中核をなすcat、ls、rmなどのユーティリティの プログラム群、ないし、その開発とメンテナンスを⾏うGNUプロジェクトのサブプロジェクトであ る。以前はfileutils、textutils、shellutilsに分かれていた。 確かにhead, fold, nlのようなコマンドはBusyboxには実装されていない シェル芸するときには重宝するが、Busyboxにはsedとawkがあるからそこまで痛⼿ではないか 14 第1部 コンテナでシェルを取れた場合にできること | コンテナベースイメージ | Alpine Linux

Slide 15

Slide 15 text

Debian 15 第1部 コンテナでシェルを取れた場合にできること | コンテナベースイメージ

Slide 16

Slide 16 text

Debianベースイメージ ← aptでインストールされていたパッケージから抽出 ● パッケージ: apt, dpkg ● ユーティリティ: coreutils, diffutils, findutils ● 便利ツール: grep, gzip, mawk, perl-base, sed, tar ● リソース管理: sysvinit-utils, util-linux(プロセス、ディスク関連など) ● シェル: bash, dash PerlやBashが入っているのがAlpineとの主な違い ただし、wgetは⼊っていない × telnet, nc, curl, wget, openssl あり物で通信したい場合はperlでゴリ押す? IO::socketは⼊っているが、Net::HTTPは⼊っていない Bashでもできないことはない(第2部でお話) 16 apt bash bsdutils coreutils dash debianutils diffutils dpkg findutils grep gzip mawk perl-base sed sysvinit-utils tar util-linux-extra util-linux 第1部 コンテナでシェルを取れた場合にできること | コンテナベースイメージ | Debian

Slide 17

Slide 17 text

任意のバイナリを実⾏する 17 第1部 コンテナでシェルを取れた場合にできること

Slide 18

Slide 18 text

シェルから任意のバイナリファイルを書き込む⽅法 "abc" (0x616263) というデータを書き込む例 いずれも追記リダイレクト(>>)を使⽤すれば複数回に実⾏分割可能 1. 外部コマンドに頼る $ echo YWJj | base64 -d >> out.bin $ echo 616263 | xxd -p -r >> out.bin 2. echoのエスケープシーケンスでゴリ押す $ echo -en "\x61\x62\x63" >> out.bin # 16進数 $ echo -en "\0141\0142\0143" >> out.bin # 8進数 ※Bash/Busyboxでは-eオプションが必要。POSIXやDashでは-eオプション無しで8進数のみ対応 -nオプションは末尾に改⾏⽂字を出⼒しないために必要 3. 展開記法でゴリ押す(Dollar-Single-Quote) $ echo -n $'\x61\x62\x63' >> out.bin ※Dollar-Single-QuoteはPOSIX-1.2024から定義された。 Bash, Busybox shellでは使⽤可能。Dashでは使⽤不可。 18 第1部 コンテナでシェルを取れた場合にできること | 任意のバイナリを実⾏する

Slide 19

Slide 19 text

実⾏フラグ付与は外部コマンドに頼らざるを得ない? 任意のバイナリを書き込めたら、次は実⾏したい しかし、 bashも含め、chmod / installはシェルにビルトイン実装されていない ビルトインコマンドで解決できたほうが、execveやforkが発⽣しない[事実]ので追跡されにくく嬉しい[は?] chmodとinstallが両⽅封殺されていると基本なすすべなし(そんな状況はあまりないが) umaskを変えても意味はない シェルのリダイレクトは、0666(& ~umask)で書き込む 0666 = rw-rw-rw- せっかくリダイレクトで実⾏ファイルを書き込んだり、curl等でファイルを落としたとしても 実⾏フラグが付けられないとその先に⾏けない? 19 ネタゾーン 第1部 コンテナでシェルを取れた場合にできること | 任意のバイナリを実⾏する

Slide 20

Slide 20 text

+xができなくても、動的リンクバイナリは実⾏できる root@34d0c4a136ee:/# date Fri Mar 7 09:37:22 UTC 2025 root@34d0c4a136ee:/# file $(which date) /usr/bin/date: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=c89b11207f6479603b0d49bf291c092c2b719293, for GNU/Linux 3.2.0, stripped root@34d0c4a136ee:/# ls -l $(which date) -rwxr-xr-x 1 root root 121904 Sep 20 2022 /usr/bin/date root@34d0c4a136ee:/# cp $(which date) ./date root@34d0c4a136ee:/# chmod -x ./date root@34d0c4a136ee:/# ./date bash: ./date: Permission denied root@34d0c4a136ee:/# /lib64/ld-linux-x86-64.so.2 ./date Fri Mar 7 09:38:09 UTC 2025 20 ローダを実⾏して バイナリを読ませる xフラグを抜くと 実⾏できない ネタゾーン 第1部 コンテナでシェルを取れた場合にできること | 任意のバイナリを実⾏する

Slide 21

Slide 21 text

read権限だけあれば実⾏できる危険性 root@34d0c4a136ee:/# /lib64/ld-linux-x86-64.so.2 ./date Fri Mar 7 09:38:09 UTC 2025 21 $ /bin/bash ./script.sh #スクリプトに実⾏権限なくてもbash経由で実⾏できるのと似ている [おまけ] Shebangを付けたファイルの実⾏は、上記とやっていることは同じ #!/bin/bash #!/usr/env/sed -f ←PATHからsedの実⾏ファイルを解決してくれる #!/bin/echo ←このファイルだけ作っておけば、実⾏すると$0を出⼒する 実はbashに限り、実⾏フラグがないファイルを実⾏するもう⼀つの⽅法がある(第2部に続く) ネタゾーン 第1部 コンテナでシェルを取れた場合にできること | 任意のバイナリを実⾏する

Slide 22

Slide 22 text

第2部 コンテナでbashを取れた場合にできること 22

Slide 23

Slide 23 text

Bash 現在の主要なLinuxディストリビューションのデフォルトシェル ashやPOSIXシェルと⽐べた特徴 ● declare (typeset) により変数型指定が可能 ○ 数値、名前参照、配列、連想配列などが使⽤可能 ● 基本コマンドの多くがビルトインとして使⽤可能 ● 拡張パス展開(extglob)が使⽤可能 ● TCP, UDP通信が使⽤可能 ○ /dev/tcp/
/ ○ /dev/udp/
/ ○ 実際には存在しないデバイスファイルだが、Bashが認識してTCP/UDP通信をリダイレクト先にする ● 変数⽂字列の置換が可能(patternはパス展開表現に従う) ○ ${parameter/pattern/string} 23 第2部 コンテナでbashを取れた場合にできること

Slide 24

Slide 24 text

バイナリを実⾏する(bash編) 24 curl実装の話をする前に... 第2部 コンテナでbashを取れた場合にできること

Slide 25

Slide 25 text

実⾏フラグがないファイルを実⾏する⽅法 builtinコマンドは⾃作できる bashの組込みコマンド⾃作によるスクリプトの⾼速化 (satさんのzenn) https://zenn.dev/satoru_takeuchi/articles/fb824d7c59ccdb6a0b38 ● 特定の構造体で作成したCコードをShared Objectにビルドする $ cc -I/usr/include/bash/ -I/usr/include/bash/include \  -fpic -shared -o object.so source.c ● 作成したShared Objectを読み込んでビルトイン化する $ enable -f ./object.so command-name enableしたShared Objectに限り、実⾏フラグがなくても実⾏可能 事前ビルドしたsoファイルをどうにかして取り寄せれば、 fork/execveなしで実⾏できる chmodコマンドをこの形式で取り寄せれば、後は好き放題できる? もっと⾔うと、Cで書けることは実質何でもできてしまう 25 #include #include #include static int myhello(WORD_LIST *list) { puts("Hello world!"); fflush(stdout); return EXECUTION_SUCCESS; } static char *desc[] = { "Show a greeting message.", "", "It's far faster than launching executable file", "because it't not necessary to call exec() and fork().", (char *)NULL }; struct builtin myhello_struct = { "myhello", // builtin command name myhello, // function called when issueing this command BUILTIN_ENABLED, // initial flag desc, // long description "myhello", // short description 0, }; 引⽤:https://zenn.dev/satoru_takeuchi/articles/fb824d7c59ccdb6a0b38 第2部 コンテナでbashを取れた場合にできること | バイナリを実⾏する(bash編) ネタゾーン

Slide 26

Slide 26 text

BashでHTTP通信をする 26 第2部 コンテナでbashを取れた場合にできること

Slide 27

Slide 27 text

/dev/tcp/
/
部分はIPアドレスでもドメインでもよいため、名前解決実装が不要。神。 基本的な使い⽅ $ echo > /dev/tcp/127.0.0.1/80 ただ、これだとメッセージを送った後すぐコネクションが切れちゃう $ exec 3<>/dev/tcp/127.0.0.1/80 $ echo >&3 $ cat <&3 こうすればコネクションを維持したまま送受信が可能 $ exec {peer}<>/dev/tcp/127.0.0.1/80 # $peerには11などの数値が格納される $ echo >&$peer fd番号が変数に格納されるので、こっちのほうが使いやすい 27 第2部 コンテナでbashを取れた場合にできること | BashでHTTP通信をする

Slide 28

Slide 28 text

最もシンプルなHTTPリクエスト製造コマンド 28 $ bash --norc --noprofile # 実験のため⼀旦カレントシェルから切り離す $ exec {peer}<>/dev/tcp/www.example.com/80 # コネクション維持のため、fdとして登録 $ cat << EOF | sed 's/$/\r/' >&$peer # ⾏末をCRLFにする GET / HTTP/1.0 Host: www.example.com User-Agent: curl Connection: close EOF $ cat <&$peer $ exit HTTP/1.0 200 OK Content-Type: text/html ETag: "84238dfc8092e5d9c0dac8ef93371a07:1736799080.121134" Last-Modified: Mon, 13 Jan 2025 20:11:20 GMT Cache-Control: max-age=2377 Date: Sat, 15 Mar 2025 15:42:22 GMT Content-Length: 1256 Connection: close X-N: S (以下略) 第2部 コンテナでbashを取れた場合にできること | BashでHTTP通信をする

Slide 29

Slide 29 text

第2部のトピック:bashでcurl再現 BashでHTTP通信ができるのでは? →正直何度もこすられている内容ではある(最近もXで出回っていた) Bashの機能を使えば、正しくHTTPを解釈してcURLが作れるのでは? →どこまで再現できるかやってみよう 要は下記のやり取りを再現して解釈できれば良い:PlaintextでのHTTPリクエスト、レスポンス GET / HTTP/1.0 Host: www.example.com HTTP/1.0 200 OK Content-Type: text/html Content-Length: 1256 29 第2部 コンテナでbashを取れた場合にできること | BashでHTTP通信をする

Slide 30

Slide 30 text

Bashでcurlを実装してみた 30 第2部 コンテナでbashを取れた場合にできること

Slide 31

Slide 31 text

Bashだけでcurlをつくる pcurl.bash(pseudo cURL)を作ってみた シェルの本来の機能である「PATH内の外部コマンド呼び出し」を⼀切しないという縛りプレイ →sed, awk, perl, iconv, nkf に頼らない /bin に bash しかない状態でどこまでできるのかの検証 ソースはこちら:https://github.com/ryotosaito/pcurl ⼤前提:pcurl.bashの限界 ● TLS通信(https://)はできない  →opensslコマンドが使えれば、 openssl s_client で後はどうにでもなる ● HTTP/1.0 のみ使⽤可能  →HTTP/1.1では Transfer-Encoding: chunk をパースできる必要がある  (実はできるかも?未検証) 31 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた

Slide 32

Slide 32 text

pcurl.bashで実装できたオプション $ ./pcurl.bash -h cURL clone made using only bash Usage: ./workspace/pcurl.bash/pcurl.bash [options]... -A, --user-agent Send User-Agent to Server -d, --data HTTP POST data -H, --header Insert HTTP Header -h, --help Print help -L, --location Continue request after receiving Location header -o, --output Output Response to OUTPUT file -v, --verbose Verbose output -X, --request Request using -x, --proxy Use proxy --connect-to connect to host2:port2 instead 32 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた https://github.com/ryotosaito/pcurl

Slide 33

Slide 33 text

pcurl.bashで実⾏できるコマンド [1/2] # 以下は ./pcurl.bash を curl に差し替えても動作する $ ./pcurl.bash http://wttr.in/Tokyo # wttr.inはUser-Agent:curlでアクセスすると、CLIで天気予報を返してくれる $ ./pcurl.bash -o index.html example.com:80 # http://やURLの/が不⾜していたり、ポート番号が指定されていても処理できる # -oフラグでレスポンスボディを書き込む $ ./pcurl.bash -v -L http://httpbin.org/redirect/5 # -vオプションはデバッグ出⼒(極⼒curlに寄せている) # -LオプションはリダイレクトのLocationに⾃動アクセスする $ ./pcurl.bash -X PUT -d "key1=val1&key2=val2" http://httpbin.org/put # -Xオプションでメソッド指定 # -dオプションでリクエストボディを付加 33 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた https://github.com/ryotosaito/pcurl

Slide 34

Slide 34 text

pcurl.bashで実⾏できるコマンド [2/2] $ ./pcurl.bash -x http://localhost:3128 http://www.example.com/index.html # プロキシを経由するアクセス # プロキシに対して GET http://www.example.com/index.html HTTP/1.0 を投げる $ ./pcurl.bash -v --connect-to hb.org:80:httpbin.org:80 http://hb.org/get # --connect-toは、Hostヘッダと実際の接続先を揃えずにアクセスできる⽅法 # DNSをいじったり/etc/hostsを変更する⼿間いらずでアクセスできる $ ./pcurl.bash -vx localhost:3128 --connect-to hb.org:80:httpbin.org:80 http://hb.org/get # プロキシと--connect-toの合わせ技 # プロキシに対して CONNECT httpbin.org:80 HTTP/1.0 # httpbin.orgとのコネクションが成⽴するので、そこからは通常のアクセス # GET /get HTTP/1.1 # HOST hb.org 34 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた https://github.com/ryotosaito/pcurl

Slide 35

Slide 35 text

実装の壁とBashならではの対策 35 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた https://github.com/ryotosaito/pcurl

Slide 36

Slide 36 text

実装の壁と解決策 36 TCP通信をする ⽂字列を編集する 複雑なパースを⾏う -H オプションで任意のヘッダを管理する 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた | 実装の壁とbashならではの対策 https://github.com/ryotosaito/pcurl

Slide 37

Slide 37 text

実装の壁と解決策 37 複雑なパースを⾏う -H オプションで任意のヘッダを管理する ⽂字列を編集する TCP通信をする /dev/tcp/
/ 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた | 実装の壁とbashならではの対策 https://github.com/ryotosaito/pcurl

Slide 38

Slide 38 text

⽂字列編集:パラメータ展開 POSIXのパラメータ展開(⽂字列の部分削除) ${param#word} ${param##word} 前方一致のwordを削除(#は最短一致、##は最長一致) ${param%word} ${param%%word} 後方一致のwordを削除(%は最短一致、%%は最長一致) word部分は、パス展開の glob表現が使える(*, ?, [) ● *   任意の⽂字列 ● ?  任意の1⽂字 ● [...] カッコ内の各⽂字のいずれか1⽂字 ○ [!...] [^...] カッコ内のいずれの⽂字でもない⽂字 ○ [:class:]  ⽂字クラスclassに含まれる⽂字(詳細割愛) 38 $ URL="http://www.example.com/" $ SCHEME="${URL%%://*}" # ://以降の⽂字列を取っ払うとhttpだけが残る 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた | 実装の壁とbashならではの対策 https://github.com/ryotosaito/pcurl

Slide 39

Slide 39 text

⽂字列編集:置換 Bashの追加機能に、⽂字列置換がある ${param/pattern/string} 変数の⽂字列中のpatternをstringに置換する ここでもpatternはパス展開のglobが使える(*, ?, [) 最も⻑いマッチが1つだけ置換される 置換のバリエーション ● ${param/#pattern/string} 変数内で最初に出現したpattern置換する ● ${param/%pattern/string} 変数内に最後に出現したpattern置換する ● ${param//pattern/string} 変数内に出現したpatternをすべて置換する(/g) ● ${param/pattern} 置換する代わりに削除する(${param/pattern/}と同じ) 39 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた | 実装の壁とbashならではの対策 https://github.com/ryotosaito/pcurl

Slide 40

Slide 40 text

Bashの限界:grepができない ここまで扱ったのは ● globにマッチする⽂字列の削除 ● globにマッチする⽂字列の置換 マッチした⽂字列のみ抽出をする、いわば grep -o のような操作はbashだけではできない そのため、⽂字列を抽出するときは⼀癖ある操作をする必要がある 「Content-Type: text/html」 から 「Content-Type」 を抽出するには →grepの場合:grep -oE '^[^:]+' (⾏頭から:までを抽出する) →bashの場合:${var%%:*} (:から⾏末までを削除する) 40 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた | 実装の壁とbashならではの対策 https://github.com/ryotosaito/pcurl

Slide 41

Slide 41 text

実装の壁と解決策 41 複雑なパースを⾏う -H オプションで任意のヘッダを管理する TCP通信をする ⽂字列を編集する /dev/tcp/
/ パラメータ展開 ${param#word} ${param%word} パターン置換  ${param/pattern/string} 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた | 実装の壁とbashならではの対策 https://github.com/ryotosaito/pcurl

Slide 42

Slide 42 text

複雑なパース:拡張パス展開 extglob 通常のパス展開ではPOSIXと同様のglobしか使えない(*, ?, [) extglobを有効にすると、下記の拡張記法が使える pattern-listは pattern1|pattern2|... のようにパイプで区切られた複数のパターン ● ?(pattern-list) :pattern-listのうちどれかが0…1回 ● *(pattern-list) :pattern-listのうちどれかが0…*回 ● +(pattern-list) :pattern-listのうちどれかが1…*回 ● @(pattern-list) :pattern-listのうちどれかが1回 ● !(pattern-list) :pattern-listのうちどれにも該当しない 42 $ shopt -s extglob # extglobの有効化 $ RESP_LINE="HTTP/1.1 200 OK" $ RESP_STATUS="${RESP_LINE##HTTP/+([[:digit:].]) }" # 0-9か"."のいずれかが1回以上マッチ # RESP_STATUS="200 OK" 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた | 実装の壁とbashならではの対策 https://github.com/ryotosaito/pcurl

Slide 43

Slide 43 text

実装の壁と解決策 43 TCP通信をする ⽂字列を編集する 複雑なパースを⾏う -H オプションで任意のヘッダを管理する /dev/tcp/
/ パラメータ展開 ${param#word} ${param%word} パターン置換  ${param/pattern/string} extglob 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた | 実装の壁とbashならではの対策 https://github.com/ryotosaito/pcurl

Slide 44

Slide 44 text

ヘッダ管理:連想配列 Bashにはdeclare, typeset(declareと同じ), local(ローカルスコープ)があり、変数の型が指定できる 例:declare -nで名前参照、declare -rでreadonly declare -Aで連想配列を定義できる(Bashの配列は1次元のみなので注意) 44 $ declare -A assoc_arr=( [key1]=val1 [key2]=val2 ) # 初期化 $ assoc_arr[key3]=val3 # 追加 $ echo ${assoc_arr[key2]} # アクセス val2 $ for key in "${!assoc_arr[@]}"; do echo "assoc_arr[$key]=${assoc_arr[$key]}"; done # key列挙 assoc_arr[key2]=val2 assoc_arr[key3]=val3 assoc_arr[key1]=val1 # key列挙の順番は辞書順や挿⼊順ではない 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた | 実装の壁とbashならではの対策 https://github.com/ryotosaito/pcurl

Slide 45

Slide 45 text

# 規定ヘッダを先に定義しておく declare -A HEADERS=( [Accept-Encoding]="identity" [Connection]="Close" [User-Agent]="curl" ) # コマンドライン引数を使って、Headerの引数の上書きをする while getopts H: OPT; do case "$OPT" in -H) # コロンの前後でKeyとValueを区切って代⼊している HEADERS[${OPTARG%%:*}]="${OPTARG#*: }" shift;; esac done # 中略 exec {stdout}>&1 # オリジナルの標準出⼒を別fdに保存 exec >&$peer # 以降の出⼒はサーバに⾶んでいく # リクエスト⾏ echo -en "$METHOD $URL HTTP/1.0\r\n" # ヘッダ⾏ for key in "${!HEADERS[@]}" do echo -en "$key: ${HEADERS[$key]}\r\n" done echo -en "\r\n" # 標準出⼒復活 exec >&$stdout # レスポンス出⼒ cat <&$peer 連想配列を⽤いたヘッダ管理の実装例 Headerを連想配列管理しているのは、規定ヘッダの上書きを容易にするため →pcurlは現時点で同じヘッダを複数⼊れられる実装にはなっていないです。許して 45 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた | 実装の壁とbashならではの対策 https://github.com/ryotosaito/pcurl

Slide 46

Slide 46 text

declare -pの便利な使い⽅ declare -pの出⼒は、そのままdeclareコマンドとして叩けるようになっている 関数の出⼒として使⽤すると、構造体を返すような実装ができる 46 # 引数URLの構成要素を連想配列にして返す parse_url() { URL="$1" # 中略 local -A return=( [URL]="$URL" [SCHEME]="$SCHEME" [HOST]="$HOST" [PORT]="$PORT" [PATH]="$PATH" ) declare -p return } $ parse_url "http://www.example.com/index.html" declare -A return=([PORT]="80" [SCHEME]="http" [URL]="http://www.example.com/index.html" [HOST]="www.example.com" [PATH]="/index.html" ) $ parsed="$(parse_url "http://www.example.com/index.html")" # declareコマンド⽂字列を変数parsedに格納 $ eval "${parsed/return/TARGET}" # 変数名をreturnからTARGETに変更してから評価 $ declare -p TARGET declare -A TARGET=([PORT]="80" [SCHEME]="http" [URL]="http://www.example.com/index.html" [HOST]="www.example.com" [PATH]="/index.html" ) 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた | 実装の壁とbashならではの対策 https://github.com/ryotosaito/pcurl

Slide 47

Slide 47 text

実装の壁と解決策 47 TCP通信をする ⽂字列を編集する 複雑なパースを⾏う -H オプションで任意の ヘッダを管理する /dev/tcp/
/ パラメータ展開 ${param#word} ${param%word} パターン置換  ${param/pattern/string} extglob 連想配列(declare -A) 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた | 実装の壁とbashならではの対策 https://github.com/ryotosaito/pcurl

Slide 48

Slide 48 text

pcurl.bashの材料 ● ⼊出⼒コマンド ○ echo, cat, read ● [[ (test) コマンドオプション ○ ==, =~, -eq, -le, -gt, -n, -z, -v ● ⽂字列削除、置換 ○ ${param#word}, ${param%word}, ${param/pattern/string} ○ *, [...], [:digit:], $'\r', +(pattern) ● 特別なパラメータ展開 ○ ${#param}, ${!param[@]} ● 変数宣⾔ ○ declare(local), -A, -i, -p ● リダイレクト ○ exec, >&, <&, {word}<> ● その他 ○ shopt, true, getopts, shift, eval, exit, $() 48 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた https://github.com/ryotosaito/pcurl

Slide 49

Slide 49 text

他にも実装できそうなこと 準備時間の都合で⼀部オプションしか実装できなかったが、他にも多数の実装⾒込みがある ● -I, --head HEADメソッド+ヘッダを標準出⼒ ● -i, --include ヘッダを標準出⼒ ● -d, --data @ Bodyデータをファイルから読み込み ● -F --form multipart/form-dataのボディ形式 ● -f, --fail エラー時にボディを出⼒しない ● -r, --range, -e, --referer -H 実装を転⽤するだけ ● --post301, --post302, --post303 リダイレクト先にもPOSTする ● -b, --cookie, -c, --cookie-jar Cookieデータの参照(-b)、保存(-c) ● -O, --remote-name URLと同じファイル名で保存する ● -J, --remote-header-name Content-Dispositionヘッダ指定のファイル名で保存する ● --output-dir ファイルの保存先ディレクトリを指定 ● --proxy-header プロキシ接続時のヘッダ 49 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた https://github.com/ryotosaito/pcurl

Slide 50

Slide 50 text

pcurl.bashの使い道 50 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた https://github.com/ryotosaito/pcurl

Slide 51

Slide 51 text

HTTPが実装できたらできること できること ● curl/wgetが封殺されただけの環境でも、迂回的にHTTP通信ができる  →他のコマンドに制限がなければrpm/debファイルを落として、そのままrpm/dpkgなど…  →chmod.soをホストしてDL、builtin化してもよい ● IMDSへのアクセス  →実⾏環境がクラウドなら169.254.169.254にHTTPアクセスできる ● サイドカープロキシがPlain HTTP対応なら他のアプリにもアクセスできる? できないこと ● k8s API serverへのアクセス(基本的にHTTPSアクセスが必須なので) ● 署名が必要なアクセス(AWS Sigv4、Oauth1.0など) 「必要最低限の実⾏ファイルしか置いていない」→Bashがあるけど⼤丈夫?(過剰) 51 第2部 コンテナでbashを取れた場合にできること | Bashでcurlを再現してみた | pcurl.bashの使い道 https://github.com/ryotosaito/pcurl

Slide 52

Slide 52 text

おまけ:rbash 52 第2部 コンテナでbashを取れた場合にできること

Slide 53

Slide 53 text

rbash、またはbash -r $ ls -l $(which rbash) lrwxrwxrwx 1 root root 4 3月 14 2024 /usr/bin/rbash -> bash 53 rbashやbash -rで起動すると制限(restricted)モードになり、下記ができなくなる(⼀例) ● cd ● enable ● exec ● PATHやENVの更新 ● /を含む⽂字列でのコマンド起動(絶対パス‧相対パス) ● リダイレクト(>, <, >&, <&等)の実施 ● set +rによる制限の解除 シェルインタフェースとしてはじめからrbashを⽤意する場合には有効だが、 バイナリがbashと同⼀である以上シェルを取られるシナリオを考慮する場合はあまり効果がない 第2部 コンテナでbashを取れた場合にできること | おまけ:rbash

Slide 54

Slide 54 text

第3部 コンテナハードニングを考える 54

Slide 55

Slide 55 text

「シェルを取らせない」から「シェルがない」へ 55 暴論に⾒える解決策が実現できる 「シェルを取れると任意コマンドが実⾏できてしまう」 →シェルをなくせばいいじゃない 「system関数が実⾏できると任意の実⾏ファイルが実⾏できてしまう」 →コマンドをなくせばいいじゃない コンテナは単⼀プロセスだけでも動作するように環境を構成できる 軽量化だけでなく、セキュリティ観点でもミニマライズは有効(VMにはできないメリット) 第3部 コンテナハードニングを考える

Slide 56

Slide 56 text

minimalなベースイメージ Distroless(https://github.com/GoogleContainerTools/distroless) OSとして最低限のファイルのみ(/etc/passwdなど)が同梱されたイメージ 実⾏ファイルは⼀切なく、標準ではシェルすら含まれていない 基本のタグの他、有名な⾔語処理系のためのイメージも⽤意されている python3, java17, java21, nodejs18, nodejs20, nodejs22 →次ページ Scratch(https://hub.docker.com/_/scratch) ファイルが⼀切含まれていない、最も基本的なイメージ 実⾏時に /dev /proc /sys といった特別なディレクトリ配下のファイルだけは⽣える 56 第3部 コンテナハードニングを考える

Slide 57

Slide 57 text

Distroless Imageごとに同梱ファイルが異なる TagごとにユーザとBusyboxが異なる ⾔語環境イメージをそのまま起動すると、 シェルがなくても処理系コマンドが実⾏する Python, Node.jsならREPLが使える 57 Tag→ latest nonroot debug debug- nonroot User root (1) nonroot (63352) root (1) nonroot (63352) Busybox × × ○ ○ Image→ (余⽩のため 省略表記) static base-nossl base cc java python nodejs ca-certificates ○ ○ ○ ○ ○ /etc/passwd ○ ○ ○ ○ ○ /tmp ○ ○ ○ ○ ○ tzdata ○ ○ ○ ○ ○ glibc × ○ ○ ○ ○ libssl × × ○ ○ ○ libgcc1 × × × ○ × ⾔語環境 × × × × ○ 第3部 コンテナハードニングを考える

Slide 58

Slide 58 text

Distrolessの実⾏ファイル⼀覧 $ docker run --rm -it gcr.io/distroless/base-debian12:debug / # find / -type f -executable /etc/update-motd.d/10-uname /etc/ssl/certs/ca-certificates.crt /usr/share/doc/ca-certificates/copyright /usr/lib/os-release /lib/x86_64-linux-gnu/libc.so.6 # baseに同梱 /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 # baseに同梱 /.dockerenv /busybox/busybox # debugに同梱 58 第3部 コンテナハードニングを考える

Slide 59

Slide 59 text

minimalコンテナのトラブルシューティング 59 第3部 コンテナハードニングを考える

Slide 60

Slide 60 text

それでも有事の際にいろいろなコマンドを使いたい! 運⽤中のコンテナに障害調査したいときはどうするか 60 デプロイ種別 ネットワーク プロセス ファイルシステム 単⼀コンテナ ‧(nsenter -n) ‧デバッグコンテナをアタッチ ‧(nsenter -p) ‧デバッグコンテナをアタッチ デバッグコンテナをアタッチ docker-compose services.name.network_mode を設定したデバッグコンテナ services.name.pid を設定したデバッグコンテナ services.name.pid を設定したデバッグコンテナ Kubernetes サイドカーに デバッグコンテナを作成 サイドカーに デバッグコンテナを作成 (shareProcessNamespace: true) サイドカーに デバッグコンテナを作成 (shareProcessNamespace: true) Kubernetes > 1.25 エフェメラルコンテナ エフェメラルコンテナ エフェメラルコンテナ 第3部 コンテナハードニングを考える | minimalコンテナのトラブルシューティング

Slide 61

Slide 61 text

コンテナを実装する技術:Namespace プロセス同⼠がお互いの存在に⼲渉しないように隔離できる技術 コンテナを起動するとPID=1のプロセスが実⾏されるが、ホストから⾒たときは別のプロセスIDになる 61 $ docker run --rm --name nstest -itd busybox sleep infinite ed6cb808df1376c1bbcc8dff4f031bd23cbcc437e81ef62f8b1e8a72691644c1 $ docker exec -it nstest ps PID USER TIME COMMAND 1 root 0:00 sleep infinite 7 root 0:00 ps $ docker inspect --format '{{.State.Pid}}' nstest # コンテナの実PIDを取得 7735 $ ps 7735 PID TTY STAT TIME COMMAND 7735 pts/0 Ss+ 0:00 sleep infinite 同じプロセスなのに IDが違う 第3部 コンテナハードニングを考える | minimalコンテナのトラブルシューティング

Slide 62

Slide 62 text

Namespaceは複数の領域に分かれている ● mount namespace ○ プロセスにマウントされるファイルシステムを分離する ● network namespace ○ プロセスに割り当てられるネットワークやIPアドレスを分離する ● PID namespace ○ プロセスIDのテーブルを分離する 他にも下記namespaceがある ● UTS namespace :hostnameやdomainname ● IPC namespace :プロセス間通信(IPC) ● user namespace :UID, GIDやその権限 ● cgroup namespace :cgroup(プロセスのリソース制限) ● time namespace :プロセスが認識する時刻 62 話半分ゾーン 第3部 コンテナハードニングを考える | minimalコンテナのトラブルシューティング

Slide 63

Slide 63 text

単⼀コンテナのデバッグ 63 第3部 コンテナハードニングを考える | minimalコンテナのトラブルシューティング

Slide 64

Slide 64 text

nsenter -n:network namespaceに⼊る $ docker run -d --rm --name python-server gcr.io/distroless/python3-debian12 python3 -m http.server c19a9495cb6335fdee0c6d5e43c05061f9fd00df7bdec2fe6d374163118dcae5 $ docker exec python-server curl -I http://localhost:8000 # コンテナ内にcurlはない OCI runtime exec failed: exec failed: unable to start container process: exec: "curl": executable file not found in $PATH: unknown $ docker inspect --format '{{.State.Pid}}' python-server # コンテナのPIDを取得 10152 $ sudo nsenter -t 10152 -n curl -I http://localhost:8000 # ホストのcurlをコンテナNWで実⾏ HTTP/1.0 200 OK Server: SimpleHTTP/0.6 Python/3.11.2 Date: Thu, 13 Mar 2025 15:30:29 GMT Content-type: text/html; charset=utf-8 Content-Length: 741 64 話半分ゾーン 第3部 コンテナハードニングを考える | minimalコンテナのトラブルシューティング | 単⼀コンテナのデバッグ

Slide 65

Slide 65 text

nsenter:コンテナホストからのアクセス nsenterコマンドを使うと、指定プロセスのnamespaceにアクセスできる nsenter -t PID コマンド ● -m, --mount:mount namespaceにアクセスする ● -n, --net:network namespaceにアクセスする ● -p, --pid:PID namespaceにアクセスする ● etc… 前ページのコマンドは -n オプションでnetwork namespaceに⼊っていた $ sudo nsenter -t 10152 -n curl -I http://localhost:8000 65 話半分ゾーン 第3部 コンテナハードニングを考える | minimalコンテナのトラブルシューティング | 単⼀コンテナのデバッグ

Slide 66

Slide 66 text

minimal環境のnsenterはクセがある:実⽤に難あり 実質mount namespaceにアクセスできない ⼀般的に、コンテナにnsenterするときは -m (--mount) オプションを付けてfilesystemに⼊る $ sudo nsenter -t 10152 -m ls / # コンテナ内のファイルへアクセスする が、どうやらマウント→コマンド実⾏の順序のため、コンテナ内のPATHから実⾏ファイルを探し出すらしい → distrolessのmount namespaceに⼊ると当然コマンドが⼀切⼊っていないので、デバッグできない ※奥の⼿として、ホストのbind mountやvolumeパスへ直接アクセスするという⼿がある(邪道なので割愛) ネットワークデバッグにも⽀障がある ネットワークのデバッグのために -n (--net) だけを使⽤しても、⼗分なデバッグができないことがある 例)ホストの /etc/resolv.conf がsystemd-resolved を向いているため、名前解決ができない 66 話半分ゾーン 第3部 コンテナハードニングを考える | minimalコンテナのトラブルシューティング | 単⼀コンテナのデバッグ

Slide 67

Slide 67 text

おまけ:docker debug Docker Desktopの有償ライセンス版では、 docker debug コマンドが⽤意されている https://docs.docker.com/reference/cli/docker/debug/ ⾮常に便利そうだが、動作確認ができないので割愛 67 話半分ゾーン 第3部 コンテナハードニングを考える | minimalコンテナのトラブルシューティング | 単⼀コンテナのデバッグ

Slide 68

Slide 68 text

改めて、単⼀コンテナのデバッグ docker run コマンドには、PID namespace, network namespaceを共有するオプションがある nsenter に⽐べると取り回しがしやすい→評価◯ 68 $ docker run --rm -it --pid=container:python-server --network=container:python-server busybox / # wget localhost:8000 # --network にアクセスしているとネットワークを共有できる Connecting to localhost:8000 (127.0.0.1:8000) saving to 'index.html' index.html 100% |******************************************| 741 0:00:00 ETA 'index.html' saved / # ls /proc/1/root/usr/bin # --pid にアクセスしているとfilesystemにもアクセスできる python python3 python3.11 第3部 コンテナハードニングを考える | minimalコンテナのトラブルシューティング | 単⼀コンテナのデバッグ

Slide 69

Slide 69 text

docker-composeのデバッグ 69 第3部 コンテナハードニングを考える | minimalコンテナのトラブルシューティング

Slide 70

Slide 70 text

docker-composeでのNamespace共有 compose.yaml内で指定可能なnamespace ● network_mode:network namespaceの指定 ● pid:PID namespaceの指定 各項⽬に設定可能な値 ● host:ホストと共有 ● service::指定サービスと共有 デバッグコンテナの起動 70 $ docker compose --profile debug up -d # docker compose start debug $ docker compose exec debug sh $ docker compose stop debug services: app: image: gcr.io/distroless/python3-debian12 command: -m http.server # デバッグ用コンテナ debug: image: busybox command: sleep infinity profiles: ["debug"] network_mode: "service:app" pid: "service:app" depends_on: - app dockerと同じデバッグ⽅法を取るのもあり 第3部 コンテナハードニングを考える | minimalコンテナのトラブルシューティング | docker-composeのデバッグ

Slide 71

Slide 71 text

Kubernetes Podのデバッグ 71 第3部 コンテナハードニングを考える | minimalコンテナのトラブルシューティング

Slide 72

Slide 72 text

$ kubectl exec -it deployment/myapp -c debug-sidecar -- sh / # wget localhost:8000 # ネットワークデバッグ Connecting to localhost:8000 (127.0.0.1:8000) saving to 'index.html' index.html 100% |*****************| 741 0:00:00 ETA 'index.html' saved / # ps PID USER TIME COMMAND 1 65535 0:00 /pause 7 root 0:00 python3 -m http.server 14 root 0:00 sleep infinity 20 root 0:00 sh 26 root 0:00 ps / # ls /proc/7/root/usr/bin # ファイルシステムにアクセス python python3 python3.11 Kubernetesの場合:デバッグ⽤サイドカー 72 apiVersion: apps/v1 kind: Deployment metadata: name: myapp spec: replicas: 1 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: shareProcessNamespace: true containers: - name: main-container image: gcr.io/distroless/python3 command: ["python3", "-m", "http.server"] - name: debug-sidecar image: busybox command: ["sleep", "infinity"] もう少し良い⽅法がある(次ページ) 第3部 コンテナハードニングを考える | minimalコンテナのトラブルシューティング | Kubernetes Podのデバッグ

Slide 73

Slide 73 text

Kubernetesの秘技:エフェメラルコンテナ エフェメラルコンテナ:⼀時的に作成するサイドカーコンテナ 前に紹介したデバッグ⽤サイドカーを使い捨てで特定podだけにアタッチできる https://kubernetes.io/ja/docs/concepts/workloads/pods/ephemeral-containers/ v1.16からalpha、v1.25からstable(2025/3/11時点でlatestはv1.32) コンテナ作成以降のデバッグ⼿順は前ページと同じ 73 $ kubectl debug -it myapp-6d9d8fb966-x4mqt --image=busybox --target main-container / # wget localhost:8000 Connecting to localhost:8000 (127.0.0.1:8000) saving to 'index.html' index.html 100% |*****************| 741 0:00:00 ETA 'index.html' saved / # ls /proc/7/root/usr/bin python python3 python3.11 第3部 コンテナハードニングを考える | minimalコンテナのトラブルシューティング | Kubernetes Podのデバッグ

Slide 74

Slide 74 text

シェル落としのハードニングは実際どうなの 特にエフェメラルコンテナでのデバッグ⽅法が成熟しているので、実践にも耐えうるのではないか /proc/{pid}/root に気づくまで、シェルなしコンテナのファイルシステムは⾒れないと思っていた 事前にアプリケーションディレクトリをボリューム化しておくしかなければ厳しい評価になっていた KubernetesのPodトラブルシューティングのドキュメントは結構⼿厚い https://kubernetes.io/ja/docs/tasks/debug/debug-application/debug-running-pod/ ただし、デバッグ⽅法が(kubectl exec等に⽐べると)特殊ではある CTFならまだしも通常運⽤のシェルで /proc/ はあんまり使わないだろうし 利便性と安全性を天秤にかけた上で、チームの運⽤‧セキュリティポリシーに従って決定するのが良い 74 第3部 コンテナハードニングを考える | minimalコンテナのトラブルシューティング

Slide 75

Slide 75 text

Fin 75