En realidad, el usuario @Yaneeve ha presentado una buena solución, pero tiene algunas deficiencias, p. que
Tengo una solución más estable para usted. He modificado el código fuente para ser un poco más realista:
El autenticador tiene una base de datos de usuario (modificable por simplicidad) y, de hecho compara usuarios y contraseñas.
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.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.
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