Avançar para a área de conteúdo

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Todas as informações enviadas são seguras.

  • Fechar [x]

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Todas as informações enviadas são seguras.

  • Fechar [x]

Dica: Proteja seu código contra a vulnerabilidade do finalizador

Um padrão para evitar a criação de classes inválidas

Neil D. Masson, Java Support Engineer, IBM
Neil Masson
Neil Masson trabalhou no desenvolvimento e suporte à linguagem Java por muitos anos. Atualmente, seu foco é a melhoria da qualidade e da segurança dos releases Java.

Resumo:  Seu código Java talvez esteja vulnerável a uma exploração baseada na finalização. Saiba como a exploração funciona e como modificar seu código para evitar esse ataque.

Data:  22/Jul/2011
Nível:  Intermediário Também disponível em :   Inglês
Atividade:  812 visualizações
Comentários:  


Os finalizadores podem causar uma vulnerabilidade no código Java usado para criar objetos. A exploração é uma variação da técnica conhecida de usar um finalizador para ressuscitar um objeto. Quando um objeto com o método finalize() fica inacessível, ele é colocado em uma fila para ser processado posteriormente. Esta dica explica como a exploração funciona e mostra como é possível proteger o código contra ela. Todos os exemplos de código estão disponíveis para download.

A ideia dos finalizadores é permitir que um método Java libere recursos nativos que precisem ser devolvidos ao sistema operacional. Infelizmente, qualquer código Java pode ser executado em um finalizador, permitindo um o código como o da Listagem 1


Listagem 1. Classe que pode ser ressuscitada

public class Zombie {
  static Zombie zombie;

  public void finalize() {
    zombie = this;
  }
}

Quando o finalizador Zombie é chamado, ele pega o objeto que está sendo finalizado, — referenciado por this — , e o armazena em uma variável zombie estática. Agora, o objeto pode ser alcançado novamente e não pode ser coletado como lixo.

Uma versão mais insidiosa desse código permite que até mesmo um objeto parcialmente construído seja ressuscitado. Mesmo que um objeto não passe em algum critério de correção em seu inicializador, ele ainda pode ser criado por um finalizador, como na Listagem 2:


Listagem 2. Criando uma classe ilegal

public class Zombie2 {
  static Zombie2 zombie;
  int value;

  public Zombie2(int value) {
    if(value < 0) {
      throw new IllegalArgumentException("Negative Zombie2 value");
    }
    this.value = value;
  }
  public void finalize() {
    zombie = this;
  }
}

Na Listagem 2, o efeito da verificação do argumento value é negado pela existência do método finalize() .

Como o ataque funciona

Naturalmente, é pouco provável que alguém escreva um código como o da Listagem 2. Mas uma vulnerabilidade pode surgir se a classe for subclassificada, como na Listagem 3:


Listagem 3. Uma classe vulnerável

class Vulnerable {
  Integer value = 0;
 
  Vulnerable(int value) {
    if(value <= 0) {
      throw new IllegalArgumentException("Vulnerable value must be positive");
    }
    this.value = value;
  }
  @Override
  public String toString() {
    return(value.toString());
  }
}

A classe Vulnerable na Listagem 3 foi projetada para evitar que um valor não positivo de value seja configurado. Essa intenção é subvertida pelo método AttackVulnerable() , mostrado na Listagem 4:


Listagem 4. Uma classe para subverter a classe Vulnerable

class AttackVulnerable extends Vulnerable {
  static Vulnerable vulnerable;

  public AttackVulnerable(int value) {
    super(value);
  }

  public void finalize() {
    vulnerable = this;
  }

  public static void main(String[] args) {
    try {
      new AttackVulnerable(-1);
    } catch(Exception e) {
      System.out.println(e);
    }
    System.gc();
    System.runFinalization();
    if(vulnerable != null) {
      System.out.println("Vulnerable object " + vulnerable + " created!");
    }
  }
}

No método main() da classe AttackVulnerable , é feita uma tentativa de instanciar um novo objeto AttackVulnerable . Como o valor de value está fora do intervalo, é lançada uma exceção que é capturada no bloco catch . As chamadas System.gc() e System.runFinalization() incentivam a VM a executar um ciclo de coleta de lixo e os finalizadores. Estas chamadas não são necessárias para o ataque ter sucesso, mas servem para demonstrar o resultado final do ataque, que a criação de um objeto Vulnerable com valor inválido.

A execução das etapas de teste apresenta o seguinte resultado:

java.lang.IllegalArgumentException: Vulnerable value must be positive

Vulnerable object 0 created!

Por que o valor de Vulnerable é 0 e não -1? Note que no construtor Vulnerable , na Listagem 3, a atribuição a value não acontece até depois da verificação do argumento. Assim, value tem seu valor inicial, que nesse caso é 0.

Esse tipo de ataque pode ser usado até mesmo para evitar verificações de segurança explícitas. Por exemplo, a classe Insecure da Listagem 5 foi projetada para lançar a SecurityException se for executada sob um SecurityManager e o responsável pela chamada não tiver permissão de gravar no diretório atual:


