実際、ユーザー@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
つまり、ロガーは元の結果をログに記録し、それをハッカーアスペクトに伝播します。ハッカーアスペクトは、最初に優先され、コールチェーン全体を制御するため、最後にそれを操作できます。最終決定権を持つことは、ハッカーが通常望んでいることですが、この場合、ログに記録される内容(一部の認証はtrue、一部はfalse)とアプリケーションの実際の動作(ハッキングされたため常にtrue)の間に不一致が表示されます。
アンチハッカーの側面:
さて、最後になりましたが、アドバイスの実行を傍受し、それらがハッカーの可能性のある側面から来ている可能性があるかどうかを判断したいと思います。良いニュースは次のとおりです。AspectJには--nomenestomenと呼ばれるポイントカットがありadviceexecution()
ます。:-)
アドバイス実行の参加ポイントには、を介して決定できる引数があります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()
のある既知のアスペクトのホワイトリストを指定します。
- アドバイスにはsのない署名が
around()
あるためbefore()
、アドバイスのみを対象としています。after()
AroundClosure
ターゲットメソッドヒューリスティックを使用したハッカー対策のアドバイス:
残念ながら、アラウンドクロージャの対象となる方法を特定する方法が見つからなかったため、ハッカー対策のアドバイスの範囲を、ハッキングから保護したい方法を具体的に対象とするアドバイスに限定する正確な方法はありません。この例では、返される配列の内容をヒューリスティックにチェックすることでスコープを絞り込むことができますAroundClosure.getState()
。
- 最初のパラメータとしてのアドバイスのターゲットオブジェクト(インスタンスであるかどうかを確認する必要があり
Authenticator
ます)、
- ターゲットメソッド呼び出しのパラメータ(
Authenticator.authenticate()
2つある必要があるため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();
}
}
出力は上記と同じままですが、ハッカーの側面に複数のアドバイスがある場合、または複数のハッカーの側面にさえある場合は、違いがわかります。このバージョンは範囲を絞り込んでいます。これが必要かどうかはあなた次第です。より単純なバージョンを使用することをお勧めします。その場合、常に最新のホワイトリストを持つようにポイントカットを更新するように注意する必要があります。
長い答えで申し訳ありませんが、私は問題が魅力的であると感じ、私の解決策を可能な限り説明しようとしました。