2009-02-02 35 views
82

Trong môi trường máy chủ mô phỏng trong đó người dùng được phép gửi mã riêng của mình để được máy chủ điều hành, rõ ràng sẽ thuận lợi cho bất kỳ mã do người dùng gửi nào được chạy bên cạnh hộp cát , không giống như Applet nằm trong trình duyệt. Tôi muốn có thể tận dụng chính JVM, thay vì thêm một lớp VM khác để cô lập các thành phần đã gửi này.Sandbox chống lại mã độc trong ứng dụng Java

Loại hạn chế này có vẻ là có thể bằng cách sử dụng mô hình hộp cát Java hiện có, nhưng có cách nào năng động để kích hoạt cho các phần do người dùng gửi của ứng dụng đang chạy không?

Trả lời

99
  1. Chạy mã không tin cậy trong chuỗi của riêng nó. Điều này ví dụ như ngăn chặn các vấn đề với vòng lặp vô hạn và như vậy, và làm cho các bước trong tương lai dễ dàng hơn. Có chủ đề chính đợi cho thread kết thúc, và nếu mất quá nhiều thời gian, hãy hủy nó bằng Thread.stop. Thread.stop không được chấp nhận, nhưng vì mã không đáng tin cậy không được truy cập vào bất kỳ tài nguyên nào, nên sẽ an toàn khi giết nó.

  2. Đặt SecurityManager trên Chủ đề đó. Tạo một lớp con của SecurityManager ghi đè checkPermission(Permission perm) để chỉ cần ném một SecurityException cho tất cả các quyền trừ một vài lựa chọn. Có một danh sách các phương thức và quyền mà chúng yêu cầu ở đây: Permissions in the JavaTM 6 SDK.

  3. Sử dụng Trình nạp lớp tùy chỉnh để tải mã không đáng tin cậy. Trình nạp lớp của bạn sẽ được gọi cho tất cả các lớp mà mã không tin cậy sử dụng, vì vậy bạn có thể làm những việc như vô hiệu hóa quyền truy cập vào các lớp JDK riêng lẻ. Điều cần làm là có một danh sách trắng các lớp JDK được cho phép.

  4. Bạn có thể muốn chạy mã không tin cậy trong một JVM riêng biệt.Trong khi các bước trước đó sẽ làm cho mã an toàn, có một điều khó chịu mà mã bị cô lập vẫn có thể làm: phân bổ bộ nhớ càng nhiều càng tốt, điều này làm cho dấu chân có thể nhìn thấy của ứng dụng chính phát triển.

JSR 121: Application Isolation API Specification được thiết kế để giải quyết vấn đề này, nhưng rất tiếc nó chưa được triển khai.

Đây là một chủ đề khá chi tiết và tôi chủ yếu viết tất cả những điều này trên đầu của tôi.

Nhưng dù sao, một số không hoàn hảo, sử dụng-at-bạn-của-rủi ro, có lẽ lỗi (giả) mã:

ClassLoader

class MyClassLoader extends ClassLoader { 
    @Override 
    public Class<?> loadClass(String name) throws ClassNotFoundException { 
    if (name is white-listed JDK class) return super.loadClass(name); 
    return findClass(name); 
    } 
    @Override 
    public Class findClass(String name) { 
    byte[] b = loadClassData(name); 
    return defineClass(name, b, 0, b.length); 
    } 
    private byte[] loadClassData(String name) { 
    // load the untrusted class data here 
    } 
} 

SecurityManager

class MySecurityManager extends SecurityManager { 
    private Object secret; 
    public MySecurityManager(Object pass) { secret = pass; } 
    private void disable(Object pass) { 
    if (pass == secret) secret = null; 
    } 
    // ... override checkXXX method(s) here. 
    // Always allow them to succeed when secret==null 
} 

Chủ đề

