Java 애플리케이션의 악성 코드에 대한 샌드 박스


91

사용자가 자신의 코드를 제출하여 서버에서 실행할 수있는 시뮬레이션 서버 환경에서는 사용자가 제출 한 코드를 샌드 박스 측면에서 실행하는 것이 분명히 유리합니다. 이는 애플릿이 브라우저 내에있는 것과는 다릅니다. 제출 된 구성 요소를 격리하기 위해 다른 VM 계층을 추가하는 대신 JVM 자체를 활용할 수 있기를 원했습니다.

이러한 종류의 제한은 기존 Java 샌드 박스 모델을 사용하여 가능해 보이지만 실행중인 응용 프로그램의 사용자가 제출 한 부분에 대해서만이를 활성화하는 동적 방법이 있습니까?

답변:


109
  1. 자체 스레드에서 신뢰할 수없는 코드를 실행하십시오. 예를 들어 무한 루프 등의 문제를 방지하고 향후 단계를 더 쉽게 수행 할 수 있습니다. 메인 스레드가 스레드가 완료 될 때까지 기다리도록하고, 너무 오래 걸리면 Thread.stop으로 종료합니다. Thread.stop은 더 이상 사용되지 않지만 신뢰할 수없는 코드는 리소스에 액세스 할 수 없으므로 안전하게 죽일 수 있습니다.

  2. 해당 스레드에 SecurityManager 를 설정하십시오 . checkPermission (Permission perm) 을 재정의하는 SecurityManager의 하위 클래스 를 만들어 일부 권한을 제외한 모든 권한에 대해 SecurityException 을 발생시킵니다. 여기에 메소드 및 필요한 권한 목록이 있습니다 . Java TM 6 SDK의 권한 .

  3. 사용자 정의 ClassLoader를 사용하여 신뢰할 수없는 코드를로드하십시오. 클래스 로더는 신뢰할 수없는 코드가 사용하는 모든 클래스에 대해 호출되므로 개별 JDK 클래스에 대한 액세스를 비활성화하는 등의 작업을 수행 할 수 있습니다. 할 일은 허용되는 JDK 클래스의 화이트리스트를 갖는 것입니다.

  4. 별도의 JVM에서 신뢰할 수없는 코드를 실행할 수 있습니다. 이전 단계에서는 코드를 안전하게 만들 수 있지만, 격리 된 코드가 여전히 할 수있는 한 가지 성가신 일이 있습니다. 가능한 한 많은 메모리를 할당하면 메인 애플리케이션의 가시적 인 공간이 커집니다.

JSR 121 : Application Isolation API Specification 은이 문제를 해결하도록 설계되었지만 안타깝게도 아직 구현되지 않았습니다.

이것은 매우 상세한 주제이며, 나는 대부분이 모든 것을 내 머리 위에 쓰고 있습니다.

그러나 어쨌든 불완전하고 위험에 따라 사용하고 버그가있는 (의사) 코드가 있습니다.

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

보안 관리자

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
}

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
이 코드는 약간의 작업이 필요할 수 있습니다. JVM 가용성에 대해 실제로 보호 할 수 없습니다. 프로세스를 종료 할 준비를하십시오 (아마 자동으로). 코드는 다른 스레드 (예 : 종료 자 스레드)로 이동합니다. Thread.stopJava 라이브러리 코드에 문제가 발생합니다. 마찬가지로 Java 라이브러리 코드에는 권한이 필요합니다. 훨씬 더 허용하는 SecurityManager사용 java.security.AccessController. 클래스 로더는 사용자 코드의 자체 클래스에 대한 액세스도 허용해야합니다.
Tom Hawtin-tackline

4
이것이 매우 복잡한 주제라는 점을 감안할 때 Java "플러그인"을 안전한 방식으로 처리하는 기존 솔루션이 없습니까?
Nick Spacek

10
이 접근 방식의 문제는 SecurityManager를 System으로 설정할 때 실행중인 스레드에 영향을 줄뿐만 아니라 다른 스레드에도 영향을 미친다는 것입니다!
Gelin Luo

2
죄송하지만 thread.stop ()은 throwable로 잡을 수 있습니다. (thread.isAlive) Thread.stop () 동안 할 수 있지만 예외를 포착하는 함수를 재귀 적으로 호출 할 수 있습니다. 내 PC에서 테스트 한 결과 재귀 함수가 stop ()보다 우세합니다. 이제 CPU와 리소스를 훔치는 가비지 스레드가 있습니다
Lesto

9
System.setSecurityManager(…)전체 JVM에 영향을 미치는 사실 외에도 해당 메소드를 호출하는 스레드뿐만 아니라 스레드를 기반으로 보안 결정을 내리는 아이디어는 Java가 1.0에서 1.1로 전환되었을 때 포기되었습니다. 이때 어떤 스레드가 코드를 실행하는지에 관계없이 신뢰할 수없는 코드는 신뢰할 수있는 코드를 호출 할 수 있으며 그 반대의 경우도 마찬가지입니다. 개발자는 실수를 반복해서는 안됩니다.
Holger

18

분명히 그러한 계획은 모든 종류의 보안 문제를 제기합니다. Java에는 엄격한 보안 프레임 워크가 있지만 사소한 것은 아닙니다. 이를 망쳐 놓고 권한이없는 사용자가 중요한 시스템 구성 요소에 액세스하도록 할 가능성을 간과해서는 안됩니다.

