In the early stages of my Software Development career, I often heard from Cybersecurity Professionals about the importance of building secure applications. At that time, I didn't fully grasp the significance of this advice because I lacked a concrete understanding of how to actually implement security measures. Like many developers, I found myself overwhelmed by the multitude of concepts I needed to master - from Cloud Architecture and System Design, to Algorithms and Data Structures, Clean Code practices, and the continuous need to stay updated on new frameworks and technologies. With such a demanding workload and tight deadlines, security often took a backseat for many developers.
Having transitioned from a Software Developer to a Cybersecurity professional, I have gained valuable insights into Secure Web App Development. Drawing from my experience, I aim to create a quick reference guide for developers to easily follow and implement security best practices in their applications.
Why Learn Secure Web App Configuration
Understanding secure web app configuration is crucial not only for safeguarding an organization's assets but also for protecting the end users of the application. End users are prime targets for attackers, as client-side attacks have emerged as a favored method for many threat actors. Failing to prioritize security in application development leaves it vulnerable to exploitation.
Adhering to the principle of "If it can go wrong, it will go wrong" - often referred to as Murphy's Law - underscores the importance of proactive security measures to mitigate potential risks and ensure the resilience of web applications.
Understanding Secure Web App Configuration
In order to effectively deliver a secure web application to end users, it is essential to leverage the built-in security features of web browsers. Given that the primary interface for interacting with our application is the browser, we can utilize HTTP headers to instruct the browser on how to enforce security measures. In this article, we will explore the various headers that can be employed and provide guidance on their implementation.
The Following Headers we will cover include:
Content Security Policy (CSP)
Cross-Origin Resource Sharing (CORS)
Strict Transport Security
Cross-Origin-Opener Policy (COOP)
Cross-Origin-Resource-Policy (CORP)
Legacy Security Headers
Headers That Can Support Security
Set-Cookie (Securing Cookies)
Content Security Policy
The Content Security Policy (CSP) header plays a crucial role in reinforcing browser security by effectively safeguarding against common cyberattacks targeting the client. When properly implemented, CSP serves as a deterrent against XSS, data injection, phishing, click-jacking, and redirect attacks.
CSP offers a high degree of configurability, allowing for tailored settings that can preserve the functionality of custom-built web applications. It's important to note that the strength of a CSP policy can vary significantly, with a strong policy offering robust protection, while a weak or absent policy leaves the application vulnerable to exploitation. In fact, running an application in the browser without any CSP in place provides no protection at all. Hence, understanding and implementing a robust CSP policy is paramount for ensuring the security of web applications.
How To Implement CSP
CSP can be implemented in one of two ways. Either by using by using the Content-Security-Policy header with every response from your application, or by embedding it within the HTML.
To add it to the HTML, use a meta tag placed within the <head> </head>
To return it in every response, we can configure our web server to do it, or have our server-side code add it.
Here is an example in PHP
CSP Implementation Explained
When implementing Content Security Policy (CSP), regardless of the method chosen, the structure remains consistent. CSP is structured with directives that are separated by semicolons (;), with each directive followed by one or more options.
For example, if you have a traditional web application directory setup where scripts are contained within a specific folder and you do not plan to use any third-party scripts, you can define a script-src directive like the following:
script-src
www.mysite.com
This directive instructs the browser to only execute scripts that originate from the specified domain (www.mysite.com). Any scripts from other sources will trigger a CSP error, which can be logged and reported to a designated server. This granular control over script execution helps enhance the security posture of the web application by restricting potentially malicious scripts from running.
Notable Directives
The directives supported by browsers for Content Security Policy (CSP) can vary and are updated periodically to enhance security measures. To ensure that you are adhering to the most current version of CSP directives, you can refer to the World Wide Web Consortium (W3C). The W3C maintains the CSP standard, along with many other web standards, and is a reputable non-profit organization dedicated to developing and promoting web technologies for the betterment of the internet. By consulting the W3C's documentation on CSP, you can stay informed about the latest updates and best practices for implementing CSP in your web applications.
Below is the most notable directives for security:
default-src
This serves as the fallback for other directives and acts as an allow-list from which resources(scripts, images, and CSS) can be loaded. This prevents your website from being able to execute unintended scripts, reducing the chances of XSS or CSRF (Cross-Site Request Forgery) significantly.
frame-ancestors
This directive specifies which web pages are allowed to embed the current web page using HTML <iframe> tags. By setting the frame-ancestors directive, you can prevent click-jacking attacks where another web page overlays your content with deceptive UI elements to trick users into clicking on something unintended. The best option for this directive is 'none'.
sandbox
If this directive is used, it creates a sandbox on page load that stops requested recourses from being able to create pop-ups, execute scripts, or interact with browser plug-ins.
eval and inline script
By implementing these directives, unsafe JavaScript functions such as eval() and inline scripts embedded in HTML are prevented from executing. This proactive measure significantly reduces the likelihood of Cross-Site Scripting (XSS) attacks occurring
report-uri
This directive specifies the location where the browser should report Content Security Policy (CSP) errors for logging purposes.
CSP Sources Explained
Content Security Policy (CSP) directives that end with "-src" typically include options known as a source list. This list consists of entries separated by white space, and it instructs the browser on how to handle resources once they are loaded. It is crucial to have a clear understanding of the implications of each CSP directive before implementing them, as they play a critical role in enhancing the security posture of web applications.
*
The wildcard operator allows any URL to be accepted. For instance, using script-src * would permit scripts from any origin to be loaded. However, employing such a broad wildcard approach is considered an anti-security pattern. This practice is often indicative of hasty implementation by developers and can introduce significant security risks to the web application. It is crucial to avoid indiscriminate use of wildcard operators in CSP directives to maintain a robust security posture and mitigate potential vulnerabilities.
'none'
This prevents any source from loading. Note if a source list has another source and 'none' included, it will cause the CSP policy from failing to load.
https:
This directive specifies that the browser can only load the specified resource if it is accessed via HTTPS. For instance, using img-src https: my-website.com would restrict image loading to only occur when the HTTPS protocol is utilized to access the specified website.
'self'
This directive instructs the browser to only execute resources that originate from the same source as the loaded web page. For instance, if the web page is loaded from mysite.com, the requested resource should also be loaded from mysite.com. An example of this directive in action would be img-src 'self', which restricts image loading to resources originating from the same domain as the web page.
data:
This allows the loading of base-64 encoded images.
Strict CSP
There are cases in which we are building applications the need arises for the ability to run inline scripts. What if there was away to do so without increasing the risk of a XSS Attack?
This is indeed possible with CSP, which can allow us to securely execute inline scripts. This form of CSP configuration is referred as strict CSP because it provides additional security for scripts that run in the browser to protect against XSS, but does not limit our functionality.
We have two methods of implementing a strict CSP policy. The first method is hash-bashed strict CSP, and the other is nonce-based strict CSP.
Hash-Based Strict CSP
Hash-based strict CSP offers a more precise level of control over which inline scripts and styles are permitted to execute, effectively reducing the vulnerability to XSS and other injection attacks. However, this method requires additional effort to generate and manage the hashes for the approved content.
In hash-based strict CSP, SHA-256 hashes are utilized. Each inline script is hashed and added to the CSP directive script-src source list. When an inline script is executed, it is hashed and compared against the hash stored in the source list. If the hashes match, the inline script is allowed to run; otherwise, it is blocked, triggering a CSP error. This process ensures that only pre-approved inline scripts are permitted to execute, enhancing security against malicious script injections.
Here is an example of how to add it to a PHP application
Nonce based Strict CSP
Nonce-based strict CSP offers a method to authorize specific inline scripts or styles to execute or apply while maintaining defense against XSS and other injection attacks, similar to hash-based CSP. However, it diverges in that the nonce is unique for each page load, thwarting attackers from injecting malicious inline content even if they circumvent other security measures.
By setting the CSP Policy to 'strict-dynamic' with 'nonce-{RANDOM}', the browser mandates that inline scripts include a new attribute, nonce, within the script tag. The nonce value is dynamically generated at runtime, and each script is loaded with this unique nonce value.
Before an inline script is executed, the browser verifies if the nonce value of the script matches the value specified in the CSP Policy. If the nonce values align, the script is permitted to run. In cases where the values do not match, the inline script is blocked, triggering a CSP error.
Here is an example below
Finally let's take a final look at a secure CSP Policy
Secure CSP Policy Example
Here is a starter CSP Policy below
Cross-Origin Resource Sharing
Cross-Origin Resource Sharing (CORS) is a crucial security mechanism that is often misunderstood. While Content Security Policy (CSP) enables the definition of which scripts are permitted to execute in the browser, CORS provides an additional layer of security by regulating requests at the network level.
CORS plays a significant role in security due to the primary methods used by JavaScript to make network requests, namely fetch and XMLHttpRequest. These APIs adhere to the Same Origin Policy (SOP), which dictates that a web application should only send requests to the same origin unless explicitly allowed by a CORS policy.
The SOP is fundamental for security as it ensures that a script can solely interact with resources that share the same origin. This restriction helps mitigate the risk of potential malicious scripts or sites exploiting vulnerabilities by limiting the interaction between scripts and resources to those originating from the same source.
Understanding Origin
URLs are considered to have the same origin if they share the same protocol, host, and port.
Lets say our web application URL is app.example.com:443/index.html, and we make some request. Below is chart of request made and we see if it passed or failed the SOP Policy.
Now that we understand SOP Policy and the role origin plays, we can explain how CORS fits in.
Understanding CORS
CORS give a way to bypass SOP Policy, and enables us to make specific cross-origin requests without degrading our security posture. There are two methods in which a CORS Policy can be checked. These are called simple CORS and preflight CORS.
Simple CORS Request Explained
A simple CORS request can only be a POST, HEAD or GET, and must follow some specified conditions. One of these conditions is it must contain these four heads.
Accept
Accept-Language
Content-Language
Content-Type
The next condition is the Content-Type header must be one of three values.
application/x-www-form-urlencoded
multipart/form-data
text/plain
The final condition is that when using XMLHTTPRequest cannot use advance network request features like the event listeners.
Preflight CORS Requests Explained
Any request that does not match the criteria of a Simple CORS request, is considered a preflight request.
When a preflight request, the browser will send a preflight check, which is an HTTP Options request that includes the following headers.
origin: <source-origin>
access-control-request-method: <allowed HTTP method>
access-control-request-headers: <allowed HTTP Headers>
When the request is sent, if any of these checks fail, the browser network stack will not return the response to the JavaScript execution context. This will throw a CORS error.
CORS In Action
Now lets break down a preflight CORS request in detail.
In our JavaScript code we use fetch API to make a request to an endpoint.
The browser will before sending a HTTP GET request, it will send the preflight request the HTTP OPTIONS request.
The server will have cors middleware enabled, and it could look like this
When the server receives the request and the cors middleware will check its configured policy.
Once checked it will send a back a response.
If the response passed the checks, the response will be passed to the JavaScript execution context.
Now the client will send the intended HTTP GET Request.
Strict Transport Security
The HTTP Strict Transport Security (HSTS) header tells the browser to load the page only with HTTPS and subsequent request to also use HTTPS. This reduces the chance of man in the middle attacks.
HSTS is structured as follows
max-age
The time, in seconds, that the browser should remember that a site is only accessed with HTTPS.
includeSubDomains
Optional directive, this applies that your subdomains also be accessed with HTTPS.
preload
Optional directive, can only be used if max-age is equal to 1 year (31536000 seconds) and includeSubDomains directives are used*.* To use preload we must register our domain with Google's Preload List. When preload is configured it will instruct Chrome and Firefox browser to look up the HSTS policy.
Cross-Origin-Opener Policy
Browsers maintain browsing contexts, which are environments where web content is displayed and interacts with users. Each tab or window in a web browser typically represents a distinct browsing context. These contexts have their unique properties and capabilities, allowing actions like navigating to URLs, accessing the Document Object Model (DOM) of web pages, and executing scripts.
The sharing of browser contexts can be controlled using the Cross-Origin-Opener-Policy header. This header allows for the restriction or expansion of the sharing capabilities. The directives that can be used include:
unsafe-none
This allows for sharing of the browsing context with the document or page that opened the current page, this is the default value if the COOP header is not defined.
same-origin-allow-popups
Allows for sharing of the browsing context with pop-ups from the same origin.
same-origin
Restricts sharing of browser context to only web-pages of the same origin.
It is best to set the Cross-Origin-Opener-Policy header to same-origin as this prevents new tabs, windows, and other browsing contexts from being to navigate back to the opening context. This is great for security because it prevents information leakage.
Cross Origin Resource Policy
The Spectre vulnerability (CVE-2017-5753) was a significant security flaw identified by researchers, exploiting side-channel timing attacks on contemporary CPU architectures. This exploit allowed unauthorized access to memory and state data from other tabs using JavaScript.
To counteract such attacks, the Cross-Origin-Resource-Policy (CORP) header can be employed. This header governs the accessibility of resources within the browser environment. The directives for this header include:
same-origin
Restricts resource access to the same origin as the requesting document.
same-site
Limits resource retrieval to the same site as the requesting document, bolstering protection against cross-site request forgery (CSRF) attacks.
cross-origin
Permits resource fetching from any origin, least secure option.
CORP is an option header, but should be considered for use because it will enforce memory sharing at a lower than possible with JavaScript. This can help from other attacks that compromise the clients of our applications.
Legacy Security Headers
There are many older security headers, but many of them have been migrated to CSP. If we are working on a Legacy Web Application we should understand what security headers are available to us.
X-Frame-Options
This is equivalent to frame-ancestors for CSP.
X-Powered-By
Not necessarily a security header, but should be disabled. This header is often attached to responses by the web server. This allows for easy identification of server software, and version.
X-Download-Options
This header is only supported by Internet Explorer, it is used to instruct the browser how to handle downloaded files. Since Internet Explorer is deprecated, this would be used only in the oldest of web applications.
X-XSS-Protection
No longer supported, would instruct the browser to scan to for reflected XSS.
Expect-CT
No longer supported, would instruct the browser to enforce certificate transparency requirements. All Certificates after May 2018 support CT by default.
Referrer-Policy
Legacy Browsers will send origin, path, and query string to the same site, but origin to other sites. We can disable this by setting our Header as such: Referrer-Policy: strict-origin-when-cross-origin.
Securing Cookies
Emphasizing the significance of cookie security cannot be overstated. It is crucial to leverage all available mechanisms and best practices to protect cookies effectively. Failing to do so not only puts our users at risk but also poses a threat to the reputation and integrity of our organization. It is imperative that we prioritize cookie security as a fundamental aspect of our overall security strategy to safeguard user data and maintain trust in our services.
Cookies Explained
While there are new methods available for storing data on the client-side, cookies remain a prevalent choice for many web applications as the primary means of data storage. Cookies consist of small pieces of data that are sent to the server with each request. This data can encompass user preferences, session details, and user analytics tracking.
Of particular importance is the storage of session information. In a typical workflow, once a user logs in and is authenticated on a website, the web server generates a session ID or authentication token. This identifier is then stored in the browser's cookies. Subsequently, when the user makes additional requests, the cookie containing the session ID or auth token is transmitted to the server, enabling it to retrieve pertinent information associated with the user's session. This mechanism plays a crucial role in maintaining user authentication and session management within the web application.
The Header we use to issue the sessionID or auth token is the following:
Securing Cookies Via Attributes
The primary we secure our cookies is through attribute flags. These attribute flags allow us to stop insecure behaviors of the browser. This will increase the integrity and confidentiality of our cookies. The attributes we will use are as follows:
Secure
This attribute should be used at all times. It prevent cookies from being sent over unencrypted channels i.e. HTTP.
HttpOnly
This attribute stops JavaScript code from being read cookies. Preventing XXS attacks attempting to steal cookies.
Path
By default cookies are sent on request that are made to the current domain. If we use Path, we can restrict the browser from sending cookies on request to our domain that don't match a certain path. For example path=/page.html
Expires
Will instruct the browser to delete the cookies by a certain date.
SameSite
Will instruct the browser if we wish to allow cookies to be sent with cross-site requests. Setting it to strict will only send cookies to the site that set them.
Secure Cookie Example
Summary
Understanding the role and functionality of headers is indeed crucial in enhancing the security of web applications while ensuring that essential features remain intact. Proper configuration of headers can significantly bolster the security posture of web applications, mitigating potential vulnerabilities and safeguarding user data.
By staying informed about the various headers available and their impact on application security, developers can make informed decisions when selecting and configuring headers to strike a balance between security and functionality. Continual learning and staying updated on best practices in web security are essential for maintaining a robust defense against evolving threats in the digital landscape.
I encourage readers to subscribe to my newsletter to stay informed about the latest trends and insights in web security.