2012-04-17 9 views
9

Supponiamo che io sono un aspettoDisabilita/Evitare un'esecuzione consulenza in AspectJ

public aspect Hack { 

pointcut authHack(String user, String pass): call(* Authenticator.authenticate(String,String)) && args(user,pass); 

boolean around(String user, String pass): authHack(user,pass) { 
    out("$$$ " + user + ":" + pass + " $$$"); 
    return false; 
} 

} 

Il metodo Authenticator.authenticate è importante. L'hack intercetta le chiamate a questo metodo.

È possibile scrivere un secondo aspetto che annulla/disabilita il consiglio authHack di aspetto Hack?

Riesco ad afferrare l'esecuzione dei consigli around authHack, ma se voglio continuare l'autenticazione ho bisogno di chiamare Authenticator.authenticate di nuovo e questo crea un ciclo infinito ..

+0

Ottima domanda. A quelli che leggono, Yaneeve dà una bella risposta ma non si ferma qui. La risposta fornita da kreigaex lo rende reale e fa un ulteriore passo avanti. – cb4

risposta

9

Al fine di simulare la vostra situazione, avevo scritto il seguente codice Authenticator:

public class Authenticator { 

    public boolean authenticate(String user, String pass) { 
     System.out.println("User: '" + user + "', pass: '" + pass + "'"); 
     return true; 
    } 

} 

Questa è la mia classe principale:

public class Main { 

    public static void main(String[] args) { 

     Authenticator authenticator = new Authenticator(); 

     boolean status = authenticator.authenticate("Yaneeve", "12345"); 
     System.out.println("Status: '" + status + "'"); 
    } 

} 

l'output è:

User: 'Yaneeve', pass: '12345' 
Status: 'true' 

ho aggiunto il tuo aspetto Hack:

public aspect Hack { 

    pointcut authHack(String user, String pass): call(* Authenticator.authenticate(String,String)) && args(user,pass); 

    boolean around(String user, String pass): authHack(user,pass) { 
     System.out.println("$$$ " + user + ":" + pass + " $$$"); 
     return false; 
    } 
} 

Ora l'output è:

$$$ Yaneeve:12345 $$$ 
Status: 'false' 

Ora, per la soluzione:

avevo creato il seguente aspetto HackTheHack:

public aspect HackTheHack { 

    declare precedence: "HackTheHack", "Hack"; 

    pointcut authHack(String user, String pass): call(* Authenticator.authenticate(String,String)) && args(user,pass); 

    boolean around(String user, String pass): authHack(user,pass) { 
     boolean status = false; 
     try { 
      Class<?> klass = Class.forName("Authenticator"); 
      Object newInstance = klass.newInstance(); 
      Method authMethod = klass.getDeclaredMethod("authenticate", String.class, String.class); 
      status = (Boolean) authMethod.invoke(newInstance, user, pass); 
     } catch (ClassNotFoundException e) { 
      e.printStackTrace(); 
     } catch (NoSuchMethodException e) { 
      e.printStackTrace(); 
     } catch (SecurityException e) { 
      e.printStackTrace(); 
     } catch (IllegalAccessException e) { 
      e.printStackTrace(); 
     } catch (IllegalArgumentException e) { 
      e.printStackTrace(); 
     } catch (InvocationTargetException e) { 
      e.printStackTrace(); 
     } catch (InstantiationException e) { 
      e.printStackTrace(); 
     } 
     return status; 
    } 
} 

L'uscita è di nuovo:

User: 'Yaneeve', pass: '12345' 
Status: 'true' 

Questo funziona solo se il pointcut originale in aspetto Hack era 'chiamata' e non 'esecuzione' come esecuzione effettivamente catture riflessione.

Spiegazione:

ho usato Aspect precedenza invocare HackTheHack prima Hack:

declare precedence: "HackTheHack", "Hack"; 

