itsource

Java 응용 프로그램의 악성 코드에 대한 샌드박스

mycopycode 2023. 1. 8. 14:38
반응형

Java 응용 프로그램의 악성 코드에 대한 샌드박스

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

이러한 제한은 기존 Java 샌드박스 모델에서는 가능한 것 같습니다만, 실행 중인 애플리케이션의 사용자가 제출한 부분만 사용할 수 있는 동적인 방법이 있습니까?

  1. 자체 스레드에서 신뢰할 수 없는 코드를 실행합니다.예를 들어 무한 루프 등의 문제를 방지하고 향후 단계를 쉽게 수행할 수 있습니다.메인 스레드에 스레드가 완료될 때까지 기다렸다가 시간이 너무 오래 걸리는 경우 Thread.stop을 사용하여 스레드를 종료합니다.Thread.stop은 권장되지 않지만 신뢰할 수 없는 코드는 리소스에 액세스할 수 없으므로 중지하는 것이 안전합니다.

  2. 해당 스레드에 Security Manager를 설정합니다.Security Manager 서브클래스를 만듭니다.이 서브클래스는 단순히 보안을 슬로우하기 위해 checkPermission(Permission perm)을 덮어씁니다.일부 선택 권한을 제외한 모든 권한에 대한 예외입니다.여기에는 방법 및 필요한 권한 목록이 나와 있습니다. Java 6 SDK의TM 권한입니다.

  3. 커스텀 ClassLoader를 사용하여 신뢰할 수 없는 코드를 로드합니다.신뢰할 수 없는 코드가 사용하는 모든 클래스에 대해 클래스 로더가 호출되므로 개별 JDK 클래스에 대한 액세스를 비활성화할 수 있습니다.여기서 해야 할 일은 허용된 JDK 클래스의 화이트리스트를 작성하는 것입니다.

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

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

이것은 꽤 상세한 주제이고, 나는 대부분 이것을 즉석에서 쓰고 있다.

하지만 어쨌든, 불완전하고, 위험에 따라 사용할 수 있고, 아마 버그가 있는 (의사) 코드:

클래스 로더

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

Security Manager(Security Manager)

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

분명히 그러한 계획은 모든 종류의 보안 문제를 야기한다.Java는 엄격한 보안 프레임워크를 가지고 있지만, 그것은 사소한 것이 아닙니다.이를 망치고 권한이 없는 사용자가 중요한 시스템 컴포넌트에 접근할 수 있도록 하는 것은 간과해서는 안 됩니다.

이 경고는 차치하고 소스 코드의 형태로 사용자 입력을 받는 경우 가장 먼저 해야 할 일은 자바 바이트 코드로 컴파일하는 것입니다.AFIAC, 이것은 네이티브로는 할 수 없기 때문에, javac에 시스템 콜을 발신해, 소스 코드를 디스크의 바이트 코드로 컴파일 할 필요가 있습니다.를 위한 시작점으로 사용할 수 있는 튜토리얼을 소개합니다.편집: 코멘트에서 학습한 바와 같이 자바 코드는 실제로 javax.tools를 사용하여 소스에서 네이티브로 컴파일할 수 있습니다.자바 컴파일러

JVM 바이트 코드가 있으면 ClassLoader의 defineClass 함수를 사용하여 JVM에 로드할 수 있습니다.로드된 이 클래스의 보안 컨텍스트를 설정하려면 ProtectionDomain을 지정해야 합니다.ProtectionDomain의 최소 생성자에는 CodeSource와 PermissionCollection이 모두 필요합니다.Permission Collection은 여기서 주로 사용하는 개체입니다. 이 개체를 사용하여 로드된 클래스가 가진 정확한 권한을 지정할 수 있습니다.이러한 권한은 최종적으로 JVM의 Access Controller에 의해 강제됩니다.

여기에는 여러 가지 오류 지점이 있을 수 있으며, 구현하기 전에 모든 사항을 완전히 이해하도록 매우 주의해야 합니다.

Java-Sandbox는 제한된 권한 집합으로 Java 코드를 실행하기 위한 라이브러리입니다.

화이트리스트 클래스 및 리소스 세트에만 액세스할 수 있도록 하는 데 사용할 수 있습니다.개별적인 방법에 대한 접근을 제한할 수는 없는 것 같습니다.이를 위해 커스텀클래스 로더와 보안 매니저가 있는 시스템을 사용합니다.

사용한 적은 없지만 디자인도 훌륭하고 문서도 꽤 잘 갖추어져 있는 것 같습니다.

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

승인된 답변의 문제를 해결하기 위해 커스텀은SecurityManagerJVM의 모든 스레드에 적용됩니다.슬레드별이 아니라 커스텀을 작성할 수 있습니다.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();
  }

}

ToggleSecurirtyManagerPermission단순한 구현일 뿐입니다.java.security.Permission인증된 코드만 Security Manager를 활성화/비활성화할 수 있도록 합니다.다음과 같습니다.

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

}

이 문제에 대한 스레드 세이프 솔루션은 다음과 같습니다.

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

아르노

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

  1. JVM이 아닌 개별 가상 머신을 생성하지만 각 학생에 대해 가능한 최소 구성의 OS를 가진 실제 가상 머신을 생성하는 방법이 있습니다.
  2. JRE for Java 또는 라이브러리는 프로그래밍 언어에 따라 설치합니다.이러한 머신에서 학생들이 컴파일하여 실행할 수 있도록 합니다.

매우 복잡하고 많은 작업이 수반된다는 것을 알지만 Oracle Virtual Box는 이미 가상 머신을 동적으로 생성하거나 복제할 수 있는 Java API를 제공하고 있습니다.https://www.virtualbox.org/sdkref/index.html (브이엠웨어에서도 같은 API를 제공하고 있습니다.

Linux 의 최소 사이즈와 구성의 배포에 대해서는, 다음의 URL 를 참조해 주세요.http://www.slitaz.org/en/,

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

또한 이러한 VM 내부에서는 Java용 Sandbox(보안 매니저)와 같은 추가 보안을 제공하거나 Linux에서 사용자 고유의 계정을 생성하여 액세스를 제한할 수 있습니다.

이게 도움이 되었으면 좋겠어!!

커스텀 Security Manager 및 Access Controller를 사용해야 합니다.자세한 내용은 Java 보안 아키텍처 및 Sun의 기타 보안 문서참조하십시오.

언급URL : https://stackoverflow.com/questions/502218/sandbox-against-malicious-code-in-a-java-application

반응형