Làm thế nào để giới hạn setAccessible chỉ sử dụng “hợp pháp”?


100

Càng tìm hiểu về sức mạnh của nó java.lang.reflect.AccessibleObject.setAccessible, tôi càng ngạc nhiên về những gì nó có thể làm được. Điều này được điều chỉnh từ câu trả lời của tôi cho câu hỏi ( Sử dụng phản chiếu để thay đổi File.separatorChar tĩnh cuối cùng để kiểm tra đơn vị ).

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"
   }
}

Bạn có thể làm những điều thực sự thái quá:

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"
   }
}

Có lẽ các nhà thiết kế API nhận ra setAccessiblecó thể bị lạm dụng như thế nào , nhưng phải thừa nhận rằng nó có các mục đích sử dụng hợp pháp để cung cấp. Vì vậy, câu hỏi của tôi là:

  • Những mục đích sử dụng thực sự hợp pháp để làm setAccessiblegì?
    • Có thể Java đã được thiết kế để KHÔNG có nhu cầu này ngay từ đầu?
    • Hậu quả tiêu cực (nếu có) của thiết kế như vậy là gì?
  • Bạn có thể setAccessiblechỉ sử dụng hợp pháp không?
    • Là nó chỉ thông qua SecurityManager?
      • Làm thế nào nó hoạt động? Danh sách trắng / danh sách đen, mức độ chi tiết, v.v.?
      • Có thường phải cấu hình nó trong các ứng dụng của bạn không?
    • Tôi có thể viết các lớp của mình thành setAccessible-proof bất kể SecurityManagercấu hình không?
      • Hay tôi đang phụ lòng của bất cứ ai quản lý cấu hình?

Tôi đoán còn một câu hỏi quan trọng nữa là: TÔI CÓ CẦN PHẢI LÀM VIỆC VỀ VIỆC NÀY KHÔNG ???

Không có lớp học nào của tôi có bất kỳ hình thức nào về quyền riêng tư có thể thực thi được. Mô hình singleton (đặt nghi ngờ về giá trị của nó sang một bên) hiện không thể thực thi. Như các đoạn trích của tôi ở trên cho thấy, ngay cả một số giả định cơ bản về cách hoạt động cơ bản của Java thậm chí còn không được đảm bảo.

NHỮNG VẤN ĐỀ NÀY KHÔNG CÓ THẬT ???


Được rồi, tôi vừa xác nhận: nhờ setAccessible, các chuỗi Java KHÔNG bất biến.

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!"
   }
}

Tôi có phải là người duy nhất nghĩ rằng đây là một mối quan tâm LỚN?


