セキュリティの脆弱性を理解する:攻撃を防ぐための最初のステップ

私が10代のころ、地元の電話会社は新サービスを始めました - プレミアム電話(通称1-900番号)。楽しかったのは、これらにかかる料金を回避する方法を見つけたことでした。1-900番号がリダイレクトされる連続するローカル番号にかけるんです。たとえば、1-900番の「サポート番号」が555-555だったら、555-455から555-655の間のすべての番号にかけて、大当たりするまで探すんです。

何時間もかけてこれらの番号に電話をし、無料で何度も通話をしました。この攻撃は今日でも存在し、Insecure Direct Object References(IDOR)と呼ばれています。

IDOR

デジタル世界におけるIDORは、私たちの10代の頃の悪戯に似ています。これは、正しいID番号が見つかるまで様々なIDを順番に試すことを意味します。数年前にParlerというソーシャルネットワークが、ユーザーが順番に数値IDを振られていたため、このタイプの攻撃で被害を受けました。ユーザーがリクエストを送ってそのネットワークの全ユーザーリストをダウンロードすることができたのです。

例えば、彼らのURLはこんな感じでした: https://site.com/viewUser?id=999

人々がする必要があるのは、有効な連続する番号をループしてリクエストを送り、そのサイトの全員のユーザー情報を取得することです。これは簡単なことで、比較的低いテクニカルスキルを持つ人でも成し遂げられます。

このような攻撃を避けるためには、推測可能なまたは連続した数値IDを末端のユーザーに露出させないことが推奨されます。UUIDは長く思えるかもしれませんが、より安全な代替手段です。さらに、リクエストのチェックが実装されるべきです。もしユーザーが彼らが接続していないユーザーの情報を要求している場合、そのリクエストはブロックされるべきです。他の効果的な緩和策には、大量のデータをつかむことを防ぐために、リクエストの割当てやリクエスト間の遅延を設定することがあります。

これらについてはこれ以上言及しません、なぜならこれらは通常プロビジョニング中のAPIゲートウェイ層で実装されるからです。コードでこれを書くことはできますが、たくさんのエンドポイントと複雑さを持つ可能性が高く困難なタスクです。覚えておくべきルールは、できるだけ少ないコードを書くことです。コードが多いほどバグが多くなり、悪意のあるハッカーにとって攻撃対象が広がります。

脆弱性とエクスプロイト

アプリケーションセキュリティにおける重要な用語は「脆弱性」です。これは攻撃を受ける可能性のある弱点やバグのことで、あなたの家を囲むフェンスにある穴に例えられます。これらの脆弱性は、あなたのコード、ライブラリ、Java自体、オペレーティングシステム、さらには物理的なハードウェアに存在することがあります。しかし、すべての脆弱性が「攻撃可能」というわけではありません。あなたのフェンスに穴があるからといって、必ずしもあなたの家にアクセスできるというわけではないように、脆弱性があるとしても必ずしもあなたのコードがハッキングされるわけではありません。私たちの目標は、できるだけ多くの穴を塞ぎ、システムを攻撃する作業をより困難にすることです。

セキュリティについてよく知られたたとえの玉ねぎですが、それは非常に理にかなっています。私たちはすべての層でセキュリティを施行する必要があります。昨年露見したLog4Shellのエクスプロイトでは、重大なゼロデイの脆弱性がありました。ゼロデイの脆弱性とは、以前に誰も知らなかった新たに見つかった脆弱性であり、フェンスに新たな穴があったといえます。

Log4Shellの脆弱性は、人々が最初に検証せずに情報をログに記録していたことに依存していました。この脆弱性が知られる前からそれは悪習慣でした。もしあなたがその脆弱性を持つLog4Jバージョンを使っていたとしても、データを消毒していたのであれば、その脆弱性にもかかわらず安全だったでしょう。

SQLインジェクション

SQLインジェクションは、クエリ文字列を手動で連結して独自のクエリを構築することを含みます。こんな脆弱なSQLを見てみましょう:

String sql = "SELECT * from Users WHERE id = " + id;

先ほど使用したようなサンプルURLを考えると、こんなURLをリクエストすることができます:https://site.com/viewUser?id=1 OR true=true

このURLの結果、攻撃者はユーザーのすべてを取得することになります。というのも、条件は以下になるからです:

SELECT * from Users WHERE id = 1 OR true=true

これは常に真です。これは比較的穏やかな結果です。SQLステートメントはテーブルを削除してデータベース全体を削除するように連鎖させることができます。この問題の解決策は、すべてのコンテンツを文字列として扱う実装に対して、プリペアドステートメントの構文を使用することです。これにより、SQLキーワードが悪用されるのを防ぐことができます。例えば:

PreparedStatement sql = connection.prepareStatement("SELECT * from Users WHERE id = ?");
sql.setString(1, id);

