Skip to main content

セキュアコードレビュー:セキュリティコードレビューのベストプラクティス8選

2020年4月20日

0 分で読めます
入力値の検証、認証情報の保護、認証、依存関係のテストなど、セキュリティを考慮したコードレビューのベストプラクティスを8つ紹介するSnykのチートシート

チートシートをダウンロード!

「レビューするコードにセキュリティ上の問題がないか確認することは、常に大切です。何をチェックすればよいかわからない方のために、次回のコードレビューに役立つチェックリストをご紹介します!コードレビューを適切に行うのは簡単ではありません。特に、どのような問題を探せばよいのかはっきりわからない場合はなおさらです。DevSecOpsのアプローチでは、セキュリティテストを開発プロセスの早い段階に移すことで、ワークフローの設計、開発、CI/CDの各段階で脆弱性を発見し、修正できるようにします。レビューするコードにセキュリティ上の問題がないか確認することは、常に大切です。何をチェックすればよいかわからない方のために、次回のコードレビューに役立つチェックリストをご紹介します!」

コードをレビューする際は、すべてのコードが同じように書かれているわけではないことを理解しましょう。レビュー対象のコードの背後にあるもの、つまり保護しようとしているデータや資産についても考えてください。こうした実務知識は、チェックリストに簡単に加えられるものではありません。しかし、このチートシートのヒントをドメイン知識と併せて活用すれば、どこに時間をかけるべきか、どこに高いリスクや異なる種類の攻撃が潜んでいるかを判断する助けになります。最もリスクの高い領域を特定するには、攻撃ツリーを作成して、最初に、または最も注力すべき箇所を明らかにするのも有効です。

それでは、今後のプルリクエストをレビューする際にチェックできる、セキュアコードレビューのヒント8選を見ていきましょう!

1. すべての入力をサニタイズして検証する

現代のWebアプリケーションは、さまざまなサードパーティの入力を扱う必要があります。たとえば、ブラウザー上でエンドユーザーが直接入力する情報は、わかりやすい例です。開発者なら、ユーザーが予期しない内容を入力できる場合、実際にそうすることはよくご存じでしょう。ユーザーからの直接入力を適切に検証、サニタイズすることは、コンテンツインジェクションに対する脆弱性を防ぐための基本的なベストプラクティスです。しかし、確認すべきなのはユーザーの直接入力だけではありません。システムの外部境界から入ってくる入力は、すべて有害な可能性があるものとして扱う必要があります。たとえば、次のようなものです。

  • データフィード

  • ファイル

  • イベント — Functions as a Serviceなどのプラットフォームと密接に連携するイベント駆動型システム

  • 他のシステムからのデータレスポンス

  • Cookie

さらに、一見自分で制御できているように見える入力も、有害な場合があります。悪意のあるユーザーがデータベースに直接接続できると、たとえば悪意のあるコードを挿入し、システム上で実行させるバックドアになり得ます。同じことは、次のものにも当てはまります。

  • コマンドライン引数

  • 環境変数

  • システムプロパティ

  • データストレージ

自分で管理しているように見える入力も含め、すべての入力を検証し、サニタイズしてください。入力が妥当かどうかを確認しましょう。型安全な言語の型システムを活用すると、大いに役立ちます。さらに、形式、範囲、サイズ、ファイルの種類や名前を確認し、何事も当然と思わないでください。ユーザー入力は、保存または使用する前に、十分に検証されたライブラリを使ってサニタイズするのが望ましいです。

2. シークレットをコードや設定ファイルに保存しない

認証情報、トークン、その他のシークレットを変数や定数として保存するのは、あまりにも簡単です。動作確認のために一時的に使っているだけ、と思ってしまうからです。しかし、削除し忘れたコードがそのままリポジトリに入ってしまうこともあります。レビューするコードに機密情報が含まれていないか、必ず確認してください。Gitベースのコードリポジトリを使っている場合は、git-secretsなどの便利なツールを利用できます。Gitのコミット前フックを通じてコミットを静的解析し、パスワードや機密情報をリポジトリにプッシュしようとしていないか確認できます。機密情報が不適切に保存されていることを示す、設定済みの正規表現パターンに一致すると、コミットは拒否されます。プッシュが少し遅くなるかもしれませんが、十分に価値があります。

チーム全体で、認証情報をコードとして保存しないルールを設けることは、開発ワークフローで好ましくない操作を監視する優れた方法です。本番環境でシークレットを管理するには、Vaultなどのツールを活用しましょう。最後に、Keycloak(現在はRed Hatの複数の開発者がメンテナンス)などのID・ユーザー管理ツールチェーンの利用も検討してください。

