JavaRush /Java Blog /Random-ID /Keamanan di Java: praktik terbaik

Keamanan di Java: praktik terbaik

Dipublikasikan di grup Random-ID
Dalam aplikasi server, salah satu indikator terpenting adalah keamanan. Ini adalah salah satu jenis Persyaratan Non-Fungsional . Keamanan di Java: praktik terbaik - 1Keamanan memiliki banyak komponen. Tentu saja, untuk sepenuhnya mencakup semua prinsip dan tindakan perlindungan yang diketahui, Anda perlu menulis lebih dari satu artikel, jadi mari kita fokus pada hal yang paling penting. Seseorang yang berpengalaman dalam topik ini, akan dapat mengatur semua proses dan memastikan bahwa proses tersebut tidak menciptakan lubang keamanan baru, akan dibutuhkan di tim mana pun. Tentu saja, Anda tidak boleh berpikir bahwa jika Anda mengikuti praktik ini, aplikasi akan sepenuhnya aman. TIDAK! Tapi pasti akan lebih aman jika bersama mereka. Pergi.

1. Memberikan keamanan pada tingkat bahasa Java

Pertama-tama, keamanan di Java dimulai pada tingkat fitur bahasa. Inilah yang akan kita lakukan jika tidak ada pengubah akses?... Anarki, apalagi. Bahasa pemrograman membantu kita menulis kode aman dan juga memanfaatkan banyak fitur keamanan implisit:
  1. Pengetikan yang kuat. Java adalah bahasa yang diketik secara statis yang menyediakan kemampuan untuk mendeteksi kesalahan tipe pada waktu proses.
  2. Pengubah akses. Berkat mereka, kita dapat mengonfigurasi akses ke kelas, metode, dan bidang kelas sesuai kebutuhan.
  3. Manajemen memori otomatis. Untuk tujuan ini, kami (Javaist ;)) memiliki Pengumpul Sampah, yang membebaskan Anda dari konfigurasi manual. Ya, terkadang masalah muncul.
  4. Pemeriksaan bytecode: Java mengkompilasi menjadi bytecode, yang diperiksa oleh runtime sebelum menjalankannya.
Antara lain, ada rekomendasi dari Oracle mengenai keamanan. Tentu saja, ini tidak ditulis dengan “gaya tinggi” dan Anda mungkin tertidur beberapa kali saat membacanya, tapi itu sepadan. Dokumen yang sangat penting adalah Pedoman Pengkodean Aman untuk Java SE , yang memberikan tip tentang cara menulis kode aman. Dokumen ini berisi banyak informasi berguna. Jika memungkinkan, itu pasti layak untuk dibaca. Untuk menggugah minat terhadap materi ini, berikut beberapa tips menarik:
  1. Hindari membuat serial kelas yang sensitif terhadap keamanan. Dalam hal ini, Anda bisa mendapatkan antarmuka kelas dari file serial, belum lagi data yang sedang diserialkan.
  2. Cobalah untuk menghindari kelas data yang bisa berubah. Ini memberikan semua manfaat dari kelas yang tidak dapat diubah (misalnya keamanan thread). Jika ada objek yang bisa berubah, hal ini dapat menyebabkan perilaku yang tidak terduga.
  3. Buat salinan objek yang bisa diubah kembali. Jika suatu metode mengembalikan referensi ke objek internal yang dapat diubah, maka kode klien dapat mengubah keadaan internal objek tersebut.
  4. Dan seterusnya…
Secara umum, Secure Coding Guidelines for Java SE berisi kumpulan tips dan trik cara menulis kode di Java dengan benar dan aman.

2. Hilangkan kerentanan injeksi SQL

