Como limitar setAccessible apenas para usos “legítimos”?

100

Quanto mais aprendo sobre o poder do java.lang.reflect.AccessibleObject.setAccessible, mais espantado fico com o que ele pode fazer. Isso foi adaptado de minha resposta à pergunta ( Usando reflexão para alterar File.separatorChar final estático para teste de unidade ).

import java.lang.reflect.*;

public class EverythingIsTrue {
   static void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);

      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

      field.set(null, newValue);
   }
   public static void main(String args[]) throws Exception {      
      setFinalStatic(Boolean.class.getField("FALSE"), true);

      System.out.format("Everything is %s", false); // "Everything is true"
   }
}

Você pode fazer coisas verdadeiramente ultrajantes:

public class UltimateAnswerToEverything {
   static Integer[] ultimateAnswer() {
      Integer[] ret = new Integer[256];
      java.util.Arrays.fill(ret, 42);
      return ret;
   }   
   public static void main(String args[]) throws Exception {
      EverythingIsTrue.setFinalStatic(
         Class.forName("java.lang.Integer$IntegerCache")
            .getDeclaredField("cache"),
         ultimateAnswer()
      );
      System.out.format("6 * 9 = %d", 6 * 9); // "6 * 9 = 42"
   }
}

Presumivelmente, os designers da API percebem o quão abusivo setAccessiblepode ser, mas devem ter admitido que ela tem usos legítimos para fornecê-la. Então, minhas perguntas são:

  • Para que são os usos verdadeiramente legítimos setAccessible?
    • O Java poderia ter sido projetado para NÃO ter essa necessidade em primeiro lugar?
    • Quais seriam as consequências negativas (se houver) de tal projeto?
  • Você pode restringir apenas setAccessiblepara usos legítimos?
    • É apenas através SecurityManager?
      • Como funciona? Whitelist / blacklist, granularity, etc?
      • É comum ter que configurá-lo em seus aplicativos?
    • Posso escrever minhas aulas para serem à setAccessibleprova, independentemente da SecurityManagerconfiguração?
      • Ou estou à mercê de quem gerencia a configuração?

Acho que mais uma pergunta importante é: PRECISO ME PREOCUPAR COM ISSO ???

Nenhuma das minhas aulas tem qualquer aparência de privacidade obrigatória. O padrão singleton (colocando de lado as dúvidas sobre seus méritos) agora é impossível de aplicar. Como mostram meus trechos acima, mesmo algumas suposições básicas de como funciona o Java fundamental não estão nem perto de serem garantidas.

ESTES PROBLEMAS NÃO SÃO REAIS ???


Ok, acabei de confirmar: graças a setAccessible, strings Java NÃO são imutáveis.

import java.lang.reflect.*;

public class MutableStrings {
   static void mutate(String s) throws Exception {
      Field value = String.class.getDeclaredField("value");
      value.setAccessible(true);
      value.set(s, s.toUpperCase().toCharArray());
   }   
   public static void main(String args[]) throws Exception {
      final String s = "Hello world!";
      System.out.println(s); // "Hello world!"
      mutate(s);
      System.out.println(s); // "HELLO WORLD!"
   }
}

Eu sou o único que acha que isso é uma grande preocupação?