そもそも認証情報をリポジトリに入れない方法は数多くあり、できるだけ多くを実践するのが最善です。それでも、機密情報が紛れ込む可能性は常にあります。GitRobやtruffleHogなどのツールを使い、パターンマッチングでコードベースをスキャンして機密情報を探し、リポジトリを定期的に監査することも検討しましょう。

3. サードパーティのオープンソース依存関係によって新たな脆弱性が持ち込まれていないかテストする

現代のアプリケーション開発は、サードパーティのライブラリに大きく依存しています。npm、Maven、Gradle、PyPIなどのパッケージマネージャーを使えば、公開されているライブラリやフレームワークを簡単に利用できます。開発者は、定型的な機能の作成ではなく、特定のビジネスロジックに集中したいものです。フレームワークやライブラリに複雑な処理を任せるのは、当然の選択です。

アプリケーションがいくつの直接依存関係を使用しているのか、把握できていないことも多いでしょう。一般的なプロジェクトでは、自分たちのコードが占める割合はわずか1%程度で、残りはインポートされたライブラリやフレームワークということもあります。本番環境に投入されるコードの多くは自分たちが書いたものではありませんが、私たちはそれらに大きく依存しています。また、アプリケーションがいくつの推移的依存関係を使用しているのかも、把握できていない可能性が高いでしょう。最近の大規模なフレームワークは、さらに他のライブラリに依存し、そのライブラリもまた別のライブラリに依存しています。ライブラリやフレームワークを1つ取り込むだけで、気づかないうちに少なくとも十数個のライブラリやフレームワークを追加していることもあります。その結果、依存関係がアプリケーション全体の大部分を占めることになります。オープンソース依存関係は再利用されるため、攻撃者にとっては多くの被害者を狙える格好の標的となり、攻撃が増えています。そのため、アプリケーションの依存関係ツリー全体に既知の脆弱性がないことを確認することが重要です。

Snykを例に見てみましょう。Snykはプロジェクトを静的解析して、使用中の脆弱な依存関係を検出し、修正を支援します。SnykのUIからリポジトリをテストして問題を見つけられるほか、プルリクエストをテストし、新たな脆弱性が見つかった場合にテストを失敗させることで、脆弱なライブラリが追加されないようにできます。修正を自動化したプルリクエストも利用できます。

作業スタイルに合わせて、リポジトリをSnykのUIに接続する方法や、CLI(CLIチートシートを参照)、ビルドシステムとのインテグレーション、IDEプラグインを使ってローカルマシンでプロジェクトをスキャンする方法を選べます。開発者のローカルマシンから本番環境まで、そしてその間のあらゆる段階で、依存関係を自動的に解析し、迅速なフィードバックを得られるようにしましょう。

4. セキュアな認証を徹底する

認証とは、ユーザー、サービス、またはエンティティ(内部・外部を問わず)が、本人であることを確認することです。ユーザーが認証情報を提示するような単純なものから、サーバーがTLS証明書を提示し、申告どおりのサーバーであることを検証するものまであります。認証はユーザーやサービスに許可された操作を示すものではなく、本人であることを確認するものです。ここでは、覚えておきたい認証のベストプラクティスをいくつか紹介します。

相手が名乗ったとおりの人物ではないと想定する

本人であることを証明する認証情報が提示されるまでは、相手が名乗ったとおりの人物ではないという前提で対応しましょう。ユーザーやサービスにはデータへのアクセス権がないと想定するのが、もちろん最も安全です。コードにもその前提を反映してください。

パスワードの複雑さを徹底する

ユーザー名については、特にメールアドレスを使用する場合、柔軟に扱うことを検討しましょう。たとえば、Patch@snyk.ioとpatch@snyk.ioを区別する意味はほとんどありません。一方で、パスワードの複雑さ(大文字1文字以上、小文字1文字以上(a-z)、数字1文字以上(0-9)、特殊文字1文字以上)と長さ(NIST SP800-132)を徹底することは重要です。ランダムなものも、そうでないものも含めて、長く複雑なパスワードをすべて覚えるのは難しいですよね。でも、もう2020年です。パスワードマネージャーを活用しましょう!

機密性の高い操作の前に再認証する

資金の移動や機密性の高い操作を行う前にユーザーに認証情報を再入力してもらうことで、クロスサイトリクエストフォージェリ(CSRF)やセッションハイジャック攻撃のリスクを軽減できます。攻撃者は、ユーザーの認証情報を一度も入力させることなく、こうした機密性の高い操作を実行する可能性があります。ユーザーにとって不便なセキュリティ対策ではありますが、長期的にはユーザーを守ることにつながります。

TLSクライアント認証

