Hộp cát chống mã độc trong ứng dụng Java


91

Trong môi trường máy chủ mô phỏng nơi người dùng được phép gửi mã của riêng họ để máy chủ chạy, rõ ràng sẽ có lợi cho bất kỳ mã nào do người dùng gửi được chạy trong hộp cát, không giống như các Applet 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.

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

Câu trả lời:


109
  1. Chạy mã không đáng tin cậy trong chuỗi của chính nó. Ví dụ, điều này ngăn chặn các vấn đề với vòng lặp vô hạn, và làm cho các bước trong tương lai dễ dàng hơn. Để luồng chính chờ cho luồng kết thúc và nếu mất quá nhiều thời gian, hãy giết nó bằng Thread.stop. Thread.stop không được dùng nữa, nhưng vì mã không đáng tin cậy sẽ không có quyền 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 Thread đó. 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 ngoại trừ một số quyền được chọn. Có một danh sách các phương thức và các quyền mà chúng yêu cầu ở đây: Các quyền trong Java TM 6 SDK .

  3. Sử dụng ClassLoader tùy chỉnh để tải mã không đáng tin cậy. Trình tải lớp của bạn sẽ được gọi cho tất cả các lớp mà mã không đá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ẻ. Việc cần làm là có một danh sách trắng các lớp JDK được phép.

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

JSR 121: Đặc tả API cách ly ứng dụng được thiết kế để giải quyết vấn đề này, nhưng tiếc là nó chưa có triển khai.

Đây là một chủ đề khá chi tiết, và tôi chủ yếu viết điều này ra khỏi đầu.

Nhưng dẫu sao, một số mã không hoàn hảo, tự chịu rủi ro, có thể là lỗi (giả):

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

Quản lí an ninh

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ự đề phòng tính khả dụng của JVM. Hãy chuẩn bị để giết quá trình (có thể là tự động). Mã truy cập vào các chủ đề khác - ví dụ như chủ đề hoàn thiện. Thread.stopsẽ gây ra sự cố 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 nhiều để cho phép SecurityManagersử dụng java.security.AccessController. Trình tải lớp có lẽ cũng nên cho phép truy cập vào các lớp riêng của mã người dùng.
Tom Hawtin - tackline

3
Cho rằng đây là một chủ đề phức tạp, không có giải pháp nào hiện có để xử lý các "plugin" Java một cách an toàn?
Nick Spacek

9
Vấn đề của cách tiếp cận này là khi bạn đặt SecurityManager thành Hệ thống, nó không chỉ ảnh hưởng đến chuỗi đang chạy mà còn ảnh hưởng đến các chuỗi khác!
Gelin Luo

2
Xin lỗi nhưng thread.stop () có thể được xử lý bằng hàm throwable. Bạn có thể while (thread.isAlive) Thread.stop (), nhưng sau đó tôi có thể gọi đệ quy một hàm bắt ngoại lệ. Đã thử nghiệm trên máy tính của tôi, hàm đệ quy chiến thắng khi dừng (). Bây giờ bạn có một chuỗi rác, ăn cắp cpu và tài nguyên
Lesto

8
Bên cạnh thực tế là System.setSecurityManager(…)sẽ ảnh hưởng đến toàn bộ JVM, không chỉ luồng gọi phương thức đó, ý tưởng đưa ra quyết định bảo mật dựa trên luồng đã bị loại bỏ khi Java chuyển từ 1.0 sang 1.1. Tại thời điểm này, người ta đã nhận ra rằng mã không đáng tin cậy có thể gọi mã đáng tin cậy và ngược lại, bất kể luồng nào thực thi mã. Không nhà phát triển nào nên lặp lại sai lầm.
Holger

18

Rõ ràng là một kế hoạch như vậy làm tăng tất cả các loại lo ngại về an ninh. Java có một khung bảo mật nghiêm ngặt, nhưng nó không hề tầm thường. Không nên bỏ qua khả năng vặn 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 quan trọng của hệ thống.

Cảnh báo đó sang một bên, nếu bạn đang sử 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ã bytecode của Java. AFIAK, điều này không thể được thực hiện nguyên bản, vì vậy bạn sẽ cần thực hiện lệnh gọi hệ thống tới javac và biên dịch mã nguồn thành bytecode trên đĩa. Đây là một hướng dẫn có thể được sử dụng như một điểm khởi đầu cho việc này. Chỉnh sửa : như tôi đã học trong các nhận xét, bạn thực sự có thể biên dịch mã Java từ nguồn nguyên bản bằng cách sử dụng javax.tools.JavaCompiler