poligenelubrificantes
fonte
30
Você deve ver o que eles podem fazer em C ++! (Oh, e provavelmente C #.)
Tom Hawtin - tackline

Respostas:

105

PRECISO ME PREOCUPAR COM ISSO ???

Isso depende inteiramente dos tipos de programas que você está escrevendo e para que tipo de arquitetura.

Se você está distribuindo um componente de software chamado foo.jar para as pessoas do mundo, você está completamente à mercê delas de qualquer maneira. Eles podem modificar as definições de classe dentro de seu .jar (por meio de engenharia reversa ou manipulação direta de bytecode). Eles podem executar seu código em sua própria JVM, etc. Nesse caso, não adiantará se preocupar.

Se você está escrevendo um aplicativo da web que faz interface apenas com pessoas e sistemas via HTTP e controla o servidor de aplicativos, também não é uma preocupação. Claro que os colegas programadores em sua empresa podem criar códigos que quebrem seu padrão singleton, mas apenas se realmente quiserem.

Se o seu trabalho futuro for escrever código na Sun Microsystems / Oracle e você tiver a tarefa de escrever código para o núcleo Java ou outros componentes confiáveis, é algo que você deve estar ciente. Preocupar-se, entretanto, só fará você perder o cabelo. Em qualquer caso, eles provavelmente farão você ler as Diretrizes de Codificação Segura junto com a documentação interna.

Se você for escrever miniaplicativos Java, a estrutura de segurança é algo que você deve conhecer. Você descobrirá que applets não assinados tentando chamar setAccessible resultarão apenas em SecurityException.

setAccessible não é a única coisa que evita as verificações de integridade convencionais. Existe uma classe Java principal não API chamada sun.misc.Unsafe que pode fazer praticamente tudo o que quiser, incluindo acessar a memória diretamente. O código nativo (JNI) pode contornar esse tipo de controle também.

Em um ambiente de área restrita (por exemplo, Java Applets, JavaFX), cada classe tem um conjunto de permissões e acesso a Unsafe, setAccessible e as implementações nativas definidas são controladas pelo SecurityManager.

"Os modificadores de acesso Java não se destinam a ser um mecanismo de segurança."

Isso depende muito de onde o código Java está sendo executado. As principais classes Java usam modificadores de acesso como um mecanismo de segurança para impor a sandbox.

Quais são os usos verdadeiramente legítimos de setAccessible?

As classes principais do Java o usam como uma maneira fácil de acessar coisas que devem permanecer privadas por razões de segurança. Como exemplo, a estrutura de serialização Java a usa para invocar construtores de objetos privados ao desserializar objetos. Alguém mencionou System.setErr, e seria um bom exemplo, mas curiosamente, todos os métodos da classe System setOut / setErr / setIn usam código nativo para definir o valor do campo final.

Outro uso legítimo óbvio são os frameworks (persistência, frameworks web, injeção) que precisam espiar dentro dos objetos.

Os depuradores, na minha opinião, não se enquadram nesta categoria, pois normalmente não rodam no mesmo processo JVM, mas sim na interface com a JVM por outros meios (JPDA).

O Java poderia ter sido projetado para NÃO ter essa necessidade em primeiro lugar?

Essa é uma pergunta muito profunda para responder bem. Imagino que sim, mas você precisaria adicionar alguns outros mecanismos que podem não ser tão preferíveis.

Você pode restringir setAccessible apenas para usos legítimos?

A restrição OOTB mais direta que você pode aplicar é ter um SecurityManager e permitir setAccessible apenas para código vindo de certas fontes. Isso é o que Java já faz - as classes Java padrão que vêm de seu JAVA_HOME têm permissão para fazer setAccessible, enquanto classes de miniaplicativo não assinadas de foo.com não têm permissão para fazer setAccessible. Como foi dito antes, essa permissão é binária, no sentido de que se tem ou não. Não há uma maneira óbvia de permitir que setAccessible modifique certos campos / métodos enquanto desabilita outros. Usando o SecurityManager, você pode, no entanto, impedir que as classes façam referência a certos pacotes completamente, com ou sem reflexão.

Posso escrever minhas classes para serem definidas à prova de acessibilidade, independentemente da configuração do SecurityManager? ... Ou fico à mercê de quem gerencia a configuração?

Você não pode e com certeza é.

Sami Koivu
fonte
"Se você está distribuindo um componente de software chamado foo.jar para as pessoas do mundo, você está completamente à mercê deles de qualquer maneira. Eles podem modificar as definições de classe dentro do seu .jar (por meio de engenharia reversa ou manipulação direta de bytecode). Eles poderia executar seu código em sua própria JVM, etc. Neste caso, se preocupar não vai adiantar nada. " Este ainda é o caso? Algum conselho sobre como dificultar a adulteração de software (espero que significativamente)? É difícil simplesmente jogar aplicativos de desktop java fora da janela por causa disso.
Sero
@naaz, eu teria que dizer que nada mudou. É que a arquitetura está trabalhando contra você. Posso alterar qualquer software em execução na minha máquina sobre o qual eu tenha controle total. O impedimento mais forte pode ser legal, mas não imagino que funcione para todos os cenários. Acho que os binários Java estão entre os mais fáceis de reverter, mas quem tem experiência vai adulterar qualquer coisa. A ofuscação ajuda, tornando difícil fazer grandes mudanças, mas qualquer coisa booleana (como uma verificação de direitos autorais) ainda é trivial para contornar. Você pode tentar colocar as partes principais em um servidor.
Sami Koivu
Que tal denuvo?
Sero
15
  • Para que são os usos verdadeiramente legítimos setAccessible?

Teste de unidade, internos da JVM (por exemplo, implementação System.setError(...)) e assim por diante.

  • O Java poderia ter sido projetado para NÃO ter essa necessidade em primeiro lugar?
  • Quais seriam as consequências negativas (se houver) de tal projeto?

Muitas coisas seriam impossíveis de implementar. Por exemplo, várias injeções de persistência, serialização e dependência de Java dependem de reflexão. E praticamente qualquer coisa que dependa das convenções JavaBeans em tempo de execução.

  • Você pode restringir apenas setAccessiblepara usos legítimos?
  • É apenas através SecurityManager?

Sim.

  • Como funciona? Whitelist / blacklist, granularity, etc?

Depende da permissão, mas acredito que a permissão de uso setAccessibleseja binária. Se desejar granularidade, você precisará usar um carregador de classes diferente com um gerenciador de segurança diferente para as classes que deseja restringir. Eu acho que você poderia implementar um gerenciador de segurança personalizado que implemente uma lógica mais refinada.

  • É comum ter que configurá-lo em seus aplicativos?

Não.

  • Posso escrever minhas aulas para serem à setAccessibleprova, independentemente da SecurityManagerconfiguração?
    • Ou estou à mercê de quem gerencia a configuração?

Não, você não pode, e sim você é.

A outra alternativa é "impor" isso por meio de ferramentas de análise de código-fonte; por exemplo, costume pmdou findbugsregras. Ou revisão seletiva do código identificado por (digamos) grep setAccessible ....

Em resposta ao acompanhamento

Nenhuma das minhas aulas tem qualquer aparência de privacidade obrigatória. O padrão singleton (colocando de lado as dúvidas sobre seus méritos) agora é impossível de aplicar.

Se isso o preocupa, suponho que você precise se preocupar. Mas realmente você não deve tentar forçar outros programadores a respeitar suas decisões de design. Se as pessoas são estúpidas o suficiente para usar a reflexão para criar gratuitamente várias instâncias de seus singletons (por exemplo), elas podem viver com as consequências.

Por outro lado, se você quer dizer "privacidade" para abranger o significado de proteger informações confidenciais da divulgação, você está errando na árvore. A maneira de proteger dados confidenciais em um aplicativo Java é não permitir que código não confiável na caixa de proteção de segurança que lida com dados confidenciais. Os modificadores de acesso Java não se destinam a ser um mecanismo de segurança.

<Exemplo de string> - Sou o único que acha que isso é uma grande preocupação?

Provavelmente não o único :-). Mas IMO, isso não é uma preocupação. É fato aceito que o código não confiável deve ser executado em uma sandbox. Se você tem um código de confiança / um programador de confiança fazendo coisas como essa, seus problemas são piores do que Strings mutáveis ​​inesperadamente. (Pense em bombas lógicas, exfiltração de dados por meio de canais secretos , etc.)

