9

私が側面を持っているとしましょう

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

}

Authenticator.authenticate方法は重要です。ハックは、このメソッドの呼び出しを傍受します。

authHackハックアスペクトのアドバイスをキャンセル/無効にする2番目のアスペクトを書くことは可能ですか?

アドバイスの実行をキャッチすることはできますaround authHackが、認証を続行したい場合は、もう一度呼び出す必要がありAuthenticator.authenticate、これにより無限ループが発生します。

4

3 に答える 3

9

あなたの状況をシミュレートするために、私は次のオーセンティケーターコードを書きました:

public class Authenticator {

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

}

これは私のメインクラスです:

public class Main {

    public static void main(String[] args) {

        Authenticator authenticator = new Authenticator();

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

}

出力は次のとおりです。

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

私はあなたのハックの側面を追加しました:

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

これで、出力は次のようになります。

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

今解決策のために:

私は次の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;
    }
}

出力は再び:

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

これは、実行が実際にリフレクションをキャッチするため、ハックアスペクトの元のポイントカットが「実行」ではなく「呼び出し」であった場合にのみ機能します。

説明:

アスペクトの優先順位を使用して、ハックの前にHackTheHackを呼び出しました。

declare precedence: "HackTheHack", "Hack";

次に、リフレクション(メソッドの繰り返しのルックアップを減らすために最適化できることに注意してください)を使用して、ハックアラウンドアドバイスなしで元のメソッドを呼び出すだけです。これは2つの理由で可能になりました:

  1. authHackポイントカット:(pointcut authHack(String user, String pass): call(* Authenticator.authenticate(String,String)) && args(user,pass);両方の側面で)call()代わりにを使用しますexecution()
  2. 私はproceed()HackTheHackに電話しませんでした

マニングのAspectJinAction、Second Editionを紹介したいと思います。これにより、私は次のように正しい方向に進んでいます。

6.3.1アドバイスの順序

これまで見てきたように、システムには複数の側面が存在するため、さまざまな側面のアドバイスが1つのジョインポイントに適用されることがよくあります。これが発生すると、AspectJは次の優先ルールを使用してアドバイスが適用される順序を決定します。後で、優先順位を制御する方法を説明します。

1優先順位の高いアスペクトは、優先順位の低いアスペクトの前に、ジョインポイントに関するビフォアアドバイスを実行します。

2優先度の高いアスペクトは、優先度の低いアスペクトの後のジョインポイントでアフターアドバイスを実行します。

3優先順位の高いアスペクトのアラウンドアドバイスは、優先順位の低いアスペクトのアラウンドアドバイスを囲みます。この種の配置により、precedence()の呼び出しを制御することにより、優先順位の高いアスペクトが優先順位の低いアドバイスを実行するかどうかを制御できます。優先順位の高いアスペクトがアドバイス本文でproceed()を呼び出さない場合、優先順位の低いアスペクトが実行されないだけでなく、アドバイスされたジョインポイントも実行されません。

于 2012-04-19T09:25:31.613 に答える
4

実際、ユーザー@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();
    }
}

出力は上記と同じままですが、ハッカーの側面に複数のアドバイスがある場合、または複数のハッカーの側面にさえある場合は、違いがわかります。このバージョンは範囲を絞り込んでいます。これが必要かどうかはあなた次第です。より単純なバージョンを使用することをお勧めします。その場合、常に最新のホワイトリストを持つようにポイントカットを更新するように注意する必要があります。

長い答えで申し訳ありませんが、私は問題が魅力的であると感じ、私の解決策を可能な限り説明しようとしました。

于 2014-06-05T10:51:22.860 に答える
-1

私はあなたがproceed()呼び出しを逃していると思います。おそらく必要なのは次のようなものです。

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

}
于 2012-04-18T00:05:01.877 に答える