Home Web App Security

Web App Security

General purpose guide for testing Web Applications. This list is not supposed to be exhaustive, nor is the information absolute. Each web application is different and the business case needs to be thought about when assessing the below items. The checks are not explained in depth on purpose and should be used as a reference only.

  1. HTTP Headers
  2. Session & Cookies
  3. Authentication
  4. Authorisation
  5. Data Storage
  6. Input Validation
  7. Server Configuration

HTTP Headers

Server Version Disclosure

Ensure the server does not disclose any information about the underlying server software or operating system.

HTTP/1.1 200 OK
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Server: Apache/2.4.43 (Win64) OpenSSL/1.1.1g PHP/7.4.5
X-Powered-By: Express

X-Frame Options - Missing

Ensure HTTP responses that contain HTML pages have the X-Frame-Options header set.

X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
X-Frame-Options: ALLOW-FROM uri

X-Content-Type-Options - Missing

Ensure the HTTP responses contain the X-Content-Type-Options: nosniff header.

CORS - Wildcard

Check if the HTTP response contains the wildcard CORS header. This may be desired in some applications, but is most often not required.

Access-Control-Allow-Origin: *

CORS - Reflected Origin

Check if the HTTP response reflects the value provided in the Origin request header in the Access-Control-Allow-Origin response header. If the Access-Control-Allow-Credentials: true response header is also present, this can leaded to authenticated content being loaded cross-origin, as well as bypassing anti-CSRF tokens in certain cases.

Request Header

Origin: https://attacker.com

Response Header

Access-Control-Allow-Origin: https://attacker.com

HSTS - Missing

Check that the HTTP Strict-Transport-Security (HSTS) header is present on all HTTP responses.

Strict-Transport-Security: max-age=31536000; includeSubDomains
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

HSTS - Insecure

Check for insecure configurations of the HSTS header. These include a max-age of 0 which effectively disables the control, or missing directives such as includeSubDomains or preload. Please note that the additional directives may not always be required.

Strict-Transport-Security: max-age=0; includeSubDomains
Strict-Transport-Security: max-age=31536000

CSP - Missing

Check that the Content-Security-Policy (CSP) header is present on HTTP responses that return HTML and JavaScript. The CSP header is required on JavaScript responses when using web workers as they do not inherit the Content-Security-Policy of the site. The CSP header is not required on other response types such as JSON or CSS. However, it does not hurt to include on all responses if the CSP header is small.

Content-Security-Policy: <policy-directive>; <policy-directive>
Content-Security-Policy: default-src 'self'; connect-src 'none';

CSP - Insecure

Check if the Content-Security-Policy header contains 'unsafe-eval' or 'unsafe-inline'. These drastically reduce the protection CSP provides against XSS attacks and should NOT be included if possible.

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; connect-src 'none';

Referrer-Policy - Missing

Check that the Referrer-Policy header is present on HTTP responses that return HTML to minimise data leakage via the Referer header added by the browser.

NOTE that referrer polices can also be added to HTML elements. See MDN Web Docs below for more info.

Referrer-Policy: no-referrer
Referrer-Policy: strict-origin

Referrer-Policy - Insecure

Check that the Referrer-Policy header is not set to an insecure setting. no-referrer is best, but other settings such as origin and strict-origin may be required depending on the application.

Referrer-Policy: unsafe-url
Referrer-Policy: no-referrer-when-downgrade

X-XSS-Protection - Missing

Check if the X-XSS-Protection header is present. This header is mostly not used by modern browsers and is not required if a strong Content-Security-Policy is used that blocks inline JavaScript.

X-XSS-Protection: 1
X-XSS-Protection: 1; mode=block

X-XSS-Protection - Insecure

Check if the X-XSS-Protection header is present but has been configured to disable XSS filtering.

X-XSS-Protection: 0

Host Header Injection

Check if the value in the Host request header is reflected in any location in the server response. This includes locations such as links and form submission locations.

Header Injection

Check if headers can be injected into the server response. A real world example of this happening:

  • When sending OPTIONS requests to this particular server, the server reflected all the request headers as response headers.
  • This could be used as a way of extract cookies using JavaScript because the Cookie header is not automatically removed from the HTTP response like the Set-Cookie header is.