Kerentanan yang unik. Keunikannya terletak pada kenyataan bahwa ini adalah salah satu kerentanan yang paling terkenal dan paling umum. Jika Anda tidak tertarik dengan masalah keamanan, maka Anda tidak akan mengetahuinya. Apa itu injeksi SQL? Ini adalah serangan terhadap database dengan memasukkan kode SQL tambahan di tempat yang tidak diharapkan. Katakanlah kita memiliki metode yang mengambil beberapa parameter untuk menanyakan database. Misalnya, nama pengguna. Kode dengan kerentanan akan terlihat seperti ini:
// Метод достает из базы данных всех пользователей с определенным именем
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) {
   //переводит в коллекцию юзеров
}
Dalam contoh ini, query sql disiapkan terlebih dahulu dalam baris terpisah. Tampaknya, apa masalahnya, bukan? Mungkin masalahnya adalah lebih baik menggunakan String.format? TIDAK? Lalu bagaimana? Mari kita tempatkan diri kita sebagai penguji dan memikirkan apa yang bisa disampaikan dalam nilai tersebut firstName. Misalnya:
  1. Anda dapat memberikan apa yang diharapkan - nama pengguna. Kemudian database akan mengembalikan semua pengguna dengan nama tersebut.
  2. Anda dapat meneruskan string kosong: maka semua pengguna akan dikembalikan.
  3. Atau Anda dapat meneruskan yang berikut ini: “''; DROP TABLE PENGGUNA;”. Dan di sini akan timbul masalah yang lebih besar. Kueri ini akan menghapus tabel dari database. Dengan semua datanya. SETIAP ORANG.
Bisakah Anda bayangkan masalah apa yang bisa ditimbulkannya? Kemudian Anda dapat menulis apa pun yang Anda inginkan. Anda dapat mengubah nama semua pengguna, Anda dapat menghapus alamat mereka. Ruang lingkup sabotase sangat luas. Untuk menghindari hal ini, Anda harus berhenti memasukkan kueri yang sudah jadi dan membangunnya menggunakan parameter. Ini harus menjadi satu-satunya cara untuk menanyakan database. Dengan cara ini Anda dapat menghilangkan kerentanan ini. Contoh:
// Метод достает из базы данных всех пользователей с определенным именем
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) {
   //переводим в коллекцию юзеров
}
Dengan cara ini, kerentanan ini dapat dihindari. Bagi mereka yang ingin mendalami masalah ini lebih dalam dari artikel ini, berikut adalah contoh yang bagus . Bagaimana Anda tahu jika Anda memahami bagian ini? Jika lelucon di bawah ini menjadi jelas, maka ini adalah tanda pasti bahwa inti dari kerentanan sudah jelas :D Keamanan di Java: praktik terbaik - 2

3. Pindai dan terus perbarui dependensi

Apa artinya? Bagi yang belum tahu apa itu ketergantungan, saya akan jelaskan: ini adalah arsip jar dengan kode yang terhubung ke proyek menggunakan sistem pembangunan otomatis (Maven, Gradle, Ant) untuk menggunakan kembali solusi orang lain. Misalnya, Project Lombok , yang menghasilkan getter, setter, dll. untuk kita saat runtime. Dan jika kita berbicara tentang aplikasi besar, mereka menggunakan banyak dependensi berbeda. Ada yang bersifat transitif (artinya setiap dependensi dapat memiliki dependensinya sendiri, dan seterusnya). Oleh karena itu, penyerang semakin memperhatikan dependensi sumber terbuka, karena dependensi tersebut sering digunakan dan dapat menyebabkan masalah bagi banyak klien. Penting untuk memastikan bahwa tidak ada kerentanan yang diketahui di seluruh pohon ketergantungan (seperti yang terlihat). Dan ada beberapa cara untuk melakukan ini.

Gunakan Snyk untuk pemantauan

