实际上用户@Yaneeve 提出了一个很好的解决方案,但它有一些缺点,例如
- 仅适用于
call()
,不适用于execution()
,
- 需要反思,
- 需要
declare precedence
,
- 需要事先知道黑客的类和包名(好的,可以通过使用
*
优先声明来规避)。
我有一个更稳定的解决方案给你。我已将源代码修改为更真实一点:
身份验证器:
身份验证器有一个用户数据库(为简单起见硬编码)并实际比较用户和密码。
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);
}
}
应用:
该应用程序有一个入口点并尝试验证一些用户,并打印结果:
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"));
}
}
应用程序的输出如下:
Status: true
Status: false
Status: true
Status: false
Status: false
身份验证记录器方面:
我想添加一个关于身份验证方法的建议的法律方面around()
,就像稍后的黑客方面一样。
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;
}
}
输出变为:
[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
如您所见,只要系统没有被黑客入侵,“状态”和“认证结果”是相同的。这里没有惊喜。
黑客方面:
现在让我们破解系统。我们可以始终返回 true(肯定的身份验证结果)或始终为某个用户返回 true - 无论我们喜欢什么。proceed()
如果我们想获得它的副作用,我们甚至可以返回原始调用,但我们仍然可以始终返回 true,这就是我们在这个示例中所做的:
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;
}
}
输出变为:
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
因为黑客方面声明自己是通知优先级中的最后一个(即在同一连接点上的一系列嵌套proceed()
调用中的最内层外壳,它的返回值将通过调用链向上传播到记录器方面,这就是为什么记录器在从内部方面接收到已操作的身份验证结果后打印它。
如果我们把声明改成declare precedence : Hack, *;
输出如下:
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
即记录器现在记录原始结果并将其沿调用链传播到黑客方面,黑客方面可以在最后操纵它,因为它是优先的,因此可以控制整个调用链。拥有最终决定权是黑客通常想要的,但在这种情况下,它会显示记录的内容(一些身份验证为真,一些为假)与应用程序的实际行为方式(始终为真,因为它被黑客入侵)之间的不匹配。
反黑客方面:
现在,最后但并非最不重要的一点是,我们要拦截建议执行并确定它们是否可能来自可能的黑客方面。好消息是:AspectJ 有一个称为adviceexecution()
- nomen est omen 的切入点。:-)
建议执行连接点具有可以通过确定的参数thisJoinPoint.getArgs()
。不幸的是,AspectJ 无法通过 将它们绑定到参数args()
。如果截获的通知是around()
类型的,则第一个adviceexecution()
参数将是一个AroundClosure
对象。如果您run()
在此闭包对象上调用该方法并指定正确的参数(可以通过 确定getState()
),则效果是不会执行实际的通知主体,而只会隐式proceed()
调用。这有效地禁用了拦截的建议!
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();
}
}
结果输出是:
[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
如你看到的,
- 结果现在与没有黑客方面相同,即我们有效地禁用了它,
- 不必知道黑客方面的类或包名称,但在我们的切入点中,我们指定了一个不应
catchHack()
禁用的已知方面的白名单,即运行不变,
- 我们只针对建议,
around()
因为建议的签名没有s。before()
after()
AroundClosure
具有目标方法启发式的反黑客建议:
不幸的是,我发现无法确定周围闭包所针对的方法,因此没有确切的方法将反黑客建议的范围限制为专门针对我们想要防止黑客攻击的方法的建议。在此示例中,我们可以通过启发式检查返回的数组的内容来缩小范围,AroundClosure.getState()
其中包含
- 通知的目标对象作为第一个参数(我们需要检查它是否是一个
Authenticator
实例),
- 目标方法调用的参数(因为
Authenticator.authenticate()
必须有两个String
)。
我通过反复试验发现了这些知识是无证的(就像建议执行参数的内容一样)。无论如何,此修改启用启发式:
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();
}
}
输出与上面相同,但如果在黑客方面甚至多个黑客方面有多个建议,您会看到差异。这个版本正在缩小范围。如果你想要这个取决于你。我建议你使用更简单的版本。在这种情况下,您只需要小心更新切入点以始终拥有最新的白名单。
很抱歉这个冗长的答案,但我发现这个问题很有趣,并试图尽可能好地解释我的解决方案。