脆弱性レジリエンスを高めるための Clean Architecture
ソフトウェアを開発するときに、気軽に OSS ライブラリを導入していませんか?
OSS ライブラリは大変便利なものですが、ライブラリをプロジェクトに導入することは、そのライブラリが依存する他のライブラリを導入することにも繋がり、脆弱性のリスクを高めることを意味しています。
一方で、OSS 利用に伴うリスクを避けるために OSS を使わないという選択は、開発工数を増加させビジネススピードの低下に繋がるため、本末転倒な選択だと言えるでしょう。
OSS を利用してビジネススピードを確保しつつ、脆弱性のリスクを抑えるためにはどのようにすれば良いのでしょうか。
このバランスを取る方法として、脆弱性は必ず発生する前提に立ち、脆弱性への対応力「レジリエンス」を高めるアプローチがあります。
本記事では、脆弱性レジリエンスを確保するためのアーキテクチャを解説します。
なおこの記事は、ScalaMatsuri 2020 の弊社ブースでご紹介した「脆弱性対策のための Clean Architecture 〜脆弱性に対するレジリエンスを確保せよ〜」の発表内容を一部抜粋・加筆したものになります。
ブースにご来場いただいた皆様へこの場を借りて御礼申し上げます。
脆弱性とは
脆弱性とは、バグや仕様上の不備が原因となって現れたセキュリティ上の欠陥であり、安全上の弱点です。
ソースコードが公開された OSS では、さまざまな人たちによって脆弱性が発見・公開されます。
さらに、これらの脆弱性を議論・検証するために、攻撃を成功させるための実証コードも公開され、流通していきます。
攻撃者からするとこれらの脆弱性情報を利用しない手はありません。
脆弱性情報を参考に、アプリケーションに想定外の動作をさせて、本来提供されていないはずの入力を行ったり、プライベートな情報にアクセスしたりしてアプリケーションの弱点を突いてくるのです。
脆弱性を突いた有名な攻撃事例としては、2017 年の米国の信用調査会社 Equifax 社で起きた事件があげられます。
この事件では、Web アプリケーションフレームワークである Apache Struts 2 の脆弱性(CVE-2017-5638:ファイルアップロード処理に起因するリモートコード実行)が攻撃に利用され、1 億 4790 万人分もの個人情報が流出したとされています。
2017 年当時の状況を時系列に示すと、次のようになります。
3月6日 |
Apache Struts プロジェクトから脆弱性およびその修正アップデートパッチが公開される |
---|---|
3月9日 |
Equifax 社の情報システム部門からアラート発行、48 時間以内に Struts 2 のアップデートを指示 |
3月10日 |
攻撃者が Equifax 社を攻撃 |
上記のとおり、Struts プロジェクトから脆弱性パッチがリリースされてからわずか数日で攻撃が成功しています。
もし情報システム部門のアラート発行がもう数日早ければ(かつ即座に対応すれば)防げた事件ではないでしょうか?
(なお、同社は本事件を理由に、ムーディーズの格付け見通しを引き下げられています。
また 2019 年には、少なくとも 5 億 7500 万ドル(約 630 億円)の罰金を払うことで米連邦取引委員会(FTC)と和解したと発表されました)
皆様は脆弱性が公開されてから、それが自社のアプリケーションに含まれるものかスピーディーに検出する仕組みを備えられているでしょうか?
セキュリティチームからの対応指示に即座に反応できるアーキテクチャを整えられているでしょうか?
答えが NO なら続きをお読み頂き、脆弱性対策の運用を社内で真剣に検討していただけると幸いです。
依存関係の中の脆弱性
現在では全くのゼロベースでコードを書くことは稀で、何らかの要件を実現するために OSS ライブラリを活用することが多いでしょう。
プロジェクトに OSS ライブラリを導入するとき、それらの依存関係を管理しなくてはなりません。
開発者がプロジェクトに直接した依存ライブラリを「直接依存(Direct dependency)」、直接依存ライブラリが依存する他のライブラリを「推移的依存ライブラリ(Transitive library)」あるいは「間接依存ライブラリ(Indirect library)」などと呼びます。
ライブラリの依存関係の例
開発言語や用途、パッケージ管理システムにもよりますが、依存関係は直接依存ライブラリよりも、間接依存ライブラリのほうが割合が多くなるため、依存関係全体のなかでは間接依存ライブラリに脆弱性が現れやすくなります。
依存関係における脆弱性の割合
こうした間接依存ライブラリにおける脆弱性リスクは、冒頭で挙げた Struts など直接依存の脆弱性と比較するとリスクが表層化し難いようにも思えますが、近年問題視されることも多くなっています。
その一例として、2017 年に改定された OWASP Top 10 に新たに掲載された「安全ではないでデシリアライゼーション(Insecure deserialization)」が挙げられます。
この脆弱性は、デシリアライゼーション処理でコード実行や DoS 等を可能にするというものですが、Apache Commons Collection などフレームワークが利用しているライブラリ(間接依存ライブラリ)を攻撃する手法が Property-Oriented プログラミングとして公開され話題を集めました。
古典的レイヤードアーキテクチャとパッチ適用
これまでみてきたように、OSS ライブラリにはパッチ適用を必要とする脆弱性が存在します。
パッチ適用は、脆弱性の修正を何らかの方法で依存関係・依存ライブラリに取り込むことを意味します。
よく使われるパッチ適用は対象ライブラリのバージョンアップです。
ここでは、レイヤードアーキテクチャにおいて、パッチ適用がどのような結果をもたらすかみていきます。
古典的なレイヤードアーキテクチャは、以下の図のように、依存関係の方向が一直線に構成されます。
プレゼンテーション層はアプリケーション層に依存する、といったように矢印の方向が依存の方向を示します。
古典的レイヤードアーキテクチャの構成
ここで注目していただきたいのは、ドメイン層とインフラ層の関係性です。
ドメイン層にはドメインのルールやロジックが置かれ、インフラ層は上位層を支えるデータアクセス等の技術要素配置します。
ここで、インフラ層は MyBatis(データアクセスライブラリ)ライブラリと、MySQL コネクタライブラリに依存しているとしましょう。
さて、推移的(間接)依存関係では次のことが言えます。
- ドメイン層はインフラ層に依存する
- インフラ層は MyBatis および MySQL コネクタに依存する
-
ならば、ドメイン層は MyBatis および MySQL コネクタに依存する
したがって、インフラ層に配置したライブラリ(MyBatis, MySQL コネクタ)に脆弱性が現れ、そのパッチ対応を行う場合、その影響はドメイン層に影響します。
同様に、アプリケーション層はドメイン層に依存し、プレゼンテーション層はアプリケーション層に依存するため、結果としてその影響は全体に及びます。
レイヤードアーキテクチャにおける脆弱性の影響パス例
脆弱性はいつ現れるか分かりません。
そして完全に撲滅することはできません。
脆弱性がみつかったときに、素早く対応していくことが肝要ですが、各層がそれぞれ影響を受けるようでは修正対応が困難になり、作業に多くの時間を必要としてしまいます。
Clean Architecture で依存関係の影響を局所化
ここまでに挙げた問題の解決策として、アーキテクチャレベルで脆弱性への対応を考慮しておくと、いざ脆弱性が現れたときに対応しやすくなります。
依存性逆転の原則
開発者が抑えておくべきソフトウェア設計の 5 つの原則である SOLID 原則の 1 つに、依存性逆転の原則(Dependency inversion principle) というものがあります。
この原則は意訳すると、変化しやすい具象に依存せず、安定した抽象に依存せよ、という内容になります。
詳細である具象要素(先の例における MySQL コネクタなど)に依存したいときは、抽象インタフェースを設けてそちらに依存するように構成することになります。
この原則が意図することは、アプリケーション内の変化しやすい具象要素に依存したくない ということです。
Clean Architecture(あるいはオニオンアーキテクチャ)
さきほどの依存性逆転の原則を適用したアーキテクチャが下図に示す Clean Architecture(あるいはオニオンアーキテクチャ)になります。
Clean Architecture レイヤードビュー
さきほどの古典的レイヤードアーキテクチャとは異なり、ドメイン層はインフラ層に依存していません。
代わりにインフラ層が持っていた具象要素に対する抽象インタフェースを、ドメイン層やアプリケーション層が持ち、各層はそれらの抽象に依存する構成をとります。(具象要素となるインフラ層のコンポーネントは、いわゆる DI コンテナで注入させます)
インフラ層の依存性が逆転したことにより、次図のように、ドメイン層やプレゼンテーション層が、脆弱性パッチ適用に対する影響を免れています。
Clean Architecture における脆弱性の影響パス例
この Clean Architecture の構造をオニオン(玉ねぎ)構造でみると、次図のようになります。
依存関係の向きは、外側から中心に向かっています。
この構造により、変化しやすい具象要素(脆弱性の多いライブラリ)に対する修正アップデートの影響を、全体に波及することなく局所化できます。
Clean Architecture オニオンビュー
脆弱性に強いアーキテクチャへ
脆弱性に強いアーキテクチャにするためには、単に Clean Architecture (あるいはオニオンアーキテクチャ)を採用すればいいというわけではありません。
Clean Architecture では変化しやすい要素への依存を減らし、その影響を抑えることができます。
この性質を利用し、脆弱性の出やすい(特に外部とのやり取りが発生するような)ライブラリをなるべく外側に追いやり、ドメイン層を脆弱性の影響から分離します。
そして、外側の層(古典的レイヤードアーキテクチャではインフラ層と呼んでいた層)に置いたライブラリをアップデートしやすくするようテストを拡充し、各層(特に外層)の依存関係の管理を徹底していくことで、脆弱性に対応しやすいアーキテクチャになります。
脆弱性は予期せぬタイミングで発見され、その情報や攻撃コードが公開され、あっという間に攻撃が発生していきます。
そのような外部の事象に素早く反応できるようにしておくことは、脆弱性に対するレジリエンスを高めるだけに留まらず、ビジネス要件の変化にも追従しやすいアーキテクチャを実現することにも繋がります。
さいごに
ここまでの内容を簡単にまとめると以下のようになります。
- 脆弱性を検出する仕組みを導入しましょう
-
間接依存ライブラリの脆弱性にご注意ください
(手動で確認するのは困難なので、ツールを使いましょう)
-
脆弱性のリスクをオニオンの外側に追いやり、ドメイン層を保護してください
(玉ねぎだって内側の方が甘いのです)
- 脆弱性に強いアーキテクチャでビジネス要件の変更にも強くなりましょう
直接依存ライブラリだけではなく、それらのライブラリがもたらす間接依存ライブラリに脆弱性が出ていないかどうかを日々チェックするのは、たとえ品質判定のためのワンショット作業であっても人力ではとても困難です。
ツールなども利用しつつ、安全な開発環境構築していくことをおすすめします。
さいごになりますが、yamory は依存関係全体から脆弱性を検出し、大量にある脆弱性の中から、対応を要する危険な脆弱性を優先順位付けし、その対応管理まで一元管理できる、現場での運用を考えたサービスとなっています。
脆弱性対策は単にツールを導入するだけではなく、自社の開発ルールや運用にフィットさせることが重要です。
まずは yamory の無料トライアルをご利用いただき、脆弱性対策についてぜひご検討ください。
また、脆弱性対策をはじめてみたい、開発に組み込むにはどうすればよいか等ご質問がございましたら、あわせてお問い合わせいただければ幸いです。
皆様のご連絡・ご利用をお待ちしています。