Web Security Fundamentals
An introduction to various vulnerabilities of the web.
Table of contents
- Cross-Site Scripting (XSS)
- Cross-Site Request Forgery (CSRF)
- Third Party Assets
Cross-Site Scripting (XSS)
Begins with an X because CSS already means something.
It’s an injection attack, we are putting content in place which was meant as text, tricking the system to treat it as code and execute it.
Many websites are vulnerable to this type of attack.
NJCCIC Threat Analysis Report:
According to cybersecurity firm High-Tech Bridge, XSS accounts for 80% of website security flaws. In a recent study, another security firm, Tinfoil Security, tested the networks of 557 state universities and discovered that 25 percent were vulnerable to XSS.
If this happened, it allows the attacker to do a lot of things, here are some of them:
Read and write the content of your page
Redirect people to a different site, fake login pages (leading to stealing user’s credentials)
Read any data stored in your site's Cookies or LocalStorage
Send reg to a database, could for example be by registering an account and the username has a script tag in it.
A temporary response from a server, you can trick into having some code that executes on the page, could for example be a validation error message saying I’m sorry, you may not sign up for an account because
<script> is not a valid username, and then it would allow that to execute.
DOM Based XSS
Here no server is required, could for example be by passing code through the QueryParams.
This attack is a variant of the Stored XSS attack, it exploits vulnerabilities in another app, that the attacker can’t see or access under normal circumstances. For example, an attacker injects malicious data into a feedback page and when the administrator of the application is reviewing the feedback entries the attacker’s data will be loaded.
Compared to other XSS attacks, it can take up to days, weeks and even years for Blind XSS attacks to be executed, because such attacks will be executed when the administrator or moderator visits/views the vulnerable part, hence they are hard to detect and test for.
The attacker is also behind the firewall, being able to access data that requires user moderation, hence they are also able to do interesting things during such attacks.
Locations for XSS attacks to be executed:
When Query Parameters are rendered into DOM
Where users can generate rich text (i.e. drop an image)
Where users can embed content (i.e. embed iframe)
Anywhere where users input are reflected (i.e. validation error message Couldn’t...)
Where element.innerHTML is being used (innerHTML renders complete markup and not just text)
Ways to minimize the risk or prevent XSS attacks.
Never put untrusted data, the input of users, in these places:
- In a script:
<script> <%- data %> </script>
Anything in the script tags will obviously be treated as a script.
- In a HTML comment:
<!-- <%- data %> -->
Even in HTML comments, or some other tags that are usually not executed, the user can enclose the beginning of the comment and have the other part of their data being the actual script, that way it can get executed.
- In an attribute name:
<iframe <%- data %>=”value” />
In this case, what if we had an image, and instead of the src attribute, the script gets downloaded.
- In a tag name:
<<%- data %> class=”element”>
This is an extremely easy way for users to insert a script that will get executed, don’t do that.
- In a style block:
<style> <%- data %> </style>
This is very tough to do in modern browsers, but very easy in old ones, in places where you have a URL that can turn into a GET request.
Sanitizing User Data
There are two things you can do. Sanitize data before it is persistent or before it gets rendered onto the screen. Usually, you want to do both.
Escaping data before putting it in HTML, is something most modern frameworks and view libraries do, React, Ember, Vue, EJS, etc. and you have to use an unsafe way in order to have the functionality of innerHTML.
<script>alert(‘hey’)</script> will turn into
Content Security Policy (CSP)
The reason we have CSP is that browsers can’t tell the difference between code that is brought from a URL on a different domain versus code that we’re serving up ourselves.
Once we fetch that resource and process it, in the end, it’s all really a bunch of code that’s sitting in the same execution environment.
CSP allows us to inform modern browsers from where it’s allowed to receive stuff, from where it’s allowed to fetch resources, and for what types of resources.
An example would be only allowing scripts that come from the domain naruto.com.
CSP is set via an HTTP response or meta tag:
Content-Security-Policy: script-src ‘self’ https://naruto.com
In order to have multiple directives, you need to separate them via semicolon ;, such as:
Content-Security-Policy: script-src ‘self’ https://naruto.com; font-src: https://fonts.google.com
This is the same example as the first one, but we also allow font resources from the domain fonts.google.com as well.
CSPs preferred delivery mechanism is an HTTP header, but as mentioned, it is possible to set a policy on a page directly in the markup using the meta tag with an http-equiv attribute.
The last example would be equivalent to:
<meta http-equiv="Content-Security-Policy" content="script-src ‘self’ https://naruto.com; font-src: https://fonts.google.com">
When you start dealing with attachments, you can embed some dangerous stuff in them. Things that we usually see as inert documents, PDF, Images, etc.
For example in image files, there are non-visible raw data, in such data attackers can embed code or scripts. If users have the ability to rename their images, the situation could occur where the image gets treated differently from the browser (if it is of a different file type than the image ones), and the embedded code or scripts could get executed.
One way to minimize the risk of such an attack happening is to be more restrictive on the file upload types and the ability to access those types.
Compress your images. Generally, software that compresses images removes non-visible data.
Cross-Site Request Forgery (CSRF)
CSRF happens when the attacker takes advantage of cookies or basic authentication credentials that are passed along with requests.
The attacker would cause the user's web browser to perform an unwanted operation on a trusted site when the user is authenticated.
CSRF exploits the trust that a site has in a user's browser, compared to XSS which exploits the trust a user has for a particular site.
Who is vulnerable
You are vulnerable to CSRF attacks if your server looks to a cookie or basic authentication credentials sent along with the request in order to authenticate or authorize the user.
A client-side cookie is an exception. For example, if you pick something out of a client-side cookie that then needs to be put into a request header, this process is not vulnerable to CSRF, because you need to be running code on a domain that can access that cookie.
CSRF attacks work because you can write code on your site that will make a request to someone else’s site, although you can’t read the client-side cookies, you can take advantage of the cookies that are sent along with the request.
Local and session storages are alternatives that don’t have this vulnerability because neither of these is passed along by default with each request.
A way to prevent CSRF attacks is by generating a token that is unique and unpredictable, a token that the server sends the client with each page load.
The server knows the tokens it has generated, hence it will reject the request if the token is either missing or invalid.
This prevents attackers from making a fully valid HTTP request because they can not determine or predict the value of the user’s CSRF token.
Modern browsers send an Origin request header that we can validate, which determines where a request originates from.
Cross-Origin Resource Sharing (CORS)
By default, the browser's same-origin policy blocks reading a resource from a different origin.
By setting our CORS header properly, we can allow the browser to send a request from one domain to another (from an origin to another).
Most of the time the browser first sends an HTTP request using the OPTIONS method to the resource on the other origin, such a request is called “Preflighted request”. It does this in order to check if the actual request is safe to send before sending it.
Clickjacking, often described as UI redress attack or UI redressing, happens when the attacker tricks the user into clicking on an invisible element or an element disguised as another element.
Usually, clickjacking is done by displaying an invisible page or element, inside an iframe, on top of the page the user sees, tricking the user that they are clicking on the visible page, though they are clicking on the invisible/disguised element in the additional page interchanged on top of it.
An example would be a page on the user’s banking site that authorizes the transfer of money.
In modern browsers, you can use the
X-Frame-Options HTTP header to determine whether or not a browser should be allowed to render a page in a
There are two possible directives for
If you specify
DENY, the page cannot be displayed in a frame, despite the site trying to do so.
SAMEORIGIN on the other hand, allows the page to be displayed in a frame of the same origin as the page itself.
Third Party Assets
Auth0 has a wonderful blog post on this and reducing the risks of third-party dependencies.
A vulnerability in a third-party asset becomes a vulnerability in your application.
In case you don’t want to read the whole blog post, I shortened it, the key points, though the post has much more to offer.
It mentions 4 steps to take in order to reduce the risk of introducing vulnerabilities.
Assets inventory: Have a look at the third-party dependencies you are using. The packages installed are often to be found in the node_modules folder. Scripts or stylesheets should also be taken into account, whether you include a bundled copy of them or a link to a CDN.
Dependencies analysis: Once you have listed the dependencies you are using, you should analyze them for vulnerabilities.
Risk assessment: If security risks are detected, you need to take a deeper look.
Questions to ask before you decide what to do:
- How severe is that vulnerability?
- Is there any available fix?
- What is the effort to fix the vulnerability?
- Is this vulnerability relevant to your application?
Risk mitigation: If a solution exists to fix the vulnerability, you may fix it. If no solution exists, you have two options:
Collaborating with the author/vendor of the dependency to solve the vulnerability.
Find a workaround to prevent the vulnerability exploitation.
By using third-party dependencies, you are taking responsibility for the code you did not write.