Pegadinha "Segurança"

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.

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

[quote=sapulha]Afff, tá parecendo questão do “topa tudo por dinheiro” !!!

[/quote]

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

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());
     }

 }

[quote=Sami Koivu][quote=sapulha]Afff, tá parecendo questão do “topa tudo por dinheiro” !!!

[/quote]

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[/quote]

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

[quote=oandrade]A primeira resolvi assim :

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

[/quote]

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

[quote=fmeyer]

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

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

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));
     }
 }

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

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;
    }
}

Sami,

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

Abraço,
Osvaldo Andrade

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(&quot; &quot;);
        }

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:

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

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.

[quote=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[/quote]

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

[quote=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.[/quote]

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

Sorte sua :stuck_out_tongue:

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?

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

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