はじめに
こちらの記事はアドベントカレンダー「Snykを使って開発者セキュリティにまつわる記事を投稿しよう! by Snyk Advent Calendar 2022」の初日の記事として投稿しています。
私@tajima_tasoからは、Snykを使って先日発生したOpenSSLの脆弱性(CVE-2022-3786およびCVE-2022-3602)を検出し、対処する具体的な方法についてご紹介いたします。
今回は気軽に試せるようGithub上にリポジトリを用意しましたので、興味を持たれた方がいましたら一緒に手を動かしながらお試しいただければと思っております。
具体的には「コンテナ化されたアプリケーションでOpenSSLを利用していた場合」と、「自前のビルドシステムの中でOpenSSLをバンドルしているアプリケーションの場合」の2パターンでご紹介いたします。
OpenSSLの脆弱性はなんとかしないとヤバいイメージがあるので、なるべく簡単に対処したいと思っている方が多いのではないでしょうか?そんな方の一助となれば幸いです。
OpenSSLの脆弱性は、何故ヤバいのか?
そもそも、OpenSSLの脆弱性は何故ヤバいイメージがあるのでしょうか?
ソフトウェアの世界における脆弱性というのは、一般的にはセキュリティ侵害のリスクがあるバグとして認知されていると思います。OpenSSLはその存在意義がセキュリティ保護であるライブラリですので、バグの発見がそのまま脆弱性の発見である確率が他のソフトウェアより高いです。
また、セキュリティ保護を担うライブラリ自体に脆弱性があるので、それを利用するアプリケーションがいくらセキュアにコーディングされていたとしても、無条件で脆弱性をもたらしてしまう可能性が高いです。
具体的にいうと、OpenSSLはTLSプロトコルを実現する為のライブラリですので、その脆弱性はアプリケーションが行うデータ通信の過程において、秘密情報の漏えいや改ざんをもたらしてしまう可能性が高いという事です。
もちろん、「リモートから任意のコードが実行可能」のような通信ソフトウェア一般で起こりうる脆弱性が生まれる可能性もありますが、OpenSSLが内部的にアクセスする情報の性質を考慮すると、通常のアプリケーションよりも深刻な被害に繋がる可能性が高いと言えます。
2014年4月に発表された通称Heartbleed(CVE-2014-0160)と言われる深刻な脆弱性は、まさにそのパターンでした。その物騒な名前から強く記憶に残っています。ちなみに、この脆弱性発生のもとになったコミットは2012年1月1日に行われていますので、発表されるまで2年あまり経過しています。私もアナタもいつの間にか被害にあっていたかもしれません。そう思うと、脆弱性が身近な脅威として感じてきますよね。
そういった経緯から、TLSプロトコルの実装をOpenSSLに頼ってしまうのは危険という機運が高まり、LibreSSLなどのOpenSSL派生ライブラリも生まれました。
しかしながら、依然としてOpenSSLは多くのアプリケーションで利用されており、2021年9月には3系へと3年ぶりの大幅アップデートも行われました。よって、今後も開発者は継続的にOpenSSLの脆弱性に対処していく必要はあるでしょう。実際、今回発生したCVE-2022-3786およびCVE-2022-3602も3系のOpenSSLで発覚した脆弱性です。
SnykでOpenSSLの脆弱性を駆逐してみよう
さて、OpenSSLの脆弱性のヤバさがわかったところで、さっそく実践してみましょう。
CVE-2022-3786およびCVE-2022-3602の脆弱性は、OpenSSLの3.0以上3.7未満のバージョンに含まれています。しかしながら、現行のアプリケーション環境では1系を使っている環境の方がまだ多数派のようですので、OpenSSLの脆弱性というインパクトに反して、影響を受けるアプリケーションは少ないようです。詳しくは「OpenSSL に深刻度の高い脆弱性が新たに発見、この脆弱性について今知っておくべきこと」をご参照ください。
事前準備
Snykを使う為に、簡単な事前準備を行います。
手前味噌ですが、以前公開させていただいた「アプリの脆弱性もインフラの脆弱性もSnykで全部漏らさず対処しよう!」にて、Snykを利用する上での事前準備を紹介しておりますので、アカウントの作成やCLIの初期設定はそちらをご参照いただけますと幸いです。
無料ですし、2〜3分くらいで終わります。
なお、今回私が使用するSnyk CLIのバージョンは1.1064.0
です。
コンテナ化されたアプリケーションでOpenSSLを利用していた場合
思ったより影響を受けるアプリケーションが少ないとはいえ、Node.jsのv18.x系、v19.x系といったLTSなJavaScript環境ではOpenSSLの3系を利用しています。よって、今回はNode.jsのv18.12.0
を利用したコンテナ化されたアプリケーションに対して脆弱性の検出と対処を行ってみます。
v17.xも今回の脆弱性の対象になりますが、既にサポート対象外なのでサポートしているバージョンへの移行が正しいアクションかと思います。
サンプルアプリケーションのコンテナイメージをビルドする
任意の作業ディレクトリに移動していただき、サンプルアプリケーション(Dangerous Train)のリポジトリをクローンして下さい。
git clone https://github.com/yuya-tajima/dangerous-train.git && cd dangerous-train
ディレクトリ内のファイル構造は下記の通りになっているはずです。
$ ls -1
Dockerfile
MIT-LICENSE.txt
README.md
app.js
index.html
package-lock.json
package.json
このディレクトリ内のDockerfileを使って、Dockerイメージをビルドします。名前はvul-nodejs
とします。
docker image build . -t vul-nodejs:latest
Snykが行う脆弱性検出にはDockerイメージだけあれば十分なのですが、念の為ビルドしたイメージからコンテナを起動して、ブラウザでの表示を確認してみましょう。
docker container run -it --rm -p 3001:3001 $(docker images --format '{{.ID}}' --filter=reference='vul-nodejs:latest')
コンテナが起動したらhttp://localhost:3001/
にアクセスします。下記の表示が確認できます。
確認が済んだので、コンテナを起動したシェル上でCtrl + C
を押してコンテナを強制停止させます。別のシェルからdocker container stop
コマンドを実行しても構いません。
このDockerイメージの脆弱性検出を試みます。
脆弱性を検出する
コンテナの脆弱性を検出するにはsnyk container testコマンドを実行します。
snyk container test vul-nodejs:latest
するといくつか脆弱性が検出されますが、Node.jsコンテナ自体から高い重大度の脆弱性が3つ検出されました。
最初の2つの脆弱性に記載されているURLにアクセスすると、OpenSSLの脆弱性(CVE-2022-3786、CVE-2022-3602)が検出できている事がわかります。
さらに念の為、Node.jsが依存しているOpenSSLのバージョンを直接確認すると、3.0.5
ですのでやはり脆弱性が含まれているようです。
docker container run $(docker images --format '{{.ID}}' --filter=reference='vul-nodejs:latest') sh -c 'node -e "console.log(process.versions)"'
脆弱性への対処を行う
さきほどの検出した結果に、Recommendations for base image upgradeとしてアップグレードを推奨するベースイメージが記載されていました。今回は手堅く18.12.1
へとアップグレードしてみます。
Dockerfileのベースイメージがnode:18.12.0
となっているのでnode:18.12.1
へと変更します。
$ vi Dockerfile
FROM node:18.12.1 # ここだけ変更する
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3001
CMD [ "npm", "start" ]
Dockerイメージをビルドします。OpenSSLの脆弱性が解消されたDockerイメージを作成できるはずです。
docker image build . -t vul-nodejs:latest
脆弱性を再び検出する
本当にOpenSSLの脆弱性が解消されているか、再度検出を試みます。
snyk container test vul-nodejs:latest
さきほどは、下記の赤枠の箇所でDetected 3 vulnerabilities for node@18.12.0
という見出しとともに、3つの脆弱性が指摘されていましたがOpenSSLを含めて全て消えています。
よって、既知の脆弱性への対処が済んだと言えそうです。
さらに念の為、Node.jsが依存しているOpenSSLのバージョンを直接確認すると、3.0.7
ですのでやはり脆弱性が解消されているようです。
docker container run $(docker images --format '{{.ID}}' --filter=reference='vul-nodejs:latest') sh -c 'node -e "console.log(process.versions)"'
継続的に検知可能な仕組みを整える
当面のセキュリティ脅威は退けましたが、今後のセキュリティ脅威も継続的に監視する為、下記コマンドを実行しておきます。
snyk container monitor vul-nodejs:latest
コマンド実行後にSnykのダッシュボードへアクセスすると、さきほどの監視対象がプロジェクトとして管理されています。
プロジェクトと監視されている場合、新しい脅威が発生した場合の通知や、週次レポートとして報告を上げてくれるようになります。
コマンド実行時の便利なオプション
基本的にコンテナの脆弱性検出コマンドとしては上記で十分ですが、追加のオプションを指定する事で有益な結果を得られる場合があるので、ご紹介しておきます。
脆弱性解消方法について、より具体的な手順を表示する
snyk container test
コマンドの結果の中で、アップグレードを推奨するベースイメージとして先程は下記が表示されていました。
ここで、snyk container test
コマンド実行時のオプションとして、—-file=<Dockerfile名>
を指定してみます。すると、アップグレードを推奨するベースイメージが変化します。
snyk container test vul-nodejs:latest --file=Dockerfile
これは、Dockerfileを情報として与えた事によって、Snykがアップグレードを推奨するベースイメージをより明確に絞り込めた事を意味しています。よって、もし脆弱性の検出時にDockerfileが手元にあるならその情報をコンテキストとして与える事をオススメします。
アプリが使用しているOSSライブラリの脆弱性も同時に検出する
Snykでは各パッケージ管理システムのマニフェストファイルをもとに、OSSライブラリの脆弱性を検出できる機能があります。もしもこれをコンテナ内部のアプリケーションに対して実行したい場合、snyk testコマンドを実行しなければなりません。よって、コンテナアプリケーションの脆弱性を検出したいという同じ目的の為に、snyk test
コマンドとsnyk container test
のコマンドを実行する必要があります。
それでも良いのですが、snyk container test
コマンドのオプションとして、--app-vulns
を追加するとコンテナの脆弱性検出に加え、コンテナ内部のアプリケーションに対してsnyk test
コマンドを行った結果を一度に得る事ができます。
snyk container test --app-vulns vul-nodejs:latest
実行すると、さきほどのコンテナの脆弱性検出結果に加えて、Node.jsアプリケーションが使っているライブラリ(express
)に関する脆弱性も検出する事ができました。この場合、package.json
内のバージョンをアップデートする事で、これを修正する事ができます。
ただ、上記は2022年12月1日現在 (1.1064.0)の仕様です。
アプリが使用しているOSSライブラリの脆弱性も同時に検出する(近い将来)
実は近い将来--app-vulns
オプションを指定しなくても、コンテナとコンテナ内部の脆弱性検出がデフォルト動作になるようです。
snyk container test vul-nodejs:latest
逆にあえて旧来の動作のように別々で検出したい場合は、--exclude-app-vulns
オプションを指定する必要があるとの事です。
snyk container test --exclude-app-vulns vul-nodejs:latest
しかしながら、実際は別々に実行したいケースの方が少ないと思うので、この仕様変更は嬉しいですね。
自前のビルドシステムの中でOpenSSLをバンドルしているアプリケーションの場合
基本的にOSSライブラリは、特定のプログラミング言語に付随するパッケージ管理システムの中で管理されている事が多いと思います。DockerのイメージもBuild,Ship(Share) and Runというスローガンの中のShip(Share)が示しているように、共有される事を目的としてリポジトリ管理されています。
しかしながら中にはCMakeのようなビルド自動化ツールを用いて、自前でOSSライブラリを管理している場合もあるでしょう。典型的にはC言語やC++で開発されているアプリケーションが想像できます。
Snykではそのような、いわばアンマネージドに管理されているOSSライブラリの脆弱性を検出する機能として、Snyk for C / C++が提供されています。
次は、このSnyk for C / C++を使用して、OpenSSLの脆弱性(CVE-2022-3786およびCVE-2022-3602)の検出と対処を行ってみます。
ざっくりとした仕組み
アンマネージドなOSSライブラリの脆弱性を検出する為に、まず検出対象となるライブラリのローカルファイル全てをハッシュリストに変換します。それをSnykの管理サーバへ送信し、一致する可能性のある依存関係のリストを見つける事ができたら、脆弱性の有無を結果として返します。
なお、ローカルのファイル群とオリジナルのファイル群との差異が大きい場合は、検出対象のライブラリと認識されない事がありますので、少し注意です。ただ、完全一致していなくても特定は十分可能な為、通常は信頼度(confidence)が1.000
からどの程度差があるのか?を確認する程度で済むと思います。
OpenSSLのソースコードをダウンロードする
今回はOpenSSLのバージョン3.0.0のソースコードを直接ダウンロードして、脆弱性の検出を試みます。まずは、ソースコードをダウンロードしてみましょう。
curl -OL https://github.com/openssl/openssl/archive/refs/tags/openssl-3.0.0.zip && unzip openssl-3.0.0.zip
カレントディレクトリが空だった場合、上記コマンドの実行後は下記のファイル構成になっています。
$ ls -1
openssl-3.0.0.zip
openssl-openssl-3.0.0
脆弱性を検出する
openssl-openssl-3.0.0
ディレクトリに移動します。
cd openssl-openssl-3.0.0
Snyk for C / C++をCLIで利用するには、snyk test
コマンドに--unmanaged
オプションをつけて実行します。
snyk test --unmanaged
10個の脆弱性が見つかりました。
最後の2つの脆弱性に記載されているURLにアクセスすると、OpenSSLの脆弱性(CVE-2022-3786、CVE-2022-3602)が検出できている事がわかります。よって、ライブラリのソースコードそのものからOpenSSLの脆弱性を検出する事ができました。
脆弱性への対処を行う
OpenSSLライブラリのソースコードそのものに対して脆弱性検出を行っているので、対処は脆弱性の修正対応が済んでいるソースコード(たとえばバージョン3.0.7)をダウンロードするだけです。
現在OpenSSLのバージョン3.0.0のソースコードディレクトリにいる場合は、親のディレクリに移動します。
cd ..
OpenSSLのバージョン3.0.7をダウンロードします。
curl -OL https://github.com/openssl/openssl/archive/refs/tags/openssl-3.0.7.zip && unzip openssl-3.0.7.zip
下記のようなファイル構成になっているはずです。
$ ls -1
openssl-3.0.0.zip
openssl-3.0.7.zip
openssl-openssl-3.0.0
openssl-openssl-3.0.7
脆弱性を再び検出する
openssl-openssl-3.0.7
ディレクトリに移動します。
cd openssl-openssl-3.0.7
snyk testコマンドに--unmanaged
オプションを指定して実行します。
snyk test --unmanaged
脆弱性が見つかりませんでした。
よって、OpenSSLのバージョン3.0.7では既知の脆弱性への対処は済んでいると言えそうです。
継続的に検知可能な仕組みを整える
今後のセキュリティ脅威を継続的に監視する為、下記コマンドを実行しておきます。
snyk monitor --unmanaged
コマンド実行後にSnykのダッシュボードへアクセスすると、OpenSSLが監視対象のプロジェクトとして追加されています。今現在はセキュリティ脅威がない状態ですが、今後脆弱性が発見された場合は通知してくれます。
コマンド実行時の便利なオプション
最初に述べたとおり、アンマネージドなOSSライブラリの脆弱性検出はファイルそのものを入力とするので、マニフェストファイルから検索する仕組みと違ってライブラリを100%正しく特定できるとは限りません。その信頼度を確認するには、—-print-deps
オプションを指定して実行します。
snyk test --unmanaged --print-deps
今回はconfidence(信頼度)が1.000
と算出されているので、Snykがほぼ100%の自信でOpenSSLのバージョン3.0.0であるとお墨付きを与えている状態と言えます。
さらに-d
オプションを指定すると、Snykの内部的な挙動を確認したり、ハッシュリスト化の対象にしているファイル群を確認する事できます。ライブラリがうまく認識されない場合、原因を特定する為のデバッグ手段として役立つ事があるでしょう。
snyk test --unmanaged --print-deps -d
複数のライブラリの脆弱性をまとめて検出する
今回はわかりやすく単一のライブラリだけを対象としましたが、実際は複数のライブラリがバンドルされている場合が多いと思います。
$ cd <path to app directory>
$ cd deps # or a directory name such as 'vendor'
$ ls -1
openssl-3.0.0
zlib-1.2.11
その場合、下記のようにまとめて検出する事ができます。
snyk test --unmanaged --print-deps
今後にも大きく期待
Snyk for C / C++は新しくリリースされた機能なので、今後も機能追加や脆弱性検出アルゴリズムの改善によって進化していく事は間違いないでしょう。その過程を楽しみにして、継続的にキャッチアップしていきたいですね。
おわりに
OpenSSLの脆弱性の検出と対処を行う中で、Snykがどのように利用できるのか実践してみました。結果として、コンテナのベースイメージ、そしてその中に配置されるアプリケーションライブラリ(マネージド、アンマネージド問わず)に対してSnykによる脆弱性検出が有効であると確認できました。
現代的な開発フローに組み込みやすいデベロッパーファーストなSnykは、今後のサービスやプロダクトの安全を守り続ける為の選択肢の1つになりそうです。