Existem maneiras de lidar com (ou mitigar) o problema de um "mau ator" em sua equipe de desenvolvimento ou operações. Mas eles são caros e restritivos ... e exagerados para a maioria dos casos de uso.

Stephen C
fonte
1
Também é útil para coisas como implementações de persistência. O Reflections não é realmente útil para depuradores (embora fosse originalmente destinado a ser). Você não pode alterar (subclasse) de forma útil SecurityManagerpara isso, porque tudo o que você obtém é a verificação de permissão - tudo o que você realmente pode fazer é olhar para o acc e talvez percorrer a pilha, verificar o thread atual). grep java.lang.reflect.
Tom Hawtin - tackline de
@Stephen C: +1 já, mas também gostaria de receber sua opinião sobre mais uma questão que acabei de adicionar. Desde já, obrigado.
poligenelubrificantes de
Observe que grep é uma ideia terrível. Existem tantas maneiras de executar código dinamicamente em Java que você quase certamente perderá uma. O código da lista negra em geral é uma receita para o fracasso.
Antimônio
"Os modificadores de acesso Java não se destinam a ser um mecanismo de segurança." - na verdade, sim, eles são. Java é projetado para permitir que códigos confiáveis ​​e não confiáveis ​​coexistam na mesma máquina virtual - isso fazia parte de seus requisitos básicos desde o início. Mas apenas quando o sistema está sendo executado com um SecurityManager bloqueado. A capacidade de código de área restrita depende inteiramente da imposição de especificadores de acesso.
Jules
@Jules - A maioria dos especialistas em Java discordaria disso; por exemplo, stackoverflow.com/questions/9201603/…
Stephen C
11

