Pegadinha "Segurança"

19 respostas
Sami_Koivu

Uma classe Authenticator tem um método perform que recebe uma instância de Password e se o Authenticator julga que a senha contido no objeto está correto ele executa um trecho de código privilegiado (que no caso desse exemplo é um System.out.println() :wink: )

Criei duas versões desta classe, cada um está com problemas diferentes.

A classe Password é um "wrapper" simples para uma senha:

public class Password { 
    
    private char[] passwordChars;
    
    public Password(String password) {
        this.passwordChars = password.toCharArray();
    }
    
    public int length() {
        return this.passwordChars.length;
    }
    
    public char getPasswordChar(int index) {
        return this.passwordChars[index];
    }
}

Este é o primeiro Authenticator, acho que é o mais fácil:

public final class Authenticator1 {
    
    private Password adminPassword = new Password("aa!NN9sdhauwe32i");
    
    public void perform(Password password) {
        if (password == null) return;
        
        boolean passwordCorrect = true;
        try {
            if (adminPassword.length() != password.length()) {
                passwordCorrect = false; // pw length does not match
            }
            
            if (passwordCorrect) {
                for (int i=0; i &lt adminPassword.length(); i++) {
                    if (adminPassword.getPasswordChar(i) != password.getPasswordChar(i)) {
                        passwordCorrect = false;
                        break;
                    }
                }
            }
        } catch(Exception ex) {
            ex.printStackTrace();
        }
        
        if (passwordCorrect) {
            performPriviledged();
        }
    }
    
    private void performPriviledged() {
        System.out.println("Performing priviledged actions 1.");
    }
}

E o segundo que talvez seja um pouco mais difícil:

public final class Authenticator2 {
    
    private Password adminPassword = new Password("aa!NN9sdhauwe32i");
    
    public void perform(Password password) {
        if (password == null) return;
        
        boolean passwordCorrect = true;
        if (adminPassword.length() != password.length()) {
            passwordCorrect = false; // pw length does not match
        }
            
        if (passwordCorrect) {
            for (int i=0; i &lt password.length(); i++) {
                if (adminPassword.getPasswordChar(i) != password.getPasswordChar(i)) {
                    passwordCorrect = false;
                    break;
                }
            }
        }
        
        if (passwordCorrect) {
            performPriviledged();
        }
    }
    
    private void performPriviledged() {
        System.out.println("Performing priviledged actions 2.");
    }
}

Então. O objetivo: Adicione tal código (de preferência dentro do método main desta classe Test) que consegue rodar o método "performPriviledged()" de uma (ou de ambas) dessas duas classes (Authenticator1 e Authenticator2).

public class Test {
    private static final Authenticator1 authenticator1 = new Authenticator1();
    private static final Authenticator2 authenticator2 = new Authenticator2();
    
    public static void main(String[] args) {
        //authenticator1.perform(new Password("aa!NN9sdhauwe32i"));
        //authenticator2.perform(new Password("aa!NN9sdhauwe32i"));
    }
}

Coloquei comentado como seria o jeito de invocar os métodos caso a gente soubesse a senha correta. Mas vamos pretender que nós não sabemos a senha. Este é um exemplo meio tosco, mas a intenção é somente demonstrar que escrever código seguro é uma tarefa difícil e quase sempre que possível é melhor utilizar os mecanismos de segurança existentes nos containers e frameworks do que escrever seu próprio mecanismo.

19 Respostas

sapulha

Afff, tá parecendo questão do “topa tudo por dinheiro” !!!

Sami_Koivu

sapulha:
Afff, tá parecendo questão do “topa tudo por dinheiro” !!!

Infelizmente não conheço :wink: Li uma descrição do show na wikipedia, mas não consigo ver o que tem de parecido. Você poderia explicar para um gringo ignorante?

[]s,
Sami

O

A primeira resolvi assim :

public class MyPassword extends Password{
    
    public MyPassword(){
        super("");
    }
    
    public int length() {
        return Integer.parseInt("");
    }
}
public class Test {
     private static final Authenticator1 authenticator1 = new Authenticator1();
     private static final Authenticator2 authenticator2 = new Authenticator2();
     
