3

OK ASP.NET WebForms は今では古いテクノロジですが、当面は使い続ける予定です。非常に基本的なリプレイ攻撃防止メカニズムを組み込むことができるかどうかを確認しようとしています。基本的には、ユーザーが明示的にログアウトした後、誰も古いリクエストを再生できず、認証 Cookie に基づいて自動的に認証されるようにしたいのですが、それを望まないユーザーが永続的な認証 Cookie を使用できるようにしたいと考えています。新しいブラウザ セッションごとに再ログインします。後者のケースで私が観察したことから、ブラウザを終了して新しいセッションを開始すると、何もありませんCookie ヘッダーのセッション ID - フォーム認証 Cookie のみ。この場合、自動認証を許可します (少なくとも GET リクエストに対して)。「再生」の場合、Cookie ヘッダーにセッション ID がありますが、ASP.NET は実際には同じ ID を使用して新しいセッションを自動的に再作成します。幸いなことに、これを検出できます (Session.IsNewSession と Request.Headers["Cookie"] をチェックすることにより - 奇妙なことに、クライアントから送信されていない場合でもセッション Cookie を含む場合がある Request.Cookies ではありません)。ユーザーがログアウトしたときに閉じられた/放棄されたセッションのセッション ID をクライアントが送信した場合に、再ログインを強制することができます。

しかし... リプレイ アタックが意図的にセッション ID を省略した場合、認証 Cookie が永続 Cookie である場合、これを正当なブラウザー リクエストと区別する実際の方法はありません。このような場合、少なくとも「POST」リクエストをブロックできますが、リプレイ攻撃では、最初に GET を発行して新しいセッションを確立し、その後に POST を送信することができます。私が本当にwant は、ユーザーがログアウトすると、そのセッションを承認するために使用された Cookie 値が無効になったことを判断する方法です。DB に何かを保存する必要があると思います (現在有効な Cookie 値のリストである可能性が最も高い)。 、この時点で正当化できる以上の努力のようです。しかし、それ以外のもの (フォームで非表示の nonce フィールドを使用するなど) は、永続的な認証 Cookie を使用して新しいセッションを再確立するユーザーをエミュレートするだけのスクリプトになりやすいようです。 nonce フィールド値などです。ですから、どの認証 Cookie 値がまだ有効であるかを (本質的に) 追跡するために何かをサーバー側に保存する必要があると私が正しいと仮定すると、既知の低フットプリント/よく知られているライブラリはありますか?これを行う?

(ところで、今のところ、認証チケットで IsPersistent フラグを使用しています。これが false の場合、つまり、ユーザーがセッションごとの Cookie のみを使用することを明示的に選択した場合、次のブラウザー セッションで再ログインする必要があることがわかっています。 、その後、リプレイ攻撃を確実にブロックできます.しかし、trueの場合、「POST」リプレイのみをブロックするため、実際の保護はあまり提供されませんが、少なくともより洗練されたリプレイ攻撃を懸念しているユーザーは、常にセッションごとを選択することでそれらを防ぐことができます.認証)。

4

2 に答える 2

4

ASP.NET 認証でさえ、ユーザーがまだアクティブなログイン ユーザーであるかどうかを確認するために二次的なチェックが必要であると明確に述べています (たとえば、ユーザーをブロックすることができ、ユーザーがパスワードを変更した可能性があります)。フォーム認証チケットは提供しません。これらに対するセキュリティ。

UserSession は ASP.NET MVC セッションとは関係ありません。ここでは単なる名前です。

私が実装した解決策は、

  1. UserSessionsデータベースにテーブルを作成しますUserSessionID (PK, Identity) UserID (FK) DateCreated, DateUpdated
  2. FormsAuthenticationTicket には UserData というフィールドがあり、そこに UserSessionID を保存できます。

ユーザーのログイン時

public void DoLogin(){
     
     // do not call this ...
     // FormsAuthentication.SetAuthCookie(....

     DateTime dateIssued = DateTime.UtcNow;

     var sessionID = db.CreateSession(UserID);
     var ticket = new FormsAuthenticationTicket(
            userName,
            dateIssued,
            dateIssued.Add(FormsAuthentication.Timeout),
            iSpersistent,
            // userData
            sessionID.ToString());

     HttpCookie cookie = new HttpCookie(
         FormsAuthentication.CookieName,
         FormsAuthentication.Encrypt(ticket));
     cookie.Expires = ticket.Expires;
     if(FormsAuthentication.CookieDomain!=null)
         cookie.Domain = FormsAuthentication.CookieDomain;
     cookie.Path = FormsAuthentication.CookiePath;
     Response.Cookies.Add(cookie);
            
}

ユーザーを承認するには

Global.asax クラスにより、Authorize にフックできます

public void Application_Authorize(object sender, EventArgs e){
     var user = Context.User;
     if(user == null)   
         return;

     FormsIdentity formsIdentity = user.Identity as FormsIdentity;
     long userSessionID = long.Parse(formsIdentity.UserData);
     
     string cacheKey = "US-" + userSessionID;

     // caching to improve performance
     object result = HttpRuntime.Cache[cacheKey];
     if(result!=null){
         // if we had cached that user is alright, we return..
         return;
     }

     // hit the database and check if session is alright
     // If user has logged out, then all UserSessions should have been
     // deleted for this user
     UserSession session = db.UserSessions
           .FirstOrDefault(x=>x.UserSessionID == userSessionID);
     if(session != null){

          // update session and mark last date
          // this helps you in tracking and you
          // can also delete sessions which were not
          // updated since long time...
          session.DateUpdated = DateTime.UtcNow;
          db.SaveChanges();

          // ok user is good to login
          HttpRuntime.Cache.Add(cacheKey, "OK", 
               // set expiration for 5 mins
               DateTime.UtcNow.AddMinutes(5)..)

         // I am setting cache for 5 mins to avoid
         // hitting database for all session validation
         return;
     }

     // ok validation is wrong....


     throw new UnauthorizedException("Access denied");
 
}

ユーザーがログアウトするとき

public void Logout(){

    // get the ticket..
    FormsIdentity f = Context.User.Identity as FormsIdentity;
    long sessionID = long.Parse(f.UserData);

    var session = db.UserSessions.First(x=>x.UserSessionID = sessionID);
    db.UserSession.Remove(session);
    db.SaveChanges();

    FormsAuthentication.Signout();
}

** ユーザーがパスワードを変更したり、ユーザーがブロックされたり、ユーザーが削除されたりした場合... **

public void ChangePassword(){

    // get the ticket..
    FormsIdentity f = Context.User.Identity as FormsIdentity;
    long sessionID = long.Parse(f.UserData);

    var session = db.UserSessions.First(x=>x.UserSessionID = sessionID);

    // delete all sessions for the same user id
    // this will force user to relogin on all other
    // devices...
    db.Database.ExecuteSql(
        "DELETE FROM UserSessions WHERE UserID=@UserID",
        new SqlParameter("@UserID", session.UserID));
}
于 2015-09-11T06:16:20.780 に答える