30
Bạn sẽ thấy những gì họ có thể làm trong C ++! (Ồ, và có thể là C #.)
Tom Hawtin - tackline

Câu trả lời:


105

TÔI CÓ CẦN LÀM VIỆC VỀ VIỆC NÀY KHÔNG ???

Điều đó hoàn toàn phụ thuộc vào loại chương trình bạn đang viết và loại kiến ​​trúc.

Nếu bạn đang phân phối một thành phần phần mềm có tên là foo.jar cho mọi người trên thế giới, thì bạn hoàn toàn chịu sự thương xót của họ. Họ có thể sửa đổi các định nghĩa lớp bên trong .jar của bạn (thông qua kỹ thuật đảo ngược hoặc thao tác bytecode trực tiếp). Họ có thể chạy mã của bạn trong JVM của riêng họ, v.v. Trong trường hợp này, lo lắng sẽ không tốt cho bạn.

Nếu bạn đang viết một ứng dụng web chỉ giao tiếp với mọi người và hệ thống qua HTTP và bạn kiểm soát máy chủ ứng dụng, thì đó cũng không phải là vấn đề đáng lo ngại. Chắc chắn rằng các lập trình viên đồng nghiệp tại công ty của bạn có thể tạo mã phá vỡ mô hình singleton của bạn, nhưng chỉ khi họ thực sự muốn.

Nếu công việc tương lai của bạn là viết mã tại Sun Microsystems / Oracle và bạn được giao nhiệm vụ viết mã cho lõi Java hoặc các thành phần đáng tin cậy khác, thì đó là điều bạn cần lưu ý. Tuy nhiên, lo lắng sẽ chỉ khiến bạn bị rụng tóc. Trong mọi trường hợp, họ có thể sẽ yêu cầu bạn đọc Nguyên tắc mã hóa an toàn cùng với tài liệu nội bộ.

Nếu bạn định viết các ứng dụng Java, thì khung bảo mật là thứ bạn nên biết. Bạn sẽ thấy rằng các applet chưa được ký đang cố gắng gọi setAccessible sẽ chỉ dẫn đến SecurityException.

setAccessible không phải là thứ duy nhất xoay quanh việc kiểm tra tính toàn vẹn thông thường. Có một lớp Java lõi, không phải API, được gọi là sun.misc.Unsafe có thể làm bất cứ điều gì mà nó muốn, kể cả truy cập trực tiếp vào bộ nhớ. Mã gốc (JNI) cũng có thể đi quanh loại điều khiển này.

Trong môi trường hộp cát (ví dụ: Java Applet, JavaFX), mỗi lớp có một tập hợp các quyền và quyền truy cập vào các triển khai gốc không an toàn, setAccessible và xác định được kiểm soát bởi SecurityManager.

"Các công cụ sửa đổi quyền truy cập Java không nhằm mục đích trở thành một cơ chế bảo mật."

Điều đó phụ thuộc rất nhiều vào nơi mã Java đang được chạy. Các lớp Java cốt lõi sử dụng công cụ sửa đổi quyền truy cập như một cơ chế bảo mật để thực thi hộp cát.

Những cách sử dụng thực sự hợp pháp cho setAccessible là gì?

Các lớp lõi của Java sử dụng nó như một cách dễ dàng để truy cập những thứ phải được giữ kín vì lý do bảo mật. Ví dụ, khuôn khổ tuần tự hóa Java sử dụng nó để gọi các hàm tạo đối tượng riêng khi giải mã hóa các đối tượng. Ai đó đã đề cập đến System.setErr và đó sẽ là một ví dụ tốt, nhưng thật kỳ lạ là các phương thức của lớp Hệ thống setOut / setErr / setIn đều sử dụng mã gốc để đặt giá trị của trường cuối cùng.

Một cách sử dụng hợp pháp rõ ràng khác là các khuôn khổ (bền bỉ, khuôn khổ web, tiêm) cần phải nhìn vào bên trong của các đối tượng.

Theo tôi, các trình gỡ lỗi không thuộc loại này, vì chúng thường không chạy trong cùng một quy trình JVM, mà thay vào đó là giao diện với JVM sử dụng các phương tiện khác (JPDA).

Có thể Java đã được thiết kế để KHÔNG có nhu cầu này ngay từ đầu?

Đó là một câu hỏi khá sâu để trả lời tốt. Tôi tưởng tượng là có, nhưng bạn cần phải thêm một số (các) cơ chế khác mà có thể không phải là tất cả các cơ chế thích hợp.

Bạn có thể giới hạn setAccessible chỉ với những mục đích sử dụng hợp pháp không?

Hạn chế OOTB rõ ràng nhất mà bạn có thể áp dụng là phải có Trình quản lý bảo mật và chỉ cho phép setAccessible đối với mã đến từ một số nguồn nhất định. Đây là những gì Java đã làm - các lớp Java tiêu chuẩn đến từ JAVA_HOME của bạn được phép thực hiện setAccessible, trong khi các lớp applet chưa được ký từ foo.com không được phép thực hiện setAccessible. Như đã nói trước đây, quyền này là nhị phân, theo nghĩa là người ta có hoặc không. Không có cách rõ ràng nào để cho phép setAccessible sửa đổi các trường / phương thức nhất định trong khi không cho phép những người khác. Tuy nhiên, sử dụng SecurityManager, bạn có thể không cho phép các lớp tham chiếu hoàn toàn các gói nhất định, có hoặc không có phản chiếu.

Tôi có thể viết các lớp của mình thành setAccessible-proof bất kể cấu hình SecurityManager không? ... Hay tôi đang phụ lòng của bất cứ ai quản lý cấu hình?

Bạn không thể và bạn chắc chắn là như vậy.


"Nếu bạn đang phân phối một thành phần phần mềm có tên foo.jar cho mọi người trên thế giới, thì dù sao thì bạn cũng hoàn toàn chịu sự thương xót của họ. Họ có thể sửa đổi các định nghĩa lớp bên trong .jar của bạn (thông qua kỹ thuật đảo ngược hoặc thao tác trực tiếp bytecode). Họ có thể chạy mã của bạn trong JVM của riêng họ, v.v. Trong trường hợp này, lo lắng sẽ không tốt cho bạn. " đây vẫn là trường hợp? Bất kỳ lời khuyên nào về việc làm cho phần mềm giả mạo khó hơn (Hy vọng là đáng kể)? Thật khó để ném các ứng dụng java desktop ra khỏi cửa sổ vì điều này.
Sero

@naaz Tôi sẽ phải nói rằng không có gì thay đổi. Chỉ là kiến ​​trúc đang chống lại bạn. Bất kỳ phần mềm nào đang chạy trên máy tính của tôi mà tôi có toàn quyền kiểm soát, tôi có thể thay đổi. Biện pháp ngăn chặn mạnh nhất có thể là pháp luật, nhưng tôi không tưởng tượng điều đó sẽ hiệu quả với tất cả các tình huống. Tôi nghĩ rằng các mã nhị phân Java là một trong những cách dễ dàng để đảo ngược nhất, nhưng những người có kinh nghiệm sẽ làm xáo trộn bất cứ điều gì. Obfuscation hữu ích bằng cách gây khó khăn cho việc thực hiện các thay đổi lớn, nhưng bất kỳ loại boolean nào (như kiểm tra bản quyền) vẫn rất nhỏ để bỏ qua. Thay vào đó, bạn có thể thử đặt các bộ phận quan trọng trên máy chủ.
Sami Koivu

Còn denuvo thì sao?
Sero

15
  • Những mục đích sử dụng thực sự hợp pháp để làm setAccessiblegì?

Kiểm thử đơn vị, nội bộ của JVM (ví dụ: triển khai System.setError(...)), v.v.

  • Có thể Java đã được thiết kế để KHÔNG có nhu cầu này ngay từ đầu?
  • Hậu quả tiêu cực (nếu có) của thiết kế như vậy là gì?

Rất nhiều thứ sẽ không thể thực hiện được. Ví dụ, sự bền bỉ, tuần tự hóa và tiêm phụ thuộc khác nhau của Java phụ thuộc vào phản xạ. Và hầu hết mọi thứ dựa trên các quy ước JavaBeans trong thời gian chạy.

  • Bạn có thể setAccessiblechỉ sử dụng hợp pháp không?
  • Là nó chỉ thông qua SecurityManager?

Đúng.

  • Làm thế nào nó hoạt động? Danh sách trắng / danh sách đen, mức độ chi tiết, v.v.?

Nó phụ thuộc vào sự cho phép, nhưng tôi tin rằng quyền sử dụng setAccessiblelà nhị phân. Nếu bạn muốn chi tiết, bạn cần sử dụng một trình nạp lớp khác với trình quản lý bảo mật khác cho các lớp mà bạn muốn hạn chế. Tôi đoán bạn có thể triển khai một trình quản lý bảo mật tùy chỉnh thực hiện logic chi tiết hơn.

  • Có thường phải cấu hình nó trong các ứng dụng của bạn không?

Không.

  • Tôi có thể viết các lớp của mình thành setAccessible-proof bất kể SecurityManagercấu hình không?
    • Hay tôi đang phụ lòng của bất cứ ai quản lý cấu hình?

Không, bạn không thể, và có bạn là như vậy.

Giải pháp thay thế khác là "thực thi" điều này thông qua các công cụ phân tích mã nguồn; ví dụ: tùy chỉnh pmdhoặc findbugsquy tắc. Hoặc xem xét mã có chọn lọc của mã được xác định bằng (nói) grep setAccessible ....

Để trả lời theo dõi

Không có lớp học nào của tôi có bất kỳ hình thức nào về quyền riêng tư có thể thực thi được. Mô hình singleton (đặt nghi ngờ về giá trị của nó sang một bên) hiện không thể thực thi.

Nếu điều đó làm bạn lo lắng, thì tôi cho rằng bạn cần phải lo lắng. Nhưng thực sự bạn không nên cố ép các lập trình viên khác tôn trọng quyết định thiết kế của bạn. Nếu mọi người đủ ngu ngốc để sử dụng sự phản chiếu để vô cớ tạo ra nhiều trường hợp cá thể của bạn (ví dụ), họ có thể sống chung với hậu quả.

Mặt khác, nếu bạn có nghĩa là "quyền riêng tư" để bao hàm ý nghĩa bảo vệ thông tin nhạy cảm khỏi bị tiết lộ, bạn đang trồng nhầm cây. Cách để bảo vệ dữ liệu nhạy cảm trong ứng dụng Java là không cho phép mã không đáng tin cậy vào hộp cát bảo mật xử lý dữ liệu nhạy cảm. Các công cụ sửa đổi quyền truy cập Java không nhằm mục đích trở thành một cơ chế bảo mật.

<Ví dụ về chuỗi> - Tôi có phải là người duy nhất nghĩ rằng đây là một mối quan tâm LỚN không?

Có lẽ không phải là duy nhất :-). Nhưng IMO, đây không phải là một mối quan tâm. Thực tế được chấp nhận rằng mã không đáng tin cậy phải được thực thi trong hộp cát. Nếu bạn có mã đáng tin cậy / một lập trình viên đáng tin cậy làm những việc như thế này, thì vấn đề của bạn còn tệ hơn các Chuỗi có thể thay đổi bất ngờ. (Nghĩ về bom logic, lọc dữ liệu qua các kênh bí mật , v.v.)