     public static void main(String[] args) throws ClassNotFoundException {
                  
         authenticator1.perform(new MyPassword());
     }

 }
fmeyer

Sami Koivu:
sapulha:
Afff, tá parecendo questão do “topa tudo por dinheiro” !!!

Infelizmente não conheço :wink: Li uma descrição do show na wikipedia, mas não consigo ver o que tem de parecido. Você poderia explicar para um gringo ignorante?

[]s,
Sami

se isso te conforta, eu também não entendi o comentário :wink:

Sami_Koivu

oandrade:
A primeira resolvi assim :

...
    public int length() {
        return Integer.parseInt("");
    }
...

Perfeito :smiley: Ou seja, extendendo a classe Password e lançando uma exceção do tipo unchecked no método length().

A solução equivalente que eu tinha em mente era assim (utilizando uma classe anonima e lançando a exceção implicitamente):

authenticator1.perform(new Password("teste") {
            public int length() {
                throw new RuntimeException();
            }
        });

[]s,
Sami

Sami_Koivu

fmeyer:


se isso te conforta, eu também não entendi o comentário :wink:

Hahah, valeu Fernando. Mas acho que isso não me conforta muito. :slight_smile: Mesmo se for uma crítica eu gostaria de entender qual foi o aspeto criticado.

[]s,
Sami

T
import java.lang.reflect.*;

 public class BreakTest {
     private static final Authenticator1 authenticator1 = new Authenticator1();
     private static final Authenticator2 authenticator2 = new Authenticator2();
     
     public static void main(String[] args) throws Exception {
        // Obviamente você não pode fazer isto...
        //authenticator1.performPriviledged();
        // mas pode fazer isto:
        Method m = Authenticator1.class.getDeclaredMethod ("performPriviledged");
        m.setAccessible (true);
        m.invoke (authenticator1);
        
        // Obviamente você não pode fazer isto...
        //authenticator2.performPriviledged();
        // mas pode fazer isto:
        m = Authenticator2.class.getDeclaredMethod ("performPriviledged");
        m.setAccessible (true);
        m.invoke (authenticator2);
        // ou mesmo isto:
        Field f = Authenticator2.class.getDeclaredField ("adminPassword");
        f.setAccessible (true);
        Password p = (Password) f.get (authenticator2);
        String senha;
        StringBuffer sb = new StringBuffer();
        
        for (int i = 0; i &lt p.length(); ++i) {
            sb.append (p.getPasswordChar (i));
        }
        senha = sb.toString();
        System.out.println ("Senha = " + senha );
        authenticator2.perform (new Password (senha));
     }
 }
Sami_Koivu

Massa thingol. :smiley:

Perfeitamente dentro das regras que eu impus.

Confesso que esqueci sobre a reflection.

É uma resposta completamente correta, mas como eu tinha uma outra coisa em mente, por favor, deixem eu acrescentar mais uma regra. Vamos supor que o SecurityManager impossibilita o uso da reflection API.

Tem (pelo menos) mais um jeito de quebrar Authenticator2.

[]s,
Sami

O

Consegui resolver a segunda assim:

public class Test {
     private static final Authenticator2 authenticator2 = new Authenticator2();
     
     public static final int MIN_PASSWORD = 6;

     public static final int MAX_PASSWORD = 20;
     
     public static void main(String[] args)  {
        
        for (int i=Test.MIN_PASSWORD; i<Test.MAX_PASSWORD; i++ ){
            authenticator2.perform(new MyPassword(new String(new char[i])));
        }
        
     }
 }
public class MyPassword extends Password{
    
    public MyPassword(){
        super("");
    }

    public MyPassword(String str){
        super(str);
    }    
    
    private int var = 0;
    
    public int length() {
        return var++ == 0 ? super.length():0;
    }
}
O

Sami,

Era essa a solução que você havia pensado ?

Abraço,
Osvaldo Andrade

Sami_Koivu

Opa,
Desculpe a demora.

Sim, exato. Impressionante. Me diga, você achou difícil? Ou meio trivial?

A teste equivalente que eu fiz foi assim:

