前言
为了更接近目标(红队),需要先将常规打点的基础打好。虽说PHP很流行,但市场需求、语言特性和培训机构,工作中大多以Java为主,因此漏洞也是Java最多,所以学习JAVA漏洞非常有必要。
反序列化学习
最近目标:掌握Fastjson漏洞。
序列化过程
PHP
class test{ //类
$flag = "flag{111}";
}
$test = new test; //实例化
$data_ser_result = serialize($test); //序列化
$data_un_result = unserialize($data_ser_result); //反序列化
?>
序列化的过程可以看成将类进行打成压缩包,而反序列化就是解压
实例是占内存的,而类只是个抽象的,不占内存
可以发现PHP序列化和反序列化非常简单,只用调用一个行数即可,序列化后输出结果也只是一条字符串。
O:4:"test":1:{s:4:"flag";s:9:"flag{111}";} //上面代码序列化的结果
class test{ //反序列化结果
$flag = "flag{111}";
}
JAVA
Java和php序列化过程一样,只不过复杂一些。
package test;
import java.io.*;
public class Serialize{
public static void main(String args[]) throws Exception {
String obj = "ls"; //对象
//将序列化对象写入a.ser
FileOutputStream fos = new FileOutputStream("aa.ser");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(obj); //序列化
os.close();
// 从文件a.ser中读取数据
FileInputStream fis = new FileInputStream("aa.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
// 通过反序列化恢复对象obj
String obj2 = (String)ois.readObject(); //反序列化
System.out.println(obj2);
ois.close();
}
}
上面的代码是将对象obj序列化后写入aa.ser文件,然后才从aa.ser文件读取并反序列化。
序列化结果
反序列化结果
用WinHex打开aa.ser文件发现可以看到对象的信息。写到文件的便是该对象序列化后的二进制数据
JAVA小小的进阶
上面序列化的只是一个对象,但在现实中不可能就传输一个对象,因此将对象变成一个类,可以发现没有什么区别,只是多了一步,将类实例化。
package test;
import java.io.*;
class User implements Serializable {
public String name;
public int age;
public String getUserInfo(){
return "name: "+this.name +" age: "+this.age;
}
}
public class Serialize{
public static void main(String[] args) throws Exception {
User user = new User();
user.name = "啦啦啦啦啦啦啦";
user.age = 1;
try {
FileOutputStream fos = new FileOutputStream("user.txt");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(user);
os.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
FileInputStream fis = new FileInputStream("user.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
User user_out = (User) ois.readObject();
ois.close();
fis.close();
System.out.println(user_out.getUserInfo());
}
}
写入恶意代码
将User类注释,重写ObjectInputStream的readObject方法,将它放在自定义ShellClass类中。
package test;
import java.io.*;
/*class User implements Serializable {
public String name;
public int age;
public String getUserInfo(){
return "name: "+this.name +" age: "+this.age;
}
}*/
class ShellClass implements Serializable{
public String name;
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
Runtime.getRuntime().exec("calc.exe");
}
}
public class Serialize{
public static void main(String[] args) throws Exception {
//User user = new User();
//user.name = "啦啦啦啦啦啦啦";
// user.age = 1;
ShellClass shell = new ShellClass();
try {
FileOutputStream fos = new FileOutputStream("user.txt");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(shell);
os.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
FileInputStream fis = new FileInputStream("user.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
ShellClass a = (ShellClass)ois.readObject();
ois.close();
fis.close();
}
}
其实就是写一个readObject()方法,将原有的ObjectInputStream的readObject()方法重新调用,并在后面跟上自己的恶意代码
JAVA反射
java反射机制
Java 反射机制在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种 动态的获取信息 以及 动态调用对象的方法 的功能称为 java 的反射机制。
正射
Student student = new Student();
student.doHomework("数学");
反射
Class clazz = Class.forName("reflection.Student");
Method method = clazz.getMethod("doHomework", String.class);
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, "语文");
可以发现上面正射和反射看起来好像没什么区别,但对于编译的电脑来说却区别很大
一个是我想知道这个类在哪、做什么,然后我才开始运行代码,实例化后调用方法,另一个是我先运行代码,再获取有这个类在哪、做什么的。
反射的使用
User类
package test;
import java.lang.reflect.*;
class User {
public String name;
public User(String name) {
this.name=name;
}
public void setName(String name) {
this.name=name;
}
public String getName() {
return name;
}
}
获取Class类对象
获取反射中的Class对象有三种方法
Class class1 = Class.forName("test.User");
Class class1 = User.class;
User user = new User();
Class class1 = user.getClass();
利用类对象新建对象
public static void main(String[] args) throws Exception {
Class classa = Class.forName("test.User");
Constructor constructor=classa.getConstructor(String.class);
User student1 = (User) constructor.newInstance("a");
System.out.println(student1);
}
获取类成员变量
public static void main(String[] args) throws Exception {
Class user = Class.forName("test.User");
Field name= user.getField("name");
System.out.println(name);
}
获取类的方法
public static void main(String[] args) throws Exception {
Class user = Class.forName("test.User");
Method[] methods = user.getMethods();
for (int i = 0; i < methods.length; i++) {
System.out.println(methods[i]);
}
}
通过反射构造恶意代码
在上面有写过一个恶意代码
java.lang.Runtime.getRuntime().exec("calc.exe");
我们将它用反射方法写出来
Class runtimeClass=Class.forName("java.lang.Runtime");
Object runtime=runtimeClass.getMethod("getRuntime").invoke(null);
runtimeClass.getMethod("exec", String.class).invoke(runtime,"calc.exe");
为什么使用反射呢
代码乱写的,了解个意思就好,有错勿喷
不使用反射
interface Apple{
xxxxxx
}
class Red implements Apple{
public void shopping(){
System.out.println("red");
}
}
class Black implements Apple{
public void shopping(){
System.out.println("black");
}
}
class Stop{
public string color(String apple_color){
if(apple_color=="red"){
result = new Red();
}else(apple_color=="black"){
result = new Black();
}
retrun result;
}
}
class Buy{
public static void main(String[] a){
Apple s = Stop.color("red");
s.shopping();
}
}
使用反射
interface Apple{
xxxxxx
}
class Red implements Apple{
public void shopping(){
System.out.println("red");
}
}
class Black implements Apple{
public void shopping(){
System.out.println("black");
}
}
class Stop{
public string color(String apple_color){
Apple result = (Apple)Class.forName(apple_color).newInstance();
retrun result;
}
}
class Buy{
public static void main(String[] a){
Apple s = Stop.color("Apple.red");
s.shopping();
}
}
总结
可以发现上面实现的功能差不多,只不过写法不同,那这样也看不出为啥要使用反射。
仔细看Buy这个类中传参给Stop时,使用反射的多加了一个Apple类名,这里就有问题了,可以发现这个类是可以修改的,再加些方法,那我是不是可以调用任意类,去运行系统命令了。
有的人会说用反射效率差,对性能要求高,用于字段和方法接入时要远慢于直接代码,那
我就是一个安服仔!!!