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.
- HTTP Headers
- Server Version Disclosure
- X-Frame Options - Missing
- X-Content-Type-Options - Missing
- CORS - Wildcard
- CORS - Reflected Origin
- HSTS - Missing
- HSTS - Insecure
- CSP - Missing
- CSP - Insecure
- Referrer-Policy - Missing
- Referrer-Policy - Insecure
- X-XSS-Protection - Missing
- X-XSS-Protection - Insecure
- Host Header Injection
- Header Injection
- Duplicate Headers
- Session & Cookies
- Missing Attribute - Secure
- Missing Attribute - HTTPOnly
- Missing Attribute - SameSite
- Path Attribute
- Concurrent Sessions
- Session Token Entropy
- Session Timeout
- Session Termination
- Session Fixation
- Session Cookie Not Changing
- Session Not Invalidated After Changing Password
- Decode Cookies
- Lack of Logout Functionality
- Authentication
- Login with no Password
- Lack of MFA
- Account Lockout
- Account Lockout on Change Password
- No MFA Lockout
- MFA Bypass
- User Enumeration - Error Based
- User Enumeration - Time Based
- Password Strength
- Password History
- Case Insensitive Password
- Client-Side Password Requirements
- Password Not Required to Change Password
- Use of Basic Authentication
- No Method to Change Password
- User Account DoS
- Authorisation
- Data Storage
- Input Validation
- Server Configuration
HTTP Headers
Server Version Disclosure
Ensure the server does not disclose any information about the underlying server software or operating system.
1
2
3
4
HTTP/1.1 200 OK
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
1
Server: Apache/2.4.43 (Win64) OpenSSL/1.1.1g PHP/7.4.5
1
X-Powered-By: Express
X-Frame Options - Missing
Ensure HTTP responses that contain HTML pages have the X-Frame-Options
header set.
1
2
3
X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
X-Frame-Options: ALLOW-FROM uri
- More info at MDN Web Docs: X-Frame-Options Header
X-Content-Type-Options - Missing
Ensure the HTTP responses contain the X-Content-Type-Options: nosniff
header.
- More info at MDN Web Docs: X-Content-Type-Options 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.
1
Access-Control-Allow-Origin: *
- More info at MDN Web Docs: 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
1
Origin: https://attacker.com
Response Header
1
Access-Control-Allow-Origin: https://attacker.com
- More info at MDN Web Docs: Access-Control-Allow-Origin
HSTS - Missing
Check that the HTTP Strict-Transport-Security
(HSTS) header is present on all HTTP responses.
1
2
Strict-Transport-Security: max-age=31536000; includeSubDomains
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
31536000
is 1 year expressed in seconds (60*60*24*365)63072000
is 2 years expressed in seconds (60*60*24*365*2) (PREFERRED)- More info at MDN Web Docs: Strict-Transport-Security
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.
1
2
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.
1
2
Content-Security-Policy: <policy-directive>; <policy-directive>
Content-Security-Policy: default-src 'self'; connect-src 'none';
- More info at MDN Web Docs: Content-Security-Policy
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.
1
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.
1
2
Referrer-Policy: no-referrer
Referrer-Policy: strict-origin
- More info at MDN Web Docs: Referrer-Policy
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.
1
2
Referrer-Policy: unsafe-url
Referrer-Policy: no-referrer-when-downgrade
- Examples at MDN Web Docs: Referrer-Policy#Examples
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.
1
2
X-XSS-Protection: 1
X-XSS-Protection: 1; mode=block
- More info at MDN Web Docs: X-XSS-Protection
X-XSS-Protection - Insecure
Check if the X-XSS-Protection
header is present but has been configured to disable XSS filtering.
1
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 theSet-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.
1
Set-Cookie: <cookie-name>=<cookie-value>; Secure
- More info at MDN Web Docs: Set-Cookie
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.
1
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly
- More info at MDN Web Docs: Set-Cookie
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.
1
2
3
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
- More info at MDN Web Docs: Set-Cookie
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 /
).
1
2
Set-Cookie: <cookie-name>=<cookie-value>; Path=/
Set-Cookie: <cookie-name>=<cookie-value>; Path=/application
- More info at MDN Web Docs: Set-Cookie
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.
Session Cookie Not Changing
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.
Authentication
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:
- Username is checked if it’s in the Database
- If the username is invalid then return an error message.
- 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!
, Qwerty@123
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.
Authorisation
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 require a cookie or auth token to be accessed. I.e. it can’t be access without authorisation.
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
CSRF
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.
CSWSH
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
ordesc
.
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<
>
as>
"
as"
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:
1
2
3
4
POST /register HTTP/1.1
...
{"email":"[email protected]"}
1
2
3
4
HTTP/1.1 200 OK
...
{"email":"[email protected]","userId":"abcefg","isAdmin":false}
Try adding the isAdmin
key to the initial request:
1
2
3
4
POST /register HTTP/1.1
...
{"email":"[email protected]","isAdmin":true}
1
2
3
4
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
- IIS & ASP.NET
- 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.
<script
src="https://test.com/special-library.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous">
</script>
- More info at MDN Web Docs: Subresource Integrity
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.
No WAF
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.