JavaRush /Java Blog /Random-JA /Java のセキュリティ: ベスト プラクティス
Roman Beekeeper
レベル 35

Java のセキュリティ: ベスト プラクティス

Random-JA グループに公開済み
サーバー アプリケーションにおいて、最も重要な指標の 1 つはセキュリティです。これは、非機能要件のタイプの 1 つです。 Java のセキュリティ: ベスト プラクティス - 1セキュリティには多くのコンポーネントがあります。もちろん、既知の保護原則と行動をすべて完全にカバーするには、複数の記事を書く必要があるため、最も重要な部分に焦点を当てましょう。このトピックに精通していて、すべてのプロセスをセットアップし、新しいセキュリティ ホールが作成されないようにすることができる人は、どのチームでも必要になります。もちろん、これらの慣行に従えばアプリケーションが完全に安全であると考える必要はありません。いいえ!しかし、それらを使用した方が間違いなく安全です。行く。

1. Java 言語レベルでセキュリティを提供する

まず第一に、Java のセキュリティは言語機能レベルから始まります。これは、アクセス修飾子がなかったらどうするでしょうか?...アナーキーです。プログラミング言語は、安全なコードを作成するのに役立ち、また、多くの暗黙的なセキュリティ機能を利用することもできます。
  1. 強力なタイピング。Java は、実行時に型エラーを検出する機能を提供する静的に型付けされた言語です。
  2. 修飾子にアクセスします。これらのおかげで、必要な方法でクラス、メソッド、クラス フィールドへのアクセスを構成できます。
  3. 自動メモリ管理。この目的のために、私たち (Javaists ;)) は、手動構成から解放されるガベージ コレクターを用意しています。はい、時々問題が発生します。
  4. バイトコードのチェック: Java はバイトコードにコンパイルされ、実行前にランタイムによってチェックされます。
とりわけ、セキュリティに関してはOracle からの推奨事項があります。もちろん、「高尚な文体」で書かれているわけではないので、読んでいるうちに何度も眠ってしまうかもしれませんが、それだけの価値はあります。特に重要な文書は、「Java SE のセキュア コーディング ガイドライン」です。これには、セキュアなコードを記述する方法に関するヒントが記載されています。この文書には多くの役立つ情報が含まれています。可能であれば、ぜひ読んでみてください。この資料に興味を持ってもらうための興味深いヒントをいくつか紹介します。
  1. 安全性の高いクラスをシリアル化することは避けてください。この場合、シリアル化されたデータはもちろん、シリアル化されたファイルからクラス インターフェイスも取得できます。
  2. 変更可能なデータ クラスは避けるようにしてください。これにより、不変クラスのすべての利点 (スレッド セーフなど) が得られます。変更可能なオブジェクトがある場合、予期しない動作が発生する可能性があります。
  3. 返された可変オブジェクトのコピーを作成します。メソッドが内部可変オブジェクトへの参照を返す場合、クライアント コードはオブジェクトの内部状態を変更できます。
  4. 等々…
一般に、Java SE のセキュア コーディング ガイドラインには、Java でコードを正しく安全に記述する方法に関する一連のヒントとコツが含まれています。

2. SQL インジェクションの脆弱性を排除する

独特の脆弱性。この脆弱性の独自性は、最も有名な脆弱性の 1 つであり、最も一般的な脆弱性の 1 つでもあるという事実にあります。セキュリティの問題に興味がなければ、それについてはわかりません。SQLインジェクションとは何ですか? これは、予期しない場所に追加の SQL コードを挿入することによるデータベースへの攻撃です。データベースにクエリを実行するために何らかのパラメータを受け取るメソッドがあるとします。たとえば、ユーザー名。脆弱性のあるコードは次のようになります。
// Метод достает из базы данных всех пользователей с определенным именем
public List<User> findByFirstName(String firstName) throws SQLException {
   // Создается связь с базой данных
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

   // Пишем sql request в базу данных с нашим firstName
   String query = "SELECT * FROM USERS WHERE firstName = " + firstName;

   // выполняем request
   Statement statement = connection.createStatement();
   ResultSet result = statement.executeQuery(query);

   // при помощи mapToUsers переводит ResultSet в коллекцию юзеров.
   return mapToUsers(result);
}