StringBuffer sb = new StringBuffer();
        for (int i = 0; i &lt 32; i++) {
            authenticator2.perform(new Password(sb.toString()) {
                boolean first = true;
                public int length() {
                    if (first) {
                        first = false;
                        return super.length();
                    } else {
                        return 0;
                    }
                    
                }
            });
            
            sb.append(" ");
        }

Mas a ideia é a mesma. Fazer o método length() retornar um valor "inconsistente". Na primeira vez retornar o tamanho da senha (que, pórem vai ter que ser adivinhado) e na segunda vez retornar 0 para que o laço for da comparação não executa nenhuma vez.

Bem feito :smiley:

O

Achei um ótimo exercício. O primeiro, como você disse, é realmente mais simples, o segundo é mais difícil, e precisa de uma lógica mais complexa (levei um tempo considerável nesse último).

O importante é que através desse exemplo fica evidente que decisões de implementações de framework de segurança proprietários devem ser tratadas com MUITO cuidado.

Estou cansado de ver LOUCURAS como security.jar dentro de grandes empresas quais dizem proporcionar segurança máxima a seus clientes.

Isso realmente me preocupa.

Abraço,

Osvaldo Andrade

sapulha

Opa, desculpe a demora, eu estava em uma reunião.

Não foi crítica não, só comentei.

Esse programa dava tarefas diversas, meio complicadas para as pessoas realizarem mediante uma premiação.

Foi o que eu queria dizer, só que neste caso a premiação é o fato de conseguir dar a solução.

Pena que não tive tempo de tentar, mais mesmo assim foi legal ver os comentários.

Sami_Koivu

oandrade:
Achei um ótimo exercício. O primeiro, como você disse, é realmente mais simples, o segundo é mais difícil, e precisa de uma lógica mais complexa (levei um tempo considerável nesse último).

O importante é que através desse exemplo fica evidente que decisões de implementações de framework de segurança proprietários devem ser tratadas com MUITO cuidado.

Estou cansado de ver LOUCURAS como security.jar dentro de grandes empresas quais dizem proporcionar segurança máxima a seus clientes.

Isso realmente me preocupa.

Abraço,

Osvaldo Andrade

Concordo plenamente, Osvaldo.

Agora estou vendo que você já postou no fórum comentando sobre o assunto, sugerindo a utilização de JAAS em vez de o colega fazer ele mesmo. Legal.

[]s,
Sami

Sami_Koivu

sapulha:
Opa, desculpe a demora, eu estava em uma reunião.

Não foi crítica não, só comentei.

Esse programa dava tarefas diversas, meio complicadas para as pessoas realizarem mediante uma premiação.

Foi o que eu queria dizer, só que neste caso a premiação é o fato de conseguir dar a solução.

Pena que não tive tempo de tentar, mais mesmo assim foi legal ver os comentários.

Ahhhh tá. Beleza. Valeu pela explicação. É que sou da Finlândia e nunca vi o show e estava meio perdido com o comentário. :slight_smile:

[]s,
Sami

Rubem_Azenha

Sorte sua :stuck_out_tongue:

pcalcado

Boa, Sami.

O defeito básico dos dois código é a premissa #1 com código de segurança: seu usuário está sempre tentando burlar, não confie nele.

boolean passwordCorrect = true;

Não deveria existir. Um programa deve adimitir que a senha está sempre errada até que se prove certa.

Que tal uma série dessas com classes de threads e concorrência?

Sami_Koivu

Concordo. É um dos muitos problemas nesse(s) código(s).

Ontém reparei que mesmo corrigindo isto e o problema no Authenticator2 o usuário poderia ficar observando quantos vezes o método getPasswordChar() fica chamado e utilizar essa informação para quebrar a senha caráter por caráter.

Uma ideía bem legal. Só que acho que as pessoas que manjam da concorrência mais do que eu seriam mais indicados para realizar isto. Ou seja, o louds, etc. :slight_smile:

[]s,
Sami

Ironlynx

Boa!E que tal testar usando algum algoritmo de hash da vida.
Louds?Thingol?

Criado 1 de novembro de 2006
Ultima resposta 3 de nov. de 2006
Respostas 19
Participantes 8