Java應(yīng)用程序的安全沙箱機(jī)制是什么

如果你經(jīng)常閱讀源碼,你會發(fā)現(xiàn) Java 的源碼中到處都有類似于下面這一段代碼

class File {<br/>  // 判斷一個磁盤文件是否存在<br/>  public boolean exists() {<br/>    SecurityManager security = System.getSecurityManager();<br/>    if (security != null) {<br/>      security.checkRead(path);<br/>    }<br/>    ...<br/>  }<br/>}<br/>

這明顯是一個安全檢查代碼,檢查的是你是否有訪問磁盤路徑的權(quán)限,為什么 Java 語言需要這樣的安全檢查代碼呢?我們再看看客戶端套接字的 connect 函數(shù)源碼,它需要檢查用戶是否有connect 某個網(wǎng)絡(luò)地址的權(quán)限

class Socket {<br/>  public void connect(SocketAddress endpoint, int timeout) {<br/>    ...<br/>    SecurityManager security = System.getSecurityManager();<br/>    if (security != null) {<br/>       if (epoint.isUnresolved())<br/>          security.checkConnect(epoint.getHostName(), port);<br/>       else<br/>          security.checkConnect(addr.getHostAddress(), port);<br/>       }<br/>    }<br/>    ...<br/>  }<br/>}<br/>

再看服務(wù)端套接字的源碼,它會檢查端口的監(jiān)聽權(quán)限

class ServerSocket {<br/>  public void bind(SocketAddress endpoint, int backlog) {<br/>    ...<br/>    SecurityManager security = System.getSecurityManager();<br/>    if (security != null)<br/>       security.checkListen(epoint.getPort());<br/>    ...<br/>  }<br/>}<br/>

似乎所有和 IO 操作有關(guān)的方法調(diào)用都需要進(jìn)行安全檢查。看起來IO操作相關(guān)的權(quán)限檢查是可理解的,用戶進(jìn)程不能隨意訪問所有的IO資源。但是連環(huán)境變量都不讓隨意讀,而且限制的還不是所有環(huán)境變量,而是某個具體的環(huán)境變量,這安全檢查是不是有點過了?

class System {<br/>  public static String getenv(String name) {<br/>    SecurityManager sm = getSecurityManager();<br/>    if (sm != null) {<br/>       sm.checkPermission(new RuntimePermission("getenv."+name));<br/>    }<br/>    return ProcessEnvironment.getenv(name);<br/>  }<br/>}<br/>

這是因為 Java 的安全檢查管理器和操作系統(tǒng)的權(quán)限檢查不是一個概念,Java 編寫的不只是服務(wù)端應(yīng)用程序,它還可以作為客戶端跑在瀏覽器上(Applet),它還可以以 app 的形式跑在手機(jī)上(J2ME),針對不同的平臺 jvm 會使用不同的安全策略。通常情況下,針對 Applet 的限制非常嚴(yán)格,一般不允許 Applet 操作本地文件。執(zhí)行具體 IO 操作前,一旦 Java 的安全檢查通過,操作系統(tǒng)仍會進(jìn)行權(quán)限檢查。

立即學(xué)習(xí)Java免費(fèi)學(xué)習(xí)筆記(深入)”;

我們平時在本地運(yùn)行 java 程序時通常都不會默認(rèn)打開安全檢查器,需要執(zhí)行 jvm 參數(shù)才會打開

$ java -Djava.security.manager xxx<br/>$ java -Djava.security.manager -DDjava.security.policy="${policypath}"<br/>

因為安全限制條件可以定制,所以還需要提供具體的安全策略文件路徑,默認(rèn)的策略文件路徑是 JAVA_HOME/jre/lib/security/java.policy,下面讓我們來看看這個文件里都寫了些什么

// 內(nèi)置擴(kuò)展庫授權(quán)規(guī)則<br/>// 表示 JAVA_HOME/jre/lib/ext/ 目錄下的類庫可以全權(quán)訪問任意資源<br/>// 包含 javax.swing.*, javax.xml.*, javax.crypto.* 等等<br/>grant codeBase "file:${{java.ext.dirs}}/*" {<br/> permission java.security.AllPermission;<br/>};<br/><br/>// 其它類庫授權(quán)規(guī)則<br/>grant {<br/> // 允許線程調(diào)用自己的 stop 方法自殺<br/> permission java.lang.RuntimePermission "stopThread";<br/> // 允許程序監(jiān)聽 localhost 的隨機(jī)可用端口,不允許隨意訂制端口<br/> permission java.net.SocketPermission "localhost:0", "listen";<br/> // 限制獲取系統(tǒng)屬性,下面一系列的配置都是只允許讀部分內(nèi)置屬性<br/> permission java.util.PropertyPermission "java.version", "read";<br/> permission java.util.PropertyPermission "java.vendor", "read";<br/> permission java.util.PropertyPermission "java.vendor.url", "read";<br/> permission java.util.PropertyPermission "java.class.version", "read";<br/> permission java.util.PropertyPermission "os.name", "read";<br/> permission java.util.PropertyPermission "os.version", "read";<br/> permission java.util.PropertyPermission "os.arch", "read";<br/> permission java.util.PropertyPermission "file.separator", "read";<br/> permission java.util.PropertyPermission "path.separator", "read";<br/> permission java.util.PropertyPermission "line.separator", "read";<br/> permission java.util.PropertyPermission "java.specification.version", "read";<br/> permission java.util.PropertyPermission "java.specification.vendor", "read";<br/> permission java.util.PropertyPermission "java.specification.name", "read";<br/> permission java.util.PropertyPermission "java.vm.specification.version", "read";<br/> permission java.util.PropertyPermission "java.vm.specification.vendor", "read";<br/> permission java.util.PropertyPermission "java.vm.specification.name", "read";<br/> permission java.util.PropertyPermission "java.vm.version", "read";<br/> permission java.util.PropertyPermission "java.vm.vendor", "read";<br/> permission java.util.PropertyPermission "java.vm.name", "read";<br/>};<br/>