Có nhiều cách để giải quyết (hoặc giảm thiểu) vấn đề "tác nhân xấu" trong nhóm phát triển hoặc hoạt động của bạn. Nhưng chúng tốn kém và hạn chế ... và quá mức cần thiết cho hầu hết các trường hợp sử dụng.


1
Cũng hữu ích cho những thứ như triển khai bền bỉ. Phản ánh không thực sự hữu ích cho trình gỡ lỗi (mặc dù nó được dự định ban đầu). Bạn không thể thay đổi (lớp con) SecurityManagercho điều này một cách hữu ích , bởi vì tất cả những gì bạn nhận được là kiểm tra quyền - tất cả những gì bạn thực sự có thể làm là nhìn vào tài khoản và có lẽ đi bộ kiểm tra luồng hiện tại). grep java.lang.reflect.
Tom Hawtin - tackline

@Stephen C: đã +1 rồi, nhưng tôi cũng đánh giá cao ý kiến ​​đóng góp của bạn về một câu hỏi nữa mà tôi vừa thêm vào. Cảm ơn trước.
polygenelubricants

Lưu ý rằng grep là một ý tưởng khủng khiếp. Có rất nhiều cách để thực thi động mã trong Java mà bạn gần như chắc chắn sẽ bỏ lỡ một cách. Mã danh sách đen nói chung là một công thức dẫn đến thất bại.
Antimony