class MyIsolatedThread extends Thread { 
    private Object pass = new Object(); 
    private MyClassLoader loader = new MyClassLoader(); 
    private MySecurityManager sm = new MySecurityManager(pass); 
    public void run() { 
    SecurityManager old = System.getSecurityManager(); 
    System.setSecurityManager(sm); 
    runUntrustedCode(); 
    sm.disable(pass); 
    System.setSecurityManager(old); 
    } 
    private void runUntrustedCode() { 
    try { 
     // run the custom class's main method for example: 
     loader.loadClass("customclassname") 
     .getMethod("main", String[].class) 
     .invoke(null, new Object[]{...}); 
    } catch (Throwable t) {} 
    } 
} 
+4

Mã đó có thể cần một số công việc. Bạn không thể thực sự bảo vệ chống lại tính khả dụng của JVM. Hãy chuẩn bị để giết quá trình (có thể là tự động). Mã nhận được vào các chủ đề khác - ví dụ như chuỗi finaliser. 'Thread.stop' sẽ gây ra các vấn đề trong mã thư viện Java. Tương tự, mã thư viện Java sẽ yêu cầu quyền. Tốt hơn hết là cho phép 'SecurityManager' sử dụng' java.security.AccessController'. Trình nạp lớp có lẽ cũng cho phép truy cập vào các lớp riêng của mã người dùng. –

+0

@Tom: Có thể triển khai trình quản lý bảo mật theo cách sao cho nó bỏ qua mã thư viện Java. – instantsetsuna

+3

Cho rằng đây là một chủ đề phức tạp như vậy, không có giải pháp nào để xử lý Java "plugins" một cách an toàn? –

18

Rõ ràng một kế hoạch như vậy làm tăng tất cả các loại mối quan tâm về bảo mật. Java có một khung bảo mật nghiêm ngặt, nhưng nó không phải là tầm thường. Khả năng vặn nó lên và cho phép người dùng không có đặc quyền truy cập vào các thành phần hệ thống quan trọng không nên bỏ qua.

Cảnh báo đó sang một bên, nếu bạn đang dùng đầu vào của người dùng dưới dạng mã nguồn, điều đầu tiên bạn cần làm là biên dịch nó sang mã Java bytecode. AFIAK, điều này không thể được thực hiện tự nhiên, vì vậy bạn sẽ cần phải thực hiện một cuộc gọi hệ thống đến javac, và biên dịch mã nguồn thành bytecode trên đĩa. Here's một hướng dẫn có thể được sử dụng làm điểm bắt đầu cho việc này. Sửa: như tôi đã học trong các ý kiến, bạn thực sự có thể biên dịch mã Java từ nguồn tự nhiên sử dụng javax.tools.JavaCompiler

Một khi bạn có JVM bytecode, bạn có thể tải nó vào JVM sử dụng một hàm ClassLoader'sdefineClass. Để đặt bối cảnh bảo mật cho lớp được tải này, bạn sẽ cần chỉ định một ProtectionDomain. Phương thức khởi tạo tối thiểu cho số ProtectionDomain yêu cầu cả Mã nguồnSource và PermissionCollection. PermissionCollection là đối tượng sử dụng chính cho bạn ở đây- bạn có thể sử dụng nó để chỉ định các quyền chính xác mà lớp được nạp có. Các quyền này sẽ được thực thi cuối cùng bởi AccessController của JVM.

Có rất nhiều điểm có thể xảy ra lỗi ở đây và bạn nên hết sức cẩn thận để hiểu rõ mọi thứ trước khi thực hiện bất kỳ điều gì.

+2

Trình biên dịch Java khá dễ sử dụng API javax.tools của JDK 6. –

10

Java-Sandbox là thư viện để thực thi mã Java với một nhóm quyền hạn chế. Nó có thể được sử dụng để chỉ cho phép truy cập vào một tập hợp các tài nguyên và lớp học được liệt kê trắng. Có vẻ như không phải là để có thể giới hạn quyền truy cập vào các phương thức riêng lẻ. Nó sử dụng một hệ thống với trình nạp lớp tùy chỉnh và trình quản lý bảo mật để đạt được điều này.

