サーバー アプリケーションにおける最も重要な指標の 1 つはセキュリティです。これは非機能要件の一種です。セキュリティには多くのコンポーネントが含まれます。もちろん、既知のセキュリティ原則とセキュリティ対策をすべて完全にカバーするには複数の記事が必要になるため、最も重要なものについて説明します。このトピックに精通した人は、関連するすべてのプロセスを設定し、新たなセキュリティ ホールの作成を回避することができ、どのチームでも必要とされるでしょう。もちろん、これらの慣行に従えばアプリケーションが 100% 安全になると考える必要はありません。いいえ!しかし、それらを使用した方が安全であることは間違いありません。さあ行こう。
1. Java 言語レベルでセキュリティを提供する
まず第一に、Java のセキュリティは言語の機能レベルから始まります。アクセス修飾子がなかったらどうするでしょうか? そこには無秩序以外の何物でもないでしょう。このプログラミング言語は安全なコードを書くのに役立ち、また多くの暗黙的なセキュリティ機能も利用します。- 強力なタイピング。Java は静的に型付けされた言語です。これにより、実行時に型関連のエラーを捕捉できるようになります。
- 修飾子にアクセスします。これらにより、必要に応じてクラス、メソッド、フィールドへのアクセスをカスタマイズできます。
- 自動メモリ管理。このため、Java 開発者は、すべてを手動で構成する必要がないようにするガベージ コレクターを用意しています。はい、時々問題が発生します。
- バイトコード検証: Java はバイトコードにコンパイルされ、実行前にランタイムによってチェックされます。
- セキュリティに依存するクラスのシリアル化は避けてください。シリアル化では、シリアル化されたデータはもちろん、シリアル化されたファイル内のクラス インターフェイスも公開されます。
- データの可変クラスは避けるようにしてください。これにより、不変クラスのすべての利点 (スレッド セーフなど) が得られます。変更可能なオブジェクトがある場合、予期しない動作が発生する可能性があります。
- 返された可変オブジェクトのコピーを作成します。メソッドが内部可変オブジェクトへの参照を返す場合、クライアント コードによってオブジェクトの内部状態が変更される可能性があります。
- 等々…
2. SQL インジェクションの脆弱性を排除する
これは特殊な種類の脆弱性です。これは最も有名な脆弱性の 1 つであり、最も一般的な脆弱性の 1 つでもあるため、特別です。コンピュータ セキュリティに興味がなかった場合は、そのことを知らないでしょう。SQLインジェクションとは何ですか? これは、予期しない場所に追加の SQL コードを挿入するデータベース攻撃です。データベースにクエリを実行するために何らかのパラメータを受け入れるメソッドがあるとします。たとえば、ユーザー名です。脆弱なコードは次のようになります。
// This method retrieves from the database all users with a certain name
public List findByFirstName(String firstName) throws SQLException {
// Connect to the database
Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
// Compose a SQL database query with our firstName
String query = "SELECT * FROM USERS WHERE firstName = " + firstName;
// Execute the query
Statement statement = connection.createStatement();
ResultSet result = statement.executeQuery(query);
// Use mapToUsers to convert the ResultSet into a collection of users.
return mapToUsers(result);
}
private List mapToUsers(ResultSet resultSet) {
// Converts to a collection of users
}
この例では、SQLクエリを別行で事前に用意しています。それで、何が問題なのでしょうか?おそらく問題は、 String.formatを使用したほうが良いということです。いいえ?さて、それではどうでしょうか?テスターの立場に立って、firstNameの値として何が渡されるかを考えてみましょう。例えば:
- 期待されるもの、つまりユーザー名を渡すことができます。その後、データベースはその名前を持つすべてのユーザーを返します。
- 空の文字列を渡すことができます。その後、すべてのユーザーが返されます。
- ただし、「'; DROP TABLE USERS;」を渡すこともできます。そしてここで、非常に大きな問題が発生しています。このクエリはデータベースからテーブルを削除します。すべてのデータも一緒に。それのすべて。
// This method retrieves from the database all users with a certain name
public List findByFirstName(String firstName) throws SQLException {
// Connect to the database
Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
// Create a parameterized query.
String query = "SELECT * FROM USERS WHERE firstName = ?";
// Create a prepared statement with the parameterized query
PreparedStatement statement = connection.prepareStatement(query);
// Pass the parameter's value
statement.setString(1, firstName);
// Execute the query
ResultSet result = statement.executeQuery(query);
// Use mapToUsers to convert the ResultSet into a collection of users.
return mapToUsers(result);
}
private List mapToUsers(ResultSet resultSet) {
// Converts to a collection of users
}
このようにして脆弱性は回避されます。この記事をさらに詳しく知りたい方のために、ここに素晴らしい例を示します。この脆弱性を理解するとき、どうすればわかりますか? 以下の漫画のジョークが理解できた方は、おそらくこの脆弱性が何なのかを明確に理解しているでしょう :D
3. 依存関係をスキャンして最新の状態に保つ
どういう意味ですか?依存関係が何かわからない場合は、説明します。依存関係は、他の人のソリューションを再利用するために自動ビルド システム (Maven、Gradle、Ant) を使用してプロジェクトに接続されたコードを含む JAR アーカイブです。たとえば、ランタイムでゲッターやセッターなどを生成するProject Lombokなどです。大規模なアプリケーションには、非常に多くの依存関係が存在する可能性があります。推移的なものもあります (つまり、それぞれの依存関係が独自の依存関係を持つ可能性があるなど)。その結果、オープンソースの依存関係は定期的に使用されており、多くのクライアントが依存関係によって問題を引き起こす可能性があるため、攻撃者はオープンソースの依存関係にますます注目するようになりました。依存関係ツリー全体 (はい、ツリーのように見えます) に既知の脆弱性がないことを確認することが重要です。これを行うにはいくつかの方法があります。依存関係の監視に Snyk を使用する
Snyk はすべてのプロジェクトの依存関係をチェックし、既知の脆弱性にフラグを立てます。Snyk に登録し、GitHub 経由でプロジェクトをインポートできます。また、上の図からわかるように、新しいバージョンで脆弱性が修正された場合、Snyk は修正を提供し、プル リクエストを作成します。オープンソース プロジェクトでは無料で使用できます。プロジェクトは、週に 1 回、月に 1 回など、定期的な間隔でスキャンされます。すべてのパブリック リポジトリを Snyk スキャンに登録して追加しました (これらはすでに全員に公開されているため、これに関して危険なことは何もありません)。次に、Snyk はスキャン結果を表示しました:そして、しばらくして、Snyk ボットは依存関係を更新する必要があるプロジェクトでいくつかのプル リクエストを準備しました:また、:これは、脆弱性を発見し、新しいバージョンの更新を監視するための優れたツールです。GitHub セキュリティ ラボを使用する
GitHub に取り組んでいる人は誰でも、その組み込みツールを利用できます。このアプローチの詳細については、「 Announcing 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 クラスを使用する必要があります。どういう意味ですか?- データベースを操作する場合は、1 種類の POJO (エンティティ) を使用します。
- ビジネス ロジックを操作する場合は、エンティティをモデルに変換します。
- 外部と連携して HTTP リクエストを送信する場合は、別のエンティティ (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 = "Elvis lives!";
String aad = "Buddy Holly";
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 アカウントで私をフォローすることをお勧めします。そこでは、私が研究し、仕事に応用しているさまざまなテクノロジーを使用した私の作品を投稿しています。役立つリンク
- Guru99: SQL インジェクションのチュートリアル
- Oracle: Java セキュリティ リソース センター
- Oracle: Java SE の安全なコーディング ガイドライン
- Baeldung: Java セキュリティの基本
- 中: Java セキュリティを強化するための 10 のヒント
- Snyk: Java セキュリティの 10 のベスト プラクティス
- GitHub: GitHub Security Lab の発表: 世界中のコードを一緒に保護する
GO TO FULL VERSION