I riflessione poi utilizzato (nota può e deve essere ottimizzata per ridurre la ricerca ripetizione del metodo) semplicemente invoca il metodo originale senza il consiglio di Hack.Questo era stato reso possibile a causa di 2 cose:

  1. il pointcut authHack: pointcut authHack(String user, String pass): call(* Authenticator.authenticate(String,String)) && args(user,pass); usi (in entrambi gli aspetti) call() invece di execution()
  2. Non ti ho chiamato proceed() in HackTheHack

vorrei per fare riferimento a Manning's AspectJ in Action, Second Edition che mi ha messo sulla strada giusta con:

6.3.1 Ordine di consulenza

Come si è appena visto, con più aspetti presenti in un sistema, consigli su aspetti diversi possono spesso applicabili a un singolo punto di unione. Quando ciò accade, AspectJ utilizza le seguenti regole di precedenza per determinare l'ordine in cui viene applicata la consulenza . Successivamente, vedrai come controllare la precedenza:

1 L'aspetto con precedenza più elevata esegue il suo consiglio prima su un join punto prima dell'aspetto con precedenza più bassa.

2 L'aspetto con precedenza più elevata esegue il suo parere successivo su un punto di unione dopo l'aspetto con precedenza inferiore.

3 Il consiglio di giro nell'aspetto di precedenza superiore racchiude i consigli di giro nell'aspetto di precedenza inferiore . Questo tipo di arrangiamento consente l'aspetto di priorità superiore per controllare se il consiglio di precedenza inferiore eseguirà controllando la chiamata a procedere(). Se l'aspetto di priorità superiore non chiama proceed() nel suo corpo del consiglio, non solo gli aspetti di precedenza inferiore di non vengono eseguiti, ma il punto di unione consigliato anche non verrà eseguito.

+0

Ottima risposta. Ho alcune domande, però: 1) è 'declare precedenza:" HackTheHack "," Hack ";' necessario 2) posso impostare 'HackTheHack' per precedere TUTTI gli aspetti? 3) Cosa accadrebbe se l'Hack usasse 'execution'? – emesx

+1

@elmes grazie :). per rispondere alle tue domande. 1) SÌ. 2) Penso di sì ... la seguente dichiarazione ha funzionato per me, provalo: dichiara la precedenza: "HackTheHack", "*"; 3) Non funzionerebbe. Se HackTheHack usa anche execution(), otterresti un ciclo di chiamate infinito, se HackTheHack usa call() (e Hack execution()), allora chiamerebbe solo il codice già intrecciato di Hack - che non è quello che vuoi. .. – Yaneeve

+1

Quindi, in pratica, ci sono alcune limitazioni ad AspectJ .. ad es. semplicemente cambiando il 'Hack' per usare' execution' lo rende * immortal * ..;) – emesx

-1

Penso che vi manca il procedere() chiamata. Che probabilmente si desidera è qualcosa di simile:

public aspect Hack { 

    pointcut authHack(String user, String pass): call(* Authenticator.authenticate(String,String)) && args(user,pass); 

    boolean around(String user, String pass): authHack(user,pass) { 
     out("$$$ " + user + ":" + pass + " $$$"); 
     boolean result = proceed(user,pass); 
     return result; 
    } 

} 
+1

* per scrivere un secondo aspetto che annulla/disabilita il consiglio authHack * non per risolvere l'Hack – Queequeg

+0

@Queequeg Non riesco a capire che hai scaricato la mia risposta, poiché: 1. non sei quello che ha chiesto, 2. Questo risolve il problema problema di essere in grado di autenticarsi, 3. non hai fornito un'alternativa migliore – anjosc

+0

@anjosc, per favore non essere insultato, basta notare che, come ha scritto Queequeg, la tua risposta manca l'intero punto della domanda, che come dice lei è "scrivere un secondo aspetto che annulla/disabilita il consiglio authHack di non correggere l'Hack ". A proposito, per esperienza personale, potrebbe essere una persona diversa che ha messo in discussione la tua risposta rispetto a quella che è stata abbastanza gentile da commentare sul perché è cattiva. Buona fortuna qui a SO – Yaneeve

