4.2 反射
一、反射
Java 反射机制可以无视类方法、变量访问权限修饰符,可以调用任何类的任意方法、访问并修改成员变量值。也就是说只要发现一处 Java 反射调用漏洞几乎就可以为所欲为了。当然前提可能需要你能控制反射的类名、方法名和参数。
1.获取class类的几种方式
- 1. class.forName(“全类名”),如果你知道某个类的名字,想获取到这个类,就可以使⽤用forName 来获取
- 2. 类名.class,如果你已经加载了某个类,只是想获取到它的java.lang.Class 对象,那么就直接拿它的class 属性即可。这个方法其实不属于反射。
- 3. 对象.getClass(),如果上下文存在某个实例对象,可以通过getClass获取他的类
- 4.ClassLoader.getSystemClassLoader().loadClass(“全类名”)
forName有两个函数重载:
Class<?> forName(String name)
Class<?> forName(String name, boolean initialize, ClassLoader loader)
- 第一个就是我们最常见的获取class的方式,其实可以理解为第二种方式的一个封装:
Class.forName(className)
// 等于
Class.forName(className, true, currentLoader)
默认情况下, forName 的第一个参数是类名;第二个参数表示是否初始化;第三个参数就是ClassLoader。
- 使用功能 “.class” 来创建Class对象的引用时,不会自动初始化该Class对象,使用forName()会自动初始化该Class对象
- static {} 就是在“类初始化”的时候调用的,而{} 中的代码会放在构造函数的super()后面,但在当前构造函数内容的前面。
- forName 中的initialize=true 其实就是告诉Java虚拟机是否执行”类初始化“。
2.获取类之后的方法
- 实例化类对象的方法: newInstance
- 获取函数的方法: getMethod
- 执行函数的方法: invoke
3.类“初始化”执行顺序是什么
public class test {
public static void main(String[] args) {
Ref ref = new Ref();
}
}
class Ref{
static {
System.out.println("最先执行\r\n");
}
{
System.out.println("第二执行\r\n");
}
public Ref(){
System.out.println("最后执行\r\n");
}
}
其中, static {} 就是在“类初始化”的时候调⽤用的,而{} 中的代码会放在构造函数的super() 后面,但在当前构造函数内容的前面。
4.class.newInstance()
作用就是调用这个类的无参构造函数,我们有时候在写漏洞利用方法的时候,会发现使用newInstance 总是不成功,这时候原因可能是:
- 1. 你使用的类没有无参构造函数
- 2. 你使用的类构造函数是私有的
最常见的情况就是java.lang.Runtime,不能直接newInstance(),原因是Runtime 类的构造方法是私有的。因为Runtime类是单例模式,我们只能通过Runtime.getRuntime() 来获取到Runtime 对象,所以正常的payload如下:
Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec",
String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz),
"calc.exe");
5.getMethod()
通过反射获取一个类的某个特定的公有方法,因为java中类的重载,我们不能只通过一个方法名来确定一个函数,在调用getMethod 的时候,我们需要
传给他你需要获取的函数的参数类型列表。
比如的Runtime.exec 方法有6个重载:
6.invoke()
invoke的作用是执行方法,它的第一个参数是:
- 如果这个方法是一个普通方法,那么第一个参数是类对象
- 如果这个方法是一个静态方法,那么第一个参数是类
这也比较好理解了,我们正常执行方法是[1].method([2], [3], [4]…) ,其实在反射里就是method.invoke([1], [2], [3], [4]…)
所以上面的exec的代码拆分开即如下代码。
Class clazz = Class.forName("java.lang.Runtime");
Method exec = clazz.getMethod("exec", String.class);
Method getRuntime = clazz.getMethod("getRuntime");
Object invoke = getRuntime.invoke(clazz); //这里的clazz就是我们第一步反射回来的 类
exec.invoke(invoke,"calc"); //这里的invoke就是上面通过clazz类创建出来的 类对象
7.class类的其他方法
- 如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,那么我们可以通过getConstructor()指定需要确定的构造函数。
- 如果一个方法或构造方法是私有方法,我们可以通过getDeclaredMethod 系列方法获取的是当前类中**“声明”**的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了
获取构造方法
cls3.getConstructor("aaa"); //返回一个 Constructor对象,该对象反映 Constructor对象表示的类的指定的公共 类函数。
cls3.getConstructors(); //返回包含一个数组 Constructor对象反射由此表示的类的所有公共构造 类对象。
cls3.getDeclaredConstructor("bbb"); //返回一个 Constructor对象,该对象反映 Constructor对象表示的类或接口的指定 类函数。
cls3.getDeclaredConstructors(); //返回一个反映 Constructor对象表示的类声明的所有 Constructor对象的数组 类 。
获取方法
cls3.getMethod("setName", String.class); //返回一个 方法对象,getMethod会返回当前类和父类的所有public方法
cls3.getMethods(); //返回包含一个数组 方法对象反射由此表示的类或接口的所有公共方法 类对象,包括那些由类或接口和那些从超类和超接口继承的声明。
cls3.getDeclaredMethod("setName", String.class); //返回一个 方法对象,getDeclaredMethod返回的是当前的所有方法
cls3.getDeclaredMethods() //返回包含一个数组 方法对象反射的类或接口的所有声明的方法,通过此表示 类对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法。
获取成员变量
cls3.getField("aaa"); //返回一个 Field对象,它反映此表示的类或接口的指定公共成员字段 类对象。
cls3.getFields(); //返回包含一个数组 Field对象反射由此表示的类或接口的所有可访问的公共字段 类对象。
cls3.getDeclaredField("aaaa"); //返回一个 Field对象,它反映此表示的类或接口的指定已声明字段 类对象。
cls3.getDeclaredFields(); //返回的数组 Field对象反映此表示的类或接口声明的所有字段 类对象。
二、调用实验
User.java
public class User {
private String name;
private int age;
static {
System.out.println("静态");
}
{
System.out.println("无函数");
}
public User(){
System.out.println("无参构造");
}
public User(String name,int age) {
this.name=name;
this.age=age;
System.out.println("有参构造");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name=name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
CreateObject.java
Class UserClass = Class.forName("com.huawei.reflection.User"); //只调用了静态构造方法
Class UserClass = Class.forName("com.huawei.reflection.User");
User user = (User) UserClass.newInstance(); //调用无参构造方法
Class UserClass = Class.forName("com.huawei.reflection.User");
Constructor constructor= UserClass.getDeclaredConstructor(String.class,int.class); //指定构造方法为有参构造,且参数为String和int的
User user = (User) constructor.newInstance("haha",18); //调用有参构造函数创建对象
Class UserClass = Class.forName("com.huawei.reflection.User");
User user = (User) UserClass.newInstance();
Method setName = UserClass.getDeclaredMethod("setName", String.class); //获取UserClass类的setname方法
setName.invoke(user,"yoyoy"); //通过上一步的setName方法,对上面创建的user对象进行调用,并且String值为yoyoy,此时user对象的name值被赋予为yoyoy
System.out.println(user.getName()); //通过user的getName方法展示出name值
**//实验修改原有对象的属性值**
User xiaoming = new User("xiaoming", 18); //创建一个xiaoming对象
Class<? extends User> userclass = xiaoming.getClass(); //反射获取User类
Field name = userclass.getDeclaredField("name"); //获取User类的name属性
name.setAccessible(true); //因为name属性为private私有属性,需要修改作用域
name.set(xiaoming,"xiaoli"); //修改原有对象xiaoming的name属性为xiaoli
System.out.println(xiaoming.getName());
三、调用实验2
问题:如果一个类没有无参构造方法,也没有类似单例模式里的静态方法,我们怎样通过反射实例化该类呢?
1、ProcessBuilder选择List.class构造函数进行反射执行命令
//ProcessBuilder反射利用
Class process = Class.forName("java.lang.ProcessBuilder");
ProcessBuilder calc = (ProcessBuilder) process.getConstructor(List.class).newInstance(Arrays.asList("calc"));
calc.start();
如上需要进行强转,有时候我们利用漏洞的时候(在表达式上下文中)是没有这种语法的。所以,我们仍需利用反射来完成这一步
Class process = Class.forName("java.lang.ProcessBuilder");
Constructor constructor = process.getConstructor(List.class);
Object calc = constructor.newInstance(Arrays.asList("calc"));
Method start = process.getMethod("start");
start.invoke(calc);
2、ProcessBuilder选择String[].class构造函数进行反射执行命令
其中在newInstance时,因为这个函数本身接收的是一个可变长参数,我们传给ProcessBuilder 的也是一个可变长参数,二者叠加为一个二维数组,所以要写成new String[][]{{“calc”}}
Class process = Class.forName("java.lang.ProcessBuilder");
Constructor constructor1 = process.getConstructor(String[].class);
Object calc2 = constructor1.newInstance(new String[][]{{"calc"}});
Method start1 = process.getMethod("start");
start1.invoke(calc2);
问题:如果一个方法或构造方法是私有方法,我们是否能执行它呢?
比如Runtime这个类的构造函数时私有方法,我们当时只能通过Runtime.getRuntime() 来获取对象,其实我们可以通过getDeclaredConstructor 来获取这个私有的构造方法来实例化对象。
其中我们用到了setAccessible这个方法,这个是必须的。我们在获取到一个私有方法后,必须用setAccessible 修改它的作用域,否则仍然不能调用。
Class runtime = Class.forName("java.lang.Runtime");
Constructor declaredConstructor = runtime.getDeclaredConstructor();
declaredConstructor.setAccessible(true); //修改作用域
Method exec = runtime.getMethod("exec", String.class);
exec.invoke(declaredConstructor.newInstance(),"calc");
四、反射调用无关键字的命令执行
Java 反射机制实现无关键字执行命令
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Scanner;
public class ReflectionTest {
public static void exec() {
try {
System.out.println(Runtime.class.getMethod("exec", String.class).invoke(Runtime.class.getMethod("getRuntime").invoke(null), "curl -i localhost:8000"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
String str = "whoami";
// java.lang.Runtime
String runtime = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101});
// Runtime.class
Class<?> c = Class.forName(runtime);
// 获取getRuntime方法,Runtime.getRuntime()
Method m1 = c.getMethod(new String(new byte[]{103, 101, 116, 82, 117, 110, 116, 105, 109, 101}));
// 获取Runtime的exec方法,rt.exec(xxx)
Method m2 = c.getMethod(new String(new byte[]{101, 120, 101, 99}), String.class);
// Runtime.getRuntime().exec(str)
Object obj2 = m2.invoke(m1.invoke(null), str);
// 获取命令执行结果Process类的getInputStream()方法
Method m = obj2.getClass().getMethod(new String(new byte[]{103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109}));
m.setAccessible(true);
// process.getInputStream()
InputStream in = (InputStream) m.invoke(obj2, new Object[]{});
// 输出InputStream内容到
Scanner scanner = new Scanner(in).useDelimiter("\\A");
System.out.println(scanner.hasNext() ? scanner.next() : "");
} catch (Throwable t) {
t.printStackTrace();
}
}
}
参考:
P牛《Java安全漫谈》