2012-04-17 18 views
9

Supongamos que tengo un aspectoDesactivar/evite una ejecución asesoramiento en 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; 
} 

} 

El método Authenticator.authenticate es importante. El hack intercepta llamadas a este método.

¿Es posible escribir un segundo aspecto que cancele/deshabilite el consejo authHack de Hack aspect?

puedo coger la ejecución de los consejos around authHack, pero si quiero seguir la autenticación Tengo que llamar Authenticator.authenticate de nuevo y esto crea un bucle infinito ..

+0

Una gran pregunta. Para aquellos que leen, Yaneeve da una buena respuesta, pero no te detengas allí. La respuesta que da kreigaex lo hace real y lo lleva un paso más allá. – cb4

Respuesta

9

Con el fin de simular su situación, que había escrito el siguiente código autenticador:

public class Authenticator { 

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

} 

Ésta es mi clase principal:

public class Main { 

    public static void main(String[] args) { 

     Authenticator authenticator = new Authenticator(); 

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

} 

salida es:

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

que añade a su aspecto 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; 
    } 
} 

Ahora el resultado es:

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

Ahora, para la solución:

que había creado la siguiente HackTheHack aspecto:

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

La salida es de nuevo:

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

Esto sólo funciona si el punto de corte original en aspecto Hack fue 'llamada' y no 'ejecución' como la ejecución realidad atrapa reflexión.

Explicación:

Solía ​​precedencia Aspect para invocar HackTheHack antes Hack:

declare precedence: "HackTheHack", "Hack"; 

I reflexión entonces utilizado (nota puede y debe ser optimizada para reducir la búsqueda de repetición del método) para simplemente invoque el método original sin el consejo de Hack around.Esto ha sido posible gracias a 2 cosas:

  1. el punto de corte authHack: pointcut authHack(String user, String pass): call(* Authenticator.authenticate(String,String)) && args(user,pass); usos (en ambos aspectos) call() en lugar de execution()
  2. no haber llamado proceed() en HackTheHack

me gustaría para referirlo a Manning's AspectJ in Action, Second Edition que me había puesto en el camino correcto con:

6.3.1 Pedido de asesoramiento

Como acaba de ver, con múltiples aspectos presentes en un sistema, los consejos sobre los diferentes aspectos pueden aplicarse a menudo a un solo punto de unión. Cuando esto sucede, AspectJ usa las siguientes reglas de precedencia para determinar el orden en que se aplica el consejo . Más adelante, verá cómo controlar la precedencia:

1 El aspecto con mayor precedencia ejecuta su advertencia anterior en un punto de unión antes del aspecto con precedencia más baja.

2 El aspecto con mayor precedencia ejecuta sus consejos posteriores sobre un punto de unión después del aspecto con precedencia más baja.

3 La sugerencia general en el aspecto de precedencia más alta incluye la sugerencia general en el aspecto de menor precedencia . Este tipo de disposición permite que el aspecto de precedencia más alto controle si la advertencia de prioridad más baja será ejecutándose controlando la llamada a proceder(). Si el aspecto de mayor precedencia no llama a proceed() en su cuerpo de asesoramiento, no solo no se ejecutarán los aspectos de prioridad más baja , sino que el punto de unión recomendado también no se ejecutará.

+0

Muy buena respuesta. Sin embargo, tengo algunas preguntas: 1) es 'declare precedence:" HackTheHack "," Hack ";' necessary 2) ¿puedo configurar 'HackTheHack' para que preceda TODOS los aspectos? 3) ¿Qué pasa si el Hack usó 'ejecución'? – emesx

+1

@elmes gracias :). para responder tu pregunta. 1) SÍ. 2) Creo que sí ... la siguiente declaración funcionó para mí, pruébala: declara la prioridad: "HackTheHack", "*"; 3) No funcionaría. Si HackTheHack también usa execution() entonces obtendrías un bucle de llamada infinito, si HackTheHack usa call() (y Hack execution()), entonces simplemente llamaría al código ya tejido de Hack, que no es lo que quieres. .. – Yaneeve

+1

Así que, básicamente, hay algunas limitaciones para AspectJ .. por ejemplo simplemente cambiando el 'Hack' para usar' execution' lo hace * inmortal * ...;) – emesx

-1

Creo que se echa en falta el proceder() llamada. Lo que probablemente desea es algo como esto:

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

* para escribir un segundo aspecto que cancela/deshabilita el consejo de autenticación automática * no para corregir el Hack – Queequeg

+0

@Queequeg No entiendo que ha cancelado mi respuesta, ya que: 1. usted no es el que preguntó, 2. Esto resuelve el problema problema de poder autenticarse, 3. no proporcionó una mejor alternativa – anjosc

+0

@anjosc, por favor no se insultó, solo tenga en cuenta que, como Queequeg escribió, su respuesta se pierde el objetivo de la pregunta, que como ella dice es "escribir un segundo aspecto que cancela/deshabilita el consejo de autenticación automática para no reparar el Hack ". Por cierto, por experiencia personal, podría ser una persona diferente que haya votado negativamente tu respuesta que la que fue lo suficientemente amable como para comentar por qué es mala. La mejor de las suertes aquí en SO – Yaneeve

3

