Semakin banyak saya belajar tentang kekuatan java.lang.reflect.AccessibleObject.setAccessible
, semakin heran saya dengan apa yang bisa dilakukannya. Ini diadaptasi dari jawaban saya atas pertanyaan ( Menggunakan refleksi untuk mengubah File.separatorChar akhir statis untuk pengujian unit ).
import java.lang.reflect.*;
public class EverythingIsTrue {
static void setFinalStatic(Field field, Object newValue) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
}
public static void main(String args[]) throws Exception {
setFinalStatic(Boolean.class.getField("FALSE"), true);
System.out.format("Everything is %s", false); // "Everything is true"
}
}
Anda dapat melakukan hal-hal yang sangat memalukan:
public class UltimateAnswerToEverything {
static Integer[] ultimateAnswer() {
Integer[] ret = new Integer[256];
java.util.Arrays.fill(ret, 42);
return ret;
}
public static void main(String args[]) throws Exception {
EverythingIsTrue.setFinalStatic(
Class.forName("java.lang.Integer$IntegerCache")
.getDeclaredField("cache"),
ultimateAnswer()
);
System.out.format("6 * 9 = %d", 6 * 9); // "6 * 9 = 42"
}
}
Agaknya para perancang API menyadari betapa setAccessible
bisa disalahgunakannya , tetapi harus mengakui bahwa itu memiliki penggunaan yang sah untuk menyediakannya. Jadi pertanyaan saya adalah:
- Apa kegunaan yang benar-benar sah
setAccessible
?- Mungkinkah Java telah dirancang untuk TIDAK memiliki kebutuhan ini sejak awal?
- Apa konsekuensi negatif (jika ada) dari rancangan seperti itu?
- Bisakah Anda membatasi
setAccessible
untuk penggunaan yang sah saja?- Apakah hanya melalui
SecurityManager
?- Bagaimana cara kerjanya? Whitelist / blacklist, granularity, dll?
- Apakah umum untuk mengkonfigurasinya dalam aplikasi Anda?
- Dapatkah saya menulis kelas saya menjadi
setAccessible
-proof terlepas dariSecurityManager
konfigurasinya?- Atau apakah saya tergantung pada belas kasihan siapa pun yang mengelola konfigurasi?
- Apakah hanya melalui
Saya kira satu pertanyaan penting lagi adalah: APAKAH SAYA PERLU KHAWATIR TENTANG INI ???
Tak satu pun dari kelas saya yang memiliki privasi yang dapat diberlakukan apa pun. Pola tunggal (dengan mengesampingkan keraguan tentang manfaatnya) sekarang tidak mungkin untuk diterapkan. Seperti yang ditunjukkan cuplikan saya di atas, bahkan beberapa asumsi dasar tentang cara kerja fundamental Java bahkan tidak dijamin.
APAKAH MASALAH INI TIDAK NYATA ???
Oke, saya baru saja mengonfirmasi: terima kasih setAccessible
, string Java TIDAK dapat diubah.
import java.lang.reflect.*;
public class MutableStrings {
static void mutate(String s) throws Exception {
Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
value.set(s, s.toUpperCase().toCharArray());
}
public static void main(String args[]) throws Exception {
final String s = "Hello world!";
System.out.println(s); // "Hello world!"
mutate(s);
System.out.println(s); // "HELLO WORLD!"
}
}
Apakah saya satu-satunya yang berpikir bahwa ini adalah masalah yang BESAR?
sumber
Jawaban:
APAKAH SAYA PERLU KHAWATIR TENTANG INI ???
Itu sepenuhnya tergantung pada jenis program yang Anda tulis dan untuk jenis arsitektur apa.
Jika Anda mendistribusikan komponen perangkat lunak yang disebut foo.jar kepada orang-orang di dunia, Anda sepenuhnya bergantung pada belas kasihan mereka. Mereka dapat mengubah definisi kelas di dalam .jar Anda (melalui rekayasa balik atau manipulasi bytecode langsung). Mereka dapat menjalankan kode Anda di JVM mereka sendiri, dll. Dalam hal ini, mengkhawatirkan tidak ada gunanya bagi Anda.
Jika Anda menulis aplikasi web yang hanya berinteraksi dengan orang dan sistem melalui HTTP dan Anda mengontrol server aplikasi, itu juga bukan masalah. Tentu rekan pembuat kode di perusahaan Anda dapat membuat kode yang merusak pola tunggal Anda, tetapi hanya jika mereka benar-benar menginginkannya.
Jika pekerjaan masa depan Anda adalah menulis kode di Sun Microsystems / Oracle dan Anda ditugaskan untuk menulis kode untuk inti Java atau komponen tepercaya lainnya, itu adalah sesuatu yang harus Anda ketahui. Namun, mengkhawatirkan hanya akan membuat Anda kehilangan rambut. Bagaimanapun mereka mungkin akan membuat Anda membaca Pedoman Pengodean Aman bersama dengan dokumentasi internal.
Jika Anda akan menulis applet Java, kerangka keamanan adalah sesuatu yang harus Anda waspadai. Anda akan menemukan bahwa applet tak bertanda tangan yang mencoba memanggil setAccessible hanya akan menghasilkan SecurityException.
setAccessible bukanlah satu-satunya hal yang melakukan pemeriksaan integritas konvensional. Ada kelas Java inti non-API yang disebut sun.misc.Unsafe yang dapat melakukan apa saja yang diinginkannya, termasuk mengakses memori secara langsung. Kode asli (JNI) juga dapat digunakan untuk mengontrol jenis ini.
Dalam lingkungan sandbox (misalnya Java Applets, JavaFX), setiap kelas memiliki sekumpulan izin dan akses ke Unsafe, setAccessible dan implementasi asli yang menentukan dikontrol oleh SecurityManager.
"Pengubah akses Java tidak dimaksudkan sebagai mekanisme keamanan."
Itu sangat tergantung di mana kode Java dijalankan. Kelas inti Java memang menggunakan pengubah akses sebagai mekanisme keamanan untuk menerapkan sandbox.
Apa kegunaan yang benar-benar sah untuk setAccessible?
Kelas inti Java menggunakannya sebagai cara mudah untuk mengakses hal-hal yang harus dirahasiakan untuk alasan keamanan. Sebagai contoh, kerangka kerja Serialisasi Java menggunakannya untuk memanggil konstruktor objek pribadi saat deserialisasi objek. Seseorang menyebutkan System.setErr, dan itu akan menjadi contoh yang baik, tetapi anehnya metode kelas Sistem setOut / setErr / setIn semua menggunakan kode asli untuk menetapkan nilai bidang akhir.
Penggunaan sah lainnya yang jelas adalah kerangka kerja (ketekunan, kerangka web, injeksi) yang perlu mengintip ke dalam objek.
Debugger, menurut saya, tidak termasuk dalam kategori ini, karena mereka biasanya tidak berjalan dalam proses JVM yang sama, melainkan antarmuka dengan JVM menggunakan cara lain (JPDA).
Mungkinkah Java telah dirancang untuk TIDAK memiliki kebutuhan ini sejak awal?
Itu pertanyaan yang cukup dalam untuk dijawab dengan baik. Saya membayangkan ya, tetapi Anda perlu menambahkan beberapa mekanisme lain yang mungkin tidak terlalu disukai.
Dapatkah Anda membatasi setAccessible hanya untuk penggunaan yang sah?
Pembatasan OOTB paling mudah yang dapat Anda terapkan adalah memiliki SecurityManager dan mengizinkan setAccessible hanya untuk kode yang berasal dari sumber tertentu. Inilah yang sudah dilakukan Java - kelas Java standar yang berasal dari JAVA_HOME Anda diizinkan untuk melakukan setAccessible, sementara kelas applet yang tidak ditandatangani dari foo.com tidak diizinkan untuk melakukan setAccessible. Seperti yang telah dikatakan sebelumnya, izin ini adalah biner, dalam arti seseorang memilikinya atau tidak. Tidak ada cara yang jelas untuk mengizinkan setAccessible mengubah bidang / metode tertentu sambil melarang yang lain. Dengan menggunakan SecurityManager Anda bisa, bagaimanapun, melarang kelas mereferensikan paket tertentu sepenuhnya, dengan atau tanpa refleksi.
Dapatkah saya menulis kelas saya menjadi setAccessible-proof terlepas dari konfigurasi SecurityManager? ... Atau apakah saya bergantung pada siapa pun yang mengelola konfigurasi?
Anda tidak bisa dan Anda pasti begitu.
sumber
Pengujian unit, internal JVM (misalnya implementasi
System.setError(...)
), dan sebagainya.Banyak hal yang tidak bisa diterapkan. Misalnya, berbagai ketekunan Java, serialisasi, dan injeksi ketergantungan bergantung pada refleksi. Dan hampir semua hal yang bergantung pada konvensi JavaBeans saat runtime.
Iya.
Itu tergantung pada izinnya, tapi saya percaya bahwa izin untuk menggunakan
setAccessible
adalah biner. Jika menginginkan perincian, Anda harus menggunakan pemuat kelas yang berbeda dengan pengelola keamanan yang berbeda untuk kelas yang ingin Anda batasi. Saya kira Anda dapat menerapkan pengelola keamanan khusus yang menerapkan logika yang lebih halus.Tidak.
Tidak, Anda tidak bisa, dan ya Anda.
Alternatif lainnya adalah "menerapkan" ini melalui alat analisis kode sumber; misalnya adat istiadat
pmd
ataufindbugs
aturan. Atau tinjauan kode selektif dari kode yang diidentifikasi oleh (katakanlah)grep setAccessible ...
.Menanggapi tindak lanjut tersebut
Jika itu membuat Anda khawatir, maka saya rasa Anda perlu khawatir. Tetapi sebenarnya Anda tidak boleh mencoba memaksa programmer lain untuk menghormati keputusan desain Anda. Jika orang-orang cukup bodoh untuk menggunakan refleksi untuk secara serampangan membuat beberapa contoh lajang Anda (misalnya), mereka dapat hidup dengan konsekuensinya.
Di sisi lain, jika yang Anda maksud adalah "privasi" yang mencakup arti melindungi informasi sensitif dari pengungkapan, Anda menyalak pohon yang salah. Cara untuk melindungi data sensitif dalam aplikasi Java tidak dengan mengizinkan kode tidak tepercaya ke dalam sandbox keamanan yang menangani data sensitif. Pengubah akses Java tidak dimaksudkan sebagai mekanisme keamanan.
Mungkin bukan satu - satunya :-). Tapi IMO, ini bukan masalah. Ini adalah fakta yang diterima bahwa kode tidak tepercaya harus dijalankan di kotak pasir. Jika Anda memiliki kode tepercaya / pemrogram tepercaya yang melakukan hal-hal seperti ini, masalah Anda lebih buruk daripada String yang dapat berubah secara tak terduga. (Pikirkan bom logika, eksfiltrasi data melalui saluran rahasia , dan sebagainya)
Ada cara untuk menangani (atau mengurangi) masalah "aktor jahat" dalam tim pengembangan atau operasi Anda. Tapi mereka mahal dan membatasi ... dan berlebihan untuk sebagian besar kasus penggunaan.
sumber
SecurityManager
untuk ini, karena yang Anda dapatkan hanyalah pemeriksaan izin - semua yang benar-benar dapat Anda lakukan adalah melihat acc dan mungkin memeriksa tumpukan untuk memeriksa utas saat ini).grep java.lang.reflect.
Refleksi memang ortogonal terhadap keselamatan / keamanan dalam perspektif ini.
Bagaimana kita bisa membatasi refleksi?
Java memiliki manajer keamanan dan
ClassLoader
sebagai dasar dari model keamanannya. Dalam kasus Anda, saya rasa Anda perlu melihatjava.lang.reflect.ReflectPermission
.Tetapi ini tidak sepenuhnya menyelesaikan masalah refleksi. Kemampuan reflektif yang tersedia harus tunduk pada skema otorisasi terperinci yang tidak terjadi sekarang. Misalnya untuk mengizinkan kerangka kerja tertentu menggunakan refleksi (misalnya Hibernate), tetapi tidak ada sisa kode Anda. Atau untuk mengizinkan program untuk mencerminkan hanya dengan cara baca-saja, untuk tujuan debugging.
Salah satu pendekatan yang mungkin menjadi arus utama di masa depan adalah penggunaan apa yang disebut mirror untuk memisahkan kemampuan reflektif dari kelas. Lihat Cermin: Prinsip Desain untuk Fasilitas Meta-level . Namun ada berbagai penelitian lain yang menangani masalah ini. Tetapi saya setuju bahwa masalahnya lebih parah untuk bahasa dinamis daripada bahasa statis.
Haruskah kita khawatir dengan kekuatan super yang diberikan refleksi kepada kita? Iya dan tidak.
Ya dalam artian bahwa platform Java seharusnya diamankan dengan
Classloader
dan pengelola keamanan. Kemampuan untuk mengacaukan refleksi bisa dilihat sebagai sebuah terobosan.Tidak dalam arti bahwa kebanyakan sistem tidak sepenuhnya aman. Banyak kelas sering kali dapat disubkelas dan Anda berpotensi telah menyalahgunakan sistem hanya dengan itu. Tentu saja kelas bisa dibuat
final
, atau disegel sehingga tidak bisa disubkelas di toples lain. Tetapi hanya sedikit kelas yang diamankan dengan benar (misalnya String) menurut ini.Lihat jawaban ini tentang kelas akhir untuk penjelasan yang bagus. Lihat juga blog dari Sami Koivu untuk mengetahui lebih banyak tentang peretasan java seputar keamanan.
Model keamanan Java dapat dianggap tidak cukup untuk beberapa hal. Beberapa bahasa seperti NewSpeak mengambil pendekatan yang lebih radikal terhadap modularitas, di mana Anda memiliki akses hanya ke apa yang secara eksplisit diberikan kepada Anda oleh inversi ketergantungan (secara default tidak ada).
Penting juga untuk dicatat bahwa keamanan itu relatif . Pada tingkat bahasa, Anda dapat misalnya tidak mencegah bentuk modul memakan 100% CPU atau menghabiskan semua memori hingga
OutOfMemoryException
. Kekhawatiran seperti itu perlu ditangani dengan cara lain. Kita mungkin akan melihat di masa depan Java diperpanjang dengan kuota pemanfaatan sumber daya, tetapi ini bukan untuk besok :)Saya dapat mengembangkan lebih banyak tentang subjek ini, tetapi saya pikir saya telah menyampaikan maksud saya.
sumber