A reflexão é de fato ortogonal à proteção / proteção sob essa perspectiva.

Como podemos limitar a reflexão?

Java possui um gerenciador de segurança e ClassLoadercomo base para seu modelo de segurança. No seu caso, acho que você precisa dar uma olhada java.lang.reflect.ReflectPermission.

Mas isso não resolve completamente o problema da reflexão. As capacidades reflexivas disponíveis devem estar sujeitas a um esquema de autorização de baixa granularidade, o que não é o caso agora. Por exemplo, para permitir que determinado framework use reflexão (por exemplo, Hibernate), mas não o resto do seu código. Ou para permitir que um programa reflita apenas de forma somente leitura, para fins de depuração.

Uma abordagem que pode se tornar dominante no futuro é o uso dos chamados espelhos para separar os recursos reflexivos das classes. Consulte Espelhos: Princípios de Projeto para Instalações de Meta-nível . No entanto, existem várias outras pesquisas que abordam esse problema. Mas concordo que o problema é mais grave para linguagens dinâmicas do que linguagens estáticas.

Devemos nos preocupar com o superpoder que a reflexão nos dá? Sim e não.

Sim, no sentido de que a plataforma Java deve ser protegida com Classloaderum gerenciador de segurança. A capacidade de mexer com a reflexão pode ser vista como uma violação.

Não, no sentido de que a maioria dos sistemas não são totalmente seguros. Muitas classes podem frequentemente ser subclasses e você já pode abusar do sistema apenas com isso. É claro que as classes podem ser feitas finalou lacradas de forma que não possam ser subclassificadas em outro jar. Mas apenas algumas classes são protegidas corretamente (por exemplo, String) de acordo com isso.

Veja esta resposta sobre a aula final para uma boa explicação. Veja também o blog de Sami Koivu para mais informações sobre hackers java em torno da segurança.

O modelo de segurança do Java pode ser visto como insuficiente para alguns aspectos. Algumas linguagens, como NewSpeak, têm uma abordagem ainda mais radical à modularidade, em que você tem acesso apenas ao que é explicitamente fornecido a você pela inversão de dependência (por padrão, nada).

Também é importante observar que a segurança é relativa . No nível do idioma, você não pode, por exemplo, evitar que um módulo consuma 100% da CPU ou consuma toda a memória até a OutOfMemoryException. Essas preocupações precisam ser tratadas por outros meios. Talvez veremos no futuro o Java estendido com cotas de utilização de recursos, mas não é para amanhã :)

Eu poderia expandir mais o assunto, mas acho que fiz o que quero dizer.

Ewernli
fonte