相互TLS認証とも呼ばれるTLSクライアント認証では、TLSハンドシェイク時にブラウザーとサーバーの双方がTLS証明書を送信し、互いに認証します。ユーザーやサービスがサーバーからクライアント証明書を取得し、その後のやり取りで提示することで実現できます。ブラウザーを使用する場合は、証明書のインストールが必要になることがあります。

5. 最小権限の原則を徹底する

認証に加えて、認可も必要です。似た言葉ですが、意味は大きく異なります。4つ目のポイントで見たように、認証はユーザーやサービスが本人であることを証明します。一方、認可ではさらに、そのユーザーやサービスが実行しようとしているタスクや操作を許可されているかどうかを確認します。ユーザー、サービス、プロセスが、その操作を行う権限を持つロールで実行されている、またはそのロールに属していることを確認する必要があります。しかし、コーディングの観点では、実際に必要な範囲を超えてアクセス権を与えてしまいがちです。

最小権限の原則では、モジュール(対象によってプロセス、ユーザー、プログラムなど)は、正当な目的のために必要な情報とリソースにのみアクセスできなければなりません。つまり、目標の達成に必要な最小限の権限だけを、人やプロセスに付与するということです。

これを検証する優れた方法は、通常の成功ケースだけでなく、セキュリティに関わる異常ケースもテストする単体テストや統合テストを具体的に作成することです。テストでは認証に成功したうえで、許可されていない操作を試みます。アプリケーションの実行ロールを変更する場合や、特定のロールでなければ操作できない新しいリソースを導入する場合は、必ずこうしたテストを追加してください。

6. 機密データを慎重に扱う

顧客の個人情報やクレジットカード番号などの機密データが漏えいすると、深刻な被害につながる可能性があります。しかし、もっとわかりにくいケースでも同様の被害が起こり得ます。たとえば、システム内の一意な識別子を使って別のリクエストから追加データを取得できる場合、その識別子の漏えいも危険です。

まず、アプリケーションの設計を詳しく見直し、そのデータが本当に必要か判断しましょう。さらに、ログへの記録、自動補完、データの送信などを通じて機密データが公開されないようにしてください。

機密データの保存

個人を特定できる情報(PII)や金融情報などの機密データを永続化する必要がある場合は、適切な暗号化が使われていることを確認してください。GDPRへの準拠が必要なのはもちろんですが、何よりも顧客のデータが侵害されないようにすることが重要です。データを元の形式で取得する必要がある場合は強力な双方向暗号化アルゴリズムを、パスワードを保存する場合は強力な暗号学的ハッシュアルゴリズムを使用します。独自の暗号化を実装するのは避け、必要な暗号化方式を調べたうえで、十分に検証されたライブラリを使って処理しましょう。たとえば、パスワードのハッシュ化にはBCryptを使用し、あとで取得する必要のあるデータの暗号化にはTriple DES、RSA、AESなどのアルゴリズムを使用します。最も重要なのは、使用しているアルゴリズムが今も十分に安全かどうかを継続的に確認することです。今日問題なく使えるものが、明日には侵害されている可能性もあります。

機密データはメモリ上に存在する場合もあることを忘れないでください。システム上でパスワードを変更する場合は、変更前の値が不変のデータ型に一時保存されないようにしましょう。たとえば、Javaでパスワードをメモリ上に保存するためにStringを使うと、Stringは不変のため、ガベージコレクターが削除するまで元の値がメモリに残ります。この場合は、バイト配列を使う方が適切です。

機密データの転送

機密データを転送する必要がある場合は、接続が安全かどうかを確認してください。機密データは暗号化したうえで、TLS経由でのみ転送してください。また、TLSのバージョンが最新であることも確認する必要があります。クレジットカード情報をクエリパラメーターとして送信したり、HTTP経由でペイロードに平文で含めたりするのは、もちろんまったく安全ではありません。

セッションデータ

最後に、セッションデータも機密データとして扱う必要があります。機密データをCookieに保存するのではなく、セッション識別子を使って、サーバー管理のセッションにデータを保存することをおすすめします。また、Cookieが暗号化され、十分な長さ(例:128ビット)であることを確認してください。CookieのHttpOnly、Secure,、SameSiteなどの属性が正しく設定され、適切な期間が経過すると有効期限が切れることも確認しましょう。ユーザーがクライアント側でログアウトした場合は、セッションを無効化し、ほかの場所で再利用できないようにする必要があります。

7. 広く知られている攻撃から保護する