그 경고는 제쳐두고, 소스 코드의 형태로 사용자 입력을받는 경우 가장 먼저해야 할 일은이를 Java 바이트 코드로 컴파일하는 것입니다. AFIAK, 이것은 기본적으로 수행 할 수 없으므로 javac에 대한 시스템 호출을 만들고 소스 코드를 디스크의 바이트 코드로 컴파일해야합니다. 여기 에이를위한 시작점으로 사용할 수있는 튜토리얼이 있습니다. 편집 : 주석에서 배웠 듯이 실제로 javax.tools.JavaCompiler를 사용하여 소스에서 Java 코드를 실제로 컴파일 할 수 있습니다.

JVM 바이트 코드가 있으면 ClassLoader의 defineClass 함수를 사용하여 JVM으로로드 할 수 있습니다 . 이로드 된 클래스에 대한 보안 컨텍스트를 설정하려면 ProtectionDomain 을 지정해야합니다 . ProtectionDomain에 대한 최소 생성자 에는 CodeSource 및 PermissionCollection 둘 다 필요합니다 . PermissionCollection은 여기에서 기본적으로 사용되는 개체입니다.이를 사용하여로드 된 클래스가 갖는 정확한 권한을 지정할 수 있습니다. 이러한 권한은 궁극적으로 JVM의 AccessController에 의해 시행되어야합니다 .

여기에는 많은 오류 지점이 있으며 구현하기 전에 모든 것을 완전히 이해하도록 매우주의해야합니다.


2
Java 컴파일은 JDK 6의 javax.tools API를 사용하여 매우 쉽습니다.
Alan Krueger

10

자바 샌드 권한 제한된 자바 코드를 실행하는 라이브러리이다. 화이트리스트에있는 클래스 및 리소스 집합에만 액세스를 허용하는 데 사용할 수 있습니다. 개별 메서드에 대한 액세스를 제한 할 수없는 것 같습니다. 이를 위해 사용자 정의 클래스 로더 및 보안 관리자가있는 시스템을 사용합니다.

나는 그것을 사용하지 않았지만 잘 디자인되고 합리적으로 잘 문서화되어 보입니다.

@waqas는 이것이 어떻게 자신을 구현할 수 있는지 설명하는 매우 흥미로운 답변을 제공했습니다. 그러나 이러한 보안에 중요하고 복잡한 코드를 전문가에게 맡기는 것이 훨씬 안전합니다.

프로젝트는 2013 년 이후로 업데이트되지 않았으며 제작자는 "실험적"이라고 설명합니다. 홈페이지는 사라졌지 만 Source Forge 항목은 남아 있습니다.

프로젝트 웹 사이트에서 수정 된 예제 코드 :

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

사용자 정의 SecurityManager가 스레드 단위가 아닌 JVM의 모든 스레드에 적용 되는 수락 된 답변의 문제를 해결하려면 SecurityManager다음과 같이 특정 스레드에 대해 활성화 / 비활성화 할 수 있는 사용자 정의 를 만들 수 있습니다.

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();
  }

}

ToggleSecurirtyManagerPermissionjava.security.Permission인증 된 코드 만 보안 관리자를 활성화 / 비활성화 할 수 있도록 하는 간단한 구현입니다 . 다음과 같이 보입니다.

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

}


ThreadLocal을 매우 현명하게 사용하여 시스템 범위의 SecurityManager를 효과적으로 스레드 범위 (대부분의 사용자가 원하는)로 만듭니다. 또한 InheritableThreadLocal을 사용하여 허용되지 않는 속성을 신뢰할 수없는 코드에 의해 생성 된 스레드로 자동 전송하는 것을 고려하십시오.
Nick

4

어떤 제안이나 해결책을 제시하는 것은 매우 늦었지만 여전히 비슷한 종류의 문제, 좀 더 연구 지향적 인 문제에 직면 해있었습니다. 기본적으로 저는 e- 러닝 플랫폼에서 Java 코스에 대한 프로그래밍 과제를 제공하고 자동 평가를 제공하려고했습니다.

  1. 한 가지 방법은 별도의 가상 머신 (JVM이 아님)을 생성하지만 각 학생에 대해 가능한 최소 구성 OS로 실제 가상 머신을 만드는 것입니다.
  2. 학생들이 이러한 컴퓨터에서 컴파일하고 실행하기를 원하는 프로그래밍 언어에 따라 Java 용 JRE 또는 라이브러리를 설치합니다.

이것이 상당히 복잡하고 많은 작업처럼 들리지만 Oracle Virtual Box는 이미 가상 머신을 동적으로 생성하거나 복제하는 Java API를 제공합니다. https://www.virtualbox.org/sdkref/index.html (참고, VMware에서도 동일한 작업을 수행하는 API를 제공합니다)

최소 크기 및 구성 Linux 배포판은 여기 http://www.slitaz.org/en/ 에서 참조 할 수 있습니다 .

따라서 이제 학생이 엉망으로 만들거나 시도하면 메모리 나 파일 시스템 또는 네트워킹, 소켓이 최대 일 수 있으며 자신의 VM을 손상시킬 수 있습니다.

또한 내부적으로 이러한 VM에 Java 용 Sandbox (보안 관리자)와 같은 추가 보안을 제공하거나 Linux에서 사용자 별 계정을 생성하여 액세스를 제한 할 수 있습니다.

도움이 되었기를 바랍니다 !!


3

다음은 문제에 대한 스레드로부터 안전한 솔루션입니다.

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.

}

의견을 부탁합니다!

CU

아르노


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.