In the process of developing secure web applications, you need to consider a wealth of factors. Being a responsible and productive developer requires extensive expertise in engineering fundamentals, development tools, system architecture, integration, and security. It is that last one that we want to put the spotlight on today.
There has never been a better time for bad actors to profit from insecure platforms with large user bases. So it's essential to ensure that our web platforms are sound and protected from the threats that abound in the wild west of the internet.
This article is part of a series on web security that help developers strengthen their security and appropriately mitigate risks.
One such risk mitigation measure is the implementation of a proper Content Security Policy, or CSP. The objective of this article is to help any developer who needs to demystify content security. We'll quickly define CSP, demonstrate how to enable it in platforms using Vue.js, and examine potential errors and explain how to address them.
We'll be using Node.js as our platform for our Vue project. If you have little experience working with Vue.js or Node.js or if this is your first time hearing about them, we recommend that you dedicate some time to explore this source. Even though you can apply the information here to any technology stack, you might not recognize everything in it.
Let's jump right in.
Explaining Content Security Policy
Content Security Policy, or CSP, revolves around how the browser uses the resources requested by the domain. Thus, a Content Security Policy can be defined as a set of policies or instructions provided to the browser to enforce specific rules on web pages.
During the page loading process, the browser requests and renders many resources and executes a lot of code. All modern websites nowadays are by nature intricate and complex. The average web application comprises thousands of lines of HTML, CSS, JavaScript, and other resources like images and files. This code usually references other resources that need to be requested and processed by the browser that could come from the same origin (requested domain) or another origin.
Naturally, the browser must not execute any malicious code or access data that does not belong to the current website as this might open a potential door for exploits.
To prevent these exploits, Content Security Policies exist as a collaboration between major browser makers. A CSP works by allowing the server to provide a response header telling the browser to only execute resources vetted by a legitimate source. This provides a security layer that prevents attackers from taking advantage of vulnerabilities like cross-site scripting (XSS) and injection attacks.
Examples
A basic Content Security Policy header would look something like this:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self'; frame-src 'self';
Notice that each section in the header relates to a specific kind of resource. These resources may be images (img-src), scripts (script-src), styles (style-src), or something else.
Additionally, the 'self' directive declares that the browser can execute only resources from the same origin. You can expand the specificity of the policy by appending specific domains that you want to retrieve resources from. To do this, append their URLs next to 'self.'
Finally, you can allow all domains by setting '*', but don't do this unless you absolutely have to.
Content Security Policy Implemented
Now that you're familiar with the basics of a Content Security Policy, let's look at how you can implement it in your code.
Implementing an essential CSP in Vue.js is relatively straightforward as it merely involves ensuring that our server is providing the correct header on all responses. To achieve this, open your js file, which contains the application server logic, and append the following code.
app.use(function (req, res, next) {
res.setHeader(
'Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self'; frame-src 'self'"
);
next();
});
You should now see the following:
It's as simple as that. You can confirm that the response header contains the configuration mentioned above.
If your website looks broken after this, don't panic. This is to be expected once you implement the policy and corrections have not been implemented yet.
Achieving proper Content Security Policies requires a decent amount of testing and corrections. However, you don't need to keep your site broken to work on it. You can change the header to use the 'Content-Security-Policy-Report-Only' directive, which will produce alerts without enforcing the policies. This feature is beneficial for development environments where the platform's security is not essential but where the developer needs to be aware of any infringements so they can be addressed appropriately.
Solving Content Security Policy Violations
Now, you might feel overwhelmed with all the error messages the browser developer console displays. Don't fret. Addressing these issues is very straightforward and fast, especially since you can see what your site demands.
First, confirm that the resources that the alert reports are, in fact, legitimate and necessary for the proper functioning of the application. For example, you can see that your application is requesting the following:
https://code.jquery.com/jquery-3.5.1.min.js
https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js
https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.31/moment-timezone-with-data.js
https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js
https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.bundle.min.js
https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js
https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.woff2
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.woff
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.ttf
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.woff2
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.woff
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.ttf
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.woff2
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.woff
https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.ttf
There are valid resources from trust sources. Therefore, having the resources at hand, you can add them to the allow list of each respective source.
app.use(function (req, res, next) {
res.setHeader(
'Content-Security-Policy-Report-Only', "default-src 'self'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self'; frame-src 'self'"
);
next();
});
This simple change makes the alerts go away.
Neat!
Furthermore, if you want to allow all resources from a specific source domain, thus including many files in just one line, you can specify the common domain in the directive.
app.use(function (req, res, next) {
res.setHeader(
'Content-Security-Policy-Report-Only', "default-src 'self'; script-src 'self' https://code.jquery.com/jquery-3.5.1.min.js https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.31/moment-timezone-with-data.js https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.bundle.min.js https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js; style-src 'self' https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css; font-src 'self' https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.woff2 https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.woff https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-brands-400.ttf https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.woff2 https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.woff https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-regular-400.ttf https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.woff2 https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.woff https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/fa-solid-900.ttf; img-src 'self'; frame-src 'self'"
);
next();
});
Violations in In-Line Code
But what about the in-line HTML code? There are some things you can do to address these violations.
The first (and recommended) strategy is to move all in-line code and styles to a separate file and reference it. Doing so will ensure the security of your application and keep it tidy and consistent.
However, if this is not possible, you can move the code to a <script/> or <style/> tag and use a SHA hash key to tell the browser that the code is allowed. You can generate the hash key by hashing the code inside the tag with a SHA256 algorithm. Thankfully, the browser has already provided you with these hash keys in the alerts on the developer console. So you can easily add them to the directive.
app.use(function (req, res, next) {
res.setHeader(
'Content-Security-Policy-Report-Only', "default-src 'self'; script-src 'self' https://code.jquery.com https://cdnjs.cloudflare.com https://stackpath.bootstrapcdn.com https://cdn.jsdelivr.net; style-src 'self' https://stackpath.bootstrapcdn.com https://cdnjs.cloudflare.com; font-src 'self' https://cdnjs.cloudflare.com; img-src 'self'; frame-src 'self'"
);
next();
});
Furthermore, if you don't want to use the SHA hash, you can use a 'nonce' tag attribute and add it to the corresponding tag. This will serve the same purpose, but you must update each request with some server-side code.
Moving Forward
Mitigation strategies can only do so much. And for platforms that provide essential services or handle sensitive data, we need to go the extra mile to ensure the integrity and security of our platforms to our users and our stakeholders.
That being said, it's crucial to understand the tradeoffs involved in the field of risk mitigation and security implementation on the web. Developers need to be up to date with the latest developments in the market and understand the risks the platforms they are responsible for are exposed to.
Security and integrity don't have to be complex or overwhelming. That is why we recommend considering robust, well-tested solutions like StackHawk. If you want to learn more about it, please visit our home page.
This post was written by Juan Reyes. Juan is an engineer by profession and a dreamer by heart who crossed the seas to reach Japan following the promise of opportunity and challenge. While trying to find himself and build a meaningful life in the east, Juan borrows wisdom from his experiences as an entrepreneur, artist, hustler, father figure, husband, and friend to start writing about passion, meaning, self-development, leadership, relationships, and mental health. His many years of struggle and self-discovery have inspired him and drive to embark on a journey for wisdom.