grant 如果提供了 codeBase 參數(shù)就是針對具體的類庫來配置權(quán)限規(guī)則,如果沒有指定 codeBase 就是針對所有其它類庫配置的規(guī)則。

安全檢查沒有通過,那就會拋出 java.security.AccessControlException 異常。即使通過了安全檢查,操作系統(tǒng)的權(quán)限檢查也有可能失敗,此時將會拋出其他類型的異常。

如果按照上面所配置的規(guī)則,使用默認(rèn)安全策略的 JVM 將無法訪問本地文件,因為授權(quán)規(guī)則使用的是白名單。如果需要訪問本地文件,可以增加下面的規(guī)則

permission java.io.FilePermission "/etc/passwd", "read";<br/>permission java.io.FilePermission "/etc/shadow", "read,write";<br/>permission java.io.FilePermission "/xyz", "read,write,delete";<br/>// 允許讀所有文件<br/>permission java.io.FilePermission "*", "read";<br/>

Permission 的配置參數(shù)正好對應(yīng)了它的構(gòu)造器參數(shù)

public FilePermission(String path, String actions) {<br/>  super(path);<br/>  init(getMask(actions));<br/>}<br/>

Java 默認(rèn)安全規(guī)則分為幾大模塊,每個模塊都有各自的配置參數(shù)

Java應(yīng)用程序的安全沙箱機(jī)制是什么

其中 AllPermission 表示打開所有權(quán)限。還有一個不速之客 hibernatePermission,它并不是內(nèi)置的權(quán)限模塊,它是 Hibernate 框架為自己訂制的,這意味著安全規(guī)則是支持自定義擴(kuò)展的。要擴(kuò)展很容易,只需編寫一個 Permission 子類,并實現(xiàn)其四個抽象方法。

abstract class Permission {<br/>  // 權(quán)限名稱,對于文件來說就是文件名,對于套接字來說就是套接字地址<br/>  // 它的意義是子類可定制的<br/>  private String name;<br/>  // 當(dāng)前權(quán)限對象是否隱含了 other 權(quán)限<br/>  // 比如 AllPermission 的這個方法總是返回 true<br/>  public abstract boolean implies(Permission other);<br/>  // equals 和 hashcode 用于權(quán)限比較<br/>  public abstract boolean equals(Object obj);<br/>  public abstract int hashCode();<br/>  // 權(quán)限選項 read,write,xxx<br/>  public abstract String getActions();<br/>}<br/><br/>class CustomPermission extends Permission {<br/>  private String actions;<br/>  CustomPermission(string name, string actions) {<br/>    super(name)<br/>    this.actions = actions;<br/>  }<br/>  ...<br/>}<br/>

JVM 啟動時會將 profile 里面定義的權(quán)限規(guī)則加載到權(quán)限池中,用戶程序在特定的 API 方法里使用權(quán)限池來判斷是否包含調(diào)用這個 API 的權(quán)限,最終會落實到調(diào)用權(quán)限池中每一個權(quán)限對象的 implies 方法來判斷是否具備指定權(quán)限。

class CustomAPI {<br/>  public void someMethod() {<br/>    SecurityManager sec = System.getSecurityManager();<br/>    if(sec != null) {<br/>      sec.CheckPermission(new CustomPermission("xname", "xactions"));<br/>    }<br/>    ...<br/>  }<br/>}<br/>

啟用安全檢查,將會降低程序的執(zhí)行效率,如果 profile 里面定義的權(quán)限規(guī)則特別多,那么檢查效率就會很慢,使用時注意安全檢查要省著點使用。

沙箱的安全檢查點非常多,下面列舉一些常見的場景

  1. 文件操作

  2. 套接字操作

  3. 線程和線程組

  4. 類加載器控制

  5. 反射控制

  6. 線程信息獲取

  7. 網(wǎng)絡(luò)代理控制

  8. Cookie 讀寫控制

如果你的服務(wù)端程序開啟了安全檢查,就需要在 policy 配置文件里打開很多安全設(shè)置,非常繁瑣,而且配置多了,檢查的性能也會產(chǎn)生一定損耗。這點有點類似 android 的應(yīng)用權(quán)限設(shè)置,在每個 Android 應(yīng)用的配置文件里都需要羅列出一系列應(yīng)用子權(quán)限。不過用 Java 來編寫服務(wù)端程序似乎開啟安全檢查沒有任何必要。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點贊10 分享