
Использование CVE-2020-2883 в WebLogic для эксплуатации уязвимости десериализации Shiro rememberMe, однокликовая регистрация фильтра памяти AntSword shell
Данная презентация охватывает следующие темы:
Сериализация Java — это процесс преобразования объекта Java в последовательность байтов для удобного сохранения в памяти, файлах или базах данных. Метод writeObject() класса ObjectOutputStream позволяет выполнить сериализацию, преобразуя объект Java в последовательность байтов.
Десериализация Java — это процесс восстановления объекта Java из последовательности байтов. Метод readObject() класса ObjectInputStream используется для десериализации.
Простой пример можно увидеть в коде SerializeAndDeserialize. примечание: здесь особое внимание уделяется принудительному преобразованию типов в коде```java
package org.chabug.demo;
import org.chabug.entity.Dog; import org.chabug.entity.Person; import org.chabug.util.Serializables;
/* 这个例子是为了证明只要实现了Serializable接口的类都可以被序列化 并且Java内置的几大数据类型也可被序列化,因为他们都继承了Object类 */
public class SerializeAndDeserialize {
public static void main(String[] args) throws Exception {
byte[] bytes;
String s1 = "I'm a String Object....";
bytes = Serializables.serializeToBytes(s1);
Object o1 = Serializables.deserializeFromBytes(bytes);
System.out.println(o1);
String[] s2 = new String[]{"tom", "bob", "jack"};
bytes = Serializables.serializeToBytes(s2);
String[] o2 = (String[])Serializables.deserializeFromBytes(bytes);
System.out.println(o2);
int i = 123;
bytes = Serializables.serializeToBytes(i);
int o3 = (Integer) Serializables.deserializeFromBytes(bytes);
System.out.println(o3);
// 一只名叫woody的狗
Dog dog = new Dog();
dog.setName("woody");
// tom
Person tom = new Person();
tom.setAge(14);
tom.setName("tom");
tom.setSex("男");
tom.setDog(dog);
bytes = Serializables.serializeToBytes(tom);
Person o = (Person) Serializables.deserializeFromBytes(bytes);
System.out.println(o);
}
}
Строки, Integer, массивы, Object и другие встроенные типы данных Java могут быть сериализованы; наши собственные классы Person, Dog, если они реализуют интерфейс Serializable, могут выполнять сериализацию и десериализацию.
## Почему при десериализации возникают уязвимости?
Давайте посмотрим на фрагмент кода. Теперь есть вредоносный класс сущности EvilClass```java
package org.chabug.entity;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class EvilClass implements Serializable {
String name;
public EvilClass() {
System.out.println(this.getClass() + "的EvilClass()构造方法被调用!!!!!!");
}
public EvilClass(String name) {
System.out.println(this.getClass() + "的EvilClass(String name)构造方法被调用!!!!!!");
this.name = name;
}
public String getName() {
System.out.println(this.getClass() + "的getName被调用!!!!!!");
return name;
}
public void setName(String name) {
System.out.println(this.getClass() + "的setName被调用!!!!!!");
this.name = name;
}
@Override
public String toString() {
System.out.println(this.getClass() + "的toString()被调用!!!!!!");
return "EvilClass{" +
"name='" + getName() + '\'' +
'}';
}
private void readObject(ObjectInputStream in) throws Exception {
//执行默认的readObject()方法
in.defaultReadObject();
System.out.println(this.getClass() + "readObject()被调用!!!!!!");
Runtime.getRuntime().exec(new String[]{"cmd", "/c", name});
}
}
В его методе readObject есть код выполнения команды Runtime.getRuntime().exec(new String[]{"cmd", "/c", name}), параметр name — это команда для выполнения. Таким образом, мы можем создать вредоносный объект, присвоить его атрибуту name нужную команду, и при десериализации, когда сработает readObject, произойдет RCE. Как показано ниже:```java
package org.chabug.demo;
import org.chabug.entity.EvilClass; import org.chabug.util.Serializables;
public class EvilSerialize { public static void main(String[] args) throws Exception { EvilClass evilObj = new EvilClass(); evilObj.setName("calc"); byte[] bytes = Serializables.serializeToBytes(evilObj); EvilClass o = (EvilClass) Serializables.deserializeFromBytes(bytes); System.out.println(o); } }

Итак, теперь мы знаем, как десериализация приводит к удаленному выполнению кода (RCE), но в реальной разработке так напрямую не пишут, поэтому возникает необходимость поиска цепочек эксплойтов. Для уязвимости десериализации требуются три компонента:
1. Точка входа десериализации (source)
2. Целевой метод (sink)
3. Цепочка эксплойтов (gadget chain)
Если внимательно посмотреть на вывод на изображении выше, то можно заметить, что сработал не только метод readObject, но также методы toString(), конструктор без параметров, set, get. Поэтому при практическом поиске цепочек эксплойтов нужно обращать внимание не только на метод readObject().
Теперь нам нужно разобраться с **рефлексией**. Ранее мы упоминали проблему **приведения типов**. В реальной разработке в методе readObject выполняется логическая обработка: когда неизвестен конкретный тип данных переданного объекта, решение о вызове принимается через рефлексию. А рефлексия — это ключевой механизм, ведущий нас к RCE.
## Java-рефлексия
Что такое рефлексия? В слове "рефлексия" есть приставка "ре-", означающая обратное действие. Чтобы объяснить рефлексию, нужно начать с "прямого отражения". Посмотрим на код. Вот мой класс сущности:```java
package org.chabug.entity;
import java.io.IOException;
public class ReflectionClass {
String name;
public ReflectionClass(String name) {
this.name = name;
}
public ReflectionClass() {
}
public String say() {
return this.name;
}
private void evil(String cmd) {
try {
Runtime.getRuntime().exec(new String[]{"cmd","/c",cmd});
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "ReflectionClass{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Обычное написание```java package org.chabug.demo;
import org.chabug.entity.ReflectionClass;
public class ReflectionDemo { public static void main(String[] args) { ReflectionClass demo = new ReflectionClass(); demo.setName("hello"); System.out.println(demo.say()); // demo.evil("calc"); // 不能够调用private方法 } }
Очень просто: создается экземпляр ReflectionClass через `new`, а затем через этот экземпляр вызывается его метод — это и есть "прямое отражение". Но что делать, если при `new` неизвестно имя класса? Как вызвать метод, защищенный модификатором private? Здесь и проявляется роль рефлексии. Посмотрите на следующий фрагмент кода:```java
package org.chabug.demo;
import org.chabug.entity.ReflectionClass;
import java.lang.reflect.Method;