private List<User> mapToUsers(ResultSet resultSet) {
   //переводит в коллекцию юзеров
}
この例では、SQLクエリを別行で事前に用意しています。何が問題なのかと思われますよね?String.formatおそらく問題は、 ?を使用した方がよいということです。いいえ?じゃあ何?テスターの立場に立って、その価値で何を伝えることができるのかを考えてみましょうfirstName。例えば:
  1. 期待されるもの、つまりユーザー名を渡すことができます。その後、データベースはその名前を持つすべてのユーザーを返します。
  2. 空の文字列を渡すと、すべてのユーザーが返されます。
  3. または、次のように渡すこともできます。ドロップテーブルユーザー;」。そしてここでさらに大きな問題が発生します。このクエリはデータベースからテーブルを削除します。すべてのデータ付き。みんな。
これがどのような問題を引き起こすか想像できますか? そうすれば、好きなものを書くことができます。すべてのユーザーの名前を変更したり、アドレスを削除したりできます。妨害行為の範囲は広大です。これを回避するには、既製のクエリの挿入を停止し、代わりにパラメータを使用してクエリを構築する必要があります。これがデータベースにクエリを実行する唯一の方法です。これにより、この脆弱性を排除できます。例:
// Метод достает из базы данных всех пользователей с определенным именем
public List<User> findByFirstName(String firstName) throws SQLException {
   // Создается связь с базой данных
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

   // Создаем параметризированный request.
   String query = "SELECT * FROM USERS WHERE firstName = ?";

   // Создаем подготовленный стейтмент с параметризованным requestом
   PreparedStatement statement = connection.prepareStatement(query);

   // Передаем meaning параметра
   statement.setString(1, firstName);

   // выполняем request
   ResultSet result = statement.executeQuery(query);

   // при помощи mapToUsers переводим ResultSet в коллекцию юзеров.
   return mapToUsers(result);
}

private List<User> mapToUsers(ResultSet resultSet) {
   //переводим в коллекцию юзеров
}
このようにして、この脆弱性は回避されます。この記事よりもさらに深く問題を知りたい人のために、ここに素晴らしい例があります。この部分を理解しているかどうかはどうやってわかりますか? 以下のジョークが明らかになった場合、これは脆弱性の本質が明らかであるという確かな兆候です :D Java のセキュリティ: ベスト プラクティス - 2

3. 依存関係をスキャンして最新の状態に保つ

それはどういう意味ですか?依存関係が何なのかわからない人のために説明すると、依存関係は、他の人のソリューションを再利用するために、自動ビルド システム (Maven、Gradle、Ant) を使用してプロジェクトに接続されたコードを含む jar アーカイブです。たとえば、Project Lombokは実行時にゲッターやセッターなどを生成します。また、大規模なアプリケーションについて言えば、さまざまな依存関係が使用されます。推移的なものもあります (つまり、それぞれの依存関係が独自の依存関係を持つことができるなど)。したがって、オープンソースの依存関係は定期的に使用され、多くのクライアントに問題を引き起こす可能性があるため、攻撃者はますます注目を集めています。依存関係ツリー全体 (まさにそのように見えるもの) に既知の脆弱性がないことを確認することが重要です。これを行うにはいくつかの方法があります。

モニタリングに Snyk を使用する

