反射
对于反射这个概念来说,直白的讲就是:
对象可以通过反射获取他的类,类可以通过反射拿到所有⽅法(包括私有),拿到的⽅法可以调⽤
而众所周知 JAVA
是一门静态语言,我们通过反射就可以达到动态的语言的特性。
我们可以类比一下我们的老朋友 PHP
,对于 PHP
来说,一个简单的 webshell
可以完成各种操作,而对于 JAVA
来说,我们就可以用 反射
达到一些动态的特性。
比如以下代码:
public void execute(String className, String methodName) throws Exception {Class clazz = Class.forName(className);clazz.getMethod(methodName).invoke(clazz.newInstance());
}
以上代码,有几个反射中重要的方法:
- 获取类的⽅法:
forName
- 实例化类对象的方法:
newInstance
- 获取函数的方法:
getMethod
- 执行函数的方法:
invoke
但是 forName
并不是获取类的唯一方法,对于反射来说,还有两种方法来获取类:
1、如果已经有了一个类的实例,我们只需要用 obj.getClass()
来获取他的类。
2、如果你知道某个类的名字,想获取到这个类,就可以使⽤ forName
来获取。
当然除了以上反射的方法,还有一种方法:
如果你已经加载了某个类,只是想获取到它的 java.lang.Class
对象,那么就直接拿它的 class 属性即可。
如果我们看一下 forName
会发现他有两个重载方法:
public static Class<?> forName(String className) throws ClassNotFoundException { Class<?> caller = Reflection.getCallerClass(); return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException
{ Class<?> caller = null; SecurityManager sm = System.getSecurityManager(); if (sm != null) { // Reflective call to get caller class is only needed if a security manager // is present. Avoid the overhead of making this call otherwise. caller = Reflection.getCallerClass(); if (sun.misc.VM.isSystemDomainLoader(loader)) { ClassLoader ccl = ClassLoader.getClassLoader(caller); if (!sun.misc.VM.isSystemDomainLoader(ccl)) { sm.checkPermission( SecurityConstants.GET_CLASSLOADER_PERMISSION); } } } return forName0(name, initialize, loader, caller);
}
对比着来看,会发现第一个比第二个少了个点东西:
public static Class<?> forName(String className)public static Class<?> forName(String name, boolean initialize, ClassLoader loader)
对于第二种:
name
: 类名initialize
: 是否初始化ClassLoader
: 类的加载器
ClassLoader
看到这个名字就能看出来,他就是一个类的 加载器
。
而 Java
默认的 ClassLoader
是根据类的名字(类的全称,例如 : java.lang.runtime
)加载类。
反射的主要目的就是不通过 import
来导入类,这一点是攻击者想要的,而 forName
就可以做到这一点。
Java
中可以在类中编写内部类,在编译的时候会产生两个类文件如下:
public class main { public static void main(String[] args) throws Exception { System.out.println("Hello World!"); } class test1{ public String test = "hello"; }
}
会生成如下文件:
├── main$test1.class
└── main.class
而你想要通过反射拿到 test1
类的时候可以通过 Class.forName("main$test1")
的方式来加载内部类,从而一系列操作。
说了这么多,我们举个小例子:
public static void main(String[] args) throws Exception { Class c = Class.forName("java.lang.Runtime"); c.getMethod("exec", String.class).invoke(c.newInstance(), "open -a Calculator ");
}
发现会报错对吧!
Exception in thread "main" java.lang.IllegalAccessException: Class main can not access a member of class java.lang.Runtime with modifiers "private"
这里很好理解,c.newInstance()
的作用就是调用这个类的无参构造函数所以存在以下情况就会失败:
- 加载的类没有无参构造函数
- 加载的类的构造函数是私有的
我们可以看一下newInstance()
的代码:
if (cachedConstructor == null) { if (this == Class.class) { throw new IllegalAccessException( "Can not call newInstance() on the Class for java.lang.Class" ); }
这里就很明确了 if (cachedConstructor == null)
如果没有构造函数就抛出一个异常。
// Security check (same as in java.lang.reflect.Constructor)
int modifiers = tmpConstructor.getModifiers();
if (!Reflection.quickCheckMemberAccess(this, modifiers)) { Class<?> caller = Reflection.getCallerClass(); if (newInstanceCallerCache != caller) { Reflection.ensureMemberAccess(caller, this, null, modifiers); newInstanceCallerCache = caller; }
}
跟进 ensureMemberAccess
方法:
public static void ensureMemberAccess(Class<?> var0, Class<?> var1, Object var2, int var3) throws IllegalAccessException { if (var0 != null && var1 != null) { if (!verifyMemberAccess(var0, var1, var2, var3)) { throw new IllegalAccessException("Class " + var0.getName() + " can not access a member of class " + var1.getName() + " with modifiers \"" + Modifier.toString(var3) + "\""); } } else { throw new InternalError(); }
}
可以看到在这里抛出了异常,在此先不细说中间是怎么校验的。
所以我们可以这样来改造:
public static void main(String[] args) throws Exception { Class c = Class.forName("java.lang.Runtime"); c.getMethod("exec", String.class).invoke(c.getMethod("getRuntime").invoke(c), "open -a Calculator");
}
这里有两个方法需要说一下 getMethod
和 invoke
,getMethod
的作用是通过反射获取一个类的某个特定的公有方法,然后利用这个方式来获取 Runtime.exec
方法。
invoke
大家应该很熟悉,我们可以看看他的源码:
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
{ if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, obj, modifiers); } } MethodAccessor ma = methodAccessor; // read volatile if (ma == null) { ma = acquireMethodAccessor(); } return ma.invoke(obj, args);
}
这里做了一些操作,可以自己跟一下,这里就不过多叙述了。
invoke
的作用是执行方法,它的第一个参数是:
- 如果这个方法是一个普通方法,那么第一个参数是类对象
- 如果这个方法是一个静态方法,那么第一个参数是类
正常执行方法是[1].method([2], [3], [4]...)
,在反射里就是这样的method.invoke([1], [2], [3], [4]...)
。