2009-11-11 248 views
40

我想讓我的應用程序運行其他人的代碼,即插件。但是,我必須具備哪些選項才能確保安全,因此不會編寫惡意代碼。我如何控制他們可以或不可以做的事情?如何創建Java沙箱?

我偶然發現JVM有一個「內置沙箱」功能 - 它是什麼,這是唯一的方法?是否有用於製作沙盒的第三方Java庫?

我有什麼選擇?讚賞指南和例子的鏈接!

回答

19
  • 定義並註冊自己的安全管理將允許你限制的代碼做什麼 - 看Oracle文檔SecurityManager

  • 此外,請考慮創建單獨的加載代碼的機制 - 即您可以編寫或實例化另一個Classloader以從特殊位置加載代碼。您可能有一個加載代碼的約定 - 例如從特殊目錄或特殊格式的zip文件(如WAR文件和JAR文件)。如果你正在編寫一個類加載器,它會讓你處於加載代碼的工作狀態。這意味着如果你看到一些你想要拒絕的東西(或者某些依賴),你可能無法加載代碼。 http://java.sun.com/javase/6/docs/api/java/lang/ClassLoader.html

4

對於需要使用非標準AppContext類,它可以在任何時候改變一個AWT/Swing應用程序。因此,爲了提高效率,您需要啓動另一個進程來運行插件代碼,並處理兩者之間的通信(有點像Chrome)。插件過程將需要SecurityManager集合和ClassLoader來隔離插件代碼並將適當的ProtectionDomain應用於插件類。

5

看看the java-sandbox project,它允許輕鬆地創建非常靈活的沙箱來運行不受信任的代碼。

+1

感謝您發佈該圖書館,它使我的工作更容易。 –

+0

鏈接已死亡。 Google找到[this](https://sourceforge.net/projects/javasandboxlibrary/),是不是一樣? – planetguy32

+0

@Arno Mittelbach現在無法使用 – Sheldon

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. 

} 
0

關於這個問題的討論,啓發了我開始了我自己的沙箱項目。

https://github.com/Black-Mantha/sandbox

在這裏面我遇到一個重要的安全問題:「你怎麼讓沙箱外部的代碼繞過SecurityManager?」

我把沙箱代碼放在它自己的ThreadGroup中,並且總是在該組外部授予權限。如果您需要在該組中運行特權代碼(例如在回調中),則可以使用ThreadLocal爲該線程設置一個標誌。類加載器將阻止沙箱訪問ThreadLocal。另外,如果你這樣做,你需要禁止使用終結器,因爲它們在ThreadGroup之外的專用線程中運行。