Snykツールはすべてのプロジェクトの依存関係をチェックし、既知の脆弱性にフラグを立てます。そこで、たとえば GitHub 経由でプロジェクトを登録およびインポートできます。 Java のセキュリティ: ベスト プラクティス - 3また、上の図からわかるように、新しいバージョンにこの脆弱性に対する解決策がある場合、Snyk は解決策を提案し、プル リクエストを作成します。オープンソース プロジェクトでは無料で使用できます。プロジェクトは、週に 1 回、月に 1 回など、一定の頻度でスキャンされます。私はすべてのパブリック リポジトリを Snyk スキャンに登録して追加しました (これに関して危険なことは何もありません。これらはすでに誰にでも公開されています)。次に、Snyk はスキャンの結果を表示しました: Java のセキュリティ: ベスト プラクティス - 4そしてしばらくして、Snyk-bot は依存関係を更新する必要があるプロジェクトでいくつかのプルリクエストを準備しました: Java のセキュリティ: ベスト プラクティス - 5そして、これがもう 1 つあります: Java のセキュリティ: ベスト プラクティス - 6したがって、これは脆弱性を検索し、更新を監視するための優れたツールです新しいバージョン。

GitHub セキュリティ ラボを使用する

GitHub で作業している人は、組み込みツールを利用することもできます。このアプローチの詳細については、彼らのブログ「 Notice of GitHub Security Lab 」の私の翻訳で読むことができます。もちろん、このツールは Snyk よりもシンプルですが、絶対に無視してはいけません。さらに、既知の脆弱性の数は増える一方なので、Snyk と GitHub Security Lab の両方が拡大し、改善されるでしょう。

Sonatype DepShield をアクティブ化する

GitHub を使用してリポジトリを保存する場合は、MarketPlace からプロジェクトにアプリケーションの 1 つ (Sonatype DepShield) を追加できます。これを利用すると、プロジェクトの依存関係をスキャンすることもできます。さらに、何かが見つかった場合は、以下に示すように、対応する説明を含む GitHub Issue が作成されます。 Java のセキュリティ: ベスト プラクティス - 7

4. 機密データは慎重に取り扱います

Java のセキュリティ: ベスト プラクティス - 8英語のスピーチでは、「機密データ」というフレーズの方が一般的です。お客様の個人情報、クレジットカード番号、その他の個人情報が漏洩すると、取り返しのつかない損害が生じる可能性があります。まず最初に、アプリケーションの設計を詳しく調べて、実際にデータが必要かどうかを判断する必要があります。おそらくそれらのいくつかは必要ありませんが、まだ来ていない、そして来る可能性が低い未来のために追加されました。さらに、プロジェクトのログ記録中に、そのようなデータが漏洩する可能性があります。機密データがログに取り込まれるのを防ぐ簡単な方法は、toString()ドメイン エンティティ (ユーザー、学生、教師など) のメソッドをクリーンアップすることです。これにより、機密フィールドが誤って印刷されるのを防ぎます。Lombok を使用してメソッドを生成する場合toString()、アノテーションを使用して、@ToString.Excludeフィールドがメソッド経由の出力で使用されないようにすることができますtoString()。また、外部とデータを共有する場合には十分注意してください。たとえば、すべてのユーザーの名前を表示する http エンドポイントがあります。ユーザーの内部固有 ID を表示する必要はありません。なぜ?これを使用すると、攻撃者は各ユーザーに関するその他のより機密性の高い情報を取得できるためです。たとえば、Jackson を使用してPOJO をJSONにシリアル化および逆シリアル化する場合、@JsonIgnoreおよび注釈を使用して@JsonIgnoreProperties、特定のフィールドがシリアル化または逆シリアル化されるのを防ぐことができます。一般に、場所ごとに異なる POJO クラスを使用する必要があります。それはどういう意味ですか?
  1. データベースを操作するには、POJO - エンティティのみを使用します。
  2. ビジネス ロジックを操作するには、エンティティをモデルに転送します。
  3. 外部と連携して http リクエストを送信するには、3 番目のエンティティである DTO を使用します。
このようにして、どのフィールドが外部から見えるか、どのフィールドが見えないかを明確に定義できます。