Duplicate Headers

Check if the server responds with duplicate headers. This can happen normally if there is a load balancer, proxy, multiple API servers.

Session & Cookies

Missing Attribute - Secure

Ensure that all cookies used by the web application have the Secure attribute set. Some websites will have HTTP enabled so that a user can be redirected to the HTTPS version of the site. Cookies without the Secure flag can be sent during this first unencrypted HTTP request. Even tracking and non-session cookies should only be sent over HTTPS. The only exceptions are for sites that don’t use TLS, which should be rare.

Set-Cookie: <cookie-name>=<cookie-value>; Secure

Missing Attribute - HTTPOnly

Ensure that session cookies used by the web application have the HttpOnly attribute set. Cookies will this attribute set can not be accessed via JavaScript. Cookies without this attribute can be accessed by JavaScript and as a result can be stolen by an attacker if an XSS vulnerability exists in the application. Only non-sensitive cookies that need to be accessed by the front-end should be set without this attribute.

Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly

Missing Attribute - SameSite

Ensure that session cookies used by the web application have the SameSite attribute set and is not the value None. Cookies will this attribute set to Strict or Lax are more resistent to CSRF attacks as they will not be included in cross-origin requests. Using the Strict attribute is arguably simpler and more secure than using anti-CSRF tokens for most web applications as there is much less room for implementation errors.

Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Strict
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Lax
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=None; Secure

Path Attribute

Check if the Path attribute should be set for application’s session cookies. This attribute is not always required and having a value of Path=/ is not always an issue. Care should be given to this attribute if multiple applications are hosted on the same domain and/or if the application is not located at the root path (i.e. the web application is located at /application instead of at /).

Set-Cookie: <cookie-name>=<cookie-value>; Path=/
Set-Cookie: <cookie-name>=<cookie-value>; Path=/application

Concurrent Sessions

Check if a user can have multiple sessions open at once using different browsers (or using Private-Mode). This may not be an issue for some web applications, depending on the application and sensitivity of the data being dealt with.

Session Timeout

Ensure that the session is automatically invalided after a certain period of inactivity, normally 30 mins to 1 hour is standard. Note, this may not be desirable in some applications.

Session Termination

Ensure that after a logout is performed, the cookie is actually invalidated and simply cleared.

Session Fixation

Ensure after a successful login that a new session cookie is received from the server. The same session cookie that was given to a user pre-authentication SHOULD NOT be used post authentication.

A rare extension of Session Fixation where the session cookie for a user is the same for every login. This can happen if the application uses the user’s ID (or similar) as a session cookie.

Session Not Invalidated After Changing Password

Check if the user it logged out and has their session invalidated after changing their password.

Decode Cookies

Attempt to decode cookies values. Session cookies should be completely random and unpredictable.

  • This is a GOOD session cookie WK0idcE45L7AB066B5B34tNZMLsmkGAx.
  • This is a BAD session cookie VXNlcjpKb2huU21pdGg6MjAyMS0wOS0yODoyMi0xNi01Mg. (I’ll let you figure out why).

Lack of Logout Functionality

Ensure the application has an explicit logout button that invalidates a user’s session.


Login with no Password

Attempt to login to the application without using a password. This should be checked on the front-end and through an interception proxy as most apps won’t submit an empty password on login.

Lack of MFA

Check if the application requires MFA to login. Some applications may not require MFA such as low risk internal applications. Internet facing applications should almost always support MFA.

Account Lockout

Verify that a user’s account gets locked out after a certain number of incorrect attempts. This is to prevent the bruteforcing of passwords.

Account Lockout on Change Password

Verify that a user’s account gets locked out after a certain number of incorrect attempts at changing a user’s password.

No MFA Lockout

Verify that the user account get’s locked out and/or the OTP code is invalidated if the OTP code is brute forced.

MFA Bypass

If a well known solution is used, check online for known bypasses.

