I am trying to figure out how to fight a brute force attack on my website. Based on all the research I have done the top answers were Account Lockout & Captcha.
If I lock out a user then I am denying them service for x amount of time. This means that if an attacker were to attack 10 different accounts he will lock them all. Then when time is up he will lock them again. Basically he can keep at it and keep the users locked out indefinitely. The users can contact me but that is now 10 tickets I would have to deal with and I'd rather avoid that work if its possible. So what I am failing to understand exactly is how is this then useful? The attacker might not get into account but they will cause me and users a lot of grief.
How do I combat this? Ip banning seems pointless as it can be changed fairly easy.
You can add an incremental delay that doubles after each failed login attempt, after a handful of login attempts the delay gets too long for brute force to work (e.g. after 20 attempts the delay is 6 days).
[HttpPost]
public async Task<ActionResult> Login(LoginViewModel viewModel, string returnUrl)
{
// incremental delay to prevent brute force attacks
int incrementalDelay;
if (HttpContext.Application[Request.UserHostAddress] != null)
{
// wait for delay if there is one
incrementalDelay = (int)HttpContext.Application[Request.UserHostAddress];
await Task.Delay(incrementalDelay * 1000);
}
if (!ModelState.IsValid)
return View();
// authenticate user
var user = _userService.Authenticate(viewModel.Username, viewModel.Password);
if (user == null)
{
// login failed
// increment the delay on failed login attempts
if (HttpContext.Application[Request.UserHostAddress] == null)
{
incrementalDelay = 1;
}
else
{
incrementalDelay = (int)HttpContext.Application[Request.UserHostAddress] * 2;
}
HttpContext.Application[Request.UserHostAddress] = incrementalDelay;
// return view with error
ModelState.AddModelError("", "The user name or password provided is incorrect.");
return View();
}
// login success
// reset incremental delay on successful login
if (HttpContext.Application[Request.UserHostAddress] != null)
{
HttpContext.Application.Remove(Request.UserHostAddress);
}
// set authentication cookie
_formsAuthenticationService.SetAuthCookie(
user.Username,
viewModel.KeepMeLoggedIn,
null);
// redirect to returnUrl
return Redirect(returnUrl);
}
There's more details at this post
Don't display the user id used to log in publicly. Have a separate display id. For example, they might log in with their email address and choose a different name to display. If an attacker doesn't have the user id then he can't make repeated login attempts and lock another user out.
You could use a PoliteCaptcha, which only displays the captcha if JavaScript is disabled (as in most automated scripts) or when the first submit attempt fails. This makes the captcha invisible to most of your normal users, but a PITA for spammers.
Related
I am having trouble invalidating .AspNetCore.Identity.Application cookie in ASP.NET Core Identity once the user log out.
Once user clicks on log out below code will execute.
public async Task<IActionResult> Logout(LogoutInputModel model)
{
// build a model so the logged out page knows what to display
LoggedOutViewModel loggedOutViewModel = await BuildLoggedOutViewModelAsync(model.LogoutId);
_logger.LogInformation($"loggedOutViewModel : {JsonConvert.SerializeObject(loggedOutViewModel)}");
if (User?.Identity.IsAuthenticated == true)
{
// delete local authentication cookie
await _norskTakstSignInManager.SignOutAsync();
//clear cookies
var appCookies = Request.Cookies.Keys;
foreach (var cookie in appCookies)
{
Response.Cookies.Delete(cookie);
}
// raise the logout event
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
}
// check if we need to trigger sign-out at an upstream identity provider
if (loggedOutViewModel.TriggerExternalSignout)
{
// build a return URL so the upstream provider will redirect back
// to us after the user has logged out. this allows us to then
// complete our single sign-out processing.
string url = Url.Action("Logout", new { logoutId = loggedOutViewModel.LogoutId });
// this triggers a redirect to the external provider for sign-out
return SignOut(new AuthenticationProperties { RedirectUri = url }, loggedOutViewModel.ExternalAuthenticationScheme);
}
return View("LoggedOut", loggedOutViewModel);
}
This successfully clears all the cookies in the browser, however, if I grab the value of the cookie named ".AspNetCore.Identity.Application" prior to signing out, then add it back in on to the browser, then i can log in to the application without entering user credentials.
I tested few flows setting up cookie expiration time in different ways but non of them seem to work correctly.
I want to know way to invalidate the cookie without just clearing to resolve this issue.Then user should not be able to enter cookie manually and log in to the system. Any help is hugly appreciated. Thank you.
That's by design... one thing you can do is try updating the user's security stamp after logout, using UserManager.UpdateSecurityStampAsync.
This way the cookie's security stamp won't match the one in the database and the cookie will no longer be valid (however, no other cookie issued to that user will, even if they haven't "signed out"... so if a user has several sessions opened, all of those cookies will stop being valid, not just the one you signed out).
Identity doesn't track specific user sessions (it just validates the cookie against the user, and if it matches, it matches). If you want to be able to selectively remove sessions, you'll have to track them yourself
For me the best security practice is save every login and logout in one record with an unique random ID as GUID, then save this "id session" into the claims, and check this everytime the user access, if the ID in the claim is correct to that session.
For a couple of days I am playing with MembershipReboot framework and being honest it looks very good. I would use it for my applications.
However, the question that I have is about its lockout functionality. I have tried to lock my account a couple of times, but it seems that nothing happens. Here is my configuration
<membershipReboot requireAccountVerification="true" emailIsUsername="false" multiTenant="false" allowAccountDeletion="true" passwordHashingIterationCount="0" accountLockoutDuration="00:05:00" passwordResetFrequency="0" accountLockoutFailedLoginAttempts="2" />
It seems that on my third or even fourth attempt I can login without any issue. Also I have investigated the DB that Membership uses and I cannot find any flags for a locked account.
My question is - is that lockout functionality comes already implemented out of the box or I have to do my logic there? If it is implemented, so can I enable it?
Account lockout in MembershipReboot uses two properties from security settings configuration
AccountLockoutFailedLoginAttempts (int, default: 10) : Number of failed password login attempts before the account is locked out.
AccountLockoutDuration (TimeSpan, default: 5 minutes): Duration an account will be locked due to too many failed password login attempts.
In your settings your are overriding the default values. So if you try more than 2 failed login attempts within 5-minute window your account is locked for another 5 mins from your last failed login. if you try to log in five mins after your last failed login you will be logged in as the account is not locked according to the lockout logic. If you try to log-in within 5 mins and your failed attempts have not exceeded you can still log-in.
Code is better than words(Check VerifyPassword method)
You will see all required properties for account lockdown in UserAccounts table. Namely LastFailedLogin and FailedLoginCount
protected virtual bool CheckHasTooManyRecentPasswordFailures(TAccount account)
{
var result = false;
if (Configuration.AccountLockoutFailedLoginAttempts <= account.FailedLoginCount)
{
result = account.LastFailedLogin >= UtcNow.Subtract(Configuration.AccountLockoutDuration);
if (!result)
{
// if we're past the lockout window, then reset to zero
account.FailedLoginCount = 0;
}
}
if (result)
{
account.FailedLoginCount++;
}
return result;
}
I am trying to learn basic security and access limitations on ASP MVC.
So far, i have read/watched tutorials but all of them seems different from one another. If i will search something, it will lead me to another implementation which is totally different from what i have.
I implemented Authentication and custom role provider and i have some questions regarding how things work. Majority of explanations that i found from the internet seems overly complicated or outdated.
This is how i implemented my authentication.
login controller:
[HttpGet]
[ActionName("login")]
public ActionResult login_load()
{
return View();
}
[HttpPost]
[ActionName("login")]
public ActionResult login_post(string uname,string pword)
{
using (EmployeeContext emp = new EmployeeContext())
{
int success = emp.login.Where(x => x.username == uname && x.password == pword).Count();
if (success == 1)
{
FormsAuthentication.SetAuthCookie(uname, false);
return RedirectToAction("Details", "Enrollment");
}
return View();
}
}
Then i protected most of my controllers with [Authorize]
Question #1
What's the purpose of FormsAuthentication.SetAuthCookie(uname, false); and what should i typicalfly use it for? would it be alright to store the username. Do i need it for comparison later on?(further security?). It says here that Authentication ticket will be given to the username. Are those the ones with random letters?
--
After that, i decided to dive deeper and implemented a custom role provider
from roleprovider.cs(I only implemented 2 methods so far)
public override string[] GetRolesForUser(string username)
{
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
return null;
}
var cacheKey = username;
if (HttpRuntime.Cache[cacheKey] != null)
{
return (string[])HttpRuntime.Cache[cacheKey];
}
string[] roles = new string[] { };
using (MvcApplication6.Models.EmployeeContext emp = new MvcApplication6.Models.EmployeeContext())
{
roles = (from a in emp.login
join b in emp.roles on a.role equals b.id
where a.username.Equals(username)
select b.role).ToArray<string>();
if (roles.Count() > 0)
{
HttpRuntime.Cache.Insert(cacheKey, roles, null, DateTime.Now.AddMinutes(_cacheTimeoutInMinute), Cache.NoSlidingExpiration);
}
}
return roles;
}
Question #2
I am kinda confused here and i need a deep clarification: so what is basically the purpose of the cacheKey and from my example, i just made it equal to uname since i have no idea what's going on.
Question #3
Why is it returned (string[])HttpRuntime.Cache[cacheKey]; if the value is null? when is it returned and who is receiving it?
Question #4
After getting the value the list of roles from the database, this function will be called HttpRuntime.Cache.Insert(cacheKey, roles, null, DateTime.Now.AddMinutes(_cacheTimeoutInMinute), Cache.NoSlidingExpiration);. So from what i see, the roles are being inserted into the cache? is it for checking the login type later on?
Question #5
from this lines of code:
public override bool IsUserInRole(string uname, string roleName)
{
var userRoles = GetRolesForUser(uname);
return userRoles.Contains(roleName);
}
When are they exactly triggered and who provides the parameters? is the roleName from the cache?
I am having a hard time visualizing what's happening under the hood. Explanations/Referrals will be very helpful.
What's the purpose of FormsAuthentication.SetAuthCookie()?
This is ASP.NET FormsAuthentication's built-in method for dealing with authentication cookies.
How does cookie based authentication work?
Explained: Forms Authentication in ASP.NET 2.0
Basically, it's doing the hard work for you; creating a cookie for a specific user, giving it to them and then using it to recognise the same user in the future. You want to use this function to log a user in (if they enter correct credentials).
The string parameter is for a username. Yes, you can use username.
The bool parameter is for if you want the cookie to be persistent. That is, keep them logged in even if they close the browser (whether or not to use a session).
By using FormsAuthentication in this way, ASP.NET will automatically detect the user again when they visit subsequent pages.
What is basically the purpose of the cacheKey?
The Cache component of the HttpRuntime is for managing a "box" of objects that you might retrieve frequently but don't want to be hitting the database all the time for.
The Cache is implemented as a kind of Key-Value Pair. The cacheKey in your example is a key in the Key-Value collection. You can think of it like other similar data structures used in other languages.
{
"carlobrew": {
"roles": {
"Name": "Administrator"
}
}
}
So you're basically "saving" the roles of the user carlobrew in a container so that you can get them again later. The key in a Key-Value Pair is used to refer back to the data that you put in there. The key you are using to refer back to the saved information is the uname; that is, the username.
The key in Key-Value Pairs is unique, so you cannot have two keys called carlobrew.
Why is it returned (string[])HttpRuntime.Cache[cacheKey]; if the value is null?
There are two steps to using a typical "cache box" like this.
If we find the key (such as the user carlobrew) then we can simply return the data straight away. It's not if the value is null. It's if the value is not null. That's why the code is if (HttpRuntime.Cache[cacheKey] != null).
If the key cannot be found (that is, we don't have the key for carlobrew), well then we have to add it ourselves, and then return it.
Since it's a cache, ASP.NET MVC will automatically delete things from the cache when the timer expires. That's why you need to check to see if the data is null, and re-create it if it is.
The "who is receiving it" is whichever object is responsible for calling the GetRolesForUser() method in the first place.
So from what i see, the roles are being inserted into the cache?
Yes.
Basically, if the data isn't in the cache, we need to grab it from the database and put it in there ourselves, so we can easily get it back if we call the same method soon.
Let's break it down. We have:
Insert(cacheKey, roles, null, DateTime.Now.AddMinutes(_cacheTimeoutInMinute), Cache.NoSlidingExpiration);
Insert is the method. We're calling this.
cacheKey is the key part of the Key-Value Pair. The username.
roles is the object that we want to store in cache. The object can be anything we want.
DateTime.Now.AddMinutes(_cacheTimeoutInMinute) is telling ASP.NET MVC when we want this data to expire. It can be any amount of time that we want. I'm not sure what the variable _cacheTimeoutInMinute maybe it's 5 or 15 minutes.
Cache.NoSlidingExpiration is a special flag. We're telling ASP.NET that, when we access this data, don't reset the expiration timer back to its full. For example, if our timer was 15 mins, and the timer was about to expire with 1 minute to go, if we were using a sliding expiration and tried to access the data, the timer would reset back to 15 minutes and not expire the data.
Not sure what you mean by "is it for checking the login type later on". But no, there isn't any checking of login type here.
IsUserInRole
You would probably call this when the user is trying to do something. For example, if the user goes to /Admin/Index page, then you could check to see if the user is in the Administrator role. If they aren't, you'd return a 401 Unauthorized response and tell you the user they aren't allowed to access that page.
public Controller Admin
{
public ActionResult Index()
{
if (!IsUserInRole("Administrator"))
{
// redirect "not allowed"
}
return View();
}
}
I'm not sure I'm handling this the right way, but since I'm running into issues, I assume I'm not.
I have to have a corporation id sent in when loading the login screen.
Looks like this:
public ActionResult LogOn(string id)
{
var sb = new StringBuilder();
sb.AppendLine(string.Format("CorpID: {0}", id));
if(ViewBag.CorpID != null)
sb.AppendLine(string.Format("ViewBag.CorpID: {0}", ViewBag.CorpID));
Guid corpIdGuid;
if (!Guid.TryParse(id, out corpIdGuid) && string.IsNullOrEmpty(ViewBag.CorpID))
return null;
// the id passed in will take presidence over the
// viewbag unless it is blank then we use viewbag
// one way or the other viewbag.corpid should not
// be blank
if(!string.IsNullOrEmpty(id))
ViewBag.CorpID = id;
// Session["CorpId"] = id;
//Not a junk guid.. continue.
return View();
}
I need this to establish what company we will be working with during this session.
The problem I am running into, is when the cookie timeout occurs, which is set to 10 minutes, it directs them back to this login and I have no corpid anymore.
I tried the viewbag and it's being reset.
I tried a cookie, but since it expires, the data is no longer there.
I tried a Profile Manager but since they are logged it, that puts me back to nothing.
How do I maintain this CorpId when the user has timed out and put back on the login screen? I need this information for each screen I have also.
Any input would be greatly appreciated!
You need to create a separate cookie that identifies the Corporate ID that doesn't expire with user's session. Session["CorpId"] will expire with the user session and won't work.
var corpCookie = new HttpCookie("CorpID", id);
corpCookie.Expires = DateTime.Now.AddDays(30.0);
HttpContext.Current.Response.Cookies.Set(corpCookie);
Each time the user logs in, you could update the expiry to make it a sliding expiration. To retrieve the cookie value, use the following:
var corpID = HttpContext.Current.Request.Cookies.Get("CorpID").Value;
var query = from p in AdminModelContext.Users
where p.UserName == model.UserName && p.Password == encryptPassword
&& p.IsDeleted == false
select p;
IList<Users> userList = query.ToList();
if (userList.Count() > 0)
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (CheckUrl(returnUrl))
{
return Redirect(returnUrl);
}
SetRoleForUser(userList[0].RolesId);
LoggerService.Info(string.Format("Login Successful for the user : {0}",
model.UserName));
return RedirectToAction("Index", "Home");
}
I am using the following code to Login through my website. The problem that I am facing is when I logged in with a user on a specific browser and at the same time login with a different user on a different browser, and then I delete the user(logged in on the other browser). Still I am able to navigate through the pages with the deleted user logged in.
I am not finding a fair solution to put authentication logic on every page. My website is in MVC model and using Form based authentication.
Please suggest how can I put the logged in user session validation and achieve this.
None of the answers so far actually acknowledge the question.
Lets look at the control flow:
User A enters log in page, supplies valid credentials
User A is issued Ticket A.
User B enters site, supplies valid credentials.
User B is issued Ticket B.
User B then revokes User A's access CREDENTIALS.
At this point nothing happens to Ticket A. Because the Ticket is independent on the credentials. When Ticket A expires they will then be required to present their credentials and it will fail login.
So what you've noticed that kicking a live user out of your site is actually pretty hard. As you've realized the ONLY solution is to have authentication logic on EVERY request. That unfortunately is really heavy.
In the login system I built I handled this aspect by creating 2 tickets, 1 ticket that's stored in the Forms Auth ticket as normal that has a big duration, and a ticket that's stored in HttpRuntime.Cache, I set the cache expiration to 15 minutes on this ticket.
On every page request I check to see whether a user has a ticket in the cache (based off their Forms Auth ticket information), at this point if they have no ticket I do a user data refresh and poll the user database. If the user has become suspended or deleted they will be logged out at then.
Using this method I know that my site can disable a user and within 15 minutes that user will be barred from the site. If I want them immediately barred I can just cycle the app config to clear the cache and FORCE it to happen.
Normally if you have the [Authorize] attribute defined for an Controller or an Action the Authentication is checked on every post back.
The build in MembershipProvider handles all that for you. But it seems you are using your own user Database. Then you have to implement your own MembershipProvider, IPrincipal and MembershipUser and have this added to your Web.config replacing the default one.
More you'll find here how to implement your own MembershipProvider: http://msdn.microsoft.com/en-us/library/f1kyba5e.aspx
My suggestion is to create an empty MVC project and have a look at the default authentication mechanism. And if your building a new Application with a new Database, try to use the default authentication.
Your validateUser function in your own MembershipProvider could look like this.
public override bool ValidateUser(string username, string password)
{
bool isValid = false;
bool isApproved = false;
string pwd = "";
using (AdminModelContext db = new AdminModelContext())
{
var user = db.Users.FirstOrDefault(u => u.UserName == username);
if (user != null)
{
pwd = user.Password;
isApproved = user.IsApproved;
if (CheckPassword(password, pwd))
{
if (isApproved)
{
isValid = true;
user.LastLoginDate = DateTime.Now;
user.LastActivityDate = DateTime.Now;
try
{
db.SubmitChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
else
{
UpdateFailureCount(username, "password");
}
}
}
return isValid;
}
I see the problem now. I dont know how this works in MVC, but by using Authenticate_Request, you can validate if the user is still valid. The business logic may also double check if the user is still valid. But as far as I know, there is no way of iterating all the open sessions and killing the requierd ones, even in that case, the authorization cookie should be double checked on Session_Start event.
Another options is adding a global invalidated_users list on the application, and then checking that user against the invalid list. This list should only contain users that are invalidated after the application has restarted.
Link for Reading All Users Session:
http://weblogs.asp.net/imranbaloch/archive/2010/04/05/reading-all-users-session.aspx