攻撃者は今後も、予測可能で広く知られ、認識されている攻撃手法を使ってアプリケーションをハッキングしようとすると考えてよいでしょう。一般的な脆弱性やその悪用方法に関する知識が不足していると、同じセキュリティ上のミスが将来のコードでも繰り返されがちです。OWASP Top 10の脆弱性を確認し、こうした一般的な攻撃がどのように行われるかを理解しましょう。よくある脆弱性を避けるためのポイントをいくつか紹介します。

XSS

クロスサイトスクリプティング(XSS)攻撃の基本的な仕組みは、ユーザーが悪意のあるデータをアプリケーションに注入し、そのデータがHTMLドキュメントなど、実行可能なコンテキストに入り込むことです。その結果、機密情報の流出などの悪意ある動作につながる可能性があります。サイト上でユーザーが送信したデータをページに表示する場合は、XSSを防ぐ方法を学ぶ必要があります。危険な文字をHTMLエンコードするなど、XSS攻撃のリスクを減らす方法はいくつかあります。対象となる文字には、&、<、>、“、‘などがあります。こうした文字を許可しない、またはサニタイズすることで、攻撃者がHTMLタグを抜け出して悪意のあるコードを実行するのを防げます。さらに、渡されたHTML文字をHTMLエンコードし、HTMLタグを抜け出せる形式で表現されないようにできます。具体例を以下に示します。

HTMLエンティティ

HTMLエンコード

&

&amp;

<

&lt;

>

&gt;

“

&quot;

‘

&#x27;

同様の対策は、タグの属性やイベントハンドラー、スタイルプロパティにも適用できます。許可する文字をホワイトリストに登録するため、サニタイズライブラリを使うのが一般的です。既存のサニタイズライブラリを使えば、あらゆるケースに対応するためにセキュリティの専門家になる必要もありません。特に経験の浅い開発者にとって有効です。

SQLインジェクションとNoSQLインジェクション

SQLインジェクションが攻撃者にとって魅力的な理由の一つは、最終的に狙っているデータへ直接アクセスできることです。多くの場合、ハッキングは侵入を試みるシステムについて知るための手段にすぎません。攻撃者は通常、データの保存場所やアクセス方法を特定するために、さらに調査を行う必要があります。一方、SQLインジェクションは、攻撃に成功すると、データベースに保存された機密情報へ攻撃者が直接アクセスできる手段です。これはSQLデータベースだけに限りません。多くのNoSQLデータベースも同様の方法で侵害される可能性があります。

XSSとSQLインジェクションはいずれも非常によくある攻撃です。その仕組みを理解し、コード内で見つけられるようにしておきましょう。システム内のどこかで入力をサニタイズしていない場合、XSSの大きな危険信号です。SQLインジェクションについては、クエリのパラメーター化が実装されているか確認してください。クエリとパラメーターを明確に分ける必要があります。パラメーターをクエリに含める前に特定の型にバインドし、適切にサニタイズすることで、この種の攻撃を防げます。

アプリケーションが影響を受ける可能性のある脆弱性はほかにも数多くあり、リスクの高さは種類によって異なります。チームが直接作成したコードから、アプリケーションが依存するライブラリに至るまで、どの脆弱性がアプリケーションに最も影響するかを学ぶ時間を取りましょう。

8. ソースコードを自動で静的テストする

静的コード解析は、開発者にとって非常に強力なツールです。セキュリティに特化したツールが、静的アプリケーションセキュリティテスト(SAST)ツールです。SASTツールは、あなたやチームが作成したコードを静的に解析し、セキュリティ関連のバグがソースコードに入り込んでいないかを検出します。Snyk CodeのようなSASTツールを使うと、SQLインジェクションなどのコードの脆弱性を特定できます。

セキュリティ対策には、リンターよりSASTをおすすめします。リンターは静的コード解析(バグやエラーなど)に役立ちますが、誤検知が多くなりがちです。すべてのリンターはルールベースで、コード全体のコンテキストを考慮しないため、実際には問題がないのに、バグやセキュリティ上の問題としてコードを指摘するケースが数多くあります。それでも、ルールセットを細かく調整すれば、リンターで重大なミスを防げます。最善の方法は、こうしたプロセスをできる限り自動化することです。たとえば、ビルドプロセスの一部として、あるいはリポジトリに新しいプルリクエストが送信されたときに実行できます。Snyk Codeなら、これらすべてに対応し、ワークフローにSAST機能を直接追加できます。

SASTツールを導入していない場合は、手動のコードレビューで使うこともできますが、プロセスに組み込んで自動化し、誰もが明らかな問題をより早く発見できるようにするのがさらに効果的です。コードのセキュリティを手早く確認したい場合は、Snykの無料コードチェッカーツールもお試しください。

CTFを始めよう

オンデマンドのバーチャル入門ワークショップで、CTFチャレンジの解き方を学びましょう。