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函数
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可以)的内容,达到对某些通信进行监听,建立中转。
static { try { // 通过反射获取 Field f = Unsafe.class.getDeclaredField("theUnsafe"); // private 2 public f.setAccessible(true); // static unsafe = (Unsafe) f.get(null); }catch (Exception e){ e.printStackTrace(); }}
// 修改String 的 数值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); }}
// unsafepublic 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;}- 具体代码
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 文件。
- 加载
dll或者so的函数
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);}- 实例代码
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 来实例,实现禁止修改 安全管理器
- 代码
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");}- 输出结果
设置安全管理器成功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 的操作
private static synchronizedvoid setSecurityManager0(final SecurityManager s) { SecurityManager sm = getSecurityManager();
// ================================================== if (sm != null) { // ask the currently installed security manager if we // can replace it. sm.checkPermission(new RuntimePermission ("setSecurityManager")); } // ==================================================
if ((s != null) && (s.getClass().getClassLoader() != null)) { // New security manager class is not on bootstrap classpath. // Cause policy to get initialized before we install the new // security manager, in order to prevent infinite loops when // trying to initialize the policy (which usually involves // accessing some security and/or system properties, which in turn // calls the installed security manager's checkPermission method // which will loop infinitely if there is a non-system class // (in this case: the new security manager class) on the stack). 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); 即可将已设置的安全管理器关闭
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。本想再靠它再圈一把!
部分信息可能已经过时