Understanding Key Terms
- Identification: Providing an identifier (e.g., username).
- Authentication: Verifying the user’s identity.
- Session Management: Maintaining user sessions using cookies or tokens.
- Authorization: Checking if the user has the right to perform actions.
Authentication methods:
- Cookies
- HTTP basic auth
- OpenID Connect – identity layer built on top of OAuth 2.0. It allows applications to verify a user’s identity based on authentication performed by an identity provider (IdP), such as Google, Microsoft, or Okta.
- Kerberos/NTLM – Kerberos is a secure, ticket-based authentication protocol that supports mutual authentication and is widely used in Windows Active Directory. NTLM is an older, challenge-response authentication protocol used in Windows environments but is less secure and vulnerable to attacks.
- Api keys
- Certificate Authorization – is a security mechanism that uses digital certificates issued by a Certificate Authority (CA) to verify a user’s or system’s identity. It enables secure communication by ensuring that only trusted entities can access protected resources. Used mostly at enterprise sector
The worse at REST API it is when:
- HTTPS is absent
- Authentication is absent at all
It is often happens that application gives for hackers easy way to bypass Authentication by:
- Using default, publicly available authentication data,
- Storing test credentials in the application code,
- Exposing authentication data in a public code repository,
- Using weak (dictionary-based) passwords,
- Gaining access to credentials stored in files on a victim’s computer,
- Using software or devices that have built-in “service accounts,” essentially a backdoor, and the information about it is publicly available.
- Disclosure of credentials in error messages,
- Disclosure of credentials through unauthorized access to the code repository,
- Disclosure of credentials through reverse engineering of the application.
Of course, we must not forget that passwords used for authentication in email accounts or other systems may have already leaked and are available in publicly accessible databases. Therefore, it is worth proactively checking services like Have I Been Pwned to see if any of the passwords we use or have used in the past have been compromised
A question arises: how should we supply our application with the data it needs to connect to a database or an integrated system? A recommended approach is to store authentication data in the target environment and, in the code, only refer to the appropriate resources and read the required information from them. This way, we maintain the ability to automate the process of deploying new versions of the application while also overcoming the issue of securely storing credentials without hardcoding them.
For cloud-based environments, solutions such as Vault are recommended, allowing secure storage of credentials across different environments. However, using such solutions means adding another component to our infrastructure that we must manage, regularly update, and monitor for security.
Server-Side Processing
Before we move on to the main topic, let’s discuss the division between the server-side of a web application (backend) and the client-side (frontend) processed in the user’s browser. It is crucial to identify the boundary between a trusted environment (or better termed, a secure environment) and an untrusted environment.
A trusted environment refers to the backend of the application, meaning everything that happens on the server side, which we fully control. However, as soon as the server generates an HTTP response containing HTML, CSS, or JavaScript code, and the data is transmitted over the network to the client, we lose control over what happens next.
Given these facts, the browser environment of a web application must be considered untrusted. Sending alerts or executing scripts in the browser does not guarantee that the instructions we send will be executed as intended. This is particularly important because the data may be intercepted or altered through attacks such as man-in-the-middle or man-in-the-browser.
Additionally, the user has full control over their browser and can modify the application’s behavior before sending data to the server. It is crucial to recognize that all user authentication and authorization should occur exclusively on the server side. Sensitive data validation must never rely solely on the client side, as it can be easily manipulated.
User Enumeration
The authentication mechanism can be used not only for the primary purpose of authentication but also as a source of information for an attacker. From an attacker’s perspective, a valuable piece of information is determining whether a user with a given email address or login exists in the application.
If a user enters an incorrect login-password combination, the application should display a neutral message that does not reveal specific details about what went wrong in the authentication process. A good approach is displaying a message like:
“You have entered an incorrect login or password.”
This message is sufficient for the user, and from a security standpoint, it does not disclose whether the attacker provided an incorrect/non-existent user identifier or whether they entered a valid login but an incorrect password.
It is also important not to use different messages in cases where the application supports multiple account states, such as inactive or deleted accounts.
Additionally, in some cases, an attacker may determine whether a specific user has an account in the system simply by analyzing response times to HTTP requests. If a user exists in the system, the application may need to perform significantly more operations compared to a situation where an attacker queries the system for a non-existent username. This difference in response time can reveal the existence of an account.
Attack Automation – Brute-Force Attacks
Let’s assume that we do not expose authentication data in the application code, do not use default credentials, and the entire authentication mechanism is located on the server side. Does this mean we have addressed all security issues and can consider our system safe? If these conditions are met, we can say that we are at the early stage of properly implementing authentication or verification mechanisms.
The process of verifying authentication data entered by a user during login involves checking with the server to determine whether the user has provided credentials that match those stored in the system. There is nothing preventing this process from being fully automated.
Automation, in this context, refers to sending successive HTTP requests to the server, using the same username with multiple potential passwords. This raises the question: is carrying out such an attack a complex task? The answer is no. There are many tools available that facilitate this process.
One of the most popular tools for brute-force attacks is Hydra, which is included in the Kali Linux distribution.
The question arises: how can we protect against such attacks? Sometimes, it’s not necessary to eliminate the problem entirely; it may be sufficient to make the attack impractical, for example, by increasing the required time for execution.
One of the most commonly recommended solutions for defending against brute-force attacks is implementing a CAPTCHA mechanism. After several failed login attempts (e.g., three), the user is prompted to enter a displayed CAPTCHA code. However, it’s worth noting that implementing such a mechanism is only a way to slow down and hinder the guessing of authentication credentials.
Depending on the type of CAPTCHA used, it is still possible to automate this process, and from an attacker’s perspective, the only change is that they must implement additional mechanisms to read the CAPTCHA code. In the simplest cases, OCR (Optical Character Recognition) scanners or dedicated services with API access can be used to extract the text from an image.
Of course, CAPTCHA mechanisms have evolved significantly over time. Today, they do not rely solely on generating text that is difficult to read. Modern mechanisms incorporate artificial intelligence elements that evaluate multiple behavioral factors (e.g., Google reCAPTCHA).
Implementing CAPTCHA Securely
When implementing a CAPTCHA mechanism, it’s important to remember that it introduces a new set of functions that increase the attack surface of the system. The code responsible for handling the CAPTCHA should be reviewed to ensure that it does not introduce new security vulnerabilities.
From security audits, it has been observed that some applications only display a CAPTCHA but do not validate it on the server side. In such cases, removing the CAPTCHA field from an HTTP request can sometimes bypass the CAPTCHA requirement entirely.
Thus, when verifying a CAPTCHA mechanism, it is important to determine whether the application relies solely on the user’s input or if the CAPTCHA code is validated on the server. A common vulnerability occurs when an application sends a CAPTCHA identifier in a request, and the server simply checks if the received value matches the one stored on the server. If an attacker resends the same CAPTCHA identifier repeatedly, they can bypass the security mechanism.
To address this, some applications implement an alternative approach by tracking failed login attempts on the server side. In this method, after exceeding a predefined number of failed attempts, the application blocks the account and informs the user about the failed login attempts.
However, as always, the devil is in the details. Problems with implementing this server-side login attempt tracking can arise if the criteria for “failed login attempts” are not clearly defined. A critical question is: What does “storing information about failed login attempts on the server” actually mean?
Providing recommendations for improving application security is a responsible task. To do it properly, we must analyze all the pros and cons of a given solution. We already know that a brute-force attack on a login form can be slowed down by implementing a CAPTCHA mechanism.
We’ve also learned that if we count failed login attempts, this information should be stored in a database, not in a session. The next question arises: Is implementing a function that blocks accounts after a certain number of failed attempts a good solution?
It turns out that this approach can lead to another vulnerability—Denial of Service (DoS).
This may come as a surprise because DoS attacks are typically associated with sending a massive number of requests or network traffic to an application, making it unavailable due to bandwidth exhaustion or system resource depletion. However, an attacker can also use an account lockout mechanism to trigger a denial of service.
For example, if an attacker identifies a list of usernames, they could attempt a small number of failed logins for each user. The security mechanism, designed to protect against brute-force attacks, would end up locking all user accounts, preventing any legitimate users from logging in.
At first, it may seem that an administrator could manually unlock the affected accounts, but in large organizations, this could still result in prolonged system downtime for many users.
How Should a Properly Implemented Mechanism Protect Against Brute-Force Attacks?
At the time of writing this chapter, the best approach seems to be implementing multiple layers of security.
- First layer: If possible, implement a CAPTCHA mechanism after a few failed login attempts. This should be seen as the first line of defense, preventing attacks from less-determined attackers.
- Second layer: Implement soft lock protection using the device cookie mechanism.
- As a rule, we should avoid mechanisms that completely block user accounts after a certain number of failed attempts. Such full account lockouts require manual intervention by an administrator, potentially locking out legitimate users for extended periods.
- A better approach is to use a soft lock, where access to the account is partially restricted, and the user is notified of suspicious activity.
- For example, instead of blocking the account, the system can send an email with details of the suspicious login attempts and a link containing a token that allows the user to authorize unlocking their account.
- This method is similar to password reset mechanisms and avoids the risk of mass account lockouts caused by attackers targeting multiple users.
Device Cookie Protection
Another interesting protection mechanism against brute-force attacks is the device cookie approach.
- This solution assumes that when a user successfully logs in, the system should generate a special HTTP cookie (e.g., a JWT token) and associate it with the specific browser/device the user is using.
- This device cookie would then be automatically sent with every subsequent authentication request.
- The server can verify whether the authentication request includes the device cookie. This allows distinguishing between legitimate users (trusted devices) and suspicious attempts from unknown devices.
- By differentiating between trusted and untrusted devices, the system can take more aggressive security actions against suspicious logins while ensuring a smoother experience for genuine users.
- The detailed algorithm for implementing the device cookie mechanism is described in the OWASP guidelines.
Password Reset
We have now reached the point where our authentication mechanism is already resistant to most threats. But what about supporting functions related to authentication, such as password recovery or reset?
One bad practice is when an application, upon a user’s request, sends their current password or a newly generated password to their email address. This is a poor approach from a security perspective and should not even be considered. Applications should not have a password reminder function, only a reset function.
Let’s take a moment to understand what password recovery actually means. In most cases, it means that the password is stored on the server in plaintext. If the database is compromised, attackers will gain access to users’ passwords.
A slightly less catastrophic scenario assumes that passwords are stored in an encrypted format (e.g., AES encryption). However, in such cases, a single encryption key is usually used for all passwords, which still presents a security risk.
Another bad practice is automatically generating a new password and sending it via email. This approach has several flaws:
- The new password is stored in the user’s email inbox for an extended period, which makes it susceptible to theft.
- Many users do not change the automatically generated password and continue using it.
- These passwords are often random strings without special characters, as developers avoid adding symbols to make them easier to copy.
Secure Password Reset Mechanism
Now, let’s define a proper password reset process:
- A user forgets their password and requests a reset by entering their email address in a password reset form and clicking “Submit”.
- The application should check whether the entered email exists in the database.
- If the email exists, the application should display a generic message, such as:
“If your email is registered, you will receive further instructions.” - Regardless of whether the email exists or not, the same message should be displayed to the user.
- If the email exists, the system sends a password reset link to that email address.
Why is this important?
If the system returns different messages depending on whether the email exists, an attacker can use this behavior to enumerate registered users. By trying different email addresses, they can confirm which accounts exist in the system.
Password Reset Implementation
We now understand how to securely handle the email-sending mechanism during a password reset process. The next question is: what should such an email contain?
- The primary component should be a password reset link.
- This link should contain a unique token, similar to those used in session authentication.
- The email should also inform the user about the reason for receiving the message and provide a warning:
- If the user did not request a password reset, they should ignore the email and notify system administrators.
- This practice helps users develop awareness about phishing attacks.
Validating the Password Reset Request
When the user clicks the password reset link, the application will receive an HTTP request containing the previously generated reset token.
At this stage, the system should verify two key factors:
✔ Whether the token exists in the database.
✔ The token’s issue date – the request should be rejected if the token was issued beyond a specific time limit (e.g., a few hours ago).
If these conditions are met:
✔ The application should prompt the user to enter a new password.
✔ The system should verify password strength before allowing submission.
✔ The used token must be invalidated after reset completion to prevent reuse.
Avoiding Token Leaks in the URL
Developers must ensure that the reset token is not exposed beyond the application domain.
Why? If the token is leaked, an attacker could hijack the reset process and gain access to the user’s account.
Common Ways Reset Tokens Can Leak
🚨 Analytics scripts – third-party tracking scripts might capture sensitive URL parameters.
🚨 HTTP Referer headers – browsers automatically send URLs in the referer header when navigating between sites, potentially exposing reset tokens.
Security Risks with User Identifiers in URLs
Some applications mistakenly include the user’s identifier along with the reset token in the URL.
- This allows the system to directly associate the token with the user.
- However, if the reset form also includes a hidden field with the user’s global identifier, attackers may exploit this design flaw.
How an Attack Works
1️⃣ The attacker requests a password reset for their own account.
2️⃣ They receive a valid reset email and follow the reset URL.
3️⃣ When they submit a new password, they modify the request, changing the user identifier to that of their target victim.
4️⃣ If the application does not properly validate this identifier, the attacker successfully resets the victim’s password and takes over their account.
✔ Mitigation: Avoid exposing user identifiers in reset requests and ensure that reset tokens are properly validated.
Security Risks of Security Questions
When discussing secure password resets, it’s important to note that many applications still implement security questions (also known as security questions in English).
This method requires users to answer personal questions, often related to their private life or interests. In theory, only the user should know these answers, and security questions are often used during account creation (registration in an application/system).
At first glance, this might seem like a good approach, but real-world experience shows that it is highly insecure.
🔴 The biggest weakness in security is the human factor.
In the era of social media, users often share personal details publicly, making it easier for attackers to guess security question answers.
Examples of common security questions that an attacker might exploit:
✔ “What is your favorite pet’s name?”
✔ “What is your favorite sport?”
✔ “What is your mother’s maiden name?”
Even if no one in your close circle has bad intentions, it is risky to assume that attackers won’t have access to this information through public sources.
Security Risks of the Implementation
Even if a user protects their personal data and doesn’t share much online, they may still be at risk due to technical security vulnerabilities in how security questions are implemented.
Security questions are typically answered in a form and then sent via HTTP requests to the server, where the application processes them.
🚨 Potential vulnerabilities include:
- Lack of rate limiting – allowing attackers to make unlimited attempts to guess answers.
- Brute-force enumeration – trying multiple common answers until a correct one is found.
- OWASP Top 10 vulnerabilities – many security flaws can apply to how security questions are handled.
Conclusion: Should We Use Security Questions?
If there is no absolute need to use security questions, it is better to avoid them altogether.
Instead, applications should rely on more secure password reset mechanisms, such as:
✔ Sending a reset link via email.
✔ Using two-factor authentication (2FA).
Security questions are outdated and weak as a security mechanism, and their use should be discouraged in modern applications.
Best Practices: Re-authentication
Re-authentication should always be required for critical operations, such as:
✔ Changing the password or email address of a user
- Before setting a new password, the user must enter their current password.
✔ Modifying advanced application settings in the admin panel
- If such a modification could impact system availability or stability, re-authentication should be required.
Additionally, the re-authentication form must have the same level of security as the regular login form, making it resistant to brute-force attacks and other security threats.
Secure Password Storage Hygiene
Sometimes, you must prepare for the worst. One of the most critical security considerations is how passwords are stored.
The most important decision is choosing the hashing algorithm for storing passwords.
At the time of writing this chapter, the recommended approach is to use a hashing function, with a strong preference for:
✔ bcrypt
✔ Argon2
It’s essential to remember:
🔴 Passwords should be stored as hashes, not encrypted values.
🔴 Hashing is different from encryption—hashes cannot be reversed, whereas encrypted values can be decrypted if the key is compromised.
Best practice: Applications should only store password hashes in the database, never plaintext passwords or encrypted versions.
Password Security Policy
One of the key security elements of an authentication mechanism is a well-implemented password security policy.
Such a policy should define:
✔ The minimum password length allowed by the system.
✔ The character types a password must contain (or should not contain).
The application should inform users that their passwords should not be:
🚫 Single words.
🚫 Simple numeric sequences.
Additionally, the application should verify on the server side whether the user has followed the password policy recommendations.
Best Practices: password
Systems should only accept passwords that:
✔ Are at least 10 characters long.
✔ Contain at least one character from each of the four groups:
- Numbers (0-9).
- Special characters (!@#%^&).
- Lowercase letters (a-z).
- Uppercase letters (A-Z).
The minimum password length and required character groups should be determined by the security policy of each organization.
Additionally, some common security practices include:
- Checking if a new password matches any of the last few previously used passwords.
- Avoiding forced periodic password changes, as research suggests this causes more harm than good by encouraging users to choose weaker, easier-to-remember passwords.
Event Logging
A well-designed system should follow auditability principles.
✔ Auditability means being able to determine who, when, and how a user performed an action in the system.
Authentication functions should log:
- When and where users log in.
- What actions they perform in the system.
It is also important to collect additional details about the environment in which users authenticate, including:
✔ User’s IP address.
✔ Browser details (via the User-Agent header).
✔ Referrer information (if applicable).
🔴 Security Warning: Since headers like User-Agent and Referer can be manipulated by attackers, extra precautions should be taken if this information is later used in administrative logs.
✔ To prevent security risks, applications should implement protection against Cross-Site Scripting (XSS) attacks when handling logged data.
Checklist: Threat Modeling for Authentication Mechanisms
Below is a list of questions that should be asked when designing, implementing, or testing an authentication mechanism:
✔ Does the system implement a central authentication mechanism for all functions?
✔ Are there any accounts secured with default passwords?
✔ Are passwords for system services hardcoded into the application code?
✔ Does the system implement authentication layers on the server side?
✔ What mechanisms are in place to protect against brute-force attacks?
✔ How are user credentials stored in the database?
✔ Are users and system administrators notified of brute-force login attempts?
✔ Does the system implement a “hard lock” mechanism that fully disables accounts after a certain number of failed login attempts?
✔ How is the password reset mechanism implemented?
✔ Does the password reset token expire after a predetermined time?
✔ Is the system’s password complexity policy consistent across all components (e.g., password change and reset forms)?
✔ What messages does the system return when incorrect authentication details are entered in the login or password reset form?
✔ What information does the system log regarding authentication actions?
✔ Do critical system operations require re-authentication?
Session Management
HTTP protocol is stateless.
🔴 This means the server cannot distinguish whether consecutive requests come from the same user or a different one unless additional mechanisms are applied.
Solution: HTTP Cookie-Based Sessions
To solve this problem, HTTP cookie-based session management is used.
✔ The server sends an HTTP response header (Set-Cookie), instructing the browser to store a session token in local memory.
✔ The browser automatically includes this cookie in subsequent requests to the server.
✔ This allows the server to identify requests from the same user without requiring re-authentication on each request.
This method ensures proper session handling and is widely used in modern web applications. When a web browser sends subsequent requests to the same server, it attaches the cookie header.
If the application, after confirming that the user has entered valid authentication credentials, sends an HTTP response with the Set-Cookie header, then part of the server’s logic can compare the session cookie value with stored session records to identify the user who is sending requests.
At this point, we can say that the session management mechanism is working correctly.
✔ The session process appears simple:
- The system creates a session cookie with a chosen name.
- It assigns a random value to this cookie.
- It uses this value to identify the user.
However, various mistakes can occur when designing such mechanisms.
Reinventing the Wheel
One of the biggest mistakes when implementing session management is creating a custom mechanism from scratch instead of using proven solutions.
🔴 Why is this a bad idea?
This logic is similar to cryptography—building custom encryption methods is strongly discouraged because it almost always introduces security vulnerabilities.
🔹 Best practice:
Use built-in session management mechanisms provided by the framework or technology stack you are using.
Common Security Mistake: Custom Session Management
Security testing statistics shows that quite often developers have built their own session management mechanisms instead of using framework-based solutions.
🚨 Example of a flawed session implementation:
Some developers generate a session token and encrypt it using symmetric cryptography before storing it in a cookie.
- This token contains user information, such as:
✔ Username
✔ User ID from the database
Developers assume that by encrypting this information, they prevent data leaks—but this is incorrect.
Risks of Encrypting User Data in Cookies
🔴 If the encryption key is compromised, attackers can decrypt the session token and impersonate users in the system.
✔ A better approach is to use standard session management mechanisms built into modern web platforms like .NET, PHP, Java (Spring Security), and others.
Every mature framework provides its own session management interface, and applications should use these built-in mechanisms rather than trying to reinvent the wheel.
Session Regeneration
Most platforms used for web application development do not wait until the user authenticates to start the session management mechanism. Instead, session handling begins as soon as the user visits the website.
🔹 This can be observed by sending an HTTP request to an application before authentication—the response will likely contain a Set-Cookie header with a new session identifier.
However, a properly designed application should follow security best practices:
✔ The session identifier should be regenerated whenever a user’s privilege level changes.
This means:
1️⃣ When a user first visits the web application, a random session identifier is generated.
2️⃣ After the user enters valid authentication credentials, the session identifier should be changed to a new random value.
This approach ensures good session management hygiene:
- Once correct credentials are provided, the only factor that proves the user is authenticated is the session identifier.
Session Fixation Attack
Understanding why session regeneration is important helps prevent a Session Fixation attack.
🔴 What is Session Fixation?
This attack occurs when an attacker forces a victim to use a known session ID, allowing the attacker to hijack the session after authentication.
Session Fixation can happen in different ways:
1️⃣ The attacker obtains a session identifier before authentication
- This can be done via an XSS attack or by tricking the victim into using a pre-defined session ID.
2️⃣ If the session identifier remains unchanged after authentication, the attacker can use it to access the victim’s account.
✔ Prevention: Always regenerate the session ID after authentication to ensure that previous session identifiers cannot be reused for attacks.
A Session Fixation attack requires the attacker to first obtain the session identifier and then monitor whether the victim authenticates using the same session ID.
Another variant of Session Fixation assumes that the attacker already knows the session ID that will be used by the victim. In this scenario, the attacker forces the application to accept a session identifier they already control.
This attack can be carried out in several ways:
🔹 Some applications accept session identifiers passed via URL parameters.
- If an attacker tricks the user into clicking on a crafted link that includes a session ID, the application might use that ID instead of generating a new one.
- If the user authenticates while using the attacker’s session ID, the attacker gains access to the victim’s account.
🔹 If an application is vulnerable to XSS (Cross-Site Scripting), an attacker can execute JavaScript to overwrite the session cookie.
How to Prevent Session Fixation?
✔ The most important security measures include:
1️⃣ Always regenerate the session identifier after authentication.
2️⃣ Protect the application against XSS attacks to prevent JavaScript-based session overwrites.
3️⃣ Set the HttpOnly flag on session cookies to prevent JavaScript access.
Handling Multiple Sessions
Many applications allow users to log in from multiple devices or browsers at the same time.
🔹 If an application does not implement session control mechanisms, users can maintain multiple parallel sessions without restriction.
🔹 However, some applications require stricter session policies, especially in finance and security-sensitive systems.
✔ Best practice:
- If a user logs in from a new session, the system should notify the user.
- In high-security environments, a new login should invalidate old sessions and force reauthentication.
🔹 For very sensitive systems (e.g., banking):
- Each parallel session should require additional authorization (e.g., SMS code or mobile authentication).
Protecting Session Identifiers
Session identifiers are critical security tokens and must be protected to prevent hijacking.
✔ Session IDs should only be stored in HTTP cookies and should not be exposed elsewhere (e.g., in URLs).
🚨 Security risks of session IDs in URLs:
- The session ID may be stored in browser history or logs.
- The session ID can be leaked via HTTP Referer headers when navigating between sites.
- If the session ID is exposed in a URL, an attacker can easily steal it and gain unauthorized access.
Security Measures for Sessions:
✔ Use HttpOnly cookies
- This prevents JavaScript from reading session cookies, mitigating XSS-based attacks.
✔ Use the Secure flag
- Ensures that session cookies are only transmitted over HTTPS.
✔ Never include session IDs in URLs
- This prevents session identifiers from being logged or leaked via Referer headers.
✔ Monitor for session hijacking attempts
- If an account logs in from an unusual location, the system should notify the user or require additional authentication.
Session Lifetime Management
One of the more debated issues in session management is: how long should a user’s session remain active?
There is no perfect balance between security and usability.
✔ From a security perspective, the shorter the session, the better.
✔ However, frequent session expirations can frustrate users, leading them to use weak passwords to speed up the login process.
Additionally, users do not want to be logged out constantly, especially in business-critical applications. If they are, they might switch to a competitor’s system, causing the original platform to lose customers.
Security Guidelines for Session Expiry
There are no universally strict recommendations for maximum session duration.
✔ OWASP and other security organizations provide general guidelines, but no strict limits.
✔ PCI DSS (Payment Card Industry Data Security Standard) is one of the few standards that enforces a strict session expiration policy:
- Systems that process credit card data should limit user sessions to a maximum of 15 minutes.
- This value can be considered a high-security standard, though it can be adjusted based on the application’s requirements.
Two Key Session Timing Factors:
1️⃣ Idle Timeout – The session is invalidated after a period of user inactivity.
2️⃣ Maximum Session Lifetime – The session expires after a fixed duration, regardless of activity.
✔ Best security practice:
- The system should monitor the last user action.
- If the user exceeds the predefined inactivity period, the system should automatically log them out.
🔹 This protects users from unauthorized access (e.g., if they leave their workstation unattended without locking it).
🔹 Additionally, session lifetimes should not be indefinite.
- Even if a session remains active, it should expire after a reasonable period.
- This reduces the risk of session hijacking—even if an attacker steals a session ID, they can only use it for a limited time.
Session Termination (Logout Functionality)
Every authentication-based application must provide a way to log out—this is achieved by invalidating the session.
✔ While implementing logout may seem simple, in practice, it can be more complex.
✔ Properly designed systems should ensure that session termination is handled securely, preventing session reuse or hijacking.
Session Token Complexity
A session identifier (session ID) is a random string.
✔ The shorter it is, or the fewer unique characters it contains, the easier it is to predict or brute-force.
✔ According to OWASP, a session ID should be at least 128 bits in length.
✔ Developers should verify their framework’s default session settings to ensure the session identifier meets security standards.
Session Token Entropy
🔹 Session IDs should not be composed solely of numbers (e.g., 123456789).
🔹 Low entropy increases the risk of session prediction attacks.
✔ Entropy Analysis:
- Tools like Burp Suite’s Sequencer module can evaluate the randomness of session identifiers.
- Poor entropy means attackers could potentially guess session IDs and hijack user sessions.
✔ If a session ID lacks sufficient entropy, it is vulnerable to brute-force attacks and should be improved.
By following these best practices, developers can significantly enhance session security and protect users from session hijacking, fixation, and brute-force attacks.
Session ID as a Fingerprint
One of the last issues related to session management is the potential use of session ID names to determine which technology a system is using.
✔ Applications based on PHP typically use PHPSESSID as the session identifier.
✔ ASP.NET-based systems commonly use ASP.NET_SessionId.
✔ Other web technologies also provide built-in session management mechanisms with default session ID names.
While this is not a major security vulnerability, it is recommended to rename session identifiers to more generic names (e.g., session or sessionid).
Session ID as a Security Risk
Another important aspect to consider is that if an application processes session identifiers (e.g., storing them in a database), they should be treated like any other sensitive input.
✔ Session IDs must be sanitized and validated to prevent injection attacks.
✔ If stored in a database, they should be handled securely using parameterized SQL queries to prevent SQL injection.
The Problematic “Remember Me” Function
Many web users find frequent authentication frustrating.
✔ To improve user experience, developers often implement the “Remember Me” feature, allowing users to stay logged in without having to enter credentials repeatedly.
✔ However, if not implemented securely, this function introduces significant security risks.
How “Remember Me” Works and its Risks
🔹 Basic Implementation (Insecure)
- The application stores login credentials (username/password) inside cookies.
- If an attacker gains access to these cookies, they can impersonate the user.
- If the application has XSS vulnerabilities, these cookies could be stolen.
🚨 Why is this a bad practice?
✔ Sensitive data (e.g., passwords) should never be stored in cookies, even if encrypted.
A More Secure Approach
🔹 Using a Secure Token
- Instead of storing login credentials, the system generates a random token and stores it inside a secure cookie.
- This token behaves similarly to a session ID, but instead of checking credentials, the system checks whether the token exists in a database.
✔ Security Measures:
1️⃣ The token must be complex, preventing brute-force enumeration.
2️⃣ The cookie should be secured using HttpOnly and Secure flags.
3️⃣ The token’s lifespan should be limited—its expiration should be carefully chosen to balance security and usability.
Checklist: Threat Modeling for Session Management
Below is a list of security questions to ask when designing, implementing, or testing session management:
✔ Does the system use a built-in session management mechanism provided by a framework or platform?
✔ Is the session ID regenerated when user privileges change?
✔ Does the application support parallel sessions, and should it allow them?
✔ Is the session ID cookie protected using HttpOnly and Secure flags?
✔ Is the session ID at least 128 bits long?
✔ Has the session ID been tested for entropy to ensure randomness?
✔ Is the session ID ever exposed in HTTP responses (e.g., inside HTML code)?
✔ Is the session ID name set to a non-default value?
✔ Is the session ID never transmitted in URLs?
✔ How is the “Remember Me” function implemented, and is it secure?
Authorization
✔ Authentication is complete, and the system has verified the user’s identity.
✔ The next step is authorization—determining what the user is allowed to do.
✔ The purpose of authorization is to ensure that users can only access data and functionalities appropriate to their permission level.
✔ Before performing certain critical operations, the system must verify that the user has the necessary privileges.
Once a user is authenticated, the system must verify their authorization—determining what actions they are permitted to perform.
✔ In some cases, all users may have the same level of access.
✔ However, most systems require authorization rules to ensure users can only access data and functions relevant to their role.
✔ Authorization must be considered both vertically (admin vs. regular user) and horizontally (user A should not access user B’s data).
Missing Authorization and UI-Based Authorization Flaws
🚨 One of the most critical security vulnerabilities is a complete lack of authorization checks.
✔ A common mistake is “UI-based authorization”—where an application only hides certain UI elements but does not enforce authorization at the backend.
🔹 Example Attack Scenario:
1️⃣ A user modifies a resource identifier (e.g., changing an ID in a URL).
2️⃣ The application returns data without verifying permissions.
3️⃣ As a result, the user accesses unauthorized information.
✔ This issue is not limited to specific technologies—it can occur in any web application.
How Attackers Exploit Authorization Weaknesses
Modern browsers and tools allow attackers to manipulate HTTP requests.
✔ Attackers can use browser extensions like Tamper Data to modify requests in real-time.
✔ Many users are unaware of this, but security testers and hackers routinely check for such weaknesses.
Privilege Escalation Attacks
🔹 “Privilege escalation” occurs when a user gains access to functions they were not intended to use.
✔ Example:
- A low-privilege user exploits a weakness to gain admin access.
- This can be done via modifying cookies, manipulating API calls, or injecting SQL queries.
🚨 Common Vulnerability:
- If a cookie contains a field like admin=0, an attacker may change it to admin=1 to escalate privileges.
- If the system relies solely on client-side validation, an attacker can modify cookies using browser tools.
✔ Secure applications should verify user roles on the server and never trust client-side data.
The Problem with Global Identifiers
🔹 Global identifiers (e.g., incremental user IDs) can lead to authorization vulnerabilities.
✔ Example:
- A system uses sequential user IDs (user/1, user/2, user/3).
- An attacker iterates through IDs (user/4, user/5, etc.).
- If the system does not enforce authorization checks, the attacker can access other users’ data.
✔ Security testers always check for authorization flaws related to predictable resource identifiers.
🚨 Global identifiers often lead to major security risks, including:
- User enumeration (identifying all registered users).
- Unauthorized data access.
- Privilege escalation attacks.
✔ Best Practice:
- Use UUIDs (random unique identifiers) instead of sequential IDs to make guessing harder.
- Always validate access to resources on the server-side.
UUIDs and Authorization Risks
Developers often assume that if a user cannot see a certain option in an application, they will never try to access it. A common mistake related to global identifiers is allowing users to confirm transactions, access their email lists, or download files stored in the application. These resources are usually identified by global identifiers. The application displays a list of resources assigned to a specific user, identified by specific IDs, e.g., 1, 5, 15. But what happens if a user tries to access a resource with ID 2 or 6? Will the application detect that they are trying to access a resource they do not own? If no security mechanisms have been implemented, this could lead to a vulnerability known as Insecure Direct Object References (IDOR).
A recommended practice is to use identifier mapping instead of global identifiers. This means that resources assigned to a user are identified by consecutive local values (e.g., 1, 2, 3) and then mapped on the server side to global identifiers in the database. This way, the user does not know the actual global identifier, making it significantly harder, or even impossible, to bypass authorization.
However, a common security recommendation after penetration testing or security audits is to eliminate global identifiers. Many developers then decide to use UUIDs (Universally Unique Identifiers). This is a step in the right direction compared to numerical identifiers, but UUIDs do not replace resource authorization. The temptation to skip authorization checks when using UUIDs comes from their complex appearance, leading developers to believe they cannot be guessed. While this may be true, UUIDs can still be leaked or stolen from an application.
If a UUID appears in a URL (e.g., /transaction/<uuid>), it may be exposed through the HTTP Referer header. Furthermore, attackers can use vulnerabilities like Cross-Site Scripting (XSS) to extract UUIDs from an application and compromise a user’s data.
Centralized Authorization
Implementing an authorization mechanism may seem simple, but applying security to a complex system can quickly become time-consuming and complicated. The best approach is centralized authorization, ensuring that all operations and verifications pass through a single security component.
A well-designed central authorization mechanism helps avoid errors, such as forgetting to secure a specific function or operation. With a centralized approach, all resources are protected by default.
Another advantage of centralized authorization is easier maintenance. Instead of spreading security checks across multiple places in an application, centralizing them makes auditing and updating access rules much simpler.
A common recommended solution for implementing centralized authorization is an Authorization Server based on OAuth 2.0. Popular development frameworks already offer ready-made libraries that allow quick integration of such solutions.
Accountability and Non-Repudiation
A key aspect of security is accountability (ensuring every action can be traced back to the user who performed it). Critical operations, especially those requiring elevated privileges, should be strictly logged to determine who, when, and what action was taken.
Before implementing such a mechanism, developers should decide which events should be logged. Importantly, logs should include both successful actions and denied access attempts. This ensures that logs provide insights into unauthorized access attempts.
Another important concept is non-repudiation, which means a user cannot deny performing a specific action within the system. Digitally signed actions (e.g., using cryptographic signatures) are one way to achieve non-repudiation.
Checklist: Modeling Authorization Security Threats
Below is a list of questions that should be considered during design, implementation, or testing of an authorization mechanism:
✅ Does the application have a central authorization verification mechanism?
✅ Has the application been verified for complete protection of functions and resources through authorization?
✅ Does the application use global resource identifiers?
✅ Does the application log sufficient information about user actions to ensure accountability?
✅ Does the application require re-authentication for critical functions?
✅ Does the application implement protection against CSRF attacks?
What Can Be Done Better?
The problems discussed in this chapter show challenges encountered when implementing authentication, session management, and authorization mechanisms, covering most of the threats encountered in web security. Taking these issues into account during system testing will help detect and eliminate many vulnerabilities. However, security should not only focus on fixing problems but also on implementing better mechanisms that enhance the overall security level.
Two-Factor Authentication (2FA)
Given the vast amount of personal information processed by IT systems, it is reasonable to ask whether two-factor authentication (2FA) should still be treated as an optional feature or an essential element of modern authentication mechanisms required for all applications.
To start, let’s understand what two-factor authentication (2FA) means.
Example: Two-Factor Authentication Verification Form
(Illustration of a Google 2FA prompt with SMS verification code input.)
Understanding Multi-Factor Authentication (MFA)
In a standard authentication process, a user must provide a single set of credentials (usually login and password). However, if the system requires additional information, it becomes multi-factor authentication (MFA).
Authentication factors are usually classified into three groups:
- Knowledge – Something the user knows (e.g., a password, PIN, or security question).
- Possession – Something the user has (e.g., a mobile device, authentication token, or security key).
- Inherence – Something the user is (e.g., fingerprint, facial recognition, or other biometric data).
Best Practices: Implementing Two-Factor Authentication
When implementing 2FA, it is important to follow these recommendations:
✅ Disabling 2FA should only be possible after additional authorization via an independent communication channel.
✅ Authentication codes should be sent via a different communication method than SMS (e.g., avoiding SIM swap attacks).
✅ Ensure that the 2FA mechanism is not vulnerable to brute-force attacks (e.g., a past vulnerability in Slack).
✅ Ensure the security of the mechanism responsible for generating authentication codes.
✅ Verify that the application does not have outdated or unmaintained endpoints that could allow authentication bypassing 2FA.
Thank you for your attentions. If you are interested in similar content, then sign up to my newsletter: