Mecanismo de reflexão Java e questões de segurança

Mecanismo de reflexão Java e questões de segurança

Prefácio 0x00

Recentemente, no processo de resumir a vulnerabilidade de desserialização do Java, o autor encontrou uma palavra "mecanismo de reflexão do Java" que não pode ser contornada.No passado, eu sabia apenas que esse era um mecanismo para implementar a linguagem "quase dinâmica" do Java, o que é conveniente para os desenvolvedores de depurar programas No entanto, eu não esperava que a "reflexão" tenha se tornado um dos métodos de ataques de vulnerabilidade à desserialização; portanto, neste artigo, resumirei os princípios do mecanismo de reflexão e quais outros problemas de segurança existem.

Mapa mental do mecanismo reflexo:
Insira a descrição da imagem aqui

Definição do mecanismo de reflexão Java 0x01

Mecanismo de reflexão Java significa que, no estado de execução, para qualquer classe, você pode conhecer todas as propriedades e métodos dessa classe; para qualquer objeto, pode chamar qualquer um de seus métodos e propriedades; essas informações e dinâmicas obtidas dinamicamente A função de chamar o método do objeto é chamada de mecanismo de reflexão da linguagem java.

O ponto importante do mecanismo de reflexão é o "tempo de execução", que nos permite carregar, explorar e usar arquivos .class que são completamente desconhecidos durante a compilação enquanto o programa está em execução. Para resumir em uma frase, a reflexão pode realizar as propriedades e métodos de qualquer classe em tempo de execução.

0x02 vantagens e desvantagens do mecanismo de reflexão

Podemos ter uma pergunta quando somos novos na reflexão.Por que usar a reflexão para criar objetos diretamente? Ele não é perfumado? Desta vez, envolve os conceitos de compilação dinâmica e estática em Java. Vamos falar brevemente sobre isso.

  • Compilação estática: determine o tipo em tempo de compilação e ligue o objeto.
  • Compilação dinâmica: determine o tipo em tempo de execução e ligue o objeto. Maximiza a flexibilidade do Java, reflete o polimorfismo e reduz o acoplamento entre as classes.

Vantagens: O
mecanismo de reflexão pode realizar a criação dinâmica de objetos e compilação, mostrando muita flexibilidade, especialmente no desenvolvedor J2EE, sua flexibilidade é muito óbvia. Por exemplo, no desenvolvimento de um software em grande escala, quando o programa é compilado e lançado, se precisarmos atualizar algumas funções no futuro, não podemos pedir ao usuário que desinstale o software anterior e reinstale a nova versão. Se for estático, o programa inteiro precisará ser recompilado uma vez para obter a atualização da função e, se o mecanismo de reflexão for usado, ele poderá ser alcançado sem desinstalação dinâmica e só precisará ser criado e compilado dinamicamente em tempo de execução.

Desvantagens:
têm um impacto no desempenho. O mecanismo de reflexão é, na verdade, uma operação explicativa. Dizemos à JVM o que queremos fazer e como eles agrupam nossos requisitos. Esse tipo de operação é sempre mais lento do que apenas executar a mesma operação diretamente

0x03 Princípio do mecanismo de reflexão

A base principal do mecanismo de reflexão é entender a classe Class, que é um objeto de instância da classe java.lang.Class e Class é a classe de todas as classes. Para objetos comuns, geralmente usamos os seguintes métodos ao criar instâncias:

Demo test = new Demo();

Então, também podemos usar o método acima para criar um objeto de instância da classe class?

Class c = new Class();

A resposta não é boa, então analisamos o código fonte da Class e descobrimos que seu construtor é privado, o que significa que apenas a JVM pode criar objetos de Class.
Insira a descrição da imagem aqui

Embora não possamos usar new para instanciar um objeto, como criar um objeto comum, podemos obter um objeto Class aleatoriamente uma classe existente.Existem três maneiras, a seguir: (supondo que exista uma classe comum chamada Demo)

  1. Instancie uma outra classe comum e chame getClass () nesta instância para obter o objeto Class
Demo test = new Demo();
Class c1 = test.getClass();
  1. Qualquer tipo de dado (incluindo tipos básicos de dados) possui um atributo de classe "estático", portanto, chame diretamente o atributo .class para obter o objeto Class
Class c2 = Demo.class;
  1. Chame o método forNmae da classe Class para obter o objeto Class
Class c3 = Class.forName(“ReflectDemo.Demo”)//forName()

O parâmetro é o caminho real, nome do pacote, nome da classe
Insira a descrição da imagem aqui
Insira a descrição da imagem aqui

Dos três métodos de criação, o terceiro geralmente é usado, porque o primeiro criou objetos e perdeu o significado do mecanismo de reflexão; o segundo precisa importar o pacote de classes, a dependência é muito forte e o pacote será lançado sem importação Erro de compilação. O terceiro método é geralmente usado.Uma string pode ser passada ou gravada no arquivo de configuração.

O princípio do mecanismo de reflexão é mapear vários componentes da classe java em objetos java individuais, para que possamos chamar todos os membros (variáveis, métodos) da classe em tempo de execução. A figura a seguir é o processo de carregamento de classes no mecanismo de reflexão:
Insira a descrição da imagem aqui

Operação do mecanismo de reflexão Java 0x04

Através da introdução anterior, aprendemos como obter o objeto Class e, através do mecanismo de reflexão, você pode obter todas as informações dos membros do objeto Class e, em seguida, apresentamos brevemente algumas funções para obter membros:

  • Obter método de membro
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) 

// Obtenha todos os métodos desta classe, excluindo a classe pai.

public Method getMethod(String name, Class<?>... parameterTypes) 

// Obtenha todos os métodos públicos desta classe, incluindo a classe pai.

Os dois parâmetros são o nome do método e a lista de parâmetros de classe da classe de parâmetro de método (tipo de classe)
.Se houver quatro métodos de membro na classe A, a seguir:
Insira a descrição da imagem aqui
use as funções getDeclaredMethod () e getMethod () para obter todos os métodos de membro / public na classe especificada
Insira a descrição da imagem aqui
Insira a descrição da imagem aqui

  • Obter construtor
`public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)` 

// Obtenha todos os construtores desta classe, excluindo o construtor de sua classe pai.

public Constructor<T> getConstructor(Class<?>... parameterTypes) 

// Obtenha todos os construtores públicos desta classe, incluindo a classe pai.

Se houver três construtores na classe A, dois construtores públicos e um privado
Insira a descrição da imagem aqui
usam a função getDeclaredConstructor () para obter todos os construtores; use a função getConstructor () para obter apenas construtores públicos.
Insira a descrição da imagem aqui
Insira a descrição da imagem aqui

  • Obter variável de membro Campo
public Field getDeclaredField(String name) 

// Pega todas as variáveis ​​declaradas pela própria classe, excluindo as variáveis ​​da sua classe pai.

public Field getField(String name)

// Obtenha todas as variáveis ​​de membro público desta classe, incluindo suas variáveis ​​de classe pai.

A variável membro da classe também é um objeto, que é um objeto de java.lang.reflect.Field, portanto, obtemos essas informações através do método encapsulado em java.lang.reflect.Field.
Existem algumas variáveis ​​de membro
Insira a descrição da imagem aqui
com atributos diferentes na classe A: use a função getDeclaredFields () para obter todas as variáveis ​​de membro; use a função getFields () para obter apenas variáveis ​​de membro públicas.
Insira a descrição da imagem aqui
Insira a descrição da imagem aqui

Mecanismo de reflexão 0x05 e vulnerabilidade à desserialização

A principal função da vulnerabilidade de desserialização:
writeObject () serialize, output Object to Byte stream
readObject () desserialize, output Byte stream to Object

Usando o mecanismo de reflexão, reescreva o método readObject, adicione a função Runtime.getRuntime () que pode executar a execução do comando, execute o comando calc.exe para acessar a calculadora

package reflectdemo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;

public class demo implements Serializable{
	private Integer age;
	private String name;
    public demo() {}
    public demo(String name,Integer age){ //构造函数,初始化时执行
    	this.age = age;
    	this.name = name;
    } 
    private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException{
    	in.defaultReadObject();//调用原始的readOject方法
    	try {//通过反射方法执行命令;
    	Method method= java.lang.Runtime.class.getMethod("exec", String.class);
    	Object result = method.invoke(Runtime.getRuntime(), "calc.exe");    
    	}
    	catch(Exception e) {
    		e.printStackTrace();
    	}
    }
	public static void main(String[] args){
		demo x= new demo();
		operation.ser(x);
		operation.deser();
	}
}
class operation {
	public static void ser(Object obj) {
		try{//序列化操作,写数据
	        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.obj"));
	        //ObjectOutputStream能把Object输出成Byte流
	        oos.writeObject(obj);//序列化关键函数
	        oos.flush();  //缓冲流 
	        oos.close(); //关闭流
	    } catch (FileNotFoundException e) {        
	        e.printStackTrace();
	    } catch (IOException e) {
	        e.printStackTrace();
	    }
	}
	public static void deser() {
		try {//反序列化操作,读取数据
			File file = new File("object.obj");
			ObjectInputStream ois= new ObjectInputStream(new FileInputStream(file));
			Object x = ois.readObject();//反序列化的关键函数
			System.out.print(x);
			ois.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

A partir da vulnerabilidade de desserialização acima, pode-se observar que a reflexão Java pode realmente acessar métodos e propriedades particulares, o que é uma maneira de ignorar o mecanismo de segurança de segundo nível (um). Na verdade, é algo semelhante a um "backdoor" deixado pelo próprio Java para uma determinada finalidade ou para facilitar a depuração. De qualquer forma, seu princípio é desativar as verificações de segurança de acesso.
Insira a descrição da imagem aqui

Em geral, quando encontramos uma vulnerabilidade e queremos que o programa implemente a execução de comandos, há duas direções nas quais podemos trabalhar duro

  1. Códigos e funções de controle: os dados são tratados como códigos, como furos de injeção, como injeção nomeada; ou readObject é reescrito como o código de demonstração acima, e o código personalizado é adicionado
  2. Controlar entrada, dados, variáveis: use as funções e a lógica existentes no código para controlar o processo, alterando a forma do conteúdo da entrada (entradas diferentes terão fluxos lógicos diferentes e executarão o código em diferentes blocos de código)

Para vulnerabilidades de desserialização de Java, isso pertence à categoria de entrada de dados de controle. Ao chamar o mecanismo de reflexão para acionar uma vulnerabilidade, ele tem dois pontos básicos que devem ser atendidos:

  1. Há uma classe serializável, e a classe reescreveu o método readObject () (como não há injeção de código, você pode descobrir se existe essa classe na lógica de código existente)
  2. A função method.invoke aparece na lógica do método readObject () reescrito e os parâmetros são controláveis.

Segurança de reflexão 0x06

Depois de ler o acima, devemos sinceramente suspirar que o mecanismo de reflexão Java é muito poderoso. No entanto, se tivermos um certo senso de segurança, descobriremos que o mecanismo Java é muito poderoso.

A segurança é uma questão mais complexa ao lidar com reflexões. A reflexão é freqüentemente usada pelo código do tipo de estrutura.Por isso, podemos desejar que a estrutura tenha acesso total ao código, independentemente das restrições de acesso convencionais. No entanto, em outros casos, o acesso não controlado pode representar sérios riscos à segurança.

Devido a esses requisitos conflitantes, a linguagem de programação Java define uma abordagem em vários níveis para lidar com a segurança da reflexão. O modo básico é implementar as mesmas restrições à reflexão aplicadas ao acesso ao código-fonte:

  1. Acesso de qualquer local a componentes comuns
  2. Nenhum acesso ao componente privado fora da própria classe
  3. Acesso limitado a componentes protegidos e empacotados (acesso padrão)

Comparado ao C ++, o Java é uma linguagem relativamente segura. Isso está intimamente relacionado ao seu mecanismo operacional: o C ++ é executado localmente, o que significa que as permissões de quase todos os programas são teoricamente iguais. Como o Java está sendo executado em uma máquina virtual e não entra em contato diretamente com o mundo externo, o ambiente de execução do Java é realmente um ambiente "sandbox". E, como modelo de segurança do Java, inclui: uma série de componentes de segurança, como verificador de bytecode, carregador de classes, gerenciador de segurança, controlador de acesso, etc., portanto, o mecanismo de segurança do Java parece ser mais complicado.

Publicado 21 artigos originais · ganhou 14 · visitado 4075

Acho que você gosta

Origin blog.csdn.net/m0_38103658/article/details/105482035
Recomendado
Clasificación