この状況では、idの値を設定するときに、SQLキーワードや特殊文字があっても文字列として扱います。JPA(Spring Data, Hibernateなど)を使用したAPIを使用すると、SQLインジェクションからも守ることができます。

シリアライゼーション

Javaのシリアライゼーションは別の一般的な脆弱性です。ここでの教訓は、シリアライゼーションを避けるか、それを要求しないようにし、特定のタイプのシリアライゼーションをブロックするフィルターでアプリを実行することです。

これは私が前の投稿で議論したものなので、繰り返す必要はありません。

クロスサイトスクリプティング(XSS)

クロスサイトスクリプティング、またはXSSは複雑な攻撃です。これは悪意のあるスクリプトをWebサイトに注入し、そのページを訪れるすべての人のブラウザで実行されることを含みます。これにより、ユーザーのクッキーが盗まれ、攻撃者がウェブサイト上のユーザーになりすますことができます。XSSから保護するためには、ユーザー提供のデータを検証し、表示するコンテンツとして扱い、実行可能なコードとしては扱わないことが重要です。

私が登録フォームを持っていて、データベースに保存されるユーザーの入力を受け入れるとしましょう。ブログのコメント欄のように。JavaScriptコードを投稿して、ユーザーのクッキーを自分がコントロールするサイトに送信してしまうことができます。そして、この情報を盗んでユーザーになりすますことができます。これは非常に一般的で驚くべき攻撃で、スクリプトをメールによって送られるリンクにエンコードして実行することがよくあります。

これはXSS攻撃の3つのタイプです:

  • ストアドXSS(永続的) - ここで説明した攻撃は、私が投稿するコメントがデータベースに保存されるため、ストアドXSSの形式です。この時点でコメントを見るすべてのユーザーは攻撃を受けます。

  • リフレクテッドXSS(非永続的) - この形式では、攻撃者はユーザーにリンクを送る(またはユーザーがリンクをクリックするように仕向ける)ことで、悪意のあるスクリプトを含むリンクを送ります。ユーザーがリンクをクリックすると、スクリプトが実行され、自分のデータを攻撃者に送信します。スクリプトはURLに埋め込まれ、Webサーバーに反射します。これは通常フィッシング攻撃の一部です。

  • DOMベースのXSS - このタイプの攻撃は、被害者のブラウザ内で完全に発生します。Webアプリケーションのクライアント側のスクリプトが、Document Object Model(DOM)にユーザー提供のデータを書き込みます。その後、そのデータはDOMからWebアプリケーションによって読み取られ、ブラウザに出力されます。もしそのデータがJavaScriptとして解釈された場合、それは実行されます。

XSSから保護するには、すべての入力を注意深く検証する必要があります。ユーザー提供のデータが正しい形式であり、悪意のあるコンテンツを含んでいないかをチェックすることで、これらの攻撃から守ることができます。提供されたコンテンツが実行可能なコードではなく表示コンテンツとして扱われることを確認する必要があります。

JsoupライブラリにはこのようなAPIが含まれており、ユーザー提出のデータを検証する多くの方法があります。Spring Bootにはセキュリティ設定の一部としてXSS保護が含まれていることに注意してくださいが、これについては後ほど説明します。

personName = Jsoup.clean(personName, Whitelist.basic());

入力の検証がセキュリティの脆弱性に関連する一貫したテーマであることに注意してください。私たち開発者は、一般的でフレキシブルなツールを提供しようとしがちですが、それはセキュリティの脆弱性という点で私たちに対して効いてきます。問題が見えないときでも、入力オプションを制限することが重要です。

コンテンツセキュリティポリシー(CSP)

XSS攻撃を行う方法の1つは、自分のウェブサイトに外部コードを含めることです。これをブロックする一つの方法は、どのサイトが自分のサイトを含むことができるかを定義する特別なHTTPヘッダーを使用することです。これはかなり複雑なプロセスですが、Spring Securityがうまく扱ってくれます。

HttpOnlyクッキー

クッキーはブラウザでJavaScriptを使用して作成できます。これはバッドプラクティスです。理想的には、クッキーは常にサーバーから来るべきで、HTTP only(およびHTTPS only)としてマークされるべきです。これはJavaScriptコードがクッキーにアクセスすることをブロックします。

それは、いくつかのスクリプトが何らかの方法で追加されたり、悪いリンクがクリックされたとしても、それはクッキーの値にアクセスすることができないということを意味します。このため、サイトが脆弱であったとしても攻撃がクッキーを盗むことはできません。サーバーコードでクッキーを設定するときにHttpOnlyクッキーを有効にすることができます。

検証されていないリダイレクトとフォワーディング

もう一つのセキュリティの懸念は検証されていないリダイレクトとフォワーデ

こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/codenameone/understanding-security-vulnerabilities-a-first-step-in-preventing-attacks-326c