/// <summary> /// Resets the password for the user. /// </summary> /// <returns> /// The new password for the user. /// </returns> public string ResetPassword() { string newPassword = Esapi.Authenticator().GenerateStrongPassword(); ChangePassword(newPassword, newPassword); return(newPassword); }
/// <summary> Invalidate the old session after copying all of its contents to a newly created session with a new session id. /// Note that this is different from logging out and creating a new session identifier that does not contain the /// existing session contents. Care should be taken to use this only when the existing session does not contain /// hazardous contents. /// /// </summary> /// <returns> The invaldiated session. /// </returns> /// <seealso cref="Owasp.Esapi.Interfaces.IHttpUtilities.ChangeSessionIdentifier()"> /// </seealso> public IHttpSession ChangeSessionIdentifier() { IHttpRequest request = ((Authenticator)Esapi.Authenticator()).CurrentRequest; IHttpResponse response = ((Authenticator)Esapi.Authenticator()).CurrentResponse; IHttpSession session = ((Authenticator)Esapi.Authenticator()).CurrentSession; IDictionary temp = new Hashtable(); // make a copy of the session content IEnumerator e = session.GetEnumerator(); while (e != null && e.MoveNext()) { string name = (string)e.Current; object val = session[name]; temp[name] = val; } // invalidate the old session and create a new one // This hack comes from here: http://support.microsoft.com/?kbid=899918 session.Abandon(); response.Cookies.Add(new HttpCookie("ASP.NET_SessionId", "")); // copy back the session content IEnumerator i = new ArrayList(temp).GetEnumerator(); while (i.MoveNext()) { DictionaryEntry entry = (DictionaryEntry)i.Current; session.Add((string)entry.Key, entry.Value); } return(session); }
/// <summary> Formats an HTTP request into a log suitable string. This implementation logs the remote host IP address (or /// hostname if available), the request method (GET/POST), the URL, and all the querystring and form parameters. All /// the paramaters are presented as though they were in the URL even if they were in a form. Any parameters that /// match items in the parameterNamesToObfuscate are shown as eight asterisks. /// /// </summary> /// <param name="parameterNamesToObfuscate">The sensitive parameters to obfuscate in the log entry. /// </param> /// <seealso cref="Owasp.Esapi.Interfaces.ILogger.LogHttpRequest(IList)"> /// </seealso> public virtual void LogHttpRequest(IList parameterNamesToObfuscate) { IHttpRequest request = ((Authenticator)Esapi.Authenticator()).Context.Request; StringBuilder parameters = new StringBuilder(); IEnumerator i = request.Params.Keys.GetEnumerator(); while (i.MoveNext()) { string key = (string)i.Current; // Note: Do we need to deal with multiple identical values here? string value = request.Params[key]; parameters.Append(key + "="); if (parameterNamesToObfuscate != null && parameterNamesToObfuscate.Contains(key)) { parameters.Append("********"); } else { parameters.Append(value); } if (i.MoveNext()) { parameters.Append("&"); } } string msg = request.RequestType + " " + request.Url + (parameters.Length > 0 ? "?" + parameters : ""); LogSuccess(ILogger_Fields.SECURITY, msg); }
/// <summary> /// Instantiates a new user. /// </summary> /// <param name="accountName"> /// The account name for the user. /// </param> /// <param name="password1"> /// The password for the user. /// </param> /// <param name="password2"> /// The confirmation password for the user. /// </param> public User(string accountName, string password1, string password2) { Esapi.Authenticator().VerifyAccountNameStrength(accountName); if (password1 == null) { throw new AuthenticationCredentialsException("Invalid account name", "Attempt to create account " + accountName + " with a null password"); } Esapi.Authenticator().VerifyPasswordStrength(password1, null); if (!password1.Equals(password2)) { throw new AuthenticationCredentialsException("Passwords do not match", "Passwords for " + accountName + " do not match"); } this.accountName = accountName.ToLower(); try { SetHashedPassword(Esapi.Encryptor().Hash(password1, this.accountName)); } catch (EncryptionException ee) { throw new AuthenticationException("Internal error", "Error hashing password for " + this.accountName, ee); } expirationTime = new DateTime(DateTime.Now.Ticks + (1000L * 60 * 60 * 24 * 90)); // 90 days logger.LogCritical(ILogger_Fields.SECURITY, "Account created successfully: " + accountName); }
// FIXME: ENHANCE - make admin only methods separate from public API /// <summary> Sets the user's password. This is an admin-only method. /// </summary> /// <param name="newPassword1">The new password. /// </param> /// <param name="newPassword2">The confirmation of the new password. /// </param> protected internal void ChangePassword(string newPassword1, string newPassword2) { SetLastPasswordChangeTime(DateTime.Now); string newHash = Esapi.Authenticator().HashPassword(newPassword1, AccountName); SetHashedPassword(newHash); logger.LogCritical(ILogger_Fields.SECURITY, "Password changed for user: " + AccountName); }
/// <summary> /// Authenticates the user with a given password. /// </summary> /// <param name="password">The password to use for authentication.</param> /// <seealso cref="Owasp.Esapi.Interfaces.IUser.LoginWithPassword(string)"> /// </seealso> public void LoginWithPassword(string password) { if (password == null || password.Equals("")) { SetLastFailedLoginTime(DateTime.Now); throw new AuthenticationLoginException("Login failed", "Missing password: "******"Login failed", "Disabled user attempt to login: "******"Login failed", "Locked user attempt to login: "******"Login failed", "Expired user attempt to login: "******"User logged in: " + accountName); } else { throw new AuthenticationLoginException("Login failed", "Login attempt as " + AccountName + " failed"); } } catch (EncryptionException ee) { throw new AuthenticationException("Internal error", "Error verifying password for " + accountName, ee); } }
/// <summary> /// Decrypt the cookies. /// </summary> /// <returns>The decrypted name-value collection (cleartext).</returns> public IDictionary DecryptStateFromCookie() { IHttpRequest request = ((Authenticator)Esapi.Authenticator()).CurrentRequest; HttpCookieCollection cookies = request.Cookies; string encrypted = cookies["state"].Value; string cleartext = Esapi.Encryptor().Decrypt(encrypted); return(QueryToMap(cleartext)); }
/// <summary> Match a rule, based on a path. /// /// </summary> /// <param name="map">The map of rules. /// </param> /// <param name="path">The path to check. /// /// </param> /// <returns> true, if the rule is matched. /// </returns> private bool MatchRule(IDictionary map, string path) { // get users roles IUser user = Esapi.Authenticator().GetCurrentUser(); IList roles = user.Roles; // search for the first rule that matches the path and rules Rule rule = SearchForRule(map, roles, path); return(rule.allow); }
/// <summary> /// Returns the last host address used by the user. This will be used in any log messages generated by the processing /// of this request. /// </summary> /// <returns> /// Value of last host address used by user. /// </returns> /// <seealso cref="Owasp.Esapi.Interfaces.IUser.GetLastHostAddress()"> /// </seealso> public string GetLastHostAddress() { IHttpRequest request = ((Authenticator)Esapi.Authenticator()).CurrentRequest; if (request != null) { SetLastHostAddress(request.UserHostAddress); } return(lastHostAddress); }
/// <summary> /// This method forwards the request safely. /// </summary> /// <param name="context">The context for the forward.</param> /// <param name="location">The location to forward the request.</param> public void SafeSendForward(string context, string location) { // FIXME: should this be configurable? What is a good forward policy? // I think not allowing forwards to public URLs is good, as it bypasses many access controls IHttpRequest request = ((Authenticator)Esapi.Authenticator()).CurrentRequest; IHttpResponse response = ((Authenticator)Esapi.Authenticator()).CurrentResponse; // FIXME: Implement. throw new NotImplementedException(); }
/// <summary> Kill all cookies received in the last request from the browser. Note that new cookies set by the application in /// this response may not be killed by this method. /// </summary> /// <seealso cref="Owasp.Esapi.Interfaces.IHttpUtilities.KillAllCookies()"> /// </seealso> public void KillAllCookies() { IHttpRequest request = ((Authenticator)Esapi.Authenticator()).CurrentRequest; HttpCookieCollection cookies = request.Cookies; if (cookies != null) { foreach (string cookieName in cookies) { KillCookie(cookieName); } } }
/* * FIXME: Enhance - future actions might include SNMP traps, email, pager, etc... */ /// <summary> /// This method performs a security action based on an intrustion threshold. /// </summary> /// <param name="action">The action to take.</param> /// <param name="message">The message to log regarding the action.</param> private void TakeSecurityAction(string action, string message) { if (action.Equals("log")) { logger.LogCritical(ILogger_Fields.SECURITY, "INTRUSION - " + message); } if (action.Equals("disable")) { Esapi.Authenticator().GetCurrentUser().Disable(); } if (action.Equals("logout")) { ((Authenticator)Esapi.Authenticator()).Logout(); } }
// FIXME: ENHANCE - add configuration for entry pages that don't require a token /// <summary> Checks the CSRF token in the URL (see User.GetCSRFToken()) against the user's CSRF token and throws /// an exception if they don't match. /// </summary> /// <seealso cref="Owasp.Esapi.Interfaces.IHttpUtilities.VerifyCsrfToken()"> /// </seealso> public void VerifyCsrfToken() { IHttpRequest request = ((Authenticator)Esapi.Authenticator()).CurrentRequest; User user = (User)Esapi.Authenticator().GetCurrentUser(); // if this is the first request after logging in, let them pass if (user.IsFirstRequest()) { return; } if (request[user.CsrfToken] == null) { throw new IntrusionException("Authentication failed", "Possibly forged HTTP request without proper CSRF token detected"); } }
/// <summary> Set headers to protect sensitive information against being cached in the /// browser. /// </summary> /// <seealso cref="Owasp.Esapi.Interfaces.IHttpUtilities.SetNoCacheHeaders()"> /// </seealso> public void SetNoCacheHeaders() { IHttpResponse response = ((Authenticator)Esapi.Authenticator()).CurrentResponse; if (response == null) { throw new NullReferenceException("Can't set response header until current response is set, typically via login"); } // HTTP 1.1 response.AppendHeader("Cache-Control", "no-store"); response.AppendHeader("Cache-Control", "no-cache"); response.AppendHeader("Cache-Control", "must-revalidate"); // HTTP 1.0 response.AppendHeader("Pragma", "no-cache"); response.AppendHeader("Expires", DateTime.MinValue.ToString("r")); }
// FIXME: Enhance - consider adding AddQueryChecksum(String href) that would just verify that none of the parameters in the querystring have changed. Could do the same for forms. // FIXME: Enhance - also VerifyQueryChecksum() // FIXME: need to make this easier to add to forms. /// <summary> Adds the current user's CSRF token (see User.GetCSRFToken()) to the URL for purposes of preventing CSRF attacks. /// This method should be used on all URLs to be put into all links and forms the application generates. /// </summary> /// <param name="href"> The URL to append the CSRF token to. /// </param> /// <returns> The updated href with the CSRF token parameter. /// </returns> /// <seealso cref="Owasp.Esapi.Interfaces.IHttpUtilities.AddCsrfToken(string)"> /// </seealso> public string AddCsrfToken(string href) { User user = (User)Esapi.Authenticator().GetCurrentUser(); // FIXME: AAA getCurrentUser should never return null if (user.Anonymous || user == null) { return(href); } if ((href.IndexOf('?') != -1) || (href.IndexOf('&') != -1)) { return(href + "&" + user.CsrfToken); } else { return(href + "?" + user.CsrfToken); } }
/// <summary> Kills the specified cookie by setting a new cookie that expires immediately. /// </summary> /// <param name="name">The name of the cookie to kill. /// </param> /// <seealso cref="Owasp.Esapi.Interfaces.IHttpUtilities.KillCookie(string)"> /// </seealso> public void KillCookie(string name) { IHttpRequest request = ((Authenticator)Esapi.Authenticator()).CurrentRequest; IHttpResponse response = ((Authenticator)Esapi.Authenticator()).CurrentResponse; HttpCookieCollection cookies = request.Cookies; if (cookies != null) { foreach (string cookieName in cookies) { if (cookieName.Equals(name)) { string path = request.ApplicationPath; string header = name + "=deleted; Max-Age=0; Path=" + path; response.AppendHeader("Set-Cookie", header); } } } }
/// <summary> Logout this user.</summary> /// <seealso cref="Owasp.Esapi.Interfaces.IUser.Logout()"> /// </seealso> public void Logout() { Authenticator authenticator = ((Authenticator)Esapi.Authenticator()); if (!authenticator.GetCurrentUser().Anonymous) { IHttpRequest request = authenticator.CurrentRequest; IHttpSession session = authenticator.Context.Session; if (session != null) { session.Abandon(); } // TODO - Kill the correct cookie Esapi.HttpUtilities().KillCookie("ASPSESSIONID"); loggedIn = false; logger.LogSuccess(ILogger_Fields.SECURITY, "Logout successful"); authenticator.SetCurrentUser(authenticator.anonymous); } }
/// <summary> Log the message after optionally encoding any special characters that might inject into an HTML based log viewer. /// This method accepts an exception. /// /// </summary> /// <param name="type">The log type. /// </param> /// <param name="message">The log message. /// </param> /// <param name="throwable">The exception to log. /// </param> private string GetLogMessage(string type, string message, Exception throwable) { User user = (User)Esapi.Authenticator().GetCurrentUser(); string clean = message; if (((SecurityConfiguration)Esapi.SecurityConfiguration()).LogEncodingRequired) { clean = Esapi.Encoder().EncodeForHtml(message); if (!message.Equals(clean)) { clean += " (Encoded)"; } } if (throwable != null) { string fqn = throwable.GetType().FullName; int index = fqn.LastIndexOf('.'); if (index > 0) { fqn = fqn.Substring(index + 1); } StackTrace st = new StackTrace(throwable, true); // Note: Should we have exceptions with null stack traces? StackFrame[] frames = st.GetFrames(); if (frames != null) { StackFrame frame = frames[0]; clean += ("\n " + fqn + " @ " + frame.GetType() + "." + frame.GetMethod() + "(" + frame.GetFileName() + ":" + frame.GetFileLineNumber() + ")"); } } string msg = ""; if (user != null) { msg = type + ": " + user.AccountName + "/" + user.GetLastHostAddress() + " -- " + clean; } return(msg); }
public void KillCookie(string name) { HttpRequest currentRequest = ((Authenticator)Esapi.Authenticator()).CurrentRequest; HttpResponse currentResponse = ((Authenticator)Owasp.Esapi.Esapi.Authenticator()).CurrentResponse; HttpCookieCollection cookies = currentRequest.Cookies; if (cookies == null) { return; } foreach (string str1 in (NameObjectCollectionBase)cookies) { if (str1.Equals(name)) { string applicationPath = currentRequest.PhysicalPath; string str2 = name + "=deleted; Max-Age=0; Path=" + applicationPath; currentResponse.AppendHeader("Set-Cookie", str2); } } }
//FIXME:Enhance - think about having a second "transaction" password for each user /// <summary> /// Verifies the password is correct. /// </summary> /// <param name="password">The password to verify.</param> /// <returns>true, if the password is correct.</returns> /// <seealso cref="Owasp.Esapi.Interfaces.IUser.VerifyPassword(string)"> /// </seealso> public bool VerifyPassword(string password) { string hash = Esapi.Authenticator().HashPassword(password, accountName); if (hash.Equals(hashedPassword)) { SetLastLoginTime(DateTime.Now); failedLoginCount = 0; logger.LogCritical(ILogger_Fields.SECURITY, "Password verified for " + AccountName); return(true); } logger.LogCritical(ILogger_Fields.SECURITY, "Password verification failed for " + AccountName); SetLastFailedLoginTime(DateTime.Now); IncrementFailedLoginCount(); if (FailedLoginCount >= Esapi.SecurityConfiguration().AllowedLoginAttempts) { Lock(); } return(false); }
/// <summary> Sets the user's password, performing a verification of the user's old password, the equality of the two new /// passwords, and the strength of the new password. /// </summary> /// <param name="oldPassword">The old password. /// </param> /// <param name="newPassword1">The new password. /// </param> /// <param name="newPassword2">The confirmation of the new password. /// </param> /// <seealso cref="Owasp.Esapi.Interfaces.IUser.ChangePassword(string, string, string)"> /// </seealso> public void ChangePassword(string oldPassword, string newPassword1, string newPassword2) { if (!hashedPassword.Equals(Esapi.Authenticator().HashPassword(oldPassword, AccountName))) { throw new AuthenticationCredentialsException("Password change failed", "Authentication failed for password chanage on user: "******"Password change failed", "Passwords do not match for password change on user: "******"Password change failed", "Password change matches a recent password for user: "******"Password changed for user: " + AccountName); }
// FIXME: ENHANCE consider allowing both per-user and per-application quotas // e.g. number of failed logins per hour is a per-application quota /// <summary> This implementation uses an exception store in each User object to track /// exceptions. /// </summary> /// <param name="e">The exception to add. /// </param> /// <seealso cref="Owasp.Esapi.Interfaces.IIntrusionDetector.AddException(Exception)"> /// </seealso> public void AddException(Exception e) { if (e is EnterpriseSecurityException) { logger.LogWarning(ILogger_Fields.SECURITY, ((EnterpriseSecurityException)e).LogMessage, e); } else { logger.LogWarning(ILogger_Fields.SECURITY, e.Message, e); } // add the exception to the current user, which may trigger a detector User user = (User)Esapi.Authenticator().GetCurrentUser(); String eventName = e.GetType().FullName; // FIXME: AAA Rethink this - IntrusionExceptions which shouldn't get added to the IntrusionDetector if (e is IntrusionException) { return; } // add the exception to the user's store, handle IntrusionException if thrown try { user.AddSecurityEvent(eventName); } catch (IntrusionException ex) { Threshold quota = Esapi.SecurityConfiguration().GetQuota(eventName); IEnumerator i = quota.Actions.GetEnumerator(); while (i.MoveNext()) { string action = (string)i.Current; string message = "User exceeded quota of " + quota.Count + " per " + quota.Interval + " seconds for event " + eventName + ". Taking actions " + quota.Actions.ToString(); TakeSecurityAction(action, message); } } }
/// <summary> Adds the event to the IntrusionDetector. /// /// </summary> /// <param name="eventName">The event to add. /// </param> /// <seealso cref="Owasp.Esapi.Interfaces.IIntrusionDetector.AddEvent(string)"> /// </seealso> public virtual void AddEvent(string eventName) { logger.LogWarning(ILogger_Fields.SECURITY, "Security event " + eventName + " received"); // add the event to the current user, which may trigger a detector User user = (User)Esapi.Authenticator().GetCurrentUser(); try { user.AddSecurityEvent("event." + eventName); } catch (IntrusionException ex) { Threshold quota = Esapi.SecurityConfiguration().GetQuota("event." + eventName); IEnumerator i = quota.Actions.GetEnumerator(); while (i.MoveNext()) { string action = (string)i.Current; string message = "User exceeded quota of " + quota.Count + " per " + quota.Interval + " seconds for event " + eventName + ". Taking actions " + quota.Actions.ToString(); TakeSecurityAction(action, message); } } }
/// <summary> /// Encrypt values into a cookie. /// </summary> /// <param name="cleartext">The decrypted string (cleartext).</param> /// <returns>The encrypted string (ciphertext).</returns> /*public void EncryptStateInCookie(IDictionary cleartext) * { * StringBuilder sb = new StringBuilder(); * IEnumerator i = new ArrayList(cleartext).GetEnumerator(); * bool first = true; * while (i.MoveNext()) * { * try * { * if (!first) * { * sb.Append("&"); * } else * { * first = false; * } * DictionaryEntry entry = (DictionaryEntry) i.Current; * string name = Esapi.Encoder().EncodeForUrl(entry.Key.ToString()); * string cookieValue = Esapi.Encoder().EncodeForUrl(entry.Value.ToString()); * sb.Append(name + "=" + cookieValue); * * } * catch (EncodingException e) * { * // continue * } * } * // FIXME: AAA - add a check to see if cookie length will exceed 2K limit * string encrypted = Esapi.Encryptor().Encrypt(sb.ToString()); * this.SafeAddCookie("state", encrypted, - 1, null, null); * }*/ // FIXME: No progress indicator. /// <summary> Uses the .NET HttpFileCollection object. to parse the multipart HTTP request /// and extract any files therein. /// </summary> /// <param name="tempDir"> /// The temporary directory where the file is written. /// </param> /// <param name="finalDir"> /// The final directory where the file will be written. /// </param> /// <seealso cref="Owasp.Esapi.Interfaces.IHttpUtilities.GetSafeFileUploads(FileInfo, FileInfo)"> /// </seealso> public IList GetSafeFileUploads(FileInfo tempDir, FileInfo finalDir) { ArrayList newFiles = new ArrayList(); try { if (!tempDir.Exists) { tempDir.Create(); } if (!finalDir.Exists) { finalDir.Create(); } IHttpFileCollection fileCollection = ((Authenticator)Esapi.Authenticator()).CurrentRequest.Files; if (fileCollection.AllKeys.Length == 0) { throw new ValidationUploadException("Upload failed", "Not a multipart request"); } // No progress meter yet foreach (string key in fileCollection.AllKeys) { IHttpPostedFile file = fileCollection[key]; if (file.FileName != null && !file.FileName.Equals("")) { String[] fparts = Regex.Split(file.FileName, "[\\/\\\\]"); String filename = fparts[fparts.Length - 1]; if (!Esapi.Validator().IsValidFileName("upload", filename, false)) { throw new ValidationUploadException("Upload only simple filenames with the following extensions " + Esapi.SecurityConfiguration().AllowedFileExtensions, "Invalid filename for upload"); } logger.LogCritical(ILogger_Fields.SECURITY, "File upload requested: " + filename); FileInfo f = new FileInfo(finalDir.ToString() + "\\" + filename); if (f.Exists) { String[] parts = Regex.Split(filename, "\\./"); String extension = ""; if (parts.Length > 1) { extension = parts[parts.Length - 1]; } String filenm = filename.Substring(0, filename.Length - extension.Length); // Not sure if this is good enough solution for file overwrites f = new FileInfo(finalDir + "\\" + filenm + Guid.NewGuid() + "." + extension); } file.SaveAs(f.FullName); newFiles.Add(f); logger.LogCritical(ILogger_Fields.SECURITY, "File successfully uploaded: " + f); } } logger.LogCritical(ILogger_Fields.SECURITY, "File successfully uploaded: "); //session.Add("progress", System.Convert.ToString(0)); } catch (Exception ex) { if (ex is ValidationUploadException) { throw (ValidationException)ex; } throw new ValidationUploadException("Upload failure", "Problem during upload"); } return(newFiles); }