En realidad, el usuario @Yaneeve ha presentado una buena solución, pero tiene algunas deficiencias, p. que

  • sólo funciona para call(), no para execution(),
  • necesidades reflexión,
  • necesidades declare precedence,
  • necesita saber la clase y el paquete el nombre del truco de antemano (bien, que se puede evitar mediante el uso de * en la declaración de precedencia).

Tengo una solución más estable para usted. He modificado el código fuente para ser un poco más realista:

autenticador:

El autenticador tiene una base de datos de usuario (modificable por simplicidad) y, de hecho compara usuarios y contraseñas.

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

Aplicación:

La aplicación dispone de un punto de entrada e intenta autenticar unos pocos usuarios, la impresión de los resultados:

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

de salida de la aplicación es el siguiente:

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

autenticación registrador de aspecto:

Quiero añadir un aspecto legalcon un around() asesoramiento sobre el método de autenticación, al igual que el aspecto piratería más tarde.

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

La salida se convierte en:

[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 

Como se puede ver, "status" y "resultado de la autenticación" son los mismos, siempre y cuando el sistema no fue hackeado. No es sorpresa aquí.

Hacker aspecto:

Ahora vamos a hackear el sistema. Siempre podemos devolver verdadero (resultado de autenticación positiva) o siempre cierto para un determinado usuario, lo que queramos. Podemos incluso proceed() a la llamada original si quieren tener sus efectos secundarios, pero aún así siempre podemos volver verdadera, que es lo que hacemos en este ejemplo:

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

La salida cambia 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 

Dado que el aspecto de hacker se declara como el último en asesoramiento prioritario (es decir, el intérprete interno de una serie anidada de proceed() llama al mismo punto de unión, su valor de retorno se propagará por la cadena de llamada al aspecto del registrador, que es por qué el registrador imprime el resultado de autenticación ya manipulado después lo ha recibido desde el aspecto interno.

Si cambiamos la declaración de declare precedence : Hack, *; la salida es el siguiente:

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 

es decir, el registrador ahora registra el resultado original y lo propaga por la cadena de llamadas hasta el aspecto de hacker, que puede manipularlo al final porque está primero en precedencia y, por lo tanto, tiene el control de toda la cadena de llamadas. Tener la última palabra es lo que un hacker normalmente querría, pero en este caso mostraría un desajuste entre lo que se registra (algunas autenticaciones son verdaderas, algunas falsas) y cómo se comporta realmente la aplicación (siempre es cierto porque fue pirateado).

anti aspecto de hackers:

Ahora, por último pero no menos queremos interceptar las ejecuciones de asesoramiento y determinar si podrían provenir de posibles aspectos de hackers. La buena noticia es: AspectJ tiene un punto llamado adviceexecution() - nomen est omen.:-)

Los puntos de unión de ejecución de aviso tienen argumentos que se pueden determinar a través de thisJoinPoint.getArgs(). Lamentablemente, AspectJ no puede vincularlos a los parámetros a través del args(). Si el aviso interceptado es del tipo around(), el primer parámetro adviceexecution() será un objeto AroundClosure. Si llama al método run() sobre este objeto de cierre y especifica los argumentos correctos (que pueden determinarse a través del getState()), el efecto es que el cuerpo del consejo real no se ejecutará, sino que se invocará implícitamente proceed(). ¡Esto efectivamente desactiva el consejo interceptado!

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

La salida resultante es:

[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 

Como se puede ver,

  • el resultado es ahora el mismo que sin el aspecto de hackers, es decir, que efectivamente inhabilitado,
  • no era necesario conocer la clase o el nombre del paquete del hacker, pero en nuestro punto de corte catchHack() especificamos una lista blanca de aspectos conocidos que debería no ser desactivada, es decir, funcionando sin cambios,
  • que sólo están apuntando around() consejo porque before() y after() consejos tienen firmas sin AroundClosure s.

consejos Anti Hacker con la heurística método de destino:

Por desgracia he encontrado ninguna manera de determinar el método dirigido por el cierre alrededor, así que no hay manera exacta para limitar el alcance de los consejos contra el hacker consejos específicamente dirigidos al método que queremos proteger contra la piratería. En este ejemplo podemos reducir el alcance comprobando heurísticamente el contenido de la matriz devuelta por AroundClosure.getState() que consiste en

  • objeto de destino es el consejo como el primer parámetro (necesitamos comprobar si es una instancia Authenticator),
  • parámetros de la llamada al método de destino (para Authenticator.authenticate() debe haber dos String s).

Este conocimiento no está documentado (al igual que el contenido de los argumentos de la ejecución del consejo), me enteré por prueba y error. De todos modos, esta modificación permite que la heurística:

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

La salida se mantiene igual que el anterior, pero si hay consejos múltiple en el aspecto hacker o incluso múltiples aspectos de hackers que se vea la diferencia. Esta versión está reduciendo el alcance. Si quieres esto o no, depende de ti. Te sugiero que uses la versión más simple. En ese caso, solo debe tener cuidado de actualizar el punto de corte para tener siempre una lista blanca actualizada.

Perdón por la respuesta larga, pero el problema me pareció fascinante e intenté explicar mi solución lo mejor posible.

+0

Excelente respuesta. ¡Aquellos que dejan de leer después de la respuesta aceptada se están perdiendo un regalo! – cb4