Tôi chưa sử dụng nhưng có vẻ được thiết kế tốt và được ghi lại hợp lý.

@waqas đã đưa ra một câu trả lời rất thú vị giải thích cách thực hiện điều này. Nhưng nó là an toàn hơn nhiều để lại mã bảo mật quan trọng và phức tạp như vậy cho các chuyên gia.

Lưu ý rằng dự án chưa được cập nhật từ năm 2013 và những người sáng tạo mô tả dự án là "thử nghiệm". Trang chủ của nó đã biến mất nhưng mục nhập Nguồn Forge vẫn còn.

Ví dụ mã chuyển thể từ các trang web dự án:

SandboxService sandboxService = SandboxServiceImpl.getInstance(); 

// Configure context 
SandboxContext context = new SandboxContext(); 
context.addClassForApplicationLoader(getClass().getName()); 
context.addClassPermission(AccessType.PERMIT, "java.lang.System"); 

// Whithout this line we get a SandboxException when touching System.out 
context.addClassPermission(AccessType.PERMIT, "java.io.PrintStream"); 

String someValue = "Input value"; 

class TestEnvironment implements SandboxedEnvironment<String> { 
    @Override 
    public String execute() throws Exception { 
     // This is untrusted code 
     System.out.println(someValue); 
     return "Output value"; 
    } 
}; 

// Run code in sandbox. Pass arguments to generated constructor in TestEnvironment. 
SandboxedCallResult<String> result = sandboxService.runSandboxed(TestEnvironment.class, 
    context, this, someValue); 

System.out.println(result.get()); 
2

Dưới đây là một giải pháp thread-an toàn cho các vấn đề:

https://svn.code.sf.net/p/loggifier/code/trunk/de.unkrig.commons.lang/src/de/unkrig/commons/lang/security/Sandbox.java

package de.unkrig.commons.lang.security; 

import java.security.AccessControlContext; 
import java.security.Permission; 
import java.security.Permissions; 
import java.security.ProtectionDomain; 
import java.util.Collections; 
import java.util.HashMap; 
import java.util.Map; 
import java.util.WeakHashMap; 

import de.unkrig.commons.nullanalysis.Nullable; 

/** 
* This class establishes a security manager that confines the permissions for code executed through specific classes, 
* which may be specified by class, class name and/or class loader. 
* <p> 
* To 'execute through a class' means that the execution stack includes the class. E.g., if a method of class {@code A} 
* invokes a method of class {@code B}, which then invokes a method of class {@code C}, and all three classes were 
* previously {@link #confine(Class, Permissions) confined}, then for all actions that are executed by class {@code C} 
* the <i>intersection</i> of the three {@link Permissions} apply. 
* <p> 
* Once the permissions for a class, class name or class loader are confined, they cannot be changed; this prevents any 
* attempts (e.g. of the confined class itself) to release the confinement. 
* <p> 
* Code example: 
* <pre> 
* Runnable unprivileged = new Runnable() { 
*  public void run() { 
*   System.getProperty("user.dir"); 
*  } 
* }; 
* 
* // Run without confinement. 
* unprivileged.run(); // Works fine. 
* 
* // Set the most strict permissions. 
* Sandbox.confine(unprivileged.getClass(), new Permissions()); 
* unprivileged.run(); // Throws a SecurityException. 
* 
* // Attempt to change the permissions. 
* { 
*  Permissions permissions = new Permissions(); 
*  permissions.add(new AllPermission()); 
*  Sandbox.confine(unprivileged.getClass(), permissions); // Throws a SecurityException. 
* } 
* unprivileged.run(); 
* </pre> 
*/ 
public final 
class Sandbox { 

    private Sandbox() {} 

    private static final Map<Class<?>, AccessControlContext> 
    CHECKED_CLASSES = Collections.synchronizedMap(new WeakHashMap<Class<?>, AccessControlContext>()); 

    private static final Map<String, AccessControlContext> 
    CHECKED_CLASS_NAMES = Collections.synchronizedMap(new HashMap<String, AccessControlContext>()); 

