SecurityManager
曾经的我在某一次阅读Java代码的时候,发现了这一个神奇的东西。
让我有了很多圈米的Idea,本文介绍我是如何使用SecurityManager。
因为最近学习高版本Java时发现他要被删除了(555),想着用这个文章纪念纪念它。
我出生了!
自Java 1.0问世以来,SecurityManager
便成为了Java生态系统的一部分。在那个
以Applet为主导的时代,Applet
的运行依赖于Web浏览器。用户在浏览含有Applet
的网页时,
浏览器会负责下载Applet
的字节码,并在用户计算机上运行。通过设置SecurityManager
,可以防止Applet
访问本地文件系统或网络等敏感资源。
截止今日,你依旧能在各种 第三方库/JDK 源码中看到SecurityManager
的身影。
不巧的是SecurityManager
在JDK17已经被标识弃用。当你使用它的时候会警告用户
未来版本将删除 SecurityManager
。
为了方便,本文章使用JDK1.8进行,且只介绍非正常用法
非正常用法
这边只介绍 System.setSecurityManager
的用法,不对 -Djava.security.policy=/path/to/policy
进行讲解
System
中的 getProperty
函数
1 2 3 4 5 6 7 8 public static String getProperty (String key, String def) { checkKey(key); SecurityManager sm = getSecurityManager(); if (sm != null ) { sm.checkPropertyAccess(key); } return props.getProperty(key, def); }
于是我突发奇想,checkPropertyAccess
是不是可以看作为一个钩子hook
,可以通过checkPropertyAccess
获得key,再通过 setProperty
进行覆盖其 value
?
同理可以得到以下的使用方法
checkConnect
通过 Unsafe
修改String(不可变,但是Unsafe可以)的内容,达到对某些通信进行监听,建立中转。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 static { try { Field f = Unsafe.class.getDeclaredField("theUnsafe" ); f.setAccessible(true ); unsafe = (Unsafe) f.get(null ); }catch (Exception e){ e.printStackTrace(); } } public static void modify (String src, String to) { try { Field value = setAccessible(String.class.getDeclaredField("value" )); set(src, value, value.get(to)); } catch (Exception e) { throw new RuntimeException (e); } } public static void set (Object obj, Field field, Object value) throws Exception { unsafe.putObject(obj, unsafe.objectFieldOffset(field), value); } public static <T extends AccessibleObject > T setAccessible (T t) { t.setAccessible(true ); return t; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public static sun.misc.Unsafe unsafe = null ;public static void main (String[] args) throws Throwable { ServerSocket ss = new ServerSocket (1145 ); System.setSecurityManager(new SecurityManager () { @Override public void checkPermission (Permission perm) {} @Override public void checkConnect (String host, int port) { modify(host,"172.17.209.50" ); System.out.println("尝试连接 " +host + " " + port); } @Override public void checkConnect (String host, int port, Object context) { checkConnect(host,port); } }); Socket socket = new Socket ("127.0.0.1" ,1145 ); socket.close(); System.out.println("end" ); }
checkLink
众所周知,Java想要加载使用其他语言编写的的必须使用 System.load
or System.loadLibaray
两个函数之一,通过查看他们的代码可知,同样会经过 SecurityManager
的审查,同理我们可以对传入的filename进行修改,即可达到 redirect load lib,再经此伪造同样的修改后的 dll
或者 so
文件。
1 2 3 4 5 6 7 8 9 10 11 synchronized void load0 (Class<?> fromClass, String filename) { SecurityManager security = System.getSecurityManager(); if (security != null ) { security.checkLink(filename); } if (!(new File (filename).isAbsolute())) { throw new UnsatisfiedLinkError ( "Expecting an absolute path of the library: " + filename); } ClassLoader.loadLibrary(fromClass, filename, true ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static void main (String[] args) throws Throwable { System.setSecurityManager(new SecurityManager () { @Override public void checkPermission (Permission perm) {} @Override public void checkLink (String lib) { if (check(lib)) { modify(lib,"redirect_libPath" ); } } }); new Scanner (System.in).next(); System.out.println("end" ); }
checkPermission
这个函数就已经包括了第三方库中的自定义 checkPermission
行为。
可以按照特定的情况来使用。
这边我们使用 setSecurityManager 来实例,实现禁止修改 安全管理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void main (String[] args) throws Throwable { System.setSecurityManager(new SecurityManager () { @Override public void checkPermission (Permission perm) { if (perm.getName().equals("setSecurityManager" )) throw new RuntimeException ("禁止修改 SecurityManager " ); } }); System.out.println("设置安全管理器成功" ); System.setSecurityManager(new SecurityManager ()); System.out.println("end" ); }
1 2 3 4 5 6 设置安全管理器成功 Exception in thread "main" java.lang.RuntimeException: 禁止修改 SecurityManager at test.BeautifulMain$1. checkPermission(BeautifulMain.java:21 ) at java.lang.System.setSecurityManager0(System.java:300 ) at java.lang.System.setSecurityManager(System.java:291 ) at test.BeautifulMain.main(BeautifulMain.java:28 )
绕过方法
模拟以下函数的操作,删去 checkPermission
的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 private static synchronized void setSecurityManager0 (final SecurityManager s) { SecurityManager sm = getSecurityManager(); if (sm != null ) { sm.checkPermission(new RuntimePermission ("setSecurityManager" )); } if ((s != null ) && (s.getClass().getClassLoader() != null )) { AccessController.doPrivileged(new PrivilegedAction <Object>() { public Object run () { s.getClass().getProtectionDomain().implies (SecurityConstants.ALL_PERMISSION); return null ; } }); } security = s; } public static SecurityManager getSecurityManager () { return security; }
通过此函数 setSecurityManager(null); 即可将已设置的安全管理器关闭
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public static void setSecurityManager (SecurityManager securityManager) { try { SecurityManager sm = System.getSecurityManager(); if ((sm != null ) && (sm.getClass().getClassLoader() != null )) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { sm.getClass().getProtectionDomain().implies(SecurityConstants.ALL_PERMISSION); return null ; }); } Method reflectionDataMethod = Class.class.getDeclaredMethod("reflectionData" ); reflectionDataMethod.setAccessible(true ); Object reflectionData = reflectionDataMethod.invoke(System.class); Field[] res = null ; if (reflectionData != null ) { Field field = reflectionData.getClass().getDeclaredField("declaredFields" ); field.setAccessible(true ); res = (Field[]) field.get(reflectionData); } if (res == null ) { Method method = Class.class.getDeclaredMethod("getDeclaredFields0" , boolean .class); method.setAccessible(true ); res = (Field[]) method.invoke(System.class, false ); } for (Field re : res) { if (re.getName().equals("security" )) { re.setAccessible(true ); re.set(null , securityManager); } } }catch (Exception e){ e.printStackTrace(); } }
总结
这些都是我关于SecurityManager
学到的一些小玩意。
最近开始学习高版本的Java,在翻阅其源码的时候发现SecurityManager
被打上了 @Deprecated
。本想再靠它再圈一把!