Bagaimana cara membatasi setAccessible hanya untuk penggunaan yang “sah”?

100

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 setAccessiblebisa 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 setAccessibleuntuk 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 dari SecurityManagerkonfigurasinya?
      • Atau apakah saya tergantung pada belas kasihan siapa pun yang mengelola konfigurasi?

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?

poligenelubricants
sumber
30
Anda harus melihat apa yang dapat mereka lakukan di C ++! (Oh, dan mungkin C #.)
Tom Hawtin - tackline

Jawaban:

105

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.

Sami Koivu
sumber
"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. " Apakah masih demikian? Adakah saran untuk membuat perangkat lunak lebih keras (Semoga secara signifikan)? Sulit untuk membuang aplikasi desktop java dari jendela karena ini.
Sero
@naaz Saya harus mengatakan tidak ada yang berubah. Hanya saja arsitekturnya merugikan Anda. Perangkat lunak apa pun yang berjalan di mesin saya yang saya kontrol sepenuhnya, dapat saya ubah. Penangkal terkuat mungkin yang legal, tetapi saya tidak membayangkan itu akan berhasil untuk semua skenario. Saya pikir binari Java adalah yang paling mudah untuk dibalik, tetapi orang-orang dengan pengalaman akan merusak apa pun. Kebingungan membantu membuat perubahan besar menjadi sulit, tetapi boolean apa pun (seperti pemeriksaan hak cipta) masih sepele untuk dilewati. Anda dapat mencoba meletakkan bagian-bagian penting di server.
Sami Koivu
Bagaimana denuvo?
Sero
15
  • Apa kegunaan yang benar-benar sah setAccessible?

Pengujian unit, internal JVM (misalnya implementasi System.setError(...)), dan sebagainya.

  • Mungkinkah Java telah dirancang untuk TIDAK memiliki kebutuhan ini sejak awal?
  • Apa konsekuensi negatif (jika ada) dari rancangan seperti itu?

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.

  • Bisakah Anda membatasi setAccessibleuntuk penggunaan yang sah saja?
  • Apakah hanya melalui SecurityManager?

Iya.

  • Bagaimana cara kerjanya? Whitelist / blacklist, granularity, dll?

Itu tergantung pada izinnya, tapi saya percaya bahwa izin untuk menggunakan setAccessibleadalah 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.

  • Apakah umum untuk mengkonfigurasinya dalam aplikasi Anda?

Tidak.

  • Dapatkah saya menulis kelas saya menjadi setAccessible-proof terlepas dari SecurityManagerkonfigurasinya?
    • Atau apakah saya tergantung pada belas kasihan siapa pun yang mengelola konfigurasi?

Tidak, Anda tidak bisa, dan ya Anda.

Alternatif lainnya adalah "menerapkan" ini melalui alat analisis kode sumber; misalnya adat istiadat pmdatau findbugsaturan. Atau tinjauan kode selektif dari kode yang diidentifikasi oleh (katakanlah) grep setAccessible ....

Menanggapi tindak lanjut tersebut

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.

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.

<Contoh string> - Apakah saya satu-satunya yang menganggap ini masalah BESAR?

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.

Stephen C
sumber
1
Juga berguna untuk hal-hal seperti implementasi persistensi. Refleksi tidak terlalu berguna untuk debugger (meskipun awalnya dimaksudkan untuk itu). Anda tidak dapat mengubah (subclass) yang berguna SecurityManageruntuk 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.
Tom Hawtin - tackline
@Stephen C: +1 sudah, tapi saya juga menghargai masukan Anda untuk satu pertanyaan lagi yang baru saja saya tambahkan. Terima kasih sebelumnya.
poligenelubricants
Perhatikan bahwa grep adalah ide yang buruk. Ada begitu banyak cara untuk mengeksekusi kode secara dinamis di Java yang hampir pasti akan Anda lewatkan. Kode daftar hitam secara umum adalah resep untuk kegagalan.
Antimony
"Pengubah akses Java tidak dimaksudkan sebagai mekanisme keamanan." - sebenarnya, ya mereka. Java dirancang untuk memungkinkan kode tepercaya dan tidak tepercaya untuk hidup berdampingan di mesin virtual yang sama - ini adalah bagian dari persyaratan intinya sejak awal. Tetapi hanya ketika sistem berjalan dengan SecurityManager yang dikunci. Kemampuan kode sandbox bergantung sepenuhnya pada penegakan penentu akses.
Jules
@Jules - Kebanyakan ahli Java tidak setuju dengan itu; misalnya stackoverflow.com/questions/9201603/…
Stephen C
11

Refleksi memang ortogonal terhadap keselamatan / keamanan dalam perspektif ini.

Bagaimana kita bisa membatasi refleksi?

Java memiliki manajer keamanan dan ClassLoadersebagai dasar dari model keamanannya. Dalam kasus Anda, saya rasa Anda perlu melihat java.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 Classloaderdan 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.

ewernli
sumber