/// <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> /// Decrypt the query string. /// </summary> /// <param name="encrypted">The encrypted string (ciphertext).</param> /// <returns>The decrypted name-value collection (cleartext).</returns> public IDictionary DecryptQueryString(string encrypted) { // FIXME: AAA needs test cases string cleartext = Esapi.Encryptor().Decrypt(encrypted); return(QueryToMap(cleartext)); }
/// <summary> In this implementation, we have chosen to use a random token that is /// stored in the User object. Note that it is possible to avoid the use of /// server side state by using either the hash of the users's session id or /// an encrypted token that includes a timestamp and the user's IP address. /// user's IP address. A relatively short 8 character string has been chosen /// because this token will appear in all links and forms. /// </summary> /// <returns> The CSRF token. /// </returns> /// <seealso cref="Owasp.Esapi.Interfaces.IUser.ResetCsrfToken()"> /// </seealso> public string ResetCsrfToken() { // user.CsrfToken = Esapi.Encryptor().hash( session.getId(),user.name ); // user.CsrfToken = Esapi.Encryptor().encrypt( address + ":" + Esapi.Encryptor().getTimeStamp(); csrfToken = Esapi.Randomizer().GetRandomString(8, Encoder.CHAR_ALPHANUMERICS); return(CsrfToken); }
/// <summary> Utility method to extract credentials and verify them. /// /// </summary> /// <param name="request"> /// The current request object. /// </param> /// <param name="response"> /// The current response object. /// </param> /// <returns> /// The authenticated user, if the username and password are correct. null, otherwise. /// </returns> private IUser LoginWithUsernameAndPassword(IHttpRequest request, IHttpResponse response) { // FIXME: AAA the login path should also be a configuration - this // should check (if loginrequest && parameters then do // loginWithPassword) string username = request[Esapi.SecurityConfiguration().UsernameParameterName]; string password = request[Esapi.SecurityConfiguration().PasswordParameterName]; // if a logged-in user is requesting to login, log them out first IUser user = GetCurrentUser(); if (user != null && !user.Anonymous) { logger.LogWarning(ILogger_Fields.SECURITY, "User requested relogin. Performing logout then authentication"); user.Logout(); } // now authenticate with username and password if (username == null || password == null) { if (username == null) { username = "******"; } throw new AuthenticationCredentialsException("Authentication failed", "Authentication failed for " + username + " because of null username or password"); } user = GetUser(username); if (user == null) { throw new AuthenticationCredentialsException("Authentication failed", "Authentication failed because user " + username + " doesn't exist"); } user.LoginWithPassword(password); return(user); }
/// <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); }
/// <summary> This method should be called for every HTTP request, to login the current user either from the session of HTTP /// request. This method will set the current user so that GetCurrentUser() will work properly. This method also /// checks that the user's access is still enabled, unlocked, and unexpired before allowing login. For convenience /// this method also returns the current user. /// /// </summary> /// <returns> The current user. /// </returns> /// <seealso cref="Owasp.Esapi.Interfaces.IAuthenticator.Login()"> /// </seealso> public IUser Login() { IHttpRequest request = Context.Request; IHttpResponse response = Context.Response; // save the current request and response in the threadlocal variables if (!Esapi.HttpUtilities().SecureChannel) { throw new AuthenticationCredentialsException("Session exposed", "Authentication attempt made over non-SSL connection. Check web.xml and server configuration"); } User user = (User)null; // if there's a user in the session then set that and quit user = (User)GetUserFromSession(request); if (user != null) { user.SetLastHostAddress(request.UserHostAddress); user.SetFirstRequest(false); } else { // try to verify credentials user = (User)LoginWithUsernameAndPassword(request, response); user.SetFirstRequest(true); } // don't let anonyous user log in if (user.Anonymous) { throw new AuthenticationLoginException("Login failed", "Anonymous user cannot be set to current user"); } // don't let disabled users log in if (!user.Enabled) { DateTime tempAux = DateTime.Now; user.SetLastFailedLoginTime(tempAux); throw new AuthenticationLoginException("Login failed", "Disabled user cannot be set to current user: "******"Login failed", "Locked user cannot be set to current user: "******"Login failed", "Expired user cannot be set to current user: " + user.AccountName); } SetCurrentUser(user); return(user); }
// 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> This method generates a redirect response that can only be used to redirect the browser to safe locations. /// Importantly, redirect requests can be modified by attackers, so do not rely information contained within redirect /// requests, and do not include sensitive infomration in a redirect. /// </summary> /// <param name="context"> /// The context for the redirection. /// </param> /// <param name="location">The URL to redirect to. /// </param> /// <returns>redirect location, if valid</returns> /// <seealso cref="Owasp.Esapi.Interfaces.IHttpUtilities.GetSafeRedirect(string, string)"> /// </seealso> public string GetSafeRedirect(string context, string location) { if (!Esapi.Validator().IsValidRedirectLocation(context, location, false)) { throw new ValidationException("Bad Redirect string", "Bad redirect location: " + location); } return(location); }
/// <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> /// Sets the hashed password. /// </summary> /// <param name="hash"> /// The hash to set the users hashed password value to. /// </param> internal void SetHashedPassword(string hash) { oldPasswordHashes.Add(hashedPassword); if (oldPasswordHashes.Count > Esapi.SecurityConfiguration().MaxOldPasswordHashes) { oldPasswordHashes.RemoveAt(0); } hashedPassword = hash; logger.LogCritical(ILogger_Fields.SECURITY, "New hashed password stored for " + AccountName); }
/// <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> /// Decrypt the string from a hidden field. /// </summary> /// <param name="encrypted">The encrypted string (ciphertext).</param> /// <returns>The decrypted string (cleartext).</returns> public string DecryptHiddenField(string encrypted) { try { return(Esapi.Encryptor().Decrypt(encrypted)); } catch (EncryptionException e) { throw new IntrusionException("Invalid request", "Tampering detected. Hidden field data did not decrypt properly.", e); } }
/// <summary> Validates the strength of the account name. /// This implementation simply verifies that account names are at least 5 characters long. This helps to defeat a /// brute force attack, however the real strength comes from the name length and complexity. /// /// </summary> /// <param name="newAccountName">The account name to validate the strength of. /// /// </param> /// <returns> true, if the account name has sufficient strength. /// /// </returns> /// <seealso cref="Owasp.Esapi.Interfaces.IAuthenticator.VerifyAccountNameStrength(string, string)"> /// </seealso> public void VerifyAccountNameStrength(string newAccountName) { if (newAccountName == null) { throw new AuthenticationCredentialsException("Invalid account name", "Attempt to create account with a null account name"); } // FIXME: ENHANCE make the lengths configurable? if (!Esapi.Validator().IsValidInput("VerifyAccountNameStrength", "AccountName", newAccountName, MAX_ACCOUNT_NAME_LENGTH, false)) { throw new AuthenticationCredentialsException("Invalid account name", "New account name is not valid: " + newAccountName); } }
/// <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); } } }
/// <summary> /// Adds a role to an account. /// </summary> /// <param name="role">The role to add. /// </param> /// <seealso cref="Owasp.Esapi.Interfaces.IUser.AddRole(string)"> /// </seealso> public void AddRole(string role) { string roleName = role.ToLower(); if (Esapi.Validator().IsValidInput("addRole", "RoleName", roleName, MAX_ROLE_LENGTH, false)) { roles.Add(roleName); logger.LogCritical(ILogger_Fields.SECURITY, "Role " + roleName + " added to " + AccountName); } else { throw new AuthenticationAccountsException("Add role failed", "Attempt to add invalid role " + roleName + " to " + AccountName); } }
/// <summary> Gets the property value from the encrypted store, decrypts it, and returns the /// plaintext value to the caller. /// </summary> /// <param name="key">The key for the property key/value pair. /// </param> /// <returns> The property (decrypted). /// </returns> /// <seealso cref="Owasp.Esapi.Interfaces.IEncryptedProperties.GetProperty(string)"> /// </seealso> public string GetProperty(string key) { lock (this) { try { return(Esapi.Encryptor().Decrypt(properties.Get(key))); } catch (Exception e) { throw new EncryptionException("Property retrieval failure", "Couldn't decrypt property", e); } } }
/// <summary> Load the rules from a file. /// /// </summary> /// <param name="f">The file to load the rules from. /// /// </param> /// <returns> The dictionary containing the rules. /// /// </returns> private IDictionary LoadRules(FileInfo f) { IDictionary map = new Hashtable(); FileStream fis = null; try { fis = new FileStream(f.FullName, FileMode.Open, FileAccess.Read); string line = ""; while ((line = Esapi.Validator().SafeReadLine(fis, 500)) != null) { if (line.Length > 0 && line[0] != '#') { Rule rule = new Rule(); string[] parts = line.Split(new string[] { "|" }, StringSplitOptions.None); // fix Windows paths rule.path = parts[0].Trim().Replace("\\", "/"); rule.roles.Add(parts[1].Trim().ToLower()); string action = parts[2].Trim(); rule.allow = action.ToUpper().Equals("allow".ToUpper()); if (map.Contains(rule.path)) { logger.LogWarning(ILogger_Fields.SECURITY, "Problem in access control file. Duplicate rule ignored: " + rule); } map[rule.path] = rule; } } return(map); } catch (Exception e) { logger.LogWarning(ILogger_Fields.SECURITY, "Problem in access control file", e); } finally { try { if (fis != null) { fis.Close(); } } catch (IOException e) { logger.LogWarning(Owasp.Esapi.Interfaces.ILogger_Fields.SECURITY, "Failure closing access control file: " + f, e); } } return(map); }
/* * 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(); } }
/// <summary> Hide the constructor for the Singleton pattern.</summary> public Randomizer() { string algorithm = Esapi.SecurityConfiguration().RandomAlgorithm; try { //Todo: Right now algorithm is ignored randomNumberGenerator = RNGCryptoServiceProvider.Create(); } catch (Exception e) { // Can't throw an exception from the constructor, but this will get // it logged and tracked new EncryptionException("Error creating randomizer", "Can't find random algorithm " + algorithm, e); } }
// 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> /// Adds a security event to the user. /// </summary> /// <param name="eventName"> /// The security event to add. /// </param> public void AddSecurityEvent(string eventName) { Event securityEvent = (Event)events[eventName]; if (securityEvent == null) { securityEvent = new Event(eventName); events[eventName] = securityEvent; } Threshold q = Esapi.SecurityConfiguration().GetQuota(eventName); if (q.Count > 0) { securityEvent.Increment(q.Count, q.Interval); } }
/// <summary> /// Adds a header to an HttpResponse after checking for special /// characters (such as CRLF injection) that could enable attacks like /// response splitting and other header-based attacks that nobody has thought /// of yet. /// </summary> /// <param name="name">The name of the header. /// </param> /// <param name="val">The value of the cookie. /// </param> /// <returns>header string</returns> /// <seealso cref="Owasp.Esapi.Interfaces.IHttpUtilities.SafeAddHeader(string, string)"> /// </seealso> public string GetSafeHeader(string name, string val) { // FIXME: AAA consider using the regex for header names and header values here Regex headerName = ((SecurityConfiguration)Esapi.SecurityConfiguration()).GetValidationPattern("HTTPHeaderName"); if (!headerName.IsMatch(name)) { throw new ValidationException("Invalid header", "Attempt to set a header name that violates the global rule in Esapi.properties: " + name); } Regex headerValue = ((SecurityConfiguration)Esapi.SecurityConfiguration()).GetValidationPattern("HTTPHeaderValue"); if (!headerValue.IsMatch(val)) { throw new ValidationException("Invalid header", "Attempt to set a header value that violates the global rule in Esapi.properties: " + headerValue); } return(name + ":" + val); }
/// <summary> Verifies a digital signature (created with the sign method) and returns /// the boolean result. /// </summary> /// <param name="signature">The signature to verify. /// </param> /// <param name="data">The data to verify the signature against. /// </param> /// <returns> true, if successful /// </returns> /// <seealso cref="Owasp.Esapi.Interfaces.IEncryptor.VerifySignature(string, string)"> /// </seealso> public bool VerifySignature(string signature, string data) { try { DSACryptoServiceProvider dsaCsp = new DSACryptoServiceProvider(asymmetricKeyPair); Encoding textConverter = Encoding.GetEncoding(encoding); byte[] signatureBytes = Esapi.Encoder().DecodeFromBase64(signature); byte[] dataBytes = textConverter.GetBytes(data); return(dsaCsp.VerifyData(dataBytes, signatureBytes)); } catch (System.Exception e) { new EncryptionException("Invalid signature", "Problem verifying signature: " + e.Message, e); return(false); } }
/// <summary> Create a digital signature for the provided data and return it in a /// string. /// </summary> /// <param name="data">The data to sign. /// </param> /// <returns> The signature. /// </returns> /// <seealso cref="Owasp.Esapi.Interfaces.IEncryptor.Sign(string)"> /// </seealso> public string Sign(string data) { try { asymmetricKeyPair.Flags = CspProviderFlags.UseMachineKeyStore; DSACryptoServiceProvider dsaCsp = new DSACryptoServiceProvider(asymmetricKeyPair); Encoding textConverter = Encoding.GetEncoding(encoding); byte[] dataBytes = textConverter.GetBytes(data); byte[] signatureBytes = dsaCsp.SignData(dataBytes); bool valid = dsaCsp.VerifyData(dataBytes, signatureBytes); return(Esapi.Encoder().EncodeForBase64(signatureBytes, true)); } catch (Exception e) { throw new EncryptionException("Signature failure" + e.StackTrace, "Can't find signature algorithm " + signatureAlgorithm, e); } }
/// <summary> Encrypts the plaintext property value and stores the ciphertext value in the encrypted store. /// </summary> /// <param name="key">The key for the property key/value pair. /// </param> /// <param name="value">The value to set the property to. /// </param> /// <returns> The value the property was set to. /// </returns> /// <seealso cref="Owasp.Esapi.Interfaces.IEncryptedProperties.SetProperty(string, string)"> /// </seealso> public virtual string SetProperty(string key, string value) { lock (this) { try { object tempObject; tempObject = properties[key]; properties[key] = Esapi.Encryptor().Encrypt(value); return((string)tempObject); } catch (Exception e) { throw new EncryptionException("Property setting failure", "Couldn't encrypt property", e); } } }