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.