一、文章的目的
這是一篇對Java安全管理器入門的文章,目的是簡單了解什么是SecurityManager,對管理器進行簡單配置,解決簡單問題。
比如在閱讀源碼的時候,發現這樣的代碼,想了解是做什么的:
SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkWrite(name); }
亦或者在本機運行正常,在服務器運行報錯,想解決問題:
Exception in thread "main" java.security.AccessControlException: access denied (java.lang.RuntimePermission createSecurityManager) at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374) at java.security.AccessController.checkPermission(AccessController.java:549) at java.lang.SecurityManager.checkPermission(SecurityManager.java:532) at java.lang.SecurityManager.<init>(SecurityManager.java:282) at xia.study._01Thread.ThreadTest.creatThread1(ThreadTest.java:18) at xia.study._01Thread.ThreadTest.main(ThreadTest.java:13)
這時候具備一些SecurityManager的基礎知識還是有必要的。
二、SecurityManager應用場景
當運行未知的Java程序的時候,該程序可能有惡意代碼(刪除系統文件、重啟系統等),為了防止運行惡意代碼對系統產生影響,需要對運行的代碼的權限進行控制,這時候就要啟用Java安全管理器。
三、管理器配置文件
3.1 默認配置文件
默認的安全管理器配置文件是 $JAVA_HOME/jre/lib/security/java.policy,即當未指定配置文件時,將會使用該配置。內容如下:
// 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 un-privileged ports permission java.net.SocketPermission "localhost:1024-", "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"; };
3.2 配置文件詳解
詳解見第五部分,此處知道有這個配置文件即可。
四、啟動安全管理器
啟動安全管理有兩種方式,建議使用啟動參數方式。
4.1 啟動參數方式
啟動程序的時候通過附加參數啟動安全管理器:
-Djava.security.manager
若要同時指定配置文件的位置那么示例如下:
-Djava.security.manager -Djava.security.policy="E:/java.policy"
4.2 編碼方式啟動
也可以通過編碼方式啟動,不過不建議:
System.setSecurityManager(new SecurityManager());
通過參數啟動,本質上也是通過編碼啟動,不過參數啟動使用靈活,項目啟動源碼如下(sun.misc.Launcher):
// Finally, install a security manager if requested String s = System.getProperty("java.security.manager"); if (s != null) { SecurityManager sm = null; if ("".equals(s) || "default".equals(s)) { sm = new java.lang.SecurityManager(); } else { try { sm = (SecurityManager)loader.loadClass(s).newInstance(); } catch (IllegalAccessException e) { } catch (InstantiationException e) { } catch (ClassNotFoundException e) { } catch (ClassCastException e) { } } if (sm != null) { System.setSecurityManager(sm); } else { throw new InternalError( "Could not create SecurityManager: " + s); } }
可以發現將會創建一個默認的SecurityManager;
五、配置文件簡單解釋
5.1 配置基本原則
在啟用安全管理器的時候,配置遵循以下基本原則:
- 沒有配置的權限表示沒有。
- 只能配置有什么權限,不能配置禁止做什么。
- 同一種權限可多次配置,取並集。
- 統一資源的多種權限可用逗號分割。
5.2 默認配置文件解釋
第一部分授權:
grant codeBase "file:${{java.ext.dirs}}/*" { permission java.security.AllPermission; };
授權基於路徑在"file:${{java.ext.dirs}}/*"的class和jar包,所有權限。
第二部分授權:
grant { permission java.lang.RuntimePermission "stopThread"; …… }
這是細粒度的授權,對某些資源的操作進行授權。具體不再解釋,可以查看javadoc。如RuntimePermission的可授權操作經查看javadoc如下:
權限目標名稱 | 權限所允許的操作 | 允許此權限所帶來的風險 |
---|---|---|
createClassLoader | 創建類加載器 | 授予該權限極其危險。能夠實例化自己的類加載器的惡意應用程序可能會在系統中裝載自己的惡意類。這些新加載的類可能被類加載器置於任意保護域中,從而自動將該域的權限授予這些類。 |
getClassLoader | 類加載器的獲取(即調用類的類加載器) | 這將授予攻擊者得到具體類的加載器的權限。這很危險,由於攻擊者能夠訪問類的類加載器,所以攻擊者能夠加載其他可用於該類加載器的類。通常攻擊者不具備這些類的訪問權限。 |
setContextClassLoader | 線程使用的上下文類加載器的設置 | 在需要查找可能不存在於系統類加載器中的資源時,系統代碼和擴展部分會使用上下文類加載器。授予 setContextClassLoader 權限將允許代碼改變特定線程(包括系統線程)使用的上下文類加載器。 |
enableContextClassLoaderOverride | 線程上下文類加載器方法的子類實現 | 在需要查找可能不存在於系統類加載器中的資源時,系統代碼和擴展部分會使用上下文類加載器。授予 enableContextClassLoaderOverride 權限將允許線程的子類重寫某些方法,這些方法用於得到或設置特定線程的上下文類加載器。 |
setSecurityManager | 設置安全管理器(可能會替換現有的) | 安全管理器是允許應用程序實現安全策略的類。授予 setSecurityManager 權限將通過安裝一個不同的、可能限制更少的安全管理器,來允許代碼改變所用的安全管理器,因此可跳過原有安全管理器所強制執行的某些檢查。 |
createSecurityManager | 創建新的安全管理器 | 授予代碼對受保護的、敏感方法的訪問權,可能會泄露有關其他類或執行堆棧的信息。 |
getenv.{variable name} | 讀取指定環境變量的值 | 此權限允許代碼讀取特定環境變量的值或確定它是否存在。如果該變量含有機密數據,則這項授權是很危險的。 |
exitVM.{exit status} | 暫停帶有指定退出狀態的 Java 虛擬機 | 此權限允許攻擊者通過自動強制暫停虛擬機來發起一次拒絕服務攻擊。注意:自動為那些從應用程序類路徑加載的全部代碼授予 "exitVM.*" 權限,從而使這些應用程序能夠自行中止。此外,"exitVM" 權限等於 "exitVM.*"。 |
shutdownHooks | 虛擬機關閉鈎子 (hook) 的注冊與取消 | 此權限允許攻擊者注冊一個妨礙虛擬機正常關閉的惡意關閉鈎子 (hook)。 |
setFactory | 設置由 ServerSocket 或 Socket 使用的套接字工廠,或 URL 使用的流處理程序工廠 | 此權限允許代碼設置套接字、服務器套接字、流處理程序或 RMI 套接字工廠的實際實現。攻擊者可能設置錯誤的實現,從而破壞數據流。 |
setIO | System.out、System.in 和 System.err 的設置 | 此權限允許改變標准系統流的值。攻擊者可以改變 System.in 來監視和竊取用戶輸入,或將 System.err 設置為 "null" OutputStream,從而隱藏發送到 System.err 的所有錯誤信息。 |
modifyThread | 修改線程,例如通過調用線程的 interrupt、stop、suspend、resume、setDaemon、setPriority、setName 和 setUncaughtExceptionHandler 方法 | 此權限允許攻擊者修改系統中任意線程的行為。 |
stopThread | 通過調用線程的 stop 方法停止線程 |
如果系統已授予代碼訪問該線程的權限,則此權限允許代碼停止系統中的任何線程。此權限會造成一定的危險,因為該代碼可能通過中止現有的線程來破壞系統。 |
modifyThreadGroup | 修改線程組,例如通過調用 ThreadGroup 的 destroy 、getParent 、resume 、setDaemon 、setMaxPriority 、stop 和 suspend 方法 |
此權限允許攻擊者創建線程組並設置它們的運行優先級。 |
getProtectionDomain | 獲取類的 ProtectionDomain | 此權限允許代碼獲得特定代碼源的安全策略信息。雖然獲得安全策略信息並不足以危及系統安全,但這確實會給攻擊者提供了能夠更好地定位攻擊目標的其他信息,例如本地文件名稱等。 |
getFileSystemAttributes | 獲取文件系統屬性 | 此權限允許代碼獲得文件系統信息(如調用者可用的磁盤使用量或磁盤空間)。這存在潛在危險,因為它泄露了關於系統硬件配置的信息以及一些關於調用者寫入文件特權的信息。 |
readFileDescriptor | 讀取文件描述符 | 此權限允許代碼讀取與文件描述符讀取相關的特定文件。如果該文件包含機密數據,則此操作非常危險。 |
writeFileDescriptor | 寫入文件描述符 | 此權限允許代碼寫入與描述符相關的特定文件。此權限很危險,因為它可能允許惡意代碼傳播病毒,或者至少也會填滿整個磁盤。 |
loadLibrary.{庫名} | 動態鏈接指定的庫 | 允許 applet 具有加載本機代碼庫的權限是危險的,因為 Java 安全架構並未設計成可以防止惡意行為,並且也無法在本機代碼的級別上防止惡意行為。 |
accessClassInPackage.{包名} | 當類加載器調用 SecurityManager 的checkPackageAccess 方法時,通過類加載器的 loadClass 方法訪問指定的包 |
此權限允許代碼訪問它們通常無法訪問的那些包中的類。惡意代碼可能利用這些類幫助它們實現破壞系統安全的企圖。 |
defineClassInPackage.{包名} | 當類加載器調用 SecurityManager 的 checkPackageDefinition 方法時,通過類加載器的 defineClass 方法定義指定的包中的類。 |
此權限允許代碼在特定包中定義類。這樣做很危險,因為具有此權限的惡意代碼可能在受信任的包中定義惡意類,比如 java.security 或 java.lang 。 |
accessDeclaredMembers | 訪問類的已聲明成員 | 此權限允許代碼查詢類的公共、受保護、默認(包)訪問和私有的字段和/或方法。盡管代碼可以訪問私有和受保護字段和方法名稱,但它不能訪問私有/受保護字段數據並且不能調用任何私有方法。此外,惡意代碼可能使用該信息來更好地定位攻擊目標。而且,它可以調用類中的任意公共方法和/或訪問公共字段。如果代碼不能用這些方法和字段將對象強制轉換為類/接口,那么它通常無法調用這些方法和/或訪問該字段,而這可能很危險。 |
queuePrintJob | 打印作業請求的開始 | 這可能向打印機輸出敏感信息,或者只是浪費紙張。 |
getStackTrace | 獲取另一個線程的堆棧追蹤信息。 | 此權限允許獲取另一個線程的堆棧追蹤信息。此操作可能允許執行惡意代碼監視線程並發現應用程序中的弱點。 |
setDefaultUncaughtExceptionHandler | 在線程由於未捕獲的異常而突然終止時,設置將要使用的默認處理程序 | 此權限允許攻擊者注冊惡意的未捕獲異常處理程序,可能會妨礙線程的終止 |
Preferences | 表示得到 java.util.prefs.Preferences 的訪問權所需的權限。java.util.prefs.Preferences 實現了用戶或系統的根,這反過來又允許獲取或更新 Preferences 持久內部存儲中的操作。 | 如果運行此代碼的用戶具有足夠的讀/寫內部存儲的 OS 特權,則此權限就允許用戶讀/寫優先級內部存儲。實際的內部存儲可能位於傳統的文件系統目錄中或注冊表中,這取決於平台 OS。 |
5.3 可配置項詳解
當批量配置的時候,有三種模式:
- directory/ 表示directory目錄下的所有.class文件,不包括.jar文件
- directory/* 表示directory目錄下的所有的.class及.jar文件
- directory/- 表示directory目錄下的所有的.class及.jar文件,包括子目錄
可以通過${}來引用系統屬性,如:
"file:${{java.ext.dirs}}/*"
六、問題解決
當出現關於安全管理的報錯的時候,基本有兩種方式來解決。
6.1 取消安全管理器
一般情況下都是無意啟動安全管理器,所以這時候只需要把安全管理器進行關閉,去掉啟動參數即可。
6.2 增加相應權限
若因為沒有權限報錯,則報錯信息中會有請求的權限和請求什么權限,如下:
Exception in thread "main" java.security.AccessControlException: access denied (java.io.FilePermission E:\pack\a\a.txt write)
上面例子,請求資源E:\pack\a\a.txt,的FilePermission的寫權限沒有,因此被拒絕。
也可以開放所有權限:
grant { permission java.security.AllPermission; };
這一篇簡單介紹Java的安全管理器,后面會對其進行詳細介紹,不過了解這些對一般應用已經足夠了。