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");
// 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);
}
}

// unsafe
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");
}

众所周知,Java想要加载使用其他语言编写的的必须使用 System.load or System.loadLibaray两个函数之一,通过查看他们的代码可知,同样会经过 SecurityManager 的审查,同理我们可以对传入的filename进行修改,即可达到 redirect load lib,再经此伪造同样的修改后的 dll 或者 so 文件。

  • 加载 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) {
// 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); 即可将已设置的安全管理器关闭

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。本想再靠它再圈一把!