本篇包含以下元素:
之前写过PHP和NodeJs的命令执行,但是一直没接触过Java的命令执行,下面是一个Java执行系统命令的例子:
package com.edu;
public class ExecDemo {
public static void main(String[] args) throws Exception{
// Runtime.getRuntime().exec("calc.exe");//打开一个记事本
Runtime run = Runtime.getRuntime();
run.exec("calc.exe");
}
}
Copy
运行这段代码,将打开Win10下的计算器。
虽然我现在都不是很清楚为什么要有一个这个机制(可以说自己的高级编程语言基础是很烂的了),以及这个反射机制的实际运用场景(目前只知道它好像连私有属性的方法和成员都能调用),但是我还是大概知道了它的使用方法。下面是利用反射机制来调用上文中的命令执行的例子。
package com.edu;
import java.lang.reflect.Method;
public class ReflectExecDemo {
public static void main(String[] args) throws Exception{
//equal: Object run = Runtime.getRuntime();
Object myRuntime = Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null);
//得到方法exec, 其参数为String类型
Method exec = Class.forName("java.lang.Runtime").getMethod("exec",String.class);
//equal: myruntime.exec("calc.exe");
exec.invoke(myRuntime, "calc.exe");
}
}
Copy
Java的一个好处就是看名字就能猜出个大概作用,所以下面就大概说一下以上用到的方法(其他方法以后遇到再说)的作用和小细节。
很明显就是获取到这个类,貌似也没什么要注意的
获取方法,有两个参数,第二个参数可以缺省: getMethod(String name, Class>...parameterTypes)
,第一个参数代表的是方法的名字,第二个参数代表的是该方法参数的类型(三个点表示可变参数列表,接收0或多个参数),如果该方法没有参数,则getMethod的第二个参数可以缺省或者为传入null。
顾名思义,即“调用”,也有两个参数:invoke(Object obj, Object ... args)
。要与getMethod结合使用,一般是先用前者获取到一个Method类,再用invoke调用,举个上文的例子应该就便于理解invoke方法的参数意义了:
Method exec = Class.forName("java.lang.Runtime").getMethod("exec",String.class);
//equal: myruntime.exec("calc.exe");
exec.invoke(myRuntime, "calc.exe");
Copy
可以看到,invoke的第一个参数即为调用该方法的(如上例的exec方法)类,第二个参数为传入该方法的参数值,同样也是一个可变参数列表。
Java中的反序列化和PHP不同的是,在Java中一个类要能够被反序列化,则其必须实现了java.io.Serializable接口(该类必须实现java.io.Serializable 接口。该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。),并且它的序列化格式也非常得不同。下面是一个简单的序列化和反序列化的demo:
1.要被反序列化的类Chenlvtang.class
package com.edu;
import java.io.Serializable;
public class Chenlvtang implements Serializable{
private String name;
private String level;
public Chenlvtang(){
this.level = "noob";
this.name = "chenlvtang";
}
}
Copy
2.进行序列化和反序列化操作的SerializeDemo.class
package com.edu;
import java.io.*;
public class SerializDemo{
public static void main(String[] args) throws Exception{
Chenlvtang chenlvtang = new Chenlvtang();
//开始序列化
FileOutputStream file = new FileOutputStream("chenlvtang.bin");
ObjectOutputStream ser = new ObjectOutputStream(file);
ser.writeObject(chenlvtang);
ser.close();
//开始反序列化
FileInputStream file1 = new FileInputStream("chenlvtang.bin");
ObjectInputStream unser = new ObjectInputStream(file1);
Object result = unser.readObject();
unser.close();
System.out.println(result);
}
}
Copy
3.序列化文件
其中ACED
是魔术头,0005
是流协议版本,其他的就暂且不深入了解。
可以看到上文中进行反序列操作的时候用了到了readObject
方法,并且是拿序列化内容进行调用,如果我们传入一个类,其中重写了readObject方法,那么在反序列化的时候是否会调用呢?下面重写chenlvtang.class, 并在里面重写readObject方法。
package com.edu;
import java.io.Serializable;
public class Chenlvtang implements Serializable{
private String name;
private String level;
public Chenlvtang(){
this.level = "noob";
this.name = "chenlvtang";
}
private void readObject(java.io.ObjectInputStream stream) throws Exception {
stream.defaultReadObject();//调用默认的方法
System.out.println("hihi");
}
}
Copy
运行结果:
可以看到,成功的调用了。那我们现在是不是可以想一下把其中的hihi换成上文中危险的命令执行呢?下面我们再次重写readObject方法
private void readObject(java.io.ObjectInputStream stream) throws Exception {
stream.defaultReadObject();//调用默认的方法
Runtime.getRuntime().exec("calc.exe");
}
Copy
运行结果:
成功执行了命令!