Обнаружение атаки повторного воспроизведения ASP.NET снова

Хорошо, ASP.NET WebForms - это старая технология, но мы будем застревать с ней в обозримом будущем, и я просто пытаюсь понять, смогу ли я внедрить очень простой механизм предотвращения повторных атак. По сути, мы хотим убедиться, что после явного выхода пользователя из системы никто не сможет воспроизвести старый запрос и автоматически подтвердить его подлинность на основе файла cookie авторизации, но мы все же хотим разрешить использование файлов cookie постоянной авторизации для пользователей, которые не хотят повторный вход в каждый новый сеанс браузера. Из того, что я наблюдал в последнем случае, если вы выходите из браузера и начинаете новый сеанс, в заголовках файлов cookie нет идентификатора сеанса - только файл cookie авторизации форм. В этом случае мы хотим разрешить автоматическую аутентификацию (по крайней мере, для запросов GET). В случае "воспроизведения" в заголовках cookie есть идентификатор сеанса, но ASP.NET фактически автоматически воссоздает новый сеанс, используя тот же идентификатор. К счастью, мы можем обнаружить это (проверив Session.IsNewSession и Request.Headers["Cookie"] - но, как ни странно, не Request.Cookies, который иногда включает в себя cookie сессии, даже если он не был отправлен клиентом), и, следовательно, это возможно принудительно выполнить повторный вход в случае, если клиент отправил идентификатор сеанса для сеанса, который был закрыт / отменен, когда пользователь вышел из системы.

Но... если атака воспроизведения преднамеренно пропускает идентификатор сеанса, то нет никакого реального способа отличить это от законного запроса браузера, когда файл cookie авторизации является постоянным файлом cookie. Вы можете по крайней мере блокировать запросы "POST" в таком случае, но атака воспроизведения может просто выполнить GET, чтобы сначала установить новый сеанс, а затем выполнить это с помощью POST. Что я действительно хочу, так это способ определить, что после того, как пользователь вышел из системы, значение cookie, которое использовалось для авторизации этого сеанса, больше не является допустимым - я думаю, что для этого потребуется сохранить что-то в БД (скорее всего, список действительных в настоящее время). значения cookie), что требует больше усилий, чем может быть оправдано на данный момент. Но также кажется, что все остальное (например, использование скрытых одноразовых полей в формах) склонно к сценарию, который просто эмулирует повторное установление пользователем нового сеанса с постоянным файлом cookie авторизации, и в этом случае они смогут определить требуемый значения полей nonce и т. д. Если предположить, что я прав, необходимо что-то сохранить на стороне сервера, чтобы отследить (по существу), какие значения файла cookie авторизации все еще действительны, существуют ли какие-либо известные библиотеки низкого уровня / известные сделай это?

(Кстати, на данный момент я использовал флаг IsPersistent в билете аутентификации - если это ложно, то есть пользователь явно выбрал использование куки-файла для каждой сессии, зная, что он будет повторно войти в систему для своей следующей сессии браузера тогда я могу надежно блокировать атаки воспроизведения, но если это правда, я блокирую только повторы "POST", которые не обеспечивают реальной защиты, но, по крайней мере, пользователи, которые обеспокоены более сложными атаками воспроизведения, могут предотвратить их, всегда выбирая сеанс аутентификация).

1 ответ

Даже Аутентификация ASP.NET ясно говорит, что вам нужно провести вторичную проверку, чтобы подтвердить, является ли пользователь активным вошедшим в систему пользователем (например, мы могли бы заблокировать пользователя, пользователь мог изменить свой пароль), билет проверки подлинности с помощью форм не предлагает любая защита от этих вещей.

UserSession не имеет ничего общего с ASP.NET MVC Session, это просто имя здесь

Решение, которое я реализовал,

  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 позволяет подключиться к авторизации

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 UerSessions WHERE UserID=@UserID",
        new SqlParameter("@UserID", session.UserID));
}

Согласно Википедии, атака с повторением сеанса - это повторение одних и тех же данных запроса.

Очень простое решение - использовать "Одноразовый пароль" (OTP). То есть связать сеанс с OTP. После получения запроса аннулируйте OTP.

Другие вопросы по тегам