Dalam aplikasi server, salah satu indikator terpenting adalah keamanan. Ini adalah salah satu jenis Persyaratan Non-Fungsional . Keamanan 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:- Pengetikan yang kuat. Java adalah bahasa yang diketik secara statis yang menyediakan kemampuan untuk mendeteksi kesalahan tipe pada waktu proses.
- Pengubah akses. Berkat mereka, kita dapat mengonfigurasi akses ke kelas, metode, dan bidang kelas sesuai kebutuhan.
- Manajemen memori otomatis. Untuk tujuan ini, kami (Javaist ;)) memiliki Pengumpul Sampah, yang membebaskan Anda dari konfigurasi manual. Ya, terkadang masalah muncul.
- Pemeriksaan bytecode: Java mengkompilasi menjadi bytecode, yang diperiksa oleh runtime sebelum menjalankannya.
- 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.
- 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.
- 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.
- Dan seterusnya…
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:
- Anda dapat memberikan apa yang diharapkan - nama pengguna. Kemudian database akan mengembalikan semua pengguna dengan nama tersebut.
- Anda dapat meneruskan string kosong: maka semua pengguna akan dikembalikan.
- 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.
// Метод достает из базы данных всех пользователей с определенным именем
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
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. Selain 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: Dan setelah beberapa saat, Snyk-bot menyiapkan beberapa Permintaan Tarik dalam proyek yang dependensinya perlu diperbarui: Dan ini satu lagi: Jadi 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:4. Tangani data sensitif dengan hati-hati
Dalam 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 metodetoString()
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.Exclude
untuk 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 @JsonIgnore
dan @JsonIgnoreProperties
untuk mencegah kolom tertentu dibuat serial atau deserialisasi. Secara umum, Anda perlu menggunakan kelas POJO yang berbeda untuk tempat berbeda. Apa artinya?
- Untuk bekerja dengan database, gunakan hanya POJO - Entitas.
- Untuk bekerja dengan logika bisnis, transfer Entitas ke Model.
- Untuk bekerja dengan dunia luar dan mengirim permintaan http, gunakan entitas ketiga - DTO.
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.SCryptPasswordEncoder
Anda 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:- Habr: Injeksi SQL untuk Pemula
- Oracle: Pusat Sumber Daya Keamanan Java
- Oracle: Pedoman Pengkodean Aman untuk Java SE
- Baeldung: Dasar-dasar Keamanan Java
- Medium: 10 tips untuk meningkatkan keamanan Java Anda
- Snyk: 10 praktik terbaik keamanan java
- JR: Pengumuman Lab Keamanan GitHub: melindungi semua kode Anda secara bersamaan
GO TO FULL VERSION