Listagem 5. Classe Insecure

import java.io.FilePermission;

public class Insecure {
  Integer value = 0;

  public Insecure(int value) {
    SecurityManager sm = System.getSecurityManager();
    if(sm != null) {
      FilePermission fp = new FilePermission("index", "write");
      sm.checkPermission(fp);
    }
    this.value = value;
  }
  @Override
  public String toString() {
    return(value.toString());
  }
}

A classe Insecure da Listagem 5 pode ser atacada da mesma maneira que antes, como mostrado na classe AttackInsecure da Listagem 6:


Listagem 6. Ataque à classe Insecure

public class AttackInsecure extends Insecure {
  static Insecure insecure;

  public AttackInsecure(int value) {
    super(value);
  }

  public void finalize() {
    insecure = this;
  }

  public static void main(String[] args) {
    try {
      new AttackInsecure(-1);
    } catch(Exception e) {
      System.out.println(e);
    }
    System.gc();
    System.runFinalization();
    if(insecure != null) {
      System.out.println("Insecure object " + insecure + " created!");
    }
  }
}

Executar o código da Listagem 6 sob um SecurityManager apresenta o seguinte resultado:

java -Djava.security.manager AttackInsecure
java.security.AccessControlException: Access denied (java.io.FilePermission index write)
Insecure object 0 created!


Como evitar o ataque

Até a terceira edição do JLS (Java Language Specification) ser implementada no Java SE 6, as únicas maneiras de evitar o ataque — usando um sinalizador initialized , proibindo definir como subclasse ou criando um finalizador final — , eram soluções insatisfatórias.

Usar um sinalizador initialized

Um modo de evitar o ataque é usar um sinalizador initialized , que é configurado como true , depois que o objeto tiver sido criado corretamente. Cada método na classe primeiro verifica se initialized foi configurado e lança uma exceção se não tiver sido. É cansativo desenvolver esse tipo de codificação, é fácil omiti-la por acidente e ela não impede que um invasor defina o método como subclasse.

Evitar definir como subclasse

É possível declarar a classe que está sendo criada como final. Isso significa que ninguém pode criar uma subclasse dessa classe, o que impede o ataque de funcionar. No entanto, essa técnica elimina a flexibilidade da capacidade de estender a classe a fim de especializá-la ou adicionar funcionalidades extras.

Criar um finalizador final

É possível criar um finalizador para a classe que está sendo criada e declará-lo como final. Isso significa que nenhuma subclasse dessa classe pode declarar um finalizador. A desvantagem dessa abordagem é que a existência do finalizador significa que o objeto é mantido ativo por mais tempo do que poderia ser.

Uma maneira nova e melhor

Para tornar mais fácil evitar esse tipo de ataque sem a introdução de código extra ou restrições, os designers de Java modificaram o JLS (veja Recursos) para dizer que, se uma exceção for lançada em um construtor antes de o java.lang.Object ser construído, o método finalize() desse método não será executado.

Mas como é possível lançar uma exceção antes de java.lang.Object ser construído? Afinal, a primeira linha de qualquer construtor deve ser uma chamada a this() ou super(). Se o construtor não incluir essa chamada explícita, uma chamada super() é acrescentada implicitamente. Assim, antes de um objeto ser criado, outro objeto da mesma classe ou de sua superclasse deve ser construído. Por fim, isso resulta na criação do próprio java.lang.Object , e depois, na criação de todas as subclasses, antes de qualquer código do método que está sendo construído ser executado.

Para entender como uma exceção pode ser lançada antes de java.lang.Object ser construído, é necessário entender a sequência exata de criação do objeto. O JLS enuncia a sequência explicitamente.

Quando um objeto é criado, a JVM:

  1. Aloca espaço para o objeto.
  2. Configura todas as variáveis de instância do objeto com seus valores padrão. Isso inclui as variáveis de instância nas superclasses do objeto.
  3. Designa as variáveis de parâmetro do objeto.
  4. Processa qualquer chamada de construtor explícita ou implícita (uma chamada para this() ou super() no construtor).
  5. Inicializa as variáveis na classe.
  6. Executa o restante do construtor.

O ponto-chave é que os parâmetros do construtor são processados antes de qualquer código dentro do construtor ser processado. Isso significa que, se for feita a validação durante o processamento dos parâmetros, é possível — lançando uma exceção — evitar que sua classe seja finalizada.

Isso resulta em uma nova versão, mostrada na Listagem 7, da classe da Listagem 3 chamada Vulnerable :


Listagem 7. Classe Invulnerable

class Invulnerable {
  int value = 0;
 
  Invulnerable(int value) {
    this(checkValues(value));
    this.value = value;
  }

  private Invulnerable(Void checkValues) {}

  static Void checkValues(int value) {
    if(value <= 0) {
      throw new IllegalArgumentException("Invulnerable value must be positive");
    }
    return null;
  }

  @Override
  public String toString() {
    return(Integer.toString(value));
  }
}