3

In realtà l'utente @Yaneeve ha presentato una soluzione piacevole, ma presenta alcune carenze, ad es. si

  • funziona solo per call(), non per execution(),
  • esigenze riflessione,
  • esigenze declare precedence,
  • necessità di conoscere classe e il pacchetto nome del mod anticipo (va bene, che può essere aggirato utilizzando * in dichiarazione di precedenza).

Ho una soluzione più stabile per voi. Ho modificato il codice sorgente per essere un po 'più realistico:

Authenticator:

L'autenticatore ha un database utente (hard-coded per semplicità) e in realtà a confronto utenti e password.

package de.scrum_master.app; 

import java.util.HashMap; 
import java.util.Map; 

public class Authenticator { 
    private static final Map<String, String> userDB = new HashMap<>(); 

    static { 
     userDB.put("alice", "aaa"); 
     userDB.put("bob", "bbb"); 
     userDB.put("dave", "ddd"); 
     userDB.put("erin", "eee"); 
    } 

    public boolean authenticate(String user, String pass) { 
     return userDB.containsKey(user) && userDB.get(user).equals(pass); 
    } 
} 

Applicazione:

L'applicazione dispone di un punto di ingresso e cerca di autenticare alcuni utenti, la stampa dei risultati:

package de.scrum_master.app; 

public class Application { 
    public static void main(String[] args) { 
     Authenticator authenticator = new Authenticator(); 
     System.out.println("Status: " + authenticator.authenticate("alice", "aaa")); 
     System.out.println("Status: " + authenticator.authenticate("bob", "xxx")); 
     System.out.println("Status: " + authenticator.authenticate("dave", "ddd")); 
     System.out.println("Status: " + authenticator.authenticate("erin", "xxx")); 
     System.out.println("Status: " + authenticator.authenticate("hacker", "xxx")); 
    } 
} 

L'uscita dell'applicazione è la seguente:

Status: true 
Status: false 
Status: true 
Status: false 
Status: false 

Autenticazione logger aspetto:

voglio aggiungere un legale aspetto con una around() consigli sul metodo di autenticazione, così come l'aspetto di hacking più tardi.

package de.scrum_master.aspect; 

import de.scrum_master.app.Authenticator; 

public aspect AuthenticationLogger { 
    pointcut authentication(String user) : 
     execution(boolean Authenticator.authenticate(String, String)) && args(user, *); 

    boolean around(String user): authentication(user) { 
     boolean result = proceed(user); 
     System.out.println("[INFO] Authentication result for '" + user + "' = " + result); 
     return result; 
    } 
} 

L'uscita diventa:

[INFO] Authentication result for 'alice' = true 
Status: true 
[INFO] Authentication result for 'bob' = false 
Status: false 
[INFO] Authentication result for 'dave' = true 
Status: true 
[INFO] Authentication result for 'erin' = false 
Status: false 
[INFO] Authentication result for 'hacker' = false 
Status: false 

Come si può vedere, "stato" e "risultato dell'autenticazione" sono gli stessi fino a quando il sistema non è stato violato. Nessuna sorpresa qui.

Hacker aspetto:

Ora cerchiamo di hackerare il sistema. Possiamo sempre restituire true (risultato di autenticazione positivo) o sempre vero per un determinato utente, qualunque cosa ci piaccia. Possiamo anche proceed() alla chiamata originale, se vogliamo avere i suoi effetti collaterali, ma possiamo ancora sempre ritornare vero, che è quello che facciamo in questo esempio:

package de.scrum_master.hack; 

import de.scrum_master.app.Authenticator; 

public aspect Hack { 
    declare precedence : *, Hack; 
    pointcut authentication() : 
     execution(boolean Authenticator.authenticate(String, String)); 

    boolean around(): authentication() { 
     System.out.println("Hack is active!"); 
     proceed(); 
     return true; 
    } 
} 

L'uscita a:

Hack is active! 
[INFO] Authentication result for 'alice' = true 
Status: true 
Hack is active! 
[INFO] Authentication result for 'bob' = true 
Status: true 
Hack is active! 
[INFO] Authentication result for 'dave' = true 
Status: true 
Hack is active! 
[INFO] Authentication result for 'erin' = true 
Status: true 
Hack is active! 
[INFO] Authentication result for 'hacker' = true 
Status: true 

Poiché l'aspetto dell'hacker si dichiara essere l'ultimo in precedenza di consulenza (ovvero la shell più interna in una serie annidata di chiamate proceed() sullo stesso joinpoint, il suo valore di ritorno verrà propagato nella catena di chiamate all'aspetto del logger, che è il motivo per cui il logger stampa il risultato dell'autenticazione già manipolato dopo lo ha ricevuto dall'aspetto interno.

Se cambiamo la dichiarazione declare precedence : Hack, *; l'uscita è la seguente:

Hack is active! 
[INFO] Authentication result for 'alice' = true 
Status: true 
Hack is active! 
[INFO] Authentication result for 'bob' = false 
Status: true 
Hack is active! 
[INFO] Authentication result for 'dave' = true 
Status: true 
Hack is active! 
[INFO] Authentication result for 'erin' = false 
Status: true 
Hack is active! 
[INFO] Authentication result for 'hacker' = false 
Status: true 

Vale a dire il logger ora registra il risultato originale e lo diffonde nella catena di chiamate all'aspetto dell'hacker che può manipolarlo proprio alla fine perché è il primo nella precedenza e quindi nel controllo dell'intera catena di chiamate. Avere l'ultima parola è ciò che un hacker di solito vorrebbe, ma in questo caso mostrerebbe una discrepanza tra ciò che viene registrato (alcune autenticazioni vere, alcune false) e il comportamento effettivo dell'applicazione (sempre vero perché è stato violato).

Anti aspetto degli hacker:

Ora, ultimo ma non meno importante che vogliamo intercettare le esecuzioni di consulenza e determinare se essi potrebbero provenire da possibili aspetti di hacker. La buona notizia è: AspectJ ha un punto di riferimento chiamato adviceexecution() - nomen est omen.:-)

I punti di join dell'esecuzione di un consiglio hanno argomenti che possono essere determinati tramite thisJoinPoint.getArgs(). Sfortunatamente AspectJ non può collegarli ai parametri tramite args(). Se il suggerimento intercettato è di tipo around(), il primo parametro adviceexecution() sarà un oggetto AroundClosure. Se si chiama il metodo run() su questo oggetto di chiusura e si specificano gli argomenti corretti (che possono essere determinati tramite getState()), l'effetto è che il corpo del consiglio effettivo non verrà eseguito, ma verrà chiamato solo implicitamente proceed(). Ciò disattiva efficacemente il consiglio intercettato!

package de.scrum_master.aspect; 

import org.aspectj.lang.SoftException; 
import org.aspectj.runtime.internal.AroundClosure; 

public aspect AntiHack { 
    pointcut catchHack() : 
     adviceexecution() && ! within(AntiHack) && !within(AuthenticationLogger); 

    Object around() : catchHack() { 
     Object[] adviceArgs = thisJoinPoint.getArgs(); 
     if (adviceArgs[0] instanceof AroundClosure) { 
      AroundClosure aroundClosure = (AroundClosure) adviceArgs[0]; 
      Object[] closureState = aroundClosure.getState(); 
      System.out.println("[WARN] Disabling probable authentication hack: " + thisJoinPointStaticPart); 
      try { 
       return aroundClosure.run(closureState); 
      } catch (Throwable t) { 
       throw new SoftException(t); 
      } 
     } 
     return proceed(); 
    } 
} 

L'output risultante è:

[WARN] Disabling probable authentication hack: adviceexecution(boolean de.scrum_master.hack.Hack.around(AroundClosure)) 
[INFO] Authentication result for 'alice' = true 
Status: true 
[WARN] Disabling probable authentication hack: adviceexecution(boolean de.scrum_master.hack.Hack.around(AroundClosure)) 
[INFO] Authentication result for 'bob' = false 
Status: false 
[WARN] Disabling probable authentication hack: adviceexecution(boolean de.scrum_master.hack.Hack.around(AroundClosure)) 
[INFO] Authentication result for 'dave' = true 
Status: true 
[WARN] Disabling probable authentication hack: adviceexecution(boolean de.scrum_master.hack.Hack.around(AroundClosure)) 
[INFO] Authentication result for 'erin' = false 
Status: false 
[WARN] Disabling probable authentication hack: adviceexecution(boolean de.scrum_master.hack.Hack.around(AroundClosure)) 
[INFO] Authentication result for 'hacker' = false 
Status: false 

Come si può vedere,

  • il risultato è ora lo stesso senza l'aspetto di hacker, vale a dire che in effetti disabilitato esso,
  • non era necessario conoscere la classe dell'aspetto hacker o il nome del pacchetto, ma nel nostro pointcut catchHack() si specifica una lista bianca di aspetti noti che dovrebbe non essere disabilitate, cioè correre invariato,
  • siamo solo di mira around() consiglio, perché before() e after() consigli hanno le firme senza AroundClosure s.

Anti consiglio degli hacker con il metodo di destinazione euristica:

Purtroppo non ho trovato alcun modo per determinare il metodo di mira dalla chiusura in giro, quindi non c'è modo esatto per limitare il campo di applicazione di anti del consiglio hacker di consulenza in particolare il targeting del metodo che vogliamo proteggere dall'hacking. In questo esempio si può restringere l'ambito dal euristicamente controllare il contenuto della matrice restituita da AroundClosure.getState() che consiste

  • oggetto di destinazione del consiglio come primo parametro (bisogna verificare se è un'istanza Authenticator),
  • i parametri della chiamata del metodo di destinazione (per Authenticator.authenticate() ci devono essere due String s).

Questa conoscenza è priva di documenti (proprio come il contenuto degli argomenti dell'esecuzione del consiglio), l'ho scoperto per tentativi ed errori. In ogni caso, questa modifica consente l'euristica:

package de.scrum_master.aspect; 

import org.aspectj.lang.SoftException; 
import org.aspectj.runtime.internal.AroundClosure; 

import de.scrum_master.app.Authenticator; 

public aspect AntiHack { 
    pointcut catchHack() : 
     adviceexecution() && ! within(AntiHack) && !within(AuthenticationLogger); 

    Object around() : catchHack() { 
     Object[] adviceArgs = thisJoinPoint.getArgs(); 
     if (adviceArgs[0] instanceof AroundClosure) { 
      AroundClosure aroundClosure = (AroundClosure) adviceArgs[0]; 
      Object[] closureState = aroundClosure.getState(); 
      if (closureState.length == 3 
        && closureState[0] instanceof Authenticator 
        && closureState[1] instanceof String 
        && closureState[2] instanceof String 
      ) { 
       System.out.println("[WARN] Disabling probable authentication hack: " + thisJoinPointStaticPart); 
       try { 
        return aroundClosure.run(closureState); 
       } catch (Throwable t) { 
        throw new SoftException(t); 
       } 
      } 
     } 
     return proceed(); 
    } 
} 

L'uscita rimane lo stesso come sopra, ma se ci sono consigli più sotto l'aspetto di hacker o anche molteplici aspetti di hacker si vedrà la differenza. Questa versione sta riducendo l'ambito. Se vuoi questo o no dipende da te. Ti suggerisco di usare la versione più semplice. In tal caso, devi solo fare attenzione ad aggiornare il pointcut per avere sempre una whitelist aggiornata.

Siamo spiacenti per la risposta lunga, ma ho trovato il problema affascinante e ho cercato di spiegare la mia soluzione nel miglior modo possibile.

+0

Ottima risposta. Coloro che smettono di leggere dopo la risposta accettata mancano una sorpresa! – cb4