Common techniques include:

  • OTP is not actually required at all, the server has just told us OTP is required. Server responses can be modified and the front-end can send us to the application. I.e. the server might respond with {"success":false} after a successful username & password auth. Changing this to {"success":true} may trick the front-end into sending additional requests or redirecting to the application without needing the OTP code.
  • Forcefully browsing to the application after being presented with the OTP screen. This can happen if the user is actually logged in after providing the username & password, and providing a correct OTP acts as a redirect.
  • No bruteforce protection in place
  • OTP codes from other users. OTP codes should only work for one user. I.e. OTP codes sent to user A, should not work for User B.
  • OTP code reuse. Check if previously used OTP still work. Only one code should work at a time, all other codes should be invalidated.
  • Client-side validation. OTP code returned to the front-end and the OTP check only happens client-side.

User Enumeration - Error Based

Verify the application returns the same message on a failed login attempt when using a valid and invalid username. If usernames can be enumerated, password spraying can be done against the application with a much higher chance of success.

  • GOOD error message The Username/Password is Incorrect.
  • BAD error message Username does not exist.

User Enumeration - Time Based

Verify the application takes the same amount of time to return an error message when a valid and invalid username is provided.

An example of how this can go wrong:

  1. Username is checked if it’s in the Database
  2. If the username is invalid then return an error message.
  3. If the username is valid then check the password hash (this can take a second or two if bcrypt with multiple rounds is used). If password is wrong return an error message.

Because hashing can sometimes take a noticeable amount of time, a valid and invalid user can be enumerated based on the response time.

Password Strength

Ensure the application has a password complexity requirement so as to not allow very weak passwords such as a. As an extension, common weak passwords such as Password1, Welcome1!, Qwe[email protected] that technically meet the application’s password complexity requirements should be blacklisted.

Password History

Ensure the application enforces a password history. A user should not be able to change their password to one of their previous few passwords.

Case Insensitive Password

Ensure the application checks the case of passwords that are entered. There may be limitation on very old systems & mainframes, but this is not the case for modern systems.

Client-Side Password Requirements

Ensure the password complexity requirement is enforced server-side as well as client-side. Use an interception proxy to change the password to ensure the application doesn’t solely rely on client-side controls.

Password Not Required to Change Password

Ensure that the current password is required to update a user’s password. This is a requirement so that accounts that have been temporarily taken over (perhaps via a leaked session cookie, or leaving a computer unattended) are harder to fully take over. If an attacker only has a victim’s session token, and not their password, then the attack’s access should be limited to the session only. If they can change the password without the current password, then they can fully take over the account and lock the victim out.

Use of Basic Authentication

Ensure the application does not use Basic Authentication. Basic Auth sends the username and password in every request, increasing the likelihood that they will be leaked. In addition, using Basic Auth provides no effective session management.

No Method to Change Password

Check if the application has functionality to change a user’s password.

User Account DoS

Verify if the reset password functionality immediately invalidates a user’s current password. If so then a user can be DoS’d by an attacker by only knowing their username. An more secure method is for a user to be sent a reset link, asked to answer security questions, then prompted to enter a new password.


Remove Cookies ONE at a time

Remove one cookie at a time to check what cookies are actually required for a request to succeed. It’s common for web applications to only have one session cookie, and the rest are for tracking and/or application settings/options.

Remove ALL Cookies and/or Tokens

Verify that APIs actually requires a cookies/tokens to be accessed. Same goes for update and delete APIs.

JWT Checks

If the application uses JWTs, verify they have been implemented correctly. This may be useful JWT Fuzzer Tool.

Unauthorised Data Access

Ensure users can not access data that is not accessable to them via the UI. If a user has some documents assigned to them that they can see, attempt to access other documents that are not assigned to the user. This also includes update and deleting data that has not been specifically assigned to a user.

Unauthorised API Access

Ensure lower privilege users don’t have access to resources they shouldn’t. If only Admins can access /userlist, ensure low privilege users can not. This differs from the previous point as here we are talking about entire endpoints and/or sections of the application that shouldn’t be accessed.

Horizontal Privilege Escalation

Attempt to perform actions on resources of other users at the same level. E.g. If User 1 can only access Document ABC, and User 2 can access Document DEF and GHI, attempt to perform CRUD operations on documents DEF and GHI even if it’s not possible through the UI.

