Java中4種安全沙箱機制之安全管理器及Java API
簡介
java安全沙箱的前三類保證了jvm所執行程式的完整性,使得jvm不會因為執行有漏洞或惡意的程式碼而導致出現不可預期的狀態。而第四類沙箱模型是“類安全管理器及Java API”,它能保護jvm在執行有漏洞或惡意的程式碼不會破壞外部資源。java通過稱為安全管理器的一類API來保證這類安全性。
安全策略檔案
首先介紹下安全策略檔案,如果啟用了安全管理器,預設會使用jre自帶的安全策略檔案$JAVA_HOME/jre/lib/security/java.policy來指定訪問外部資源的許可權,該策略檔案也可以通過jvm引數-Djava.security.policy來指定。
Policy檔案的主要格式如下:
keystore "some_keystore_url", "keystore_type";
grant [SignedBy "signer_names"] [, CodeBase "URL"] {
Permission permission_class_name ["target_name"] [,"action"] [,SignedBy"signer_names"];
… …
};
-
"keystore"記錄
一個keystore是一個私有金鑰(private keys)資料庫和相應的數字簽名,例如X.509證書。Policy檔案中可能只有一條keystore記錄(也可能不含有該記錄),它可以出現在檔案中grant記錄以外的任何地方。Policy配置檔案中指定的keystores用於尋找grant記錄中指定的、簽名者的公共金鑰(public keys),如果任何grant記錄指定簽名者(signer_names),那麼,keystore記錄必須出現在policy配置檔案中。
"some_keystore_url"是指keystore的URL位置,"keystore_type"是指keystore的型別。第二個選項是可選項,如果沒有指定,該型別則假定由安全屬性檔案(java.security)中的"keystore.type"屬性來確定。keystore型別定義了keystore資訊的儲存和資料格式,用於保護keystore中的私有金鑰和keystore完整性的演算法。Sun Microsystems支援的預設型別為“JKS”。
-
"grant"記錄
在Policy檔案中的每一個grant記錄含有一個CodeSource(指定程式碼)及其permission(許可
target_name用來指定目標類的位置,action用於指定目標類擁有的許可權。 target_name可以直接指定類名(可以是絕對或相對路徑),目錄名,也可以使用萬用字元/、/*或著/-。
directory/ 表示directory目錄下的所有.class檔案,不包括.jar檔案
directory/* 表示directory目錄下的所有的.class及.jar檔案
directory/- 表示directory目錄下的所有的.class及.jar檔案,包括子目錄
下面是一個policy檔案的demo:
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};
grant {
permission java.lang.RuntimePermission "stopThread";
permission java.net.SocketPermission "localhost:1099", "listen";
permission java.util.PropertyPermission "java.version", "read";
... ...
};
例如:對於java.net.SocketPermission,action可以是:listen,accept,connect,read,write;對於java.io.FilePermission,action可以是:read, write, delete和execute。
安全管理器
java的安全管理器可以定製,也可以使用jdk的預設實現java.lang.SecurityManager,啟動安全管理器的話有兩種方式,一種是通過硬編碼的方式啟動,另外一種是通過jvm引數-Djava.security.manager啟動。
下面的測試用例都使用jre的預設policy檔案配置:
grant {
... ...
permission java.util.PropertyPermission "java.version", "read";
... ...
};
該策略檔案指定了"java.version"的讀許可權,然後並沒有指定寫許可權。參考以下測試用例:
public static void main(String... args) {
String javaVersion=System.getProperty("java.version");
System.err.println(javaVersion);
System.setProperty("java.version","1.7.0_45");
String javaNewVersion=System.getProperty("java.version");
System.err.println(javaNewVersion);
}
首先讀"java.version"屬性,然後把該屬性改寫為1.7.0_45,最後再讀取它並打印出來,輸出結果為:
1.8.0_45
1.7.0_45
可以看到預設的jdk版本為1.8.0_45(1.8.0是java的主版本號,45是次版本號)。
然後前面的policy檔案只指定了read許可權,為什麼這裡卻write成功了?那是因為預設情況下java並不啟動安全管理器,可以使用硬編碼System.setSecurityManager()來啟動安全管理器,參考以下測試用例:
public static void main(String... args) {
// 啟用安全管理器
System.setSecurityManager(new SecurityManager());
String javaVersion=System.getProperty("java.version");
System.err.println(javaVersion);
System.setProperty("java.version","1.7.0_45");
String javaNewVersion=System.getProperty("java.version");
System.err.println(javaNewVersion);
}
此時的輸出結果為:
1.8.0_45
Exception in thread "main" java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.version" "write")
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:457)
at java.security.AccessController.checkPermission(AccessController.java:884)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
at java.lang.System.setProperty(System.java:792)
at test.Test.main(Test.java:9)
結果很明確:可以讀,但不能寫,我們可以來修改policy檔案,讓它支援"java.version"的寫操作:
grant {
... ...
permission java.util.PropertyPermission "java.version", "read";
permission java.util.PropertyPermission "java.version", "write";
... ...
};
此時再執行上面的用例,輸出結果為:
1.8.0_45
1.7.0_45
可以看到此時可以支援"java.version"的寫操作了。
另外使用jvm引數-Djava.security.manager也能啟用安全管理器,此時jvm啟動時會設定"java.security.manager"系統屬性為空字串"",此時會在啟動sun.misc.Launcher時初始化安全管理器,檢視sun.misc.Launcher的原始碼:
public Launcher(){
ExtClassLoader extclassloader;
try {
extclassloader = ExtClassLoader.getExtClassLoader();
} catch(IOException ioexception) {
throw new InternalError("Could not create extension class loader", ioexception);
}
try {
loader = AppClassLoader.getAppClassLoader(extclassloader);
} catch(IOException ioexception1) {
throw new InternalError("Could not create application class loader", ioexception1);
}
Thread.currentThread().setContextClassLoader(loader);
String s = System.getProperty("java.security.manager");
if(s != null) {
SecurityManager securitymanager = null;
if("".equals(s) || "default".equals(s))
securitymanager = new SecurityManager();
else
try {
securitymanager = (SecurityManager)loader.loadClass(s).newInstance();
}
catch(IllegalAccessException illegalaccessexception) { }
catch(InstantiationException instantiationexception) { }
catch(ClassNotFoundException classnotfoundexception) { }
catch(ClassCastException classcastexception) { }
if(securitymanager != null)
System.setSecurityManager(securitymanager);
else
throw new InternalError((new StringBuilder()).append("Could not create SecurityManager: ").append(s).toString());
}
}
可以看到"java.security.manager"系統屬性為空字串""時會啟用jdk的預設安全管理器SecurityManager。
Java API
java的安全機制api大部分都在java.security包下,因為原始碼很多,就不貼出來了,大家感興趣的話可以研究下。以下是一些常用的類的api介紹:
-
java.security.AccessControlContext:基於它所封裝的上下文作出系統資源訪問決定,該類最常用於將程式碼標記為享有“特權”。
-
java.security.AccessController:用於與訪問控制相關的操作和決定。java.security.SecureClassLoader此類擴充套件了 ClassLoader,支援使用相關的程式碼源和許可權定義類,這些程式碼源和許可權預設情況下可根據系統策略獲取到。
-
java.security.Provider:此類表示 Java 安全 API "provider",這裡 provider 實現了 Java 安全性的一部分或者全部。
-
java.security.Permission:表示訪問系統資源的抽象類。所有許可權都有一個名稱(對它們的解釋依賴於子類),以及用來定義特定 Permission 子類的語義的抽象方法。