Alat Snyk memeriksa semua dependensi proyek dan menandai kerentanan yang diketahui. Di sana Anda dapat mendaftar dan mengimpor proyek Anda melalui GitHub, misalnya. Keamanan di Java: praktik terbaik - 3Selain itu, seperti yang Anda lihat dari gambar di atas, jika versi yang lebih baru memiliki solusi untuk kerentanan ini, Snyk akan menawarkan untuk melakukannya dan membuat Permintaan Tarik. Ini dapat digunakan secara gratis untuk proyek sumber terbuka. Proyek akan dipindai pada frekuensi tertentu: seminggu sekali, sebulan sekali. Saya mendaftarkan dan menambahkan semua repositori publik saya ke pemindaian Snyk (tidak ada yang berbahaya dalam hal ini: mereka sudah terbuka untuk semua orang). Selanjutnya, Snyk menunjukkan hasil pemindaian: Keamanan di Java: praktik terbaik - 4Dan setelah beberapa saat, Snyk-bot menyiapkan beberapa Permintaan Tarik dalam proyek yang dependensinya perlu diperbarui: Keamanan di Java: praktik terbaik - 5Dan ini satu lagi: Keamanan di Java: praktik terbaik - 6Jadi ini adalah alat yang sangat baik untuk mencari kerentanan dan memantau pembaruan versi baru.

Gunakan Lab Keamanan GitHub

Mereka yang bekerja di GitHub juga dapat memanfaatkan alat bawaannya. Anda dapat membaca lebih lanjut tentang pendekatan ini dalam terjemahan saya dari blog mereka Pengumuman GitHub Security Lab . Alat ini tentu saja lebih sederhana daripada Snyk, tetapi Anda tidak boleh mengabaikannya. Selain itu, jumlah kerentanan yang diketahui akan terus bertambah, sehingga Snyk dan GitHub Security Lab akan diperluas dan ditingkatkan.

Aktifkan Sonatype DepShield

Jika Anda menggunakan GitHub untuk menyimpan repositori, Anda dapat menambahkan salah satu aplikasi ke proyek Anda dari MarketPlace - Sonatype DepShield. Dengan bantuannya, Anda juga dapat memindai proyek untuk mencari ketergantungan. Selain itu, jika menemukan sesuatu, Masalah GitHub akan dibuat dengan deskripsi yang sesuai, seperti yang ditunjukkan di bawah ini: Keamanan di Java: praktik terbaik - 7

4. Tangani data sensitif dengan hati-hati

Keamanan di Java: praktik terbaik - 8Dalam percakapan bahasa Inggris, frasa “data sensitif” lebih umum. Pengungkapan informasi pribadi, nomor kartu kredit, dan informasi pribadi klien lainnya dapat menyebabkan kerugian yang tidak dapat diperbaiki. Pertama-tama, Anda perlu mencermati desain aplikasi dan menentukan apakah ada data yang benar-benar diperlukan. Mungkin beberapa di antaranya tidak diperlukan, tetapi ditambahkan untuk masa depan yang belum tiba dan kecil kemungkinannya akan datang. Selain itu, selama pencatatan proyek, data tersebut mungkin bocor. Cara sederhana untuk mencegah data sensitif masuk ke log Anda adalah dengan membersihkan metode toString()entitas domain (seperti Pengguna, Siswa, Guru, dan sebagainya). Ini akan mencegah bidang sensitif dicetak secara tidak sengaja. Jika Anda menggunakan Lombok untuk menghasilkan suatu metode toString(), Anda dapat menggunakan anotasi @ToString.Excludeuntuk mencegah field tersebut digunakan dalam output melalui metode tersebut toString(). Selain itu, berhati-hatilah saat berbagi data dengan dunia luar. Misalnya, ada titik akhir http yang menampilkan nama semua pengguna. Tidak perlu menampilkan ID unik internal pengguna. Mengapa? Karena dengan menggunakannya, penyerang dapat memperoleh informasi lain yang lebih rahasia tentang setiap pengguna. Misalnya, jika Anda menggunakan Jackson untuk membuat serialisasi dan deserialisasi POJO menjadi JSON , Anda dapat menggunakan anotasi @JsonIgnoredan @JsonIgnorePropertiesuntuk mencegah kolom tertentu dibuat serial atau deserialisasi. Secara umum, Anda perlu menggunakan kelas POJO yang berbeda untuk tempat berbeda. Apa artinya?
  1. Untuk bekerja dengan database, gunakan hanya POJO - Entitas.
  2. Untuk bekerja dengan logika bisnis, transfer Entitas ke Model.
  3. Untuk bekerja dengan dunia luar dan mengirim permintaan http, gunakan entitas ketiga - DTO.
