
Exploração de vulnerabilidade de desserialização do Shiro rememberMe usando CVE-2020-2883 no WebLogic, registro com um clique de shell de memória via filtro do AntSword
Os tópicos abordados nesta apresentação incluem:
Serialização em Java é o processo de converter objetos Java em uma sequência de bytes para facilitar o armazenamento em memória, arquivos ou banco de dados. O método writeObject() da classe ObjectOutputStream pode realizar a serialização, convertendo objetos Java em sequência de bytes.
Desserialização em Java é o processo de restaurar uma sequência de bytes para um objeto Java. O método readObject() da classe ObjectInputStream é usado para desserialização.
Um exemplo simples, veja o código SerializeAndDeserialize ps: preste atenção especial na conversão forçada de tipos no código```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);
}
}
String, Integer, array, Object e outros tipos de dados integrados do Java podem ser serializados. As classes Person e Dog que escrevemos podem ser serializadas e desserializadas desde que implementem a interface Serializable.
## Por que ocorre uma vulnerabilidade durante a desserialização?
Vejamos um trecho de código. Agora, existe uma classe de entidade maliciosa 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});
}
}
Seu readObject contém código que executa comandos Runtime.getRuntime().exec(new String[]{"cmd", "/c", name}), onde o parâmetro name é o comando a ser executado. Então podemos construir um objeto malicioso, atribuir ao seu atributo name o comando a ser executado, e quando a desserialização acionar o readObject, ocorrerá RCE. Como segue:```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); } }

Agora sabemos como a desserialização pode levar a RCE, mas no desenvolvimento real não é possível escrever diretamente assim, então isso envolve a busca por cadeias de exploração. Uma vulnerabilidade de desserialização requer três coisas:
1. Ponto de entrada da desserialização (source)
2. Método alvo (sink)
3. Cadeia de gadgets (gadget chain)
Observando com atenção o resultado de saída na imagem acima, não apenas o método `readObject` é acionado, mas também `toString()`, construtor sem parâmetros, `set` e `get`. Portanto, na busca prática por cadeias de exploração, não é necessário focar apenas no método `readObject()`.
Em seguida, precisamos entender o conceito de **reflexão**. Mencionamos anteriormente o problema de **conversão de tipo forçada**. No desenvolvimento real, dentro do `readObject`, há processamento lógico: quando o tipo de dados específico do objeto de entrada é desconhecido, a reflexão é usada para determinar e invocar. E a reflexão é um meio importante para alcançarmos RCE.
## Reflexão em Java
O que é reflexão? A palavra "reflexão" contém o prefixo "re-". Para explicar a reflexão, precisamos começar pela "reflexão direta". Veja o código. Esta é minha classe de entidade.```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;
}
}
Escrita normal```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方法 } }
Muito simples: cria-se uma instância de `ReflectionClass` com `new` e depois, através dessa instância, invocam-se os métodos pertencentes a ela — isso é "reflexão direta". Mas o que fazer quando você não sabe o nome da classe ao usar `new`? E como invocar métodos protegidos por `private`? É aí que a reflexão mostra o seu valor. Veja o código a seguir:```java
package org.chabug.demo;
import org.chabug.entity.ReflectionClass;
import java.lang.reflect.Method;