Na Listagem 7, o construtor público de Invulnerable chama um construtor particular que chama o método checkValues para criar seu parâmetro. Esse método é chamado antes de o construtor fazer sua chamada para criar sua superclasse, que é o construtor de Object. Assim, se for lançada uma exceção em checkValues, o objeto Invulnerable não será finalizado.

O código da Listagem 8 tenta atacar Invulnerable:


Listagem 8. Tentativa de subverter a classe Invulnerable

class AttackInvulnerable extends Invulnerable {
  static Invulnerable vulnerable;

  public AttackInvulnerable(int value) {
    super(value);
  }

  public void finalize() {
    vulnerable = this;
  }

  public static void main(String[] args) {
    try {
      new AttackInvulnerable(-1);
    } catch(Exception e) {
      System.out.println(e);
    }
    System.gc();
    System.runFinalization();
    if(vulnerable != null) {
      System.out.println("Invulnerable object " + vulnerable + "
created!");
    } else {
      System.out.println("Attack failed");
    }
  }
}

with the addition of

    } else {
      System.out.println("Attack failed");

Com Java 5, que grava em uma versão mais antiga do JLS, um objeto Invulnerable é criado:

java.lang.IllegalArgumentException: Invulnerable value must be positive
Invulnerable object 0 created!

Java SE 6 (a partir do release de disponibilidade geral da JVM Oracle e SR9 da JVM da IBM), segue a última especificação, de modo que o objeto não é criado:

java.lang.IllegalArgumentException: Invulnerable value must be positive
Attack failed


Conclusão

Os finalizadores são recurso infeliz da linguagem Java. Embora o coletor de lixo possa recuperar automaticamente a memória que não é mais usada por objetos Java, não existe um mecanismo para reciclar recursos nativos, como a memória nativa, descritores de arquivos ou soquetes. As bibliotecas Java padrão que fornecem essa interface com esses recursos nativos geralmente têm um método close() para permitir a limpeza adequada — mas também usam finalizadores para garantir que não ocorra vazamento de recursos se um objeto não for encerrado apropriadamente.

Para outros objetos, em geral é melhor evitar os finalizadores. Não há garantia de quando um finalizador será executado, ou mesmo se ele será executado. A existência de um finalizador significa que um objeto inatingível não pode ser coletado para o lixo até que o finalizador tenha sido executado, e esse objeto talvez esteja mantendo ainda mais objetos ativos. Isso resulta em um aumento no número de objetos ativos e, portanto, no uso de heap do processo Java.

A capacidade de um finalizador reviver um objeto destinado à coleta de lixo é claramente uma consequência não intencional da maneira em que o mecanismo de finalização funciona. Implementações mais recentes da JVM agora permitem proteger seu código contra as implicações de segurança desse efeito.



Download

DescriçãoNomeTamanhoMétodo de download
Code samples for this tipj-fv.zip.zip4KBHTTP

Informações sobre métodos de download


Recursos

Aprender

Obter produtos e tecnologias

  • Avalie os produtos IBM da maneira que for melhor para você: faça download da versão de teste de um produto, avalie um produto on-line, use-o em um ambiente de nuvem ou passe algumas horas na SOA Sandbox aprendendo como implementar Arquitetura Orientada a Serviços de forma eficiente.

Discutir

  • Java security: Participe do fórum de segurança Java no developerWorks.

  • Participe da comunidade do developerWorks. Entre em contato com outros usuários do developerWorks, enquanto explora os blogs, fóruns, grupos e wikis orientados ao desenvolvedor.

Sobre o autor

Neil Masson

Neil Masson trabalhou no desenvolvimento e suporte à linguagem Java por muitos anos. Atualmente, seu foco é a melhoria da qualidade e da segurança dos releases Java.

Ajuda para Relatar Abuso

Relatar abuso

Obrigado. Esta entrada foi sinalizada para atenção do moderador.


Ajuda para Relatar Abuso

Relatar abuso

Falha no envio do Relatório de abuso. Tente novamente mais tarde.


developerWorks: Registre-se


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Selecione seu nome de exibição

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

(Deve possuir de 3 a 31 caracteres.)


Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Classificar este artigo

Comentários

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java
ArticleID=742991
ArticleTitle=Dica: Proteja seu código contra a vulnerabilidade do finalizador
publish-date=07222011
author1-email=nmasson@uk.ibm.com
author1-email-cc=jaloi@us.ibm.com

Conheça a IBM da sua cidade

Virtual Branch Office Brasil

A IBM está mais perto do que você imagina!


Tags

Help
Use o campo de pesquisa para encontrar todos os tipos de conteúdo no My developerWorks com essa tag.

Use a barra de rolagem para ver mais ou menos tags.

Tags populares mostra as principais tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Minhas tags mostra suas tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Use o campo de pesquisa para localizar todos os tipos de conteúdo no Meu developerWorks com essa tag. Tags populares mostra as tags principais para essa zona de conteúdo particular (por exemplo, tecnologia Java, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere). Minhas tags mostra as suas tags para essa zona de conteúdo em particular (por exemplo, tecnologia Java, Linux, WebSphere).