Dengan cara ini Anda dapat dengan jelas menentukan bidang mana yang akan terlihat dari luar dan mana yang tidak.

Gunakan algoritma enkripsi dan hashing yang kuat

Data rahasia pelanggan harus disimpan dengan aman. Untuk melakukan ini, Anda perlu menggunakan enkripsi. Tergantung pada tugasnya, Anda perlu memutuskan jenis enkripsi apa yang akan digunakan. Selain itu, enkripsi yang lebih kuat membutuhkan lebih banyak waktu, jadi sekali lagi Anda perlu mempertimbangkan seberapa besar kebutuhan akan enkripsi tersebut sesuai dengan waktu yang dihabiskan untuk itu. Tentu saja, Anda dapat menulis sendiri algoritmanya. Tapi ini tidak perlu. Anda dapat memanfaatkan solusi yang ada di bidang ini. Misalnya, Google Tink :
<!-- 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>
Mari kita lihat cara menggunakannya, dengan menggunakan contoh cara mengenkripsi satu cara dan cara lainnya:
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));
}

Enkripsi kata sandi

Untuk tugas ini, cara paling aman adalah menggunakan enkripsi asimetris. Mengapa? Karena aplikasinya sebenarnya tidak perlu mendekripsi kembali passwordnya. Ini adalah pendekatan umum. Kenyataannya, ketika pengguna memasukkan kata sandi, sistem mengenkripsinya dan membandingkannya dengan apa yang ada di brankas kata sandi. Enkripsi dilakukan dengan menggunakan cara yang sama, sehingga Anda dapat mengharapkannya cocok (jika Anda memasukkan kata sandi yang benar ;), tentu saja). BCrypt dan SCrypt cocok untuk tujuan ini. Keduanya merupakan fungsi satu arah (hash kriptografi) dengan algoritma komputasi kompleks yang memakan banyak waktu. Inilah yang Anda butuhkan, karena menguraikannya secara langsung akan memakan waktu lama. Misalnya, Spring Security mendukung berbagai algoritma. SCryptPasswordEncoderAnda juga bisa menggunakan BCryptPasswordEncoder. Algoritme enkripsi yang kuat saat ini mungkin akan lemah di tahun depan. Sebagai hasilnya, kami menyimpulkan bahwa perlu untuk memeriksa algoritma yang digunakan dan memperbarui perpustakaan dengan algoritma.

Alih-alih keluaran

Hari ini kita berbicara tentang keselamatan dan, tentu saja, banyak hal yang tertinggal. Saya baru saja membukakan pintu ke dunia baru untuk Anda: dunia yang menjalani kehidupannya sendiri. Dalam hal keamanan sama halnya dengan politik: jika Anda tidak terlibat dalam politik, politik akan melibatkan Anda. Secara tradisional, saya menyarankan untuk berlangganan akun Github saya . Di sana saya memposting karya saya tentang berbagai teknologi yang saya pelajari dan terapkan di tempat kerja.

tautan yang bermanfaat

Ya, hampir semua artikel di situs ini ditulis dalam bahasa Inggris. Suka atau tidak suka, bahasa Inggris adalah bahasa komunikasi programmer. Semua artikel, buku, dan majalah terbaru tentang pemrograman ditulis dalam bahasa Inggris. Itu sebabnya tautan saya ke rekomendasi sebagian besar dalam bahasa Inggris:
  1. Habr: Injeksi SQL untuk Pemula
  2. Oracle: Pusat Sumber Daya Keamanan Java
  3. Oracle: Pedoman Pengkodean Aman untuk Java SE
  4. Baeldung: Dasar-dasar Keamanan Java
  5. Medium: 10 tips untuk meningkatkan keamanan Java Anda
  6. Snyk: 10 praktik terbaik keamanan java
  7. JR: Pengumuman Lab Keamanan GitHub: melindungi semua kode Anda secara bersamaan
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION