はじめに
こちらの記事はアドベントカレンダーSnykを使ってセキュリティにまつわる記事を投稿しよう!【PR】の13日目の記事として投稿しています。
いきなりですが、日々アプリケーション開発に尽力されている開発者の皆様におかれましては、セキュリティに関して下記のような課題感をお持ちではないでしょうか?
- セキュリティに関する問題は事業存続の危機に発展する可能性があるものの、その担保が暗黙的に開発担当者に任されている状況になっていて重い
- コードレビュー時に脆弱性チェックもレビューポイントになっているが、それを指摘できるかどうかはレビュワーのセキュリティ的知見に左右されるので心もとない
- 自社のコード自体に問題がなくても、それに付随するOSSのライブラリをアプリケーションレイヤーからインフラレイヤーまで把握しきれないほど使用しているので、それらが孕んでいる脆弱性をどのように検出・対処すれば良いのか皆目検討がつかない
- 自社ではリソースがないので、外部のベンダーにセキュリティ診断実施の見積もりをもらったが、対象が大規模だったので概算でも数百万になった。そのコストに見合う必要性を提示できず、稟議が通らなかった
- 今のところ特に大きな問題は起きていないし、他に開発コストをかけるところがあるので何もしていないが、とんでもない爆弾がありそうな雰囲気は感じているのでモヤモヤしている
上記に一つでも当てはまる方は、この記事がその課題感を解消するキッカケになるかもしれません。
結論
忙しい方の為に先に結論を述べてしまいます。それは「セキュリティに課題感を感じているなら、一度Snykを使ってみましょう!」ということです。
Snykを使えばセキュリティに関する高度な専門知識も、外部のセキュリティベンダーも、追加の人的・時間的リソースも限りなく不要な状況で、セキュリティの脆弱性を安定的に検出する仕組みを構築する事ができます。
何故なら自社で書いたアプリケーションコードの脆弱性も、それらが依存するライブラリの脆弱性も、それらの基盤となるインフラ(コンテナ)の脆弱性も、Snykなら丸ごとシームレスかつ簡単に検出する事が可能だからです。
また、Snyk最大の特徴は、開発者に寄り添ったセキュリティプラットフォームだという事です。私の一エンジニアとしての所感だと、従来のセキュリティプラットフォーム(OSSやSaaS含め)よりも、エンジニアドリブンでシステム導入への動きを取りやすいソリューションに思えます。もちろん、導入だけではなく後の運用のしやすさを考えても、Snykはオススメできると思っています。
本記事の目的
「Snykを使ってみましょう!と言われても、実際どんな感じかイメージがついていないので行動を起こしにくい。何が良いのかもよくわかっていないし…」という声が聞こえてきそうです。ごもっともな意見です。私も使ってみるまで同じでした。
しかし、私は既にSnyk体験者です。ですので、開発者の皆さんに私がSnyk体験で味わった感動の何分の一かは記事によって伝える事ができます。それが今回記事を書こうと思った動機です。
そこで本記事は、**「開発者の方に、Snykがセキュリティに関する課題感を解消できるかどうかを、イメージとして感じとって頂くこと」**を目的にしました。
その為に、私が用意した脆弱性ありきのPHPアプリケーションを含んだDockerイメージを、既知のセキュリティ脆弱性に対処した上でビルドするまでの過程を確認していきます。
今回はPHPアプリケーションを例にしていますが、Snykは様々なプラットフォームに対応しています。どういったものに対応しているかは途中で補足にて言及しています。
ちなみに本記事はハンズオン的に実施できるかたちで構成していますので、Snykを自社の開発フローに組み込むとしたらどうなりそうか?をイメージしながら一緒に手を動かして試して頂けると幸いです。もちろん、ただご覧頂くだけでも全然OKです!
事前準備
今回はSnykが提供しているCLIをベースにして検証を進めていきますので、下記の事前準備が必要です。いずれも数分で終わるくらいとても簡単な手順です。無料なのでご安心下さい。
Snykにサインアップする
https://snyk.io/にアクセスしてLog inをクリックします。
ご自身のGithubかGoogleアカウントでサクッと認証可能です。また、No credit card required
の通り、利用にあたってクレジットカードの登録は不要です。こういったところにも、開発者に対するホスピタリティを感じます。
Snyk CLIをインストールする
サインアップが終了したら次にSnyk CLIをインストールします。
npmを利用する場合、下記コマンドでインストールできます。
npm install -g snyk
インストール後は下記コマンドで認証を行うことによって、すぐに利用可能です。
snyk auth
更に詳しいガイダンスは下記ドキュメントをご参照下さい。
https://docs.snyk.io/products/snyk-code/cli-for-snyk-code
サンプルアプリケーション(Sneaker)をダウンロードする
今回私が用意した脆弱性ありきのPHPアプリケーション、Sneakerを下記コマンドでGithubからダウンロードします。
git clone https://github.com/yuya-tajima/sneaker.git
README.mdに従ってdocker-compose up
を実行した後、Webブラウザにてhttp://localhost:8080/
にアクセスし、下記の画面が表示されることを確認します。
このアプリケーションを含んだDockerイメージ(Sneakerイメージ)が、セキュリティ的に安全になるよう対処していきます。
Snykを使って安全なDockerイメージを作成する
それでは本題に入っていきましょう。下記の順番でSneakerイメージに存在する脆弱性に対処していきます。それぞれ異なるレイヤーですが、Snykの一貫したインターフェースで脆弱性を検出する事が可能です。
- アプリケーションが利用するOSSライブラリの脆弱性に対処する
- アプリケーションコードの脆弱性に対処する
- インフラ(コンテナ)の脆弱性に対処する
これら全てが完了すれば、現時点で最も安全なDockerイメージが作成できる見込みです。
アプリケーションが利用するOSSライブラリの脆弱性に対処する
RubyであればRubyGems、Node.jsでいえばnpm、PHPであればComposer、Pythonであればpipのようなパッケージ管理システムによって提供されたOSSライブラリ(パッケージ)をアプリケーションが利用している場合に、それらに対する既知の脆弱性を検出します。
今回はPHPアプリケーションですので、Composerによって提供されるOSSライブラリが対象です。
脆弱性を検出する
ダウンロードしたリポジトリのディレクトリ直下で、下記コマンドを実行します。snyk test
はOSSライブラリの脆弱性を検出する事ができるコマンドです。引数として指定しているhtml
ディレクトリは検出対象のルートディレクトリです。
snyk test html
実行すると、さっそく何か発見できたようです。
表示されたInfoのリンクにアクセスすると、どうやらCVE-2016-5385に関わる危険な脆弱性があることがわかりました。それと同時に、この脆弱性への対処方法もわかりました。
脆弱性への対処を行う
それではSnykから提示された対処を行ってみましょう。まずは、アプリケーションのディレクトリまで移動します。
cd html
composer.json
を編集します。
vi composer.json
guzzlehttp/guzzle
を6.2.1
に指定します。
{
"autoload": {
"psr-4": {
"App\\": "./"
}
},
"require": {
"guzzlehttp/guzzle": "6.2.1"
}
}
コンテナ内で、パッケージのアップデートを行います。
docker-compose run app composer update
脆弱性を再び検出する
脆弱性が解消されたかどうか、再度Snykで検出を試みます。
snyk test html
無事脆弱性が解消されたようです。
継続的に検知可能な仕組みを整える
今回はこれで脆弱性が解消されましたが、セキュリティ対策に終わりはありません。このアプリケーションが再びOSSライブラリ起因の脆弱性の危機に瀕した時、すぐに検知できる仕組みを整えたいところです。
Snykはそのようなニーズにも対応していて、下記コマンドで継続的にOSSライブラリの脆弱性を検知する事ができます。
snyk monitor html
これによりOSSライブラリに既知の脆弱性が検出された時はメールが届くようになるので、メールが届かない限りは安全とみなすことができますし、メールが届いた場合は今回のような対処を行えば良いだけです。
補足
OSSライブラリの脆弱性チェック(Snyk Open Source)に対応しているパッケージマネージャーの一覧は、下記で確認できます。ご覧の通り、主要なものは対応できています。
アプリケーションコードの脆弱性に対処する
OSSライブラリの安全が確認できたので、今度はアプリケーションコードに脆弱性がないか確認していきます。こちらに関しては自身を含めて自社のエンジニアが書いているコードなので、脆弱性があるかどうかの関心対象として最も身近だと思います。
脆弱性の動作を確認する
脆弱性の検出を試みる前に、今回検証するアプリケーションにはどのような脆弱性があるか、実際の操作によって確認しておきましょう。
クロスサイトスクリプティング(XSS)
Scriptフィールドに<script>alert('test')</script>
を入力してみます。
するとalert
関数が実行され、ダイアログが表示されました。ユーザーが任意のJavaScriptのスクリプトを実行する事が可能なので、このアプリケーションにはクロスサイトスクリプティングの脆弱性がある事がわかりました。
SQLインジェクション
今回の仕様としてSQLフィールドにJohn
というようなデータベースに存在するユーザー名前を入力すると、その人物に関する情報が取得できるSQLが実行されます。
このフィールドに対して、hoge"or 1 = 1;
という入力を行ってみます。
すると、何か意図しないSQLが実行された結果が返ってきました。意図しないSQLが実行可能なので、このアプリケーションにはSQLインジェクションの脆弱性があるとわかりました。
OSコマンドインジェクション
Commandフィールドにcat /etc/passwd
という文字列を入力します。
するとcat
コマンドによって、/etc/passwd
がただ漏れになってしまいました。OSのコマンドを外部のユーザーが自由に実行可能なので、このアプリケーションにはOSコマンドインジェクションの脆弱性がある事がわかりました。
ファイルインクルード脆弱性
Fileフィールドに、/etc/passwd
と入力してみます。
すると、/etc/passwd
の内容がPHPアプリケーションに読み込まれることによって、だだ漏れになってしまいました。アプリケーションは/etc/passwd
を読み込む事を意図していませんから、このアプリケーションにはファイルインクルード脆弱性がある事がわかりました。
脆弱性を検出する
実際に脆弱性がある事が確認できたので、Snykがこれらの脆弱性を検出できるのか試してみましょう。
snyk code test
コマンドはソースコードの脆弱性を静的解析によって検出する事ができるコマンドです。引数として指定しているhtml
をルートディレクトリとして、サブディレクトリ含めたプログラムファイル全てを走査します。
snyk code test html
結果の出力を確認してみると、上記で確認できた4つの脆弱性が危険度High
として見事に検出できています。※vendor
配下のOSSライブラリのコードも危険度Medium
として検出していますが、今回は一旦無視します。
Snykが脆弱性ありと判断した実際のコードを下記に抜粋してみます。PHPのコードを理解できる人なら、「確かにこれはダメなやつだな」とわかると思います。
<?php
require_once 'vendor/autoload.php';
# Maybe SQL Injection
$user = $_POST['sql'] ?? null;
if ($user) {
try {
$db = new \PDO(sprintf('mysql:host=db;dbname=%s',getenv('MYSQL_DATABASE')), getenv('MYSQL_USER'), getenv('MYSQL_PASSWORD'));
} catch (\PDOException $e) {
die($e->getMessage());
}
$sql = sprintf('SELECT * FROM user WHERE name = "%s"', $user);
$stmt = $db->query($sql);
$result = $stmt->fetchAll();
print_r($result);
}
# Maybe Command Injection
$cmd = $_POST['cmd'] ?? null;
if ($cmd) {
passthru($cmd);
}
# Maybe File Inclusion
$file = $_POST['file'] ?? null;
if (file_exists($file)) {
include($file);
}
# Maybe Cross-site Scripting (XSS)
$xss = $_POST['xss'] ?? '';
echo $xss;
個人的な感動ポイントは、Snykが$_POST
からローカル変数にコピーされて引き渡された変数($user
や$cmd
)も、外部からの入力値であるとちゃんと認識できている事です。
脆弱性への対処を行う
それでは上記脆弱性への対処を行ったコードを下記に抜粋します。これで一応、上記脆弱性が再現しない事は確認済みです。
<?php
require_once 'vendor/autoload.php';
# Prevent SQL Injection
$user = $_POST['sql'] ?? null;
if ($user) {
try {
$db = new \PDO(sprintf('mysql:host=db;dbname=%s',getenv('MYSQL_DATABASE')), getenv('MYSQL_USER'), getenv('MYSQL_PASSWORD'));
} catch (\PDOException $e) {
die($e->getMessage());
}
$stmt = $db->prepare('SELECT * FROM user WHERE name = :user');
$stmt->bindValue(':user', $user, \PDO::PARAM_STR);
$stmt->execute();
$result = $stmt->fetchAll();
print_r($result);
}
# Prevent Command Injection
passthru('/bin/date');
# Prevent File Inclusion
$file = dirname(__DIR__) . '/inc/header.php';
if (file_exists($file)) {
include($file);
}
# Prevent Cross-site Scripting (XSS)
$xss = $_POST['xss'] ?? '';
echo htmlspecialchars($xss);
脆弱性を再び検出する
実際の操作で脆弱性が再現しない事は確認できたので、再度Snykによる検出を試みます。
snyk code test html
下記の通り、誤検出する事なく、アプリケーションコード起因による4つの危険度High
な脆弱性が消えました。
まさかの事態へ備える
今どきのアプリケーションはスクラッチで自社開発されるというケースはほとんどなく、何らかのフレームワークの枠組みの中で開発するケースが多いという個人的所感を持っています。そして、そのような状況下では、自社のコード内に深刻な脆弱性が紛れ込む可能性は低いと思っています。
だからこそ、**「深刻なセキュリティ脆弱性があるOSSライブラリを使わないように、検出とアップデートを行う仕組みを整える事が最も大事」**だと思うのですが、だからといってアプリケーションコードが絶対安心というわけではありません。まさかの事態というのは、いつだって最悪なかたちで起こるからです。
現実的には、OSSフレームワークの枠組みから漏れた自社独自の「ん?なんなんだ、それは?」と思われるような実装が行われているケースがあります。たとえばSQLインジェクションは今日日ほとんどないかもしれませんが、アプリ内から外部コマンドの実行を許容しているケースは意外とまだあるのではないでしょうか?
最悪なケースだと、誰も詳細がわからないので塩漬けされて運用されているような、スクラッチ開発されたレガシーアプリケーションなんかがあるかもしれません。しかもそういうアプリケーションに限って、業務上とても重要なシステムだったりします。
そういったシステムに潜む脆弱性はOSSと違って誰も面倒を見てくれませんので、内々でメンテナンスしていかなければなりません(何千行、何百ファイル…)。いつかリプレイスされるまでのその間、Snykがセキュリティ担保の一旦を担ってくれるのは、開発者にとってとても心強いと思います(私は心強いです!)。
補足
ソースコードの脆弱性チェック(Snyk Code)で対応しているのは、2021年12月13日現在下記の言語です。しかしながら、下記ドキュメントを読むとGo、Ruby、Kotlinあたりは近い将来対応されそうな雰囲気を感じます。
- Java
- JavaScript
- TypeScript
- Python
- PHP
- C#
インフラ(コンテナ)の脆弱性に対処する
ここまでで、アプリケーションレイヤーの脆弱性への対処は完了しました。残るはインフラ(コンテナ)の脆弱性への対処です。ここはスコープが広すぎて一番大変かと思われるかもしれませんが、めちゃくちゃ簡単です。むしろ一番楽かもしれません。
脆弱性を検出する
snyk container
コマンドは、コンテナイメージの脆弱性を検出する事ができるコマンドです。
今回検出対象とするイメージは、最初のdocker-compose up
でビルドされているはずです。イメージ名はsneaker
という名前でビルドされているので、引数としてsneaker
を指定します。
snyk container test sneaker
結果として具体的にどういう脆弱性があるのかが漏れなく出力された後、最後に下記のような出力が得られます。
sneaker
イメージに対して278
の問題が見つかっており、そのうちcriticalが14
、highが73
あるという事のようです。
そして、次にとるべきアクションとして有用な提案も得られています。
- マイナーバージョンのアップグレードであれば、ベースイメージを
php:7.4.24-apache
にアップグレードする事を推奨 - メジャーバージョンのアップグレードであれば、ベースイメージを
php:8.1.0RC4-apache
にアップグレードする事を推奨 - ベースイメージを
php:7.4.24-bullseye
含めいくつかの代替イメージへアップグレードする事を推奨
マイナーアップグレードで脆弱性への対処を行う
上記の提案をもとに、今回はマイナーバージョンのアップグレードを行うことにします。Dockerfile
を編集し、FROMの指定をphp:7.4.1-apache
から推薦されたphp:7.4.24-apache
に変更します。
FROM php:7.4.24-apache
イメージをビルドします。
docker-compose build
ビルド後、アプリが問題なく立ち上がることを確認します
docker-compose up
脆弱性を再び検出する
再ビルドしたイメージで再び脆弱性の検出を試みます。
snyk container test sneaker
検出された問題の数が87
まで減り、そのうちcriticalが2
、highが3
まで減りました。
代替イメージで脆弱性への対処を行う
マイナーバージョンのアップグレードだとphp:7.4.24-apache
が現状最もセキュアなイメージでしたが、まだ代替イメージが推薦されています。せっかくなので、この際Snykが推薦し続ける限りアップグレードしてみます。
Dockerfile
のFROMをphp:8.0.11-zts-bullseye
に編集します。
FROM php:8.0.11-zts-bullseye
今度はビルドとイメージの立ち上げまでを同時に確認してしまいましょう。
docker-compose up --build
脆弱性を再び検出する(三度目の正直)
三度目の正直ということわざを信じて、再度ビルドしたイメージに対して脆弱性の検出を試みます。
snyk container test sneaker
According to our scan, you are currently using the most secure version of the selected base image
というように、Snykから「現在最もセキュアなバージョンのベースイメージを使っている」というお墨付きを頂きました。
セキュリティ対策に終わりはありませんが、とりあえず今のところ最も安全なDockerイメージを作成する事ができたようです。
実はデータベースとして使っているmysqlのコンテナイメージも、敢えて既知の脆弱性があるイメージを使用しています。余力のある方はそちらに対しても脆弱性の検出と対処を行ってみて下さい。
継続的に検知可能な仕組みを整える
OSSライブラリと同様に、コンテナイメージに対しても継続的に検知して、セキュリティの脅威に対応できる仕組みを整えたいところです。特にコンテナのようにステークホルダーとなるライブラリが多い場合は、それぞれの対応にタイムラグが発生するのでOSSライブラリ以上に重要かもしれません。
実際、今回は最新でもなおcritial
な脆弱性を2つ残している状態です。場合によっては別口でコンテナイメージをビルドするという対応を迫られるかもしれません。その意思決定を行う為にも情報はキャッチアップしておける状態にしたいです。
もちろん、Snykはそのようなニーズにも対応していて、下記コマンドの実行によってセキュリティに関する情報をEメールにて検知する事ができます。
snyk container monitor sneaker
ちなみに実行後に提供されるスナップショットのURLでは、下記のように状況を詳しく確認できます。その結果、たとえcritical
な脆弱性であっても、アプリケーションが提供するサービスに悪影響を及ぼす可能性が低いものであれば、トレードオフのバランスを考えた意思決定がしやすいと思います。
補足
SnykはローカルのDockerイメージに対する脆弱性チェックだけでなく、Docker HubやECR、GCRといったリモートリポジトリに対するチェックも可能です。
https://docs.snyk.io/products/snyk-container/image-scanning-library
なお、本記事ではインフラ≒コンテナとしてコンテナイメージの脆弱性チェック(Snyk Container)を試みましたが、SnykではAWS CloudFormationやKubernetesの設定ファイルといったInfrastructure as Codeに対する脆弱性チェック機能(Snyk Infrastructure as Code)も提供されています。詳しくは、下記リンクをご参照下さい。
https://docs.snyk.io/products/snyk-infrastructure-as-code/snyk-cli-for-infrastructure-as-code
AWS Cloud Development Kit(CDK)といった比較的新しいフレームワークにも対応している事から、Snykの開発が活発に行われている事がおわかり頂けるかと思います。
まとめ
以上で現状において最もセキュリティ的に安全なDockerイメージをビルドする事ができました。一連の過程を通して本記事の目的(開発者の方に、Snykがセキュリティに関する課題感を解消できるかどうかを、イメージとして感じとって頂くこと)は達成できたでしょうか?
ここで紹介したもの以外にも、脆弱性のあるライブラリに対して現実的な修正アドバイスをしてくれる機能、AWSのCodePipelineやGithub Actions等の、主要なCI/CDパイプラインにSnykの脆弱性検出プロセスを組み込む機能等の、開発者にとって魅力的な機能が沢山提供されています。他にも開発途中で公開されているものが沢山あるので、要チェックです。
何気に嬉しいのは、2021年12月13日現在において個人利用は無料だという事です。しかも無料のわりには十分すぎる機能を試す事ができます。
ただし、無料プランでは一月あたりの検出試行回数に制限がありますので、個人で一通り試してみて自分の開発チームで有用であると判断できる場合には、有料プランへの加入やアップグレードを検討すると良いでしょう。
このようにSnykは開発者に寄り添った非常に高機能なセキュリティプラットフォームです。少しでも興味を持って頂けたなら、ご自身でも是非試して頂きたいと思います!
最後まで、ご覧いただきありがとうございました!