強力な暗号化とハッシュ アルゴリズムを使用する

機密の顧客データは安全に保管する必要があります。これを行うには、暗号化を使用する必要があります。タスクに応じて、使用する暗号化の種類を決定する必要があります。さらに、暗号化を強化すると時間がかかるため、暗号化の必要性が暗号化に費やす時間をどれだけ正当化できるかを再度考慮する必要があります。もちろん、アルゴリズムを自分で書くこともできます。しかし、これは不要です。この分野では既存のソリューションを活用できます。たとえば、Google ティンク:
<!-- https://mvnrepository.com/artifact/com.google.crypto.tink/tink -->
<dependency>
   <groupId>com.google.crypto.tink</groupId>
   <artifactId>tink</artifactId>
   <version>1.3.0</version>
</dependency>
一方向ともう一方の方向で暗号化する方法の例を使用して、その使用方法を見てみましょう。
private static void encryptDecryptExample() {
   AeadConfig.register();
   KeysetHandle handle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_CTR_HMAC_SHA256);

   String plaintext = "Цой жив!";
   String aad = "Юрий Клинских";

   Aead aead = handle.getPrimitive(Aead.class);
   byte[] encrypted = aead.encrypt(plaintext.getBytes(), aad.getBytes());
   String encryptedString = Base64.getEncoder().encodeToString(encrypted);
   System.out.println(encryptedString);

   byte[] decrypted = aead.decrypt(Base64.getDecoder().decode(encrypted), aad.getBytes());
   System.out.println(new String(decrypted));
}

パスワードの暗号化

このタスクでは、非対称暗号化を使用するのが最も安全です。なぜ?なぜなら、アプリケーションは実際にパスワードを復号化する必要がないからです。これが一般的なアプローチです。実際には、ユーザーがパスワードを入力すると、システムはそれを暗号化し、パスワード保管庫にあるものと比較します。暗号化は同じ手段を使用して実行されるため、一致することが期待できます (もちろん、正しいパスワードを入力した場合 ;))。BCrypt と SCrypt はこの目的に適しています。どちらも一方向関数 (暗号化ハッシュ) であり、計算が複雑なアルゴリズムを使用するため、時間がかかります。これを正面から解読するには永遠に時間がかかるため、これがまさに必要なものです。たとえば、Spring Security はさまざまなアルゴリズムをサポートしています。を使用することSCryptPasswordEncoderもできますBCryptPasswordEncoder。現在強力な暗号化アルゴリズムも、来年には弱くなる可能性があります。その結果、使用されているアルゴリズムを確認し、アルゴリズムを使用してライブラリを更新する必要があると結論付けました。

アウトプットの代わりに

今日は安全性について話しましたが、もちろん舞台裏には多くのことが残されています。私はあなたのために新しい世界、つまり独自の人生を生きる世界への扉を開いたところです。安全保障についても、政治と同じです。あなたが政治に関与しなければ、政治があなたに関与するでしょう。従来通り、Github アカウントに登録することをお勧めします。そこでは、私が研究して仕事に応用しているさまざまなテクノロジーに関する私の作品を投稿しています。

役立つリンク

はい、サイト上のほぼすべての記事は英語で書かれています。好むと好まざるにかかわらず、プログラマーにとって英語はコミュニケーション言語です。プログラミングに関する最新の記事、書籍、雑誌はすべて英語で書かれています。そのため、私の推奨事項へのリンクはほとんど英語になっています。
  1. Habr:初心者のための SQL インジェクション
  2. Oracle: Java セキュリティ リソース センター
  3. Oracle: Java SE の安全なコーディング ガイドライン
  4. Baeldung: Java セキュリティの基本
  5. 中: Java セキュリティを強化するための 10 のヒント
  6. Snyk: Java セキュリティに関する 10 のベスト プラクティス
  7. JR: GitHub Security Lab の発表: すべてのコードをまとめて保護
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION