แซนด์บ็อกซ์ต่อต้านโค้ดที่เป็นอันตรายในแอปพลิเคชัน Java


92

ในสภาพแวดล้อมเซิร์ฟเวอร์จำลองที่ผู้ใช้ได้รับอนุญาตให้ส่งโค้ดของตัวเองเพื่อให้เซิร์ฟเวอร์เรียกใช้มันจะเป็นประโยชน์อย่างชัดเจนสำหรับโค้ดที่ผู้ใช้ส่งมาเพื่อให้รันในแซนด์บ็อกซ์ด้านข้างไม่ต่างจาก Applets ที่อยู่ในเบราว์เซอร์ ฉันต้องการใช้ประโยชน์จาก JVM เองแทนที่จะเพิ่มเลเยอร์ VM อื่นเพื่อแยกส่วนประกอบที่ส่งมาเหล่านี้

ข้อ จำกัด ประเภทนี้ดูเหมือนจะเป็นไปได้โดยใช้โมเดลแซนด์บ็อกซ์ Java ที่มีอยู่ แต่มีวิธีแบบไดนามิกในการเปิดใช้งานสำหรับเฉพาะส่วนที่ผู้ใช้ส่งมาของแอปพลิเคชันที่กำลังรันอยู่หรือไม่?

คำตอบ:


110
  1. รันโค้ดที่ไม่น่าเชื่อถือในเธรดของตัวเอง ตัวอย่างเช่นป้องกันปัญหาเกี่ยวกับลูปที่ไม่มีที่สิ้นสุดเป็นต้นและทำให้ขั้นตอนในอนาคตง่ายขึ้น รอให้เธรดหลักรอให้เธรดเสร็จสิ้นและหากใช้เวลานานเกินไปให้ฆ่าเธรดด้วย Thread.stop Thread.stop เลิกใช้งานแล้ว แต่เนื่องจากโค้ดที่ไม่น่าเชื่อถือไม่ควรเข้าถึงทรัพยากรใด ๆ จึงปลอดภัยที่จะฆ่ามัน

  2. ตั้งค่าSecurityManagerบนเธรดนั้น สร้างคลาสย่อยของ SecurityManager ซึ่งจะแทนที่checkPermission (สิทธิ์การอนุญาต)เพื่อเพียงแค่โยนSecurityExceptionสำหรับสิทธิ์ทั้งหมดยกเว้นบางส่วนที่เลือก มีรายการวิธีการและสิทธิ์ที่ต้องการที่นี่: สิทธิ์ใน Java TM 6 SDKSDK

  3. ใช้ ClassLoader ที่กำหนดเองเพื่อโหลดรหัสที่ไม่น่าเชื่อถือ ตัวโหลดคลาสของคุณจะถูกเรียกสำหรับคลาสทั้งหมดที่ใช้โค้ดที่ไม่น่าเชื่อถือดังนั้นคุณสามารถทำสิ่งต่างๆเช่นปิดการเข้าถึงคลาส JDK แต่ละคลาส สิ่งที่ต้องทำคือมี white-list ของคลาส JDK ที่อนุญาต

  4. คุณอาจต้องการเรียกใช้โค้ดที่ไม่น่าเชื่อถือใน JVM แยกต่างหาก แม้ว่าขั้นตอนก่อนหน้านี้จะทำให้โค้ดปลอดภัย แต่ก็มีสิ่งที่น่ารำคาญอย่างหนึ่งที่โค้ดแยกยังสามารถทำได้: จัดสรรหน่วยความจำให้มากที่สุดเท่าที่จะทำได้ซึ่งจะทำให้รอยเท้าที่มองเห็นได้ของแอปพลิเคชันหลักเติบโตขึ้น

JSR 121: ข้อกำหนด API การแยกแอปพลิเคชันได้รับการออกแบบมาเพื่อแก้ปัญหานี้ แต่น่าเสียดายที่ยังไม่มีการใช้งาน

นี่เป็นหัวข้อที่มีรายละเอียดค่อนข้างมากและส่วนใหญ่ฉันจะเขียนทั้งหมดนี้ไว้ด้านบนของหัว

แต่อย่างไรก็ตามรหัสบางอย่างที่ไม่สมบูรณ์ใช้ตามความเสี่ยงของคุณเองอาจเป็นข้อบกพร่อง (หลอก):

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
}

เกลียว

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.stopจะทำให้เกิดปัญหาในรหัสไลบรารี Java ในทำนองเดียวกันรหัสไลบรารี Java จะต้องการสิทธิ์ ที่ดีมากที่จะช่วยให้การใช้งานSecurityManager java.security.AccessControllerตัวโหลดคลาสควรอนุญาตให้เข้าถึงคลาสของรหัสผู้ใช้เองด้วย
Tom Hawtin - แท็กไลน์

4
เนื่องจากเรื่องนี้เป็นเรื่องที่ซับซ้อนจึงไม่มีโซลูชันสำหรับจัดการ "ปลั๊กอิน" Java ด้วยวิธีที่ปลอดภัยหรือไม่?
Nick Spacek

10
ปัญหาของวิธีนี้คือเมื่อคุณตั้งค่า SecurityManager เป็น System มันไม่เพียงส่งผลกระทบต่อเธรดที่กำลังทำงานอยู่เท่านั้น แต่ยังส่งผลกระทบต่อเธรดอื่นด้วย!
ลั่ว

2
ขออภัย แต่ thread.stop () สามารถจับได้ด้วย Throwable คุณสามารถในขณะที่ (thread.isAlive) Thread.stop () แต่ฉันสามารถเรียกฟังก์ชันที่จับข้อยกเว้นซ้ำได้ ทดสอบบนพีซีของฉันแล้วฟังก์ชันเรียกซ้ำจะชนะการหยุด () ตอนนี้คุณมีกระทู้ขยะขโมยซีพียูและทรัพยากร
Lesto

9
นอกจากข้อเท็จจริงที่System.setSecurityManager(…)จะส่งผลกระทบต่อ JVM ทั้งหมดไม่เพียง แต่เธรดที่เรียกใช้เมธอดนั้นความคิดในการตัดสินใจด้านความปลอดภัยตามเธรดได้ถูกละทิ้งเมื่อ Java เปลี่ยนจาก 1.0 เป็น 1.1 ในเวลานี้เป็นที่ยอมรับว่ารหัสที่ไม่น่าเชื่อถืออาจเรียกใช้รหัสที่เชื่อถือได้และในทางกลับกันไม่ว่าเธรดใดจะรันโค้ด ผู้พัฒนาไม่ควรทำผิดซ้ำ
Holger

18

เห็นได้ชัดว่าโครงการดังกล่าวก่อให้เกิดความกังวลด้านความปลอดภัยทุกประเภท Java มีกรอบการรักษาความปลอดภัยที่เข้มงวด แต่ก็ไม่สำคัญ ไม่ควรมองข้ามความเป็นไปได้ในการขันและปล่อยให้ผู้ใช้ที่ไม่มีสิทธิ์เข้าถึงส่วนประกอบที่สำคัญ

นอกจากนี้คำเตือนนี้หากคุณกำลังป้อนข้อมูลผู้ใช้ในรูปแบบของซอร์สโค้ดสิ่งแรกที่คุณต้องทำคือคอมไพล์เป็น Java bytecode AFIAK ไม่สามารถทำได้โดยกำเนิดดังนั้นคุณจะต้องทำการเรียกระบบไปที่ javac และรวบรวมซอร์สโค้ดเป็น bytecode บนดิสก์นี่คือบทช่วยสอนที่สามารถใช้เป็นจุดเริ่มต้นสำหรับสิ่งนี้ แก้ไข : ตามที่ฉันได้เรียนรู้ในความคิดเห็นคุณสามารถรวบรวมโค้ด Java จากซอร์สได้โดยใช้javax.tools.JavaCompiler