"Các công cụ sửa đổi quyền truy cập Java không nhằm mục đích trở thành một cơ chế bảo mật." - thực ra là có. Java được thiết kế để cho phép mã đáng tin cậy và không đáng tin cậy cùng tồn tại trong cùng một máy ảo - đây là một phần trong các yêu cầu cốt lõi của nó ngay từ đầu. Nhưng chỉ khi hệ thống đang chạy với Trình quản lý bảo mật bị khóa. Khả năng mã hộp cát phụ thuộc hoàn toàn vào việc thực thi các chỉ định truy cập.
Jules

@Jules - Hầu hết các chuyên gia Java sẽ không đồng ý với điều đó; ví dụ: stackoverflow.com/questions/9201603/…
Stephen C,

11

Sự phản ánh thực sự là trực giao với an toàn / bảo mật dưới góc độ này.

Làm thế nào chúng ta có thể hạn chế phản xạ?

Java có trình quản lý bảo mật và ClassLoaderlà nền tảng cho mô hình bảo mật của nó. Trong trường hợp của bạn, tôi đoán bạn cần phải xem xét java.lang.reflect.ReflectPermission.

Nhưng điều này không giải quyết triệt để vấn đề phản ánh. Các khả năng phản chiếu có sẵn phải tuân theo một kế hoạch ủy quyền chi tiết, điều này không phải như bây giờ. Ví dụ: cho phép một số khung công tác nhất định sử dụng phản chiếu (ví dụ: Hibernate), nhưng không cho phép phần còn lại của mã của bạn. Hoặc để cho phép một chương trình chỉ phản ánh theo cách chỉ đọc, cho mục đích gỡ lỗi.