Vertical Privilege Escalation

Attempt to perform actions on resources that required a higher privilege level (e.g. Admin or Manager privilege).

Forceful Browsing

Attempt to directly access files that are in the web root folder that are not listed by the application and see if these are accessable. Examples include:

  • Config files
  • Environment files
  • Uploaded files through the application


Attempt to perform a cross-site request forgery (CSRF) attack with a PoC and prove it’s possible. Even if it looks like it should work, verify it actually does.


If the application uses web-sockets, attempt to perform a cross-site web socket hijacking (CSWSH) attack. Ensure either the session cookie has a SameSite value that isn’t None, and/or the web-socket upgrade request Origin header is validated, as this is a restricted header that can’t be modified in the browser.

Data Storage

Sensitive Cookies

Verify that sensitive information is not stored in cookie values or as cookie names. Examples include passwords/secrets that are persistent and rarely (if ever) change.

Session Storage

Check the browser Session Storage for sensitive information. Session Storage can be accessed via JavaScript meaning any data stored here is accessable to an attacker via an XSS attack.

Session Storage can be accessed in JavaScript using sessionStorage. A specific item in Session Storage can be retrieved using sessionStorage.getItem('keyName').

Local Storage

Check the browser Local Storage for sensitive information. Local Storage can be accessed via JavaScript meaning any data stored here is accessable to an attacker via an XSS attack. Local Storage differs from Session Storage as it is persistent across page and browser reloads (assuming not in Private-Mode).

Local Storage can be accessed in JavaScript using localStorage. A specific item in Local Storage can be retrieved using localStorage.getItem('keyName').

Window Object

Check the global window object for any sensitive data that may be stored there. The window object is a globally accessable location by JavaScript executing on the front-end. It can be a convenient place to store information, but can lead to insecurities if used incorrectly to store sensitive data. The window object is accessable to an attacker via an XSS attack, therefore is not a safe place to store sensitive data. The window object can be access using window. The data stored in window can be view more cleanly using Object.entries(window).

Returned Data

WARNING this check is HIGHLY application specific and should be judged on an app by app basis.

Ensure the data returned in HTML pages and via APIs do not contain unnecessary sensitive data. An example would be returning passwords or password hashes to a user. This type of information is not required for a majority of cases and a user (and admin in most cases) should not be able to see their password after it has been set.

Input Validation

Lack of Validation

Ensure that fields only support the type of data that is expected. This can lead to numerous issues, notably XSS and SQLi. Some examples:

  • Check Phone Number fields only supports Numbers, Plus sign and whitespace.
  • Check fields like names don’t support special characters if not required such as <>()[]{}"'~\/?%$
  • Sort direction fields that normally only contain asc or desc.

Client-Side Validation

Ensure that the application does not rely on Client-side validation ONLY. Client-side validation is perfectly fine and is necessary for a good user experience, but validation must be performed Server-side as well.

Reflected Parameters

Check if parameters provided in a request are reflected in the response. A common example would be a query parameter that is reflected in the response HTML and not escaped properly. This can lead to Reflected XSS.

CSV Injection

If the application has functionality that allows data to exported to a CSV file, check if CSV injection payloads can be included in the output document.

Output Encoding

Ensure special characters are correctly escaped when they are displayed. Some Examples:

  • < as &lt;
  • > as &gt;
  • " as &quot;

Attempt to bypass the escaping using different encodings.

Error Messages

Check if the application returns descriptive Errors and/or Stack Traces if invalid input is provided. Query parameters, Body parameters, Tokens, Cookies and Header values should all be fuzzed for this.

Parameter Pollution

Add multiple query parameters with the same name and see how the server handles it. Different server software will handle this differently (some take the first value, some the last, some concatenate all of them).

Mass Assignment

Add additional unexpected keys to requests and see if the server accepts them. This can happen if the entire JSON object is used when preforming a Database operation, instead of copying out the required keys. It can also happen if no validation is done on the provided data (not checking that only the required keys were provided and no others). Example:

POST /register HTTP/1.1
{"email":"[email protected]"}
HTTP/1.1 200 OK
{"email":"[email protected]","userId":"abcefg","isAdmin":false}

Try adding the isAdmin key to the initial request:

POST /register HTTP/1.1
{"email":"[email protected]","isAdmin":true}
HTTP/1.1 200 OK
{"email":"[email protected]","userId":"123456","isAdmin":true}

File Upload

Verify file upload functionality works as intended:

  • User should not be able to control where the file is stored on the server.
  • Accepted file types and size should be enforced server-side.
  • Check if Anti-virus is installed on the Server (EICAR).
  • Check if files are stored on disk (they might be stored in the DB, or elsewhere).
  • Check if uploaded files are in a simple location like /upload/my-uploaded-file.aspx.
  • Upload Shell if applicable.

Directory Traversal

Check if files outside of the web root can be accessed. This occurs when no validation is performed on the path and file that is trying to be accessed.

Server Configuration

Directory Listing

Check if there is Directory Listing on the web server which enables a user to see the files on server. This should rarely ever be required for a web application, except for a simple file share server.

TLS Configuration

Check the TLS configuration of the application and see if older SSL/TLS versions are supported like SSLv2, SSLv3, TLSv1.0 and TLSv1.1. Check for weak ciphers and other insecure settings. Use a tool like SSLScan or TestSSL.

Wildcard Certificate

Check if the certificate used by the web application is a wildcard certificate. Wildcard certificates are not any less secure cryptographically, however, if compromised they are a bigger risk as the certificate is valid for multiple domains.

TRACE Method Enabled

Check if web server supports the HTTP TRACE method. Replace methods such as GET or POST with TRACE and see if the request data is reflected in the server response. This is almost unexploitable now due to browsers no longer supporting the TRACE method for security reasons (Cross-Site Tracing).

Outdated Libraries in Use

Check for outdated libraries/software used by the web application. Common outdated software includes:

  • jQuery
  • Bootstrap
  • React
  • Angular
  • Tomcat
  • PHP

Sensitive Information in URL

Ensure no sensitive information is sent in the URL. This includes data such as Passwords, API keys and Session Tokens. URLs can be stored in browser history, caches, and leaked via Referer header.

Lack of Subresource Integrity Checks

Ensure cross-origin resources contain integrity checks. Loading scripts cross-origin can increase the attack surface of an application. Should that cross-origin resource be compromised, an attacker now has an additional pathway into the application.


Lack of HTTPS

Ensure the website utilises TLS and doesn’t perform actions unencrypted over HTTP.

Use of HTTP (no TLS)

Ensure the website can not be used over HTTP and instead must be used over HTTPS. Servers often support HTTP (no TLS) and will immediately redirect to a HTTPS version of the application. This is okay, however it becomes an issue if application functionality can be performed over unencrypted communication.

A server that supports HTTP and HTTPS has the following issue:

  • Use of HTTP (no TLS)

A server that supports HTTP, but NOT HTTPS has the following issues:

  • Use of HTTP (no TLS)
  • Lack of HTTPS

Sequential IDs

Check the application doesn’t use sequential ID numbers for resources. If you create a document in the application and it has an ID of 3074, create another one and see if the number is slightly higher or is the next number in the sequence. Having sequential ID numbers makes resources much easier to enumerate. ID numbers should be long and completely random.


Check if common attack payloads get blocked by a Web Application Firewall (WAF). Sending requests with such payloads should result in a different error screen or a 500 response with no information.

Common attack payloads that should get blocked:

  • <script>alert(1)</script>
  • <script>alert(document.cookie)</script>
  • SELECT @@version
  • ’ UNION SELECT 1,2,3–

POST Requests as GET Requests

Attempt to change POST requests to GET requests to see if the server accepts the parameters as query parameters instead.

Rate Limiting

Check if the server implements any sort of rate limiting to prevent DoS, or brute forcing.

Database Storage

Ensure the database stores hashed information were possible. Passwords should never be stored in plain-text and should be hashed using bcrypt to mitigate brute forcing in the event of compromise. User session tokens should also be hashed for the same reason.

This post is licensed under CC BY 4.0 by the author.