เมื่อคุณมี JVM bytecode แล้วคุณสามารถโหลดลงใน JVM โดยใช้ฟังก์ชันdefineClass ของ ClassLoader ในการตั้งบริบทการรักษาความปลอดภัยสำหรับการเรียนการโหลดนี้คุณจะต้องระบุProtectionDomain ตัวสร้างน้อยหาProtectionDomainต้องใช้ทั้ง CodeSource และPermissionCollection PermissionCollection เป็นวัตถุของการใช้งานหลักสำหรับคุณที่นี่คุณสามารถใช้เพื่อระบุสิทธิ์ที่แน่นอนที่คลาสที่โหลดมี สิทธิ์เหล่านี้ควรได้รับการบังคับใช้ในท้ายที่สุดโดย JVM ของAccessController

มีจุดผิดพลาดมากมายที่เป็นไปได้ที่นี่และคุณควรระมัดระวังอย่างยิ่งในการทำความเข้าใจทุกอย่างให้ครบถ้วนก่อนที่จะดำเนินการใด ๆ


2
การคอมไพล์ Java นั้นค่อนข้างง่ายโดยใช้ javax.tools API ของ JDK 6
อลันครูเกอร์

10

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

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

}

ToggleSecurirtyManagerPermissionเป็นเพียงการนำไปใช้งานง่ายๆjava.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 "";
  }

}

3
อ้างคุณ (ของตนเอง) แหล่งที่มา: alphaloop.blogspot.com/2014/08/...และgithub.com/alphaloop/selective-security-manager
ziesemer

การใช้ ThreadLocal อย่างชาญฉลาดเพื่อสร้าง SecurityManagers ที่กำหนดขอบเขตระบบได้อย่างมีประสิทธิภาพ (ซึ่งผู้ใช้ส่วนใหญ่ต้องการ) นอกจากนี้ให้พิจารณาใช้ InheritableThreadLocal เพื่อส่งคุณสมบัติที่ไม่ได้รับอนุญาตไปยังเธรดที่สร้างโดยรหัสที่ไม่น่าเชื่อถือโดยอัตโนมัติ
Nick

4

มันสายไปแล้วที่จะให้คำแนะนำหรือแนวทางแก้ไข แต่ฉันก็ยังประสบปัญหาคล้าย ๆ กันซึ่งเน้นการวิจัยมากกว่า โดยพื้นฐานแล้วฉันพยายามจัดเตรียมข้อกำหนดและการประเมินอัตโนมัติสำหรับการกำหนดโปรแกรมสำหรับหลักสูตร Java ในแพลตฟอร์ม e-learning

  1. วิธีหนึ่งอาจเป็นสร้างเครื่องเสมือนแยกต่างหาก (ไม่ใช่ JVM) แต่เครื่องเสมือนจริงที่มีระบบปฏิบัติการขั้นต่ำที่เป็นไปได้สำหรับนักเรียนแต่ละคน
  2. ติดตั้ง JRE สำหรับ Java หรือไลบรารีตามภาษาโปรแกรมของคุณไม่ว่าคุณต้องการให้นักเรียนคอมไพล์และดำเนินการบนเครื่องเหล่านี้

ฉันรู้ว่าสิ่งนี้ฟังดูค่อนข้างซับซ้อนและมีงานมากมาย แต่ Oracle Virtual Box มี Java API เพื่อสร้างหรือโคลนเครื่องเสมือนแบบไดนามิกอยู่แล้ว https://www.virtualbox.org/sdkref/index.html (หมายเหตุแม้ VMware ก็มี API สำหรับการทำเช่นเดียวกัน)

และสำหรับขนาดขั้นต่ำและการกำหนดค่าการกระจาย Linux คุณสามารถอ้างถึงสิ่งนี้ได้ที่นี่http://www.slitaz.org/en/ ,

ดังนั้นตอนนี้หากนักเรียนทำเลอะเทอะหรือพยายามทำอาจเป็นเพราะหน่วยความจำหรือระบบไฟล์หรือเครือข่ายซ็อกเก็ตเขาสามารถทำลาย VM ของตัวเองได้มากที่สุด

นอกจากนี้ภายใน VM เหล่านี้คุณสามารถให้ความปลอดภัยเพิ่มเติมเช่น Sandbox (ตัวจัดการความปลอดภัย) สำหรับ Java หรือการสร้างบัญชีเฉพาะผู้ใช้บน 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.

}

โปรดแสดงความคิดเห็น!

จุฬา

Arno


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.