Một cách tiếp cận có thể trở thành chủ đạo trong tương lai là việc sử dụng cái gọi là gương để tách khả năng phản xạ khỏi các lớp. Xem Gương: Nguyên tắc Thiết kế cho Cơ sở Cấp Meta . Tuy nhiên, có nhiều nghiên cứu khác giải quyết vấn đề này. Nhưng tôi đồng ý rằng vấn đề nghiêm trọng hơn đối với ngôn ngữ động hơn là ngôn ngữ tĩnh.

Chúng ta có nên lo lắng về siêu năng lực mà sự phản chiếu mang lại cho chúng ta không? Có và không.

theo nghĩa là nền tảng Java được cho là phải được bảo mật Classloadervà quản lý bảo mật. Khả năng gây rối với phản xạ có thể được coi là một vi phạm.

Không có nghĩa là hầu hết các hệ thống không hoàn toàn an toàn. Nhiều lớp có thể thường xuyên được phân lớp và bạn có thể đã lạm dụng hệ thống chỉ với điều đó. Tất nhiên các lớp có thể được tạo final, hoặc niêm phong để chúng không thể được phân lớp trong jar khác. Nhưng chỉ có một số lớp được bảo mật một cách chính xác (ví dụ: Chuỗi) theo điều này.

Xem câu trả lời này về lớp cuối cùng để có lời giải thích hay. Xem thêm blog từ Sami Koivu để biết thêm về cách hack java xung quanh vấn đề bảo mật.

Mô hình bảo mật của Java có thể được coi là không đủ đối với một số khía cạnh. Một số ngôn ngữ như NewSpeak thậm chí còn có cách tiếp cận triệt để hơn đối với mô-đun, nơi bạn chỉ có quyền truy cập vào những gì được cung cấp rõ ràng cho bạn bằng cách đảo ngược phụ thuộc (theo mặc định là không có gì).

Điều quan trọng cần lưu ý là bảo mật dù sao cũng là tương đối . Ở cấp độ ngôn ngữ, bạn không thể ngăn một biểu mẫu mô-đun tiêu thụ 100% CPU hoặc sử dụng tất cả bộ nhớ lên đến a OutOfMemoryException. Những mối quan tâm như vậy cần được giải quyết bằng các phương tiện khác. Chúng ta có thể sẽ thấy trong tương lai Java được mở rộng với hạn ngạch sử dụng tài nguyên, nhưng nó không dành cho ngày mai :)

Tôi có thể mở rộng thêm về chủ đề này, nhưng tôi nghĩ rằng tôi đã đưa ra quan điểm của mình.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.