本博文轉載自java中的安全模型(沙箱機制)html
本博文整合自:Java安全——理解Java沙箱、Java 安全模型介紹、Java的沙箱機制原理入門java
咱們都知道,程序員編寫一個Java程序,默認的狀況下能夠訪問該機器的任意資源,好比讀取,刪除一些文件或者網絡操做等。當你把程序部署到正式的服務器上,系統管理員要爲服務器的安全承擔責任,那麼他可能不敢肯定你的程序會不會訪問不應訪問的資源,爲了消除潛在的安全隱患,他可能有兩種辦法:git
Java安全模型的核心就是Java沙箱(sandbox),什麼是沙箱?沙箱是一個限制程序運行的環境。沙箱機制就是將 Java 代碼限定在虛擬機(JVM)特定的運行範圍中,而且嚴格限制代碼對本地系統資源訪問,經過這樣的措施來保證對代碼的有效隔離,防止對本地系統形成破壞。沙箱主要限制系統資源訪問,那系統資源包括什麼?——CPU、內存、文件系統、網絡。不一樣級別的沙箱對這些資源訪問的限制也能夠不同。程序員
全部的Java程序運行均可以指定沙箱,能夠定製安全策略。github
在Java中將執行程序分紅本地代碼和遠程代碼兩種,本地代碼默認視爲可信任的,而遠程代碼則被看做是不受信的。對於授信的本地代碼,能夠訪問一切本地資源。而對於非授信的遠程代碼在早期的Java實現中,安全依賴於沙箱 (Sandbox) 機制。以下圖所示安全
但如此嚴格的安全機制也給程序的功能擴展帶來障礙,好比當用戶但願遠程代碼訪問本地系統的文件時候,就沒法實現。所以在後續的 Java1.1 版本中,針對安全機制作了改進,增長了安全策略,容許用戶指定代碼對本地資源的訪問權限。以下圖所示服務器
在 Java1.2 版本中,再次改進了安全機制,增長了代碼簽名。不論本地代碼或是遠程代碼,都會按照用戶的安全策略設定,由類加載器加載到虛擬機中權限不一樣的運行空間,來實現差別化的代碼執行權限控制。以下圖所示markdown
當前最新的安全機制實現,則引入了域 (Domain) 的概念。虛擬機會把全部代碼加載到不一樣的系統域和應用域,系統域部分專門負責與關鍵資源進行交互,而各個應用域部分則經過系統域的部分代理來對各類須要的資源進行訪問。虛擬機中不一樣的受保護域 (Protected Domain),對應不同的權限 (Permission)。存在於不一樣域中的類文件就具備了當前域的所有權限,以下圖所示網絡
以上提到的都是基本的 Java 安全模型概念,在應用開發中還有一些關於安全的複雜用法,其中最經常使用到的 API 就是 doPrivileged。doPrivileged 方法可以使一段受信任代碼得到更大的權限,甚至比調用它的應用程序還要多,可作到臨時訪問更多的資源。有時候這是很是必要的,能夠應付一些特殊的應用場景。例如,應用程序可能沒法直接訪問某些系統資源,但這樣的應用程序必須獲得這些資源纔可以完成功能。dom
字節碼校驗器(bytecode verifier):確保Java類文件遵循Java語言規範。這樣能夠幫助Java程序實現內存保護。但並非全部的類文件都會通過字節碼校驗,好比核心類。
虛擬機爲不一樣的類加載器載入的類提供不一樣的命名空間,命名空間由一系列惟一的名稱組成,每個被裝載的類將有一個名字,這個命名空間是由Java虛擬機爲每個類裝載器維護的,它們互相之間甚至不可見。
類裝載器採用的機制是雙親委派模式。1. 從最內層JVM自帶類加載器開始加載,外層惡意同名類得不到加載從而沒法使用;
存取控制器(access controller):存取控制器能夠控制核心API對操做系統的存取權限,而這個控制的策略設定,能夠由用戶指定。
安全管理器(security manager):是核心API和操做系統之間的主要接口。實現權限控制,比存取控制器優先級高。
安全軟件包(security package):java.security下的類和擴展包下的類,容許用戶爲本身的應用增長新的安全特性,包括:
權限是指容許代碼執行的操做。包含三部分:權限類型、權限名和容許的操做。權限類型是實現了權限的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 | 無(擁有執行任何操做的權限) | 無 |
代碼源是類所在的位置,表示爲以URL地址。
保護域用來組合代碼源和權限,這是沙箱的基本概念。保護域就在於聲明瞭好比由代碼A能夠作權限B這樣的事情。
策略文件是控制沙箱的管理要素,一個策略文件包含一個或多個保護域的項。策略文件完成了代碼權限的指定任務,策略文件包括全局和用戶專屬兩種。
爲了管理沙箱,策略文件我認爲是最重要的內容。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文件。
保存密鑰證書的地方。
經過Java命令行啓動的Java應用程序,默認不啓用沙箱。要想啓用沙箱,啓動命令須要作以下形式的變動:
java -Djava.security.manager <other args>
沙箱啓動後,安全管理器會使用兩個默認的策略文件來肯定沙箱啓動參數。固然也能夠經過命令指定:
java -Djava.security.policy=<URL>
若是要求啓動時只遵循一個策略文件,那麼啓動參數要加個等號,以下:
java -Djava.security.policy==<URL>
這個例子很簡單,首先寫一個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
再執行,結果輸出了abcd。
如上例。咱們經過自定義policy文件修改了默認沙箱的安全策略,再經過啓動參數開啓沙箱模式。這樣就能夠構造咱們本身想要的沙箱效果了。
權限名 | 用途說明 |
---|---|
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 | 容許代碼寫文件描述符 |
權限名 | 用途說明 |
---|---|
accessClipboard | 容許訪問系統的全局剪貼板 |
accessEventQueue | 容許直接訪問事件隊列 |
createRobot | 容許代碼建立AWT的Robot類 |
listenToAllAWTEvents | 容許代碼直接監聽事件分發 |
readDisplayPixels | 容許AWT Robot讀顯示屏上的像素 |
showWindowWithoutWarningBanner | 容許建立無標題欄的窗口 |
權限名 | 用途說明 |
---|---|
specifyStreamHandler | 容許在URL類中安裝新的流處理器 |
setDefaultAuthenticator | 能夠安裝鑑別類 |
requestPassworkAuthentication | 能夠完成鑑別 |
權限名 | 用途說明 |
---|---|
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 |
權限名 | 用途說明 |
---|---|
enableSubstitution | 容許實現ObjectInputStream類的enableResolveObject()方法和ObjectOutputStream類的enableReplaceObject()方法 |
enableSubclassImplementation | 容許ObjectInputStream和ObjectOutputStream建立子類,子類能夠覆蓋readObject()和writeObject()方法 |