Khi bạn đã có mã bytecode của JVM, bạn có thể tải nó vào JVM bằng cách sử dụng hàm defineClass của ClassLoader . Để thiết lập ngữ cảnh bảo mật cho lớp được tải này, bạn sẽ cần chỉ định một ProtectionDomain . Hàm tạo tối thiểu cho ProtectionDomain yêu cầu cả CodeSource và PermissionCollection . PermissionCollection là đối tượng được sử dụng chính đối với 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 tải có. Các quyền này cuối cùng sẽ được thực thi bởi AccessController của JVM .

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


2
Việc biên dịch Java khá dễ dàng bằng cách sử dụng API javax.tools của JDK 6.
Alan Krueger

10

Các Java Sandbox là một thư viện để thực hiện mã Java với một tập hạn chế các quyền. Nó có thể được sử dụng để chỉ cho phép truy cập vào một tập hợp các lớp và tài nguyên trong danh sách trắng. Dường như nó không thể hạn chế 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 tải lớp tùy chỉnh và trình quản lý bảo mật để đạt được điều này.

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

@waqas đã đưa ra một câu trả lời rất thú vị giải thích cách bạn có thể thực hiện điều này. Nhưng sẽ an toàn hơn nhiều nếu để các 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 đã không được cập nhật kể từ năm 2013 và những người sáng tạo mô tả nó là "thử nghiệm". Trang chủ của nó đã biến mất nhưng mục nhập Source Forge vẫn còn.

Mã ví dụ được điều chỉnh từ trang web của 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());

4

Để giải quyết vấn đề trong câu trả lời được chấp nhận, theo đó tùy chỉnh SecurityManagersẽ áp dụng cho tất cả các chuỗi trong JVM, thay vì trên cơ sở từng chuỗi, bạn có thể tạo tùy chỉnh SecurityManagercó thể được bật / tắt cho các chuỗi 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();
  }

}

ToggleSecurirtyManagerPermissionchỉ là một triển khai đơn giản java.security.Permissionđể đảm bảo rằng chỉ mã được ủy quyền mới có thể bật / tắt trình quản lý bảo mật. Nó trô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 "";
  }

}

3
Trích dẫn các nguồn (của riêng bạn): alphaloop.blogspot.com/2014/08/…github.com/alphaloop/selective-security-manager .
ziesemer

Việc sử dụng ThreadLocal rất thông minh để làm cho Người quản lý bảo mật trên phạm vi hệ thống có phạm vi phân luồng hiệu quả (mà hầu hết người dùng đều mong muốn). Ngoài ra, hãy xem xét việc sử dụng InhengedThreadLocal để tự động truyền thuộc tính không được phép tới các luồng sinh ra bởi mã không đáng tin cậy.
Nick

4

Chà, đã rất muộn để đưa ra bất kỳ đề xuất hoặc giải pháp nào, nhưng tôi vẫn đang đối mặt với loại vấn đề tương tự, loại có định hướng nghiên cứu nhiều hơn. Về cơ bản, tôi đang cố gắng cung cấp một điều khoản và đánh giá tự động cho các bài tập lập trình cho khóa học Java trên nền tảng e-learning.

  1. một cách có thể là Tạo một máy ảo riêng biệt (không phải JVM) mà là các máy ảo thực tế với cấu hình tối thiểu HĐH có thể cho mỗi học sinh.
  2. Cài đặt JRE cho Java hoặc các thư viện theo ngôn ngữ lập trình của bạn, tùy theo ngôn ngữ lập trình mà 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ẻ khá 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 động các máy ảo. https://www.virtualbox.org/sdkref/index.html (Lưu ý, ngay cả VMware cũng cung cấp API để làm việc tương tự)

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

Vì vậy, bây giờ nếu học sinh làm hỏng hoặc cố gắng làm điều đó, có thể là với bộ nhớ hoặc hệ thống tệp hoặc mạng, ổ cắm, tối đa anh ta có thể làm hỏng máy ảo của chính mình.

Ngoài ra trong nội bộ 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.

Hi vọng điêu nay co ich !!


3

Đây là một giải pháp an toàn cho sự cố:

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


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.