サーバー アプリケーションにおいて、最も重要な指標の 1 つはセキュリティです。これは、非機能要件のタイプの 1 つです。 セキュリティには多くのコンポーネントがあります。もちろん、既知の保護原則と行動をすべて完全にカバーするには、複数の記事を書く必要があるため、最も重要な部分に焦点を当てましょう。このトピックに精通していて、すべてのプロセスをセットアップし、新しいセキュリティ ホールが作成されないようにすることができる人は、どのチームでも必要になります。もちろん、これらの慣行に従えばアプリケーションが完全に安全であると考える必要はありません。いいえ!しかし、それらを使用した方が間違いなく安全です。行く。
1. Java 言語レベルでセキュリティを提供する
まず第一に、Java のセキュリティは言語機能レベルから始まります。これは、アクセス修飾子がなかったらどうするでしょうか?...アナーキーです。プログラミング言語は、安全なコードを作成するのに役立ち、また、多くの暗黙的なセキュリティ機能を利用することもできます。- 強力なタイピング。Java は、実行時に型エラーを検出する機能を提供する静的に型付けされた言語です。
- 修飾子にアクセスします。これらのおかげで、必要な方法でクラス、メソッド、クラス フィールドへのアクセスを構成できます。
- 自動メモリ管理。この目的のために、私たち (Javaists ;)) は、手動構成から解放されるガベージ コレクターを用意しています。はい、時々問題が発生します。
- バイトコードのチェック: 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
。例えば:
- 期待されるもの、つまりユーザー名を渡すことができます。その後、データベースはその名前を持つすべてのユーザーを返します。
- 空の文字列を渡すと、すべてのユーザーが返されます。
- または、次のように渡すこともできます。ドロップテーブルユーザー;」。そしてここでさらに大きな問題が発生します。このクエリはデータベースからテーブルを削除します。すべてのデータ付き。みんな。
// Метод достает из базы данных всех пользователей с определенным именем
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
3. 依存関係をスキャンして最新の状態に保つ
それはどういう意味ですか?依存関係が何なのかわからない人のために説明すると、依存関係は、他の人のソリューションを再利用するために、自動ビルド システム (Maven、Gradle、Ant) を使用してプロジェクトに接続されたコードを含む jar アーカイブです。たとえば、Project Lombokは実行時にゲッターやセッターなどを生成します。また、大規模なアプリケーションについて言えば、さまざまな依存関係が使用されます。推移的なものもあります (つまり、それぞれの依存関係が独自の依存関係を持つことができるなど)。したがって、オープンソースの依存関係は定期的に使用され、多くのクライアントに問題を引き起こす可能性があるため、攻撃者はますます注目を集めています。依存関係ツリー全体 (まさにそのように見えるもの) に既知の脆弱性がないことを確認することが重要です。これを行うにはいくつかの方法があります。モニタリングに Snyk を使用する
Snykツールはすべてのプロジェクトの依存関係をチェックし、既知の脆弱性にフラグを立てます。そこで、たとえば GitHub 経由でプロジェクトを登録およびインポートできます。 また、上の図からわかるように、新しいバージョンにこの脆弱性に対する解決策がある場合、Snyk は解決策を提案し、プル リクエストを作成します。オープンソース プロジェクトでは無料で使用できます。プロジェクトは、週に 1 回、月に 1 回など、一定の頻度でスキャンされます。私はすべてのパブリック リポジトリを Snyk スキャンに登録して追加しました (これに関して危険なことは何もありません。これらはすでに誰にでも公開されています)。次に、Snyk はスキャンの結果を表示しました: そしてしばらくして、Snyk-bot は依存関係を更新する必要があるプロジェクトでいくつかのプルリクエストを準備しました: そして、これがもう 1 つあります: したがって、これは脆弱性を検索し、更新を監視するための優れたツールです新しいバージョン。GitHub セキュリティ ラボを使用する
GitHub で作業している人は、組み込みツールを利用することもできます。このアプローチの詳細については、彼らのブログ「 Notice of GitHub Security Lab 」の私の翻訳で読むことができます。もちろん、このツールは Snyk よりもシンプルですが、絶対に無視してはいけません。さらに、既知の脆弱性の数は増える一方なので、Snyk と GitHub Security Lab の両方が拡大し、改善されるでしょう。Sonatype DepShield をアクティブ化する
GitHub を使用してリポジトリを保存する場合は、MarketPlace からプロジェクトにアプリケーションの 1 つ (Sonatype DepShield) を追加できます。これを利用すると、プロジェクトの依存関係をスキャンすることもできます。さらに、何かが見つかった場合は、以下に示すように、対応する説明を含む GitHub Issue が作成されます。4. 機密データは慎重に取り扱います
英語のスピーチでは、「機密データ」というフレーズの方が一般的です。お客様の個人情報、クレジットカード番号、その他の個人情報が漏洩すると、取り返しのつかない損害が生じる可能性があります。まず最初に、アプリケーションの設計を詳しく調べて、実際にデータが必要かどうかを判断する必要があります。おそらくそれらのいくつかは必要ありませんが、まだ来ていない、そして来る可能性が低い未来のために追加されました。さらに、プロジェクトのログ記録中に、そのようなデータが漏洩する可能性があります。機密データがログに取り込まれるのを防ぐ簡単な方法は、toString()
ドメイン エンティティ (ユーザー、学生、教師など) のメソッドをクリーンアップすることです。これにより、機密フィールドが誤って印刷されるのを防ぎます。Lombok を使用してメソッドを生成する場合toString()
、アノテーションを使用して、@ToString.Exclude
フィールドがメソッド経由の出力で使用されないようにすることができますtoString()
。また、外部とデータを共有する場合には十分注意してください。たとえば、すべてのユーザーの名前を表示する http エンドポイントがあります。ユーザーの内部固有 ID を表示する必要はありません。なぜ?これを使用すると、攻撃者は各ユーザーに関するその他のより機密性の高い情報を取得できるためです。たとえば、Jackson を使用してPOJO をJSONにシリアル化および逆シリアル化する場合、@JsonIgnore
および注釈を使用して@JsonIgnoreProperties
、特定のフィールドがシリアル化または逆シリアル化されるのを防ぐことができます。一般に、場所ごとに異なる POJO クラスを使用する必要があります。それはどういう意味ですか?
- データベースを操作するには、POJO - エンティティのみを使用します。
- ビジネス ロジックを操作するには、エンティティをモデルに転送します。
- 外部と連携して 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 アカウントに登録することをお勧めします。そこでは、私が研究して仕事に応用しているさまざまなテクノロジーに関する私の作品を投稿しています。役立つリンク
はい、サイト上のほぼすべての記事は英語で書かれています。好むと好まざるにかかわらず、プログラマーにとって英語はコミュニケーション言語です。プログラミングに関する最新の記事、書籍、雑誌はすべて英語で書かれています。そのため、私の推奨事項へのリンクはほとんど英語になっています。- Habr:初心者のための SQL インジェクション
- Oracle: Java セキュリティ リソース センター
- Oracle: Java SE の安全なコーディング ガイドライン
- Baeldung: Java セキュリティの基本
- 中: Java セキュリティを強化するための 10 のヒント
- Snyk: Java セキュリティに関する 10 のベスト プラクティス
- JR: GitHub Security Lab の発表: すべてのコードをまとめて保護
GO TO FULL VERSION