天天看點

java中的安全模型(沙箱機制)

本博文轉載自java中的安全模型(沙箱機制)

本博文整合自:Java安全——了解Java沙箱、Java 安全模型介紹、Java的沙箱機制原理入門

相關介紹:

 我們都知道,程式員編寫一個Java程式,預設的情況下可以通路該機器的任意資源,比如讀取,删除一些檔案或者網絡操作等。當你把程式部署到正式的伺服器上,系統管理者要為伺服器的安全承擔責任,那麼他可能不敢确定你的程式會不會通路不該通路的資源,為了消除潛在的安全隐患,他可能有兩種辦法:

  • 讓你的程式在一個限定權限的帳号下運作。
  • 利用Java的沙箱機制來限定你的程式不能為非作歹。以下用于介紹該機制。
  • 什麼是沙箱?

     Java安全模型的核心就是Java沙箱(sandbox),什麼是沙箱?沙箱是一個限制程式運作的環境。沙箱機制就是将 Java 代碼限定在虛拟機(JVM)特定的運作範圍中,并且嚴格限制代碼對本地系統資源通路,通過這樣的措施來保證對代碼的有效隔離,防止對本地系統造成破壞。沙箱主要限制系統資源通路,那系統資源包括什麼?——CPU、記憶體、檔案系統、網絡。不同級别的沙箱對這些資源通路的限制也可以不一樣。

     所有的Java程式運作都可以指定沙箱,可以定制安全政策。

    java中的安全模型:

     在Java中将執行程式分成本地代碼和遠端代碼兩種,本地代碼預設視為可信任的,而遠端代碼則被看作是不受信的。對于授信的本地代碼,可以通路一切本地資源。而對于非授信的遠端代碼在早期的Java實作中,安全依賴于沙箱 (Sandbox) 機制。如下圖所示
    java中的安全模型(沙箱機制)
    但如此嚴格的安全機制也給程式的功能擴充帶來障礙,比如當使用者希望遠端代碼通路本地系統的檔案時候,就無法實作。是以在後續的 Java1.1 版本中,針對安全機制做了改進,增加了安全政策,允許使用者指定代碼對本地資源的通路權限。如下圖所示
    java中的安全模型(沙箱機制)
    在 Java1.2 版本中,再次改進了安全機制,增加了代碼簽名。不論本地代碼或是遠端代碼,都會按照使用者的安全政策設定,由類加載器加載到虛拟機中權限不同的運作空間,來實作差異化的代碼執行權限控制。如下圖所示
    java中的安全模型(沙箱機制)
    目前最新的安全機制實作,則引入了域 (Domain) 的概念。虛拟機會把所有代碼加載到不同的系統域和應用域,系統域部分專門負責與關鍵資源進行互動,而各個應用域部分則通過系統域的部分代理來對各種需要的資源進行通路。虛拟機中不同的受保護域 (Protected Domain),對應不一樣的權限 (Permission)。存在于不同域中的類檔案就具有了目前域的全部權限,如下圖所示
    java中的安全模型(沙箱機制)
    以上提到的都是基本的 Java 安全模型概念,在應用開發中還有一些關于安全的複雜用法,其中最常用到的 API 就是 doPrivileged。doPrivileged 方法能夠使一段受信任代碼獲得更大的權限,甚至比調用它的應用程式還要多,可做到臨時通路更多的資源。有時候這是非常必要的,可以應付一些特殊的應用場景。例如,應用程式可能無法直接通路某些系統資源,但這樣的應用程式必須得到這些資源才能夠完成功能。

    組成沙箱的基本元件:

    • 位元組碼校驗器(bytecode verifier):確定Java類檔案遵循Java語言規範。這樣可以幫助Java程式實作記憶體保護。但并不是所有的類檔案都會經過位元組碼校驗,比如核心類。
    • 類裝載器(class loader):其中類裝載器在3個方面對Java沙箱起作用
    1. 它防止惡意代碼去幹涉善意的代碼;
    2. 它守護了被信任的類庫邊界;
    3. 它将代碼歸入保護域,确定了代碼可以進行哪些操作。

     虛拟機為不同的類加載器載入的類提供不同的命名空間,命名空間由一系列唯一的名稱組成,每一個被裝載的類将有一個名字,這個命名空間是由Java虛拟機為每一個類裝載器維護的,它們互相之間甚至不可見。

     類裝載器采用的機制是雙親委派模式。1. 從最内層JVM自帶類加載器開始加載,外層惡意同名類得不到加載進而無法使用;

  • 由于嚴格通過包來區分了通路域,外層惡意的類通過内置代碼也無法獲得權限通路到内層類,破壞代碼就自然無法生效。
    • 存取控制器(access controller):存取控制器可以控制核心API對作業系統的存取權限,而這個控制的政策設定,可以由使用者指定。
    • 安全管理器(security manager):是核心API和作業系統之間的主要接口。實作權限控制,比存取控制器優先級高。
    • 安全軟體包(security package):java.security下的類和擴充包下的類,允許使用者為自己的應用增加新的安全特性,包括:
  • 安全提供者
  • 消息摘要
  • 數字簽名
  • 加密
  • 鑒别
  • 沙箱包含的要素:

    1. 權限

     權限是指允許代碼執行的操作。包含三部分:權限類型、權限名和允許的操作。權限類型是實作了權限的Java類名,是必需的。權限名一般就是對哪類資源進行操作的資源定位(比如一個檔案名或者通配符、網絡主機等),一般基于權限類型來設定,有的比如java.security.AllPermission不需要權限名。允許的操作也和權限類型對應,指定了對目标可以執行的操作行為,比如讀、寫等。如下面的例子:
    permission java.security.AllPermission;    //權限類型
    permission java.lang.RuntimePermission "stopThread";    //權限類型+權限名
    permission java.io.FilePermission "/tmp/foo" "read";    //權限類型+權限名+允許的操作
               
    标準權限:
    說明 類型 權限名 操作 例子
    檔案權限 java.io.FilePermission 檔案名(平台依賴) 讀、寫、删除、執行 允許所有問價的讀寫删除執行:permission java.io.FilePermission "<< ALL FILES>>", "read,write,delete,execute";。允許對使用者主目錄的讀:permission java.io.FilePermission "${user.home}/-", "read";
    套接字權限 java.net.SocketPermission 主機名:端口 接收、監聽、連接配接、解析 允許實作所有套接字操作:permission java.net.SocketPermission ":1-", "accept,listen,connect,resolve";。允許建立到特定網站的連接配接:permission java.net.SocketPermission ".abc.com:1-", "connect,resolve";
    屬性權限 java.util.PropertyPermission 需要通路的jvm屬性名 讀、寫 讀标準Java屬性:permission java.util.PropertyPermission "java.", "read";。在sdo包中建立屬性:permission java.util.PropertyPermission "sdo.", "read,write";
    運作時權限 java.lang.RuntimePermission 多種權限名[見附錄A] 允許代碼初始化列印任務:permission java.lang.RuntimePermission "queuePrintJob"
    AWT權限 java.awt.AWTPermission 6種權限名[見附錄B] 允許代碼充分使用robot類:permission java.awt.AWTPermission "createRobot"; permission java.awt.AWTPermission "readDisplayPixels";
    網絡權限 java.net.NetPermission 3種權限名[見附錄C] 允許安裝流處理器:permission java.net.NetPermission "specifyStreamHandler";。
    安全權限 java.security.SecurityPermission 多種權限名[見附錄D]
    序列化權限 java.io.SerializablePermission 2種權限名[見附錄E]
    反射權限 java.lang.reflect.ReflectPermission suppressAccessChecks(允許利用反射檢查任意類的私有變量)
    完全權限 java.security.AllPermission 無(擁有執行任何操作的權限)

    2. 代碼源

     代碼源是類所在的位置,表示為以URL位址。

    3. 保護域

     保護域用來組合代碼源和權限,這是沙箱的基本概念。保護域就在于聲明了比如由代碼A可以做權限B這樣的事情。

    4. 政策檔案

     政策檔案是控制沙箱的管理要素,一個政策檔案包含一個或多個保護域的項。政策檔案完成了代碼權限的指定任務,政策檔案包括全局和使用者專屬兩種。

     為了管理沙箱,政策檔案我認為是最重要的内容。JVM可以使用多個政策檔案,不過一般兩個最常用。一個是全局的:$JREHOME/lib/security/java.policy,作用于JVM的所有執行個體。另一個是使用者自己的,可以存儲到使用者的主目錄下。政策檔案可以使用jdk自帶的policytool工具編輯。

    預設的政策檔案我們先參考一下:

    // Standard extensions get all permissions by default
    grant codeBase "file:${{java.ext.dirs}}/*" {
            permission java.security.AllPermission;
    };
    // default permissions granted to all domains
    grant {
            // Allows any thread to stop itself using the java.lang.Thread.stop()
            // method that takes no argument.
            // Note that this permission is granted by default only to remain
            // backwards compatible.
            // It is strongly recommended that you either remove this permission
            // from this policy file or further restrict it to code sources
            // that you specify, because Thread.stop() is potentially unsafe.
            // See the API specification of java.lang.Thread.stop() for more
            // information.
            permission java.lang.RuntimePermission "stopThread";
            // allows anyone to listen on dynamic ports
            permission java.net.SocketPermission "localhost:0", "listen";
            // permission for standard RMI registry port
            permission java.net.SocketPermission "localhost:1099", "listen";
            // "standard" properies that can be read by anyone
            permission java.util.PropertyPermission "java.version", "read";
            permission java.util.PropertyPermission "java.vendor", "read";
            permission java.util.PropertyPermission "java.vendor.url", "read";
            permission java.util.PropertyPermission "java.class.version", "read";
            permission java.util.PropertyPermission "os.name", "read";
            permission java.util.PropertyPermission "os.version", "read";
            permission java.util.PropertyPermission "os.arch", "read";
            permission java.util.PropertyPermission "file.separator", "read";
            permission java.util.PropertyPermission "path.separator", "read";
            permission java.util.PropertyPermission "line.separator", "read";
            permission java.util.PropertyPermission "java.specification.version", "read";
            permission java.util.PropertyPermission "java.specification.vendor", "read";
            permission java.util.PropertyPermission "java.specification.name", "read";
            permission java.util.PropertyPermission "java.vm.specification.version", "read";
            permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
            permission java.util.PropertyPermission "java.vm.specification.name", "read";
            permission java.util.PropertyPermission "java.vm.version", "read";
            permission java.util.PropertyPermission "java.vm.vendor", "read";
            permission java.util.PropertyPermission "java.vm.name", "read";
    };
               

     政策檔案的内容格式就是這樣,grant授權允許操作某個權限。這個預設的政策檔案就指明了jdk擴充包可以有全部權限,允許代碼stop線程,允許監聽1099端口(1099号端口,是預設的伺服器端RMI監聽端口)等等。

     另一個很重要的是參數檔案——java.security,這個檔案和政策檔案在同一個目錄下。這個參數檔案定義了沙箱的一些參數。比如預設的沙箱檔案是這樣的(截取部分):

    # The default is to have a single system-wide policy file,
    # and a policy file in the user's home directory.
    policy.url.1=file:${java.home}/lib/security/java.policy
    policy.url.2=file:${user.home}/.java.policy
    # whether or not we expand properties in the policy file
    # if this is set to false, properties (${...}) will not be expanded in policy
    # files.
    policy.expandProperties=true
    # whether or not we allow an extra policy to be passed on the command line
    # with -Djava.security.policy=somefile. Comment out this line to disable
    # this feature.
    policy.allowSystemProperty=true
               
     policy.url.*這個屬性指明了使用的政策檔案,如上文所述,預設的兩個位置就在這裡配置,使用者可以自行更改順序和存儲位置。而policy.allowSystemProperty指明是否允許使用者自行通過指令行指定policy檔案。

    5. 密鑰庫

     儲存密鑰證書的地方。

    預設沙箱

     通過Java指令行啟動的Java應用程式,預設不啟用沙箱。要想啟用沙箱,啟動指令需要做如下形式的變更:
    java -Djava.security.manager <other args>
               
    沙箱啟動後,安全管理器會使用兩個預設的政策檔案來确定沙箱啟動參數。當然也可以通過指令指定:
    java -Djava.security.policy=<URL>
               
    如果要求啟動時隻遵循一個政策檔案,那麼啟動參數要加個等号,如下:
    java -Djava.security.policy==<URL>
               

    如何使用

    1. 限制讀檔案

    這個例子很簡單,首先寫一個r.txt檔案,裡面的内容是“abcd”,再寫個程式如下讀取這個r.txt。
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.InputStream;
    public class PolicyTest {
        public static void file() {
            File f = new File("D:\\github\\CDLib\\src\\main\\resources\\security\\r.txt");
            InputStream is;
            try {
                is = new FileInputStream(f);
                byte[] content = new byte[1024];
                while (is.read(content) != -1) {
                    System.out.println(new String(content));
                }
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        public static void main(String[] args) {
            // test read file.
            file();
        }
    }
               

    發現輸出是abcd。

     接下來修改java啟動參數,加入-Djava.security.manager,啟動了安全沙箱。再運作,輸出變成了異常

    Exception in thread "main" java.security.AccessControlException: access denied ("java.io.FilePermission" "D:\github\CDLib\src\main\resources\security\r.txt" "read") 
    at java.security.AccessControlContext.checkPermission(Unknown Source) 
    at java.security.AccessController.checkPermission(Unknown Source) 
    at java.lang.SecurityManager.checkPermission(Unknown Source) 
    at java.lang.SecurityManager.checkRead(Unknown Source) 
    at java.io.FileInputStream.(Unknown Source) 
    at com.taobao.cd.security.PolicyTest.main(PolicyTest.java:15)
               

    這裡已經提示了,通路被拒絕,說明了沙箱啟動,同時也驗證了預設沙箱——禁止本地檔案通路。

    再來,我們建構一個custom.policy檔案如下:

    grant {
        permission java.io.FilePermission "D:\\github\\CDLib\\src\\main\\resources\\security\\*", "read";
    };
               
    這裡建構了一條安全政策——允許讀取security目錄下的檔案。
    -Djava.security.policy=D:\\github\\CDLib\\src\\main\\resources\\security\\custom.policy
               

    附錄

    A

    用途說明
    accessClassInPackage. 允許代碼通路指定包中的類
    accessDeclaredMembers 允許代碼使用反射通路其他類中私有或保護的成員
    createClassLoader 允許代碼執行個體化類加載器
    createSecurityManager 允許代碼執行個體化安全管理器,它将允許程式化的實作對沙箱的控制
    defineClassInPackage. 允許代碼在指定包中定義類
    exitVM 允許代碼關閉整個虛拟機
    getClassLoader 允許代碼通路類加載器以獲得某個特定的類
    getProtectionDomain 允許代碼通路保護域對象以獲得某個特定類
    loadlibrary. 允許代碼裝載指定類庫
    modifyThread 允許代碼調整指定的線程參數
    modifyThreadGroup 允許代碼調整指定的線程組參數
    queuePrintJob 允許代碼初始化一個列印任務
    readFileDescriptor 允許代碼讀檔案描述符(相應的檔案是由其他保護域中的代碼打開的)
    setContextClassLoader 允許代碼為某線程設定上下文類加載器
    setFactory 允許代碼建立套接字工廠
    setIO 允許代碼重定向System.in、System.out或System.err輸入輸出流
    setSecurityManager 允許代碼設定安全管理器
    stopThread 允許代碼調用線程類的stop()方法
    writeFileDescriptor 允許代碼寫檔案描述符

    B

    accessClipboard 允許通路系統的全局剪貼闆
    accessEventQueue 允許直接通路事件隊列
    createRobot 允許代碼建立AWT的Robot類
    listenToAllAWTEvents 允許代碼直接監聽事件分發
    readDisplayPixels 允許AWT Robot讀顯示屏上的像素
    showWindowWithoutWarningBanner 允許建立無标題欄的視窗

    C

    specifyStreamHandler 允許在URL類中安裝新的流處理器
    setDefaultAuthenticator 可以安裝鑒别類
    requestPassworkAuthentication 可以完成鑒别

    D

    addIdentityCertificate 為Identity增加一個證書
    clearProviderProperties. 針對指定的提供者,删除所有屬性
    createAccessControlContext 允許建立一個存取控制器的上下文環境
    getDomainCombiner 允許撤銷保護域
    getPolicy 檢索可以實作沙箱政策的類
    getProperty. 讀取指定的安全屬性
    getSignerPrivateKey 由Signer對象擷取私有密鑰
    insertProvider. 将指定的提供者添加到響應的安全提供者組中
    loadProviderProperties. 裝載指定的提供者的屬性
    printIdentity 列印Identity類内容
    putAllProviderProperties. 更新指定的提供者的屬性
    putProviderProperty. 為指定的提供者增加一個屬性
    removeIdentityCertificate 取消Identity對象的證書
    removeProvider. 将指定的提供者從相應的安全提供者組中删除
    removeProviderProperty. 删除指定的安全提供者的某個屬性
    setIdentityInfo 為某個Identity對象設定資訊串
    setIdentityPublicKey 為某個Identity對象設定公鑰
    setPolicy 設定可以實作沙箱政策的類
    setProperty. 設定指定的安全屬性
    setSignerKeyPair 在Signer對象中設定密鑰對
    setSystemScope 設定系統所用的IdentityScope

    E

    enableSubstitution 允許實作ObjectInputStream類的enableResolveObject()方法和ObjectOutputStream類的enableReplaceObject()方法
    enableSubclassImplementation 允許ObjectInputStream和ObjectOutputStream建立子類,子類可以覆寫readObject()和writeObject()方法

繼續閱讀