    private static final Map<ClassLoader, AccessControlContext> 
    CHECKED_CLASS_LOADERS = Collections.synchronizedMap(new WeakHashMap<ClassLoader, AccessControlContext>()); 

    static { 

     // Install our custom security manager. 
     if (System.getSecurityManager() != null) { 
      throw new ExceptionInInitializerError("There's already a security manager set"); 
     } 
     System.setSecurityManager(new SecurityManager() { 

      @Override public void 
      checkPermission(@Nullable Permission perm) { 
       assert perm != null; 

       for (Class<?> clasS : this.getClassContext()) { 

        // Check if an ACC was set for the class. 
        { 
         AccessControlContext acc = Sandbox.CHECKED_CLASSES.get(clasS); 
         if (acc != null) acc.checkPermission(perm); 
        } 

        // Check if an ACC was set for the class name. 
        { 
         AccessControlContext acc = Sandbox.CHECKED_CLASS_NAMES.get(clasS.getName()); 
         if (acc != null) acc.checkPermission(perm); 
        } 

        // Check if an ACC was set for the class loader. 
        { 
         AccessControlContext acc = Sandbox.CHECKED_CLASS_LOADERS.get(clasS.getClassLoader()); 
         if (acc != null) acc.checkPermission(perm); 
        } 
       } 
      } 
     }); 
    } 

    // -------------------------- 

    /** 
    * All future actions that are executed through the given {@code clasS} will be checked against the given {@code 
    * accessControlContext}. 
    * 
    * @throws SecurityException Permissions are already confined for the {@code clasS} 
    */ 
    public static void 
    confine(Class<?> clasS, AccessControlContext accessControlContext) { 

     if (Sandbox.CHECKED_CLASSES.containsKey(clasS)) { 
      throw new SecurityException("Attempt to change the access control context for '" + clasS + "'"); 
     } 

     Sandbox.CHECKED_CLASSES.put(clasS, accessControlContext); 
    } 

    /** 
    * All future actions that are executed through the given {@code clasS} will be checked against the given {@code 
    * protectionDomain}. 
    * 
    * @throws SecurityException Permissions are already confined for the {@code clasS} 
    */ 
    public static void 
    confine(Class<?> clasS, ProtectionDomain protectionDomain) { 
     Sandbox.confine(
      clasS, 
      new AccessControlContext(new ProtectionDomain[] { protectionDomain }) 
     ); 
    } 

    /** 
    * All future actions that are executed through the given {@code clasS} will be checked against the given {@code 
    * permissions}. 
    * 
    * @throws SecurityException Permissions are already confined for the {@code clasS} 
    */ 
    public static void 
    confine(Class<?> clasS, Permissions permissions) { 
     Sandbox.confine(clasS, new ProtectionDomain(null, permissions)); 
    } 

    // Code for 'CHECKED_CLASS_NAMES' and 'CHECKED_CLASS_LOADERS' omitted here. 

} 

hãy bình luận!

CU

Arno

2

Để giải quyết vấn đề trong câu trả lời được chấp nhận, theo đó các tùy chỉnh SecurityManager sẽ áp dụng cho tất cả các chủ đề trong JVM, chứ không phải trên một cơ sở cho mỗi chủ đề, bạn có thể tạo ra một tùy chỉnh SecurityManager có thể được bật/tắt cho chủ đề cụ thể như sau:

import java.security.Permission; 

public class SelectiveSecurityManager extends SecurityManager { 

    private static final ToggleSecurityManagerPermission TOGGLE_PERMISSION = new ToggleSecurityManagerPermission(); 

    ThreadLocal<Boolean> enabledFlag = null; 

    public SelectiveSecurityManager(final boolean enabledByDefault) { 

    enabledFlag = new ThreadLocal<Boolean>() { 

     @Override 
     protected Boolean initialValue() { 
     return enabledByDefault; 
     } 

     @Override 
     public void set(Boolean value) { 
     SecurityManager securityManager = System.getSecurityManager(); 
     if (securityManager != null) { 
      securityManager.checkPermission(TOGGLE_PERMISSION); 
     } 
     super.set(value); 
     } 
    }; 
    } 

    @Override 
    public void checkPermission(Permission permission) { 
    if (shouldCheck(permission)) { 
     super.checkPermission(permission); 
    } 
    } 

    @Override 
    public void checkPermission(Permission permission, Object context) { 
    if (shouldCheck(permission)) { 
     super.checkPermission(permission, context); 
    } 
    } 

    private boolean shouldCheck(Permission permission) { 
    return isEnabled() || permission instanceof ToggleSecurityManagerPermission; 
    } 

    public void enable() { 
    enabledFlag.set(true); 
    } 

    public void disable() { 
    enabledFlag.set(false); 
    } 

    public boolean isEnabled() { 
    return enabledFlag.get(); 
    } 

} 

ToggleSecurirtyManagerPermission chỉ là một thực hiện đơn giản của java.security.Permission để đảm bảo rằng chỉ được ủy quyền mã có thể bật/tắt trình quản lý bảo mật. Nó trông giống như thế này:

import java.security.Permission; 

public class ToggleSecurityManagerPermission extends Permission { 

    private static final long serialVersionUID = 4812713037565136922L; 
    private static final String NAME = "ToggleSecurityManagerPermission"; 

    public ToggleSecurityManagerPermission() { 
    super(NAME); 
    } 

    @Override 
    public boolean implies(Permission permission) { 
    return this.equals(permission); 
    } 

    @Override 
    public boolean equals(Object obj) { 
    if (obj instanceof ToggleSecurityManagerPermission) { 
     return true; 
    } 
    return false; 
    } 

    @Override 
    public int hashCode() { 
    return NAME.hashCode(); 
    } 

    @Override 
    public String getActions() { 
    return ""; 
    } 

} 
+2

Trích dẫn nguồn của riêng bạn: http://alphaloop.blogspot.com/2014/08/a-per-thread-java-security-manager.html và https://github.com/alphaloop/selective-security- giám đốc . – ziesemer

4

Vâng nó rất muộn để đưa ra bất cứ lời đề nghị hoặc giải pháp, nhưng tôi vẫn đang phải đối mặt loại vấn đề tương tự, loại nghiên cứu nhiều hơn theo định hướng. Về cơ bản, tôi đã cố gắng cung cấp một đánh giá dự phòng và tự động cho các bài tập lập trình cho khóa học Java trong các nền tảng học trực tuyến.

  1. một cách có thể là, Tạo một máy ảo riêng biệt (không phải JVM) nhưng máy ảo thực với cấu hình tối thiểu có thể có cho mỗi học sinh.
  2. Cài đặt JRE cho Java hoặc thư viện theo ngôn ngữ lập trình của bạn, tùy theo bạn muốn sinh viên biên dịch và thực thi trên các máy này.

Tôi biết điều này nghe có vẻ phức tạp và nhiều nhiệm vụ, nhưng Oracle Virtual Box đã cung cấp Java API để tạo hoặc sao chép máy ảo một cách linh động. https://www.virtualbox.org/sdkref/index.html (Lưu ý, thậm chí VMware cũng cung cấp API để thực hiện điều tương tự)

Và đối với kích thước tối thiểu và phân phối cấu hình Linux bạn có thể tham khảo cái này đây http://www.slitaz.org/en/,

Vì vậy, bây giờ nếu sinh viên messes lên hoặc cố gắng làm nó, có thể với bộ nhớ hoặc hệ thống tập tin hoặc mạng, ổ cắm, tối đa anh ta có thể làm hỏng VM của riêng mình.

Ngoài nội bộ vào các máy ảo này, bạn có thể cung cấp bảo mật bổ sung như Sandbox (trình quản lý bảo mật) cho Java hoặc tạo tài khoản người dùng cụ thể trên Linux và do đó hạn chế quyền truy cập.

Hy vọng điều này sẽ giúp ích !!

Các vấn đề liên quan