In our experience, based on performing hundreds of application and code reviews, user and session management—along with application and code reviews, which we previously addressed (Software Magazine, Spring Edition 2006)—present the bulk of the security issues faced by the developers of real-world applications.
Lack of attention to these issues during the design and earlier phases can result in the practitioners making assumptions: as a result, inconsistencies and perhaps even vulnerabilities can be introduced into the application. The absence of a detailed application-threat model often results in implementations that utilize weak mechanisms for authentication and the other security functions. Such mechanisms in turn are easily broken, compromising the entire application.
Definitions1
Before we delve into the technical intricacies of some of these topics, it would help to ensure that we are all on the same page with regards to definitions for the terminology that will be used repeatedly throughout this article. This is especially necessary, since a number of these terms are often overloaded, used interchangeably, or misused. Some of them even have different meanings in the physical world.
AUTHENTICATION—This is the process by which a computer, computer program, or user attempts to confirm that the computer, computer program, or user from whom the second party has received some communication is, or is not, the claimed first party.
IDENTIFICATION—Identification is how a user tells a system (or claims) who he or she is (for example, by using a username). The identification component of an access control system is normally a relatively simple mechanism based on either username or user ID. Authentication, on the other hand, is the process of proving those claims with strong evidence.
EVIDENCE—Evidence is the secret used by an entity in the system—a user or a process—to authenticate itself to the system. For instance, a password is a form of evidence. Under normal circumstances it is assumed that each entity has its own evidence that must be presented in some form to satisfy the authentication burden.
PRINCIPAL—This represents the entity in the system. All authentication and authorization decisions are tied to the principal. A principal can therefore be an authenticated or an anonymous entity.
AUTHORIZATION—Authorization (or establishment) defines a user's rights and permissions on a system. After a user (or process) is authenticated, authorization determines what that user can do on the system. While authorization is typically assumed to follow authentication, it is important to bear in mind that in a number of applications—especially web applications on the Internet—the anonymous or unauthenticated user also has certain rights and privileges associated with him or her: for instance, browsing the catalog and adding to the shopping cart.
Given these basic definitions, the rest of this article is divided into two major sections covering user management and session management. For each, we enumerate best practices as well as common mistakes and mis-configurations. We also focus on how to identify some of these anti-patterns while reviewing an application.
User Management
Like authentication and authorization above, user management is a key cornerstone in the security of a system and perhaps even its organization. User management is also an important consideration, since it encompasses significant privacy issues. What makes it even more challenging is the scope of considerations that come into play when building a strong and secure user management scheme—from password resets and account lockouts to password storage and anti-phishing mechanisms. Thus, in many ways, user management can be thought of as a hybrid category encompassing data protection, authentication and authorization, and perhaps even session management. In the next few sections of this article, we will tackle a number of key user management issues.
In the last few years, user management security has really come to the fore because of widespread phishing attacks. The graphic below2 illustrates the magnitude of the problem. Gartner3 estimates that between May 2004 and May 2005, approximately 2.4 million users in the United States were victims of phishing attacks, resulting in losses of approximately $929 million to users and about $2 billion to the companies whose customers became victims.
While phishing really is a social engineering and authentication problem, a number of the solutions proposed to combat it fall into the realm of user management. Consider, for instance, SiteKey4 from Bank of America. This is an additional step added to the usual forms-based authentication system on Bank of America’s online services. Users are expected to select an image the first time they set up SiteKey, and in future visits input the password only if the site displays the correct image. Thus, a user can know if he or she is visiting a legitimate site or a malicious phishing site.
Another massive area of concern with regard to user management is privacy. A number of applications default to using private data, such as users’ Social Security numbers, as their login credentials. This is generally regarded as a bad practice, since it increases the distribution of such private information and also increases the attack surface. For instance, a user’s Social Security number might show up in the log files, since all logons and logoffs are being audited. Similarly, with regard to password hints, storing some categories of information may not be permitted under local laws. Consider the quintessential “mother’s maiden name” question. In a number of countries, storing information about a non-customer — the customer’s mother, in this case — is not permitted, and hence such a question would be in violation of the law. Indeed, in this country, systems that store passwords in the clear — where they are accessible to database administrators, developers and other employees of the service provider — are actually violating a number of government and industry compliance regulations, such as HIPAA and PCI.
In general, where passwords are concerned, complexity must be required and encouraged. Consider, for instance, a system that uses a four-character password and only allows upper-case A–Z as valid characters. This implies there exist a total of 264 or 456,976 valid and unique passwords. Now consider if we used six-character passwords: the number of unique passwords would jump significantly, to approximately 309 million. Now, if we allowed both upper- and lower-case letters, the 10 numerics, and a variety of special characters, for a total of approximately 95 printable characters on a typical keyboard, we would end up with about 81.5 million unique password combinations if we used four-character passwords, and 6.6 x 1015 combinations if we used eight-character passwords. These numbers, while not important in and of themselves, illustrate how a small increase in complexity can result in enormous benefits from a security perspective, since it would make an exhaustive search of the password space (i.e., attempting every valid password combination) a computationally infeasible task for most practical purposes.
However, as with most things in security, developers should be aware of the usability tradeoffs in using large and complex passwords. While we definitely want a secure system, we don’t want one where users are forced to call the helpdesk for password resets every time they need to access the application. Typically, password complexity is balanced by enforcing password expiry (i.e., forcing a user to change the password in some finite amount of time). The thinking behind this is that the password only needs to be secure enough to withstand an attack until it expires and the user has changed it. Typical time periods vary between 45 to 90 days in most systems. When expiry is enforced, the system must also ensure password history is enforced, to prevent users from repeatedly reusing the same password or variations of it.
Lockouts, Error Messages, and Passwords
To deal specifically with the threat of repeated password attempts, most systems also implement an account lockout mechanism. This is an area in which one needs to be careful. For instance, consider a system that permanently locks out a user account after three failed attempts and then requires manual intervention to have the account unlocked. An attacker who is not looking to gain illegitimate access but only to cause your organization financial or reputation loss may simply proceed to lock every account, thus causing tremendous manual burden as well as a denial-of-service condition. In general, preparing for this possibility requires first that administrative accounts should never be locked out. Second, consider a phased lock-out approach, in which the account will be only temporarily locked out (for, say, 15 minutes) after the first set of failed attempts, and in which this duration is increased for each additional set of bad attempts. This approach not only deters the attacker by making bad attempts a time-consuming task; it also prevents the attacker from misusing the lockout mechanism to cause a denial of service.
Another important security consideration when it comes to password-based systems and attacks against such systems is the error message displayed to the user. In general, error messages must avoid providing the user with any information that can then be leveraged in an attack against the system. For instance, consider a login form that responds with the message: “Invalid password for username bob”. The fundamental problem with this error message is that it lets the attacker know that he or she is using a valid username and now only needs to guess the password. A good error message would say something along the lines of: “Invalid username or password”. It should be noted that this problem shows up not only on login forms but also on password reset forms and new-user signups. Error messages and the security impact they have will be covered in detail in a later article.
While account lockout mechanisms can help against online password attacks, developers still have to attempt to protect against offline guessing threats. For instance, consider what happens if an attacker obtains access to the application’s user data store. An important consideration in this case is whether the passwords in this data store are maintained in the clear. In general, the recommended approach is to use salted hashes for password storage. As explained in an earlier article, hash functions by definition are one-way, and hence the use of a strong hash function like SHA-1 prevents someone from obtaining the password by simply knowing the hash. However, even with this protection passwords can be obtained by launching a dictionary or a brute-force attack. Such an attack computes the hash for a candidate password and then compares that hash against what is in the user data store. If the two match, the attacker knows that the candidate password is the true password for that specific user account. Moreover, in such a case, the attacker can pre-compute hashes into what are often called rainbow tables.5 Thus, what essentially was meant to be a time-complexity problem for the attacker has been converted into a space-complexity problem, and given how cheap hard-disk space is today, that clearly is not much of a deterrent. (Such rainbow tables are easily available at many locations on the Internet.) To mitigate some of the risk associated with this, as well as to ensure that two users with the same password don’t end up with the same password hash, a source of randomness called a salt is often introduced into the hashing algorithm. This salt, which is unique for each user and created when the password is changed or created, must be stored with the user account information so it can be used for password verification. In this case, the password hash is computed by concatenating the true password with the salt value.
Password resets can be another potential minefield for an application. The most commonly used scheme lets the user provide a secret answer to one or more questions. All too often, however, those questions have easily guessable answers. For instance, if an attacker knows the user is from Boston and the question is “What is your favorite sports team?” it is fairly likely that the “secret” answer is going to be the Boston Red Sox, the Celtics, or perhaps the New England Patriots. With this in mind, applications should use hard-to-guess questions. A number of banking applications we have reviewed use bank transaction history as the secret answer. For instance, you might be asked about the last ATM transaction you performed. Mechanisms that simply take the username and display the hint should always be avoided.
Irrespective of what mechanism for password resets is used, the password should never be displayed on the screen. Instead, a one-time link to reset the password, or a one-time password, should be sent to the user, preferably over a secure or out-of-band method such as a phone call. The account must also be flagged to require a change of password the next time the user logs in. Because passwords (if stored as described above) cannot be recovered even by the system, the system will need to generate a new and random password. A cryptographically secure source of randomness such as /dev/urandom on UNIX systems maybe used for this purpose. On Windows machines, the RNGCryptoServiceProvider class or the CryptGenRandom CryptoAPI may be similarly used.
Finally, as with most security actions, all activity must be logged and then audited. However, developers must be careful not to log, for instance, clear-text passwords or Social Security numbers. The application should also let the user know via email whenever any user management activity is performed on his or her account. For instance, if the user’s account is locked out due to failed password attempts, then an email must be sent to the user. Thus, if the user did not perform these failed attempts, he or she can always bring it to the notice of the corporate security department for further investigation.
In the .NET 2.0 release, Microsoft has provided a set of features known as Membership6 as well as security controls for login, password resets and other user management functions. These allow ASP.NET developers to provide out-of-the-box user management based on a provider architecture. This provider architecture by default supports both Microsoft SQL Server and Microsoft Active Directory. This feature is tremendously configurable, allowing for the setup of password complexity rules, definition of password reset mechanisms, and password storage formats purely through declarative programming in the web.config file and without writing much code. Moreover, it is also extensible to support custom providers and, when combined with the health-monitoring feature that is also new to .NET 2.0, provides for a number of the best practices described in this article.
Session Management
We have now covered authentication, authorization and user management. The final member of this quartet of security mechanisms is session management. Application developers, especially those creating web applications, must pay special attention to this important mechanism. This includes aspects such as the security of the security tokens and session inactivity timeouts. Session hijacking through session identifier or cookie stealing is a common flaw that plagues a number of web applications. Essentially, an attacker simply obtains the session identifier value and then crafts a request that contains that ID. When that request reaches the server, the application will simply look up its session data store and determine that the user was logged in and will therefore provide access to all the resources that are available to that user. As a result, an attacker could potentially get unauthorized access to another user’s data.
Designers and developers should leverage the underlying frameworks (whether J2EE or ASP/ASP.NET) of session management capabilities as far as possible. These capabilities typically manifest themselves via a session ID. Infrastructure-supplied session IDs have the advantage of being sufficiently random (for instance, the ASP.NET session ID has 120 bits of entropy) so that an attacker cannot attempt a guessing or brute-force attack. The application must never rely on just the username submitted with a request when determining the session to which the request belongs. Session IDs issued by the application frameworks also typically have a relatively short and configurable lifetime, and thus can prevent the “permanently logged in” effect.
If an application cannot avoid creating its own session management mechanism — for instance, if the underlying application server does not support sessions — then the following guidelines can be helpful. Cookies should be the preferred mechanism for storing the session identifier. Cookieless sessions, while technically possible, are far more difficult to secure, since some of the security flags described below are not available. Hence, all of the onus for security would then fall onto the application developer.
When using cookies, developers must clearly define the cookie path, expiration date, and time, as well as ensure that the cookie is not persistent. After a maximum of 30 minutes of inactivity, the user must be forced to log in again so as to decrease the attack surface. Further, cookies must have the secure flag turned on This ensures that the browser will never transmit this cookie in a clear-text request. Another common mistake that developers often make is failing to issue a fresh session ID when the application switches from an insecure mode (used typically before a user logs in to the application) to a secure mode (after login). Thus, an attacker who has obtained a cookie by sniffing traffic can wait for the legitimate user to log in and then use that cookie to impersonate him or her.
Session IDs, when generated, must have sufficient entropy to prevent an attacker from guessing a valid session ID value and thus hijacking that specific session. It is typically recommended that all application-defined session identifiers or cookies must have at least 128 bits of entropy. This typically equates to 16 or more ASCII characters. Further, such session identifiers must not contain sensitive information such as passwords, or easily guessable parts, such as the username or sequential user ID numbers from a database. While not required, a number of common implementations, such as the one available in ASP.NET by default, also sign and encrypt the session identifier, thus protecting its content.
When the user is logged out, the session must be invalidated on the server side as well as on the client side. This is typically done by deleting the session entry from the server session data store, as well as by clearing the cookie in the client application or browser and in the history.
Finally, a word about cross-site scripting (XSS), which represents one of the most common vectors for stealing session identifiers today and will be the topic of a future article on data validation. Importantly, there is an in-depth session management strategy that can help mitigate some of that particular risk. The HttpOnly cookie attribute prevents access to the cookie through client-side JavaScript. While this is currently supported only in Microsoft Internet Explorer 6.0 and above, it can help prevent a malicious script from obtaining the session identifier from the document.cookie HTML DOM object.
Conclusion
In most applications, the four mechanisms of user management, session management, authentication, and authorization are largely responsible for security quality. Consequently, they also bear the brunt of most attacks. However, we hope that just as the others in this series of articles have done, this article will convince readers that building security into their applications is not difficult. All it really takes is adherence to best practices and a focus on security from the earliest stages in an application’s development lifecycle. We must emphasize again, however, that with these four mechanisms, security cannot be slapped on at the end. They represent the core of the application’s architecture and therefore must be part of the design from day one.
Summary
Strong authentication helps protect a system at all of its entry points. Authorization then helps to protect the system once the user is inside and has been authenticatedSession management allows users to have a clean and productive user experience while still keeping security paramount. (Indeed, in some ways, session management can be thought of as repeated authentication.) User management is what brings all of this together; its administrative functions are administrative as well as aimed at the end user. The bond that ties all of these together is the principle, the entity within the system. Although these four mechanisms come across as remarkably simple, they are also perhaps the single biggest source of security vulnerabilities in most applications. Fortunately, infrastructure support for them is increasing dramatically.
Rudolph Araujo is a Principal Software Security Consultant at Foundstone. Responsible for creating and delivering the threat modeling and security code review service lines, he also supervises content creation and training delivery for Foundstone’s Building Secure Software, Writing Secure Code — ASP.NET, and Writing Secure Code — C/C++ classes. Araujo has strong computer science fundamentals and more than 10 years of software development experience in C/C++ and C# on both UNIX and Windows environments. He holds a master’s degree from Carnegie Mellon University with a focus on information security, and is also a Microsoft Visual Developer–Security MVP.
Mark Curphey is the founder of the Open Web Application Security Project (OWASP), and is the Vice President of Consulting at Foundstone, now a McAfee company. OWASP, created to help organizations understand and improve the security of their Web applications, publishes a list of Top 10 Web application security vulnerabilities each January. Curphey is the former director of software security for Charles Schwab and has a Masters Degree in cryptography. He is also a Microsoft Visual Developer - Security MVP.
1 All definitions are based on those available on www.wikipedia.org
2 en.wikipedia.org/wiki/Phishing
3 www.washingtonpost.com/wp-dyn/content/article/2005/10/21/AR2005102102113.html
4 www.bankofamerica.com/newsroom/press/press.cfm?PressID=press.20050526.03.htm
5 en.wikipedia.org/wiki/Rainbow_table
6 msdn2.microsoft.com/library/yh26yfzy.aspx