Web security
Documenting known web security issues, and ways to counteract them. Notes from Mike North’s frontend masters course on web security.
Cross site scripting (XSS)
Injecting code into an app, say through an input field, which will affect the behaviour of say another page in an adverse way.
Different types of XSS
- Stored XSS — Code that gets stored against a record in the database.
- Reflected XSS — Code that gets executed on the page, (say when a field validates and accepts user input in the validation response).
- DOM Based XSS — No server, e.g. code accepted through query string params.
- Blind XSS — Code that gets executed on backend systems, (in house), which may have less security.
WYSIWYG editors, inputs, query string params and embedded content are all areas where XSS attacks can be tried.
To counteract this make sure input is decoded, and then encoded when displayed to the user. This gives you two layers of defence.
Never add direct user data between; script tags, style tags, in HTML comments, in attribute names and in tag names.
Chrome also has a built in XSS filter to counteract a DOM based XSS attack via query string params.
Content security policy (CSP)
Setting this http header will tell modern browsers which domains the app can trust, e.g. CDN domains, api sub domains...
Setting unsafe-inline
will prevent script tags which are embedded in HTML. However sometimes you want this, for these exceptions you can set a nonce
or a checksum
, against the script-src
property of the CSP.
Helmet JS
Popular javascript library for adding security headers to requests.
Cross site request forgery (CSRF)
Getting a user to hit an endpoint, (e.g. GET/POST), that they wouldn’t want to hit, but are able to due to being authenticated, (maybe with a cookie, or basic authentication, that has not expired).
Naive example, email with an image tag with a src
property embedded. If the user has a valid auth cookie, this will execute a GET without the user even knowing. In this example, transferring funds from one account to another.
<img src="http://localhost:3000/transfers/perform?accountFrom1&accountTo=2&amount=1"/>
Fake forms could also be created with hidden fields, and getting a user to click a button or link will POST/PUT the data.
To counteract this, instead of passing authorisation within a cookie that is passed with every request, read the cookie on the client and pass as an authorisation
http header. You can then authenticate against that on the server, (and not the cookie). The reason why this is more secure is that; to be able to read that cookie, you need to be running code on a domain that can access that cookie. So if you had a fake form hosted on a domain that will inevitably be different than the target, you will not be able to read that cookie to set the authorisation
header. Authorisation information could also be stored in local/session storage, (maybe as a JSON Web Token (JWT)), and passed to any requests via the authorisation
header.
Another way to counteract this type of attack is via a CSRF Token. This will create a hash/nonce, which will change on every request, (hard page reload in SPA). Any subsequent requests will only be accepted if sent along with that valid token. In the case of a SPA, this token will need to be stored in some client side state, so it can be sent along with multiple requests.
Another security measure would be to validate against the Origin
or Referer
header on the server. This can be changed in the browser, but when the request goes out it will get changed back by the browser.
Another security measure is setting your Cross-Origin Resource Sharing (CORS) headers correctly. This works by the browser sending a preflight
request to your server, which responds with information about what requests are allowed, such as; which http verbs, what headers and from which origins. If there are requests that do match this criteria in your page, you will receive a CORS error, and the request will not be made.
Clickjacking
Hosting a site with a similar domain to the target. Then positioning a fake page over an iFrame containing the actual content to get users to click a button/link they do not intend to.
An example would be if a form could be populated via query string params and the user is already authenticated via an uncleared session. And then that form loaded in an iFrame and positioned behind a fake page which tricks the user into clicking the submit button.
To counteract this you can set X-Frame-Options
http response header so that the site cannot be added to an iFrame. Possible options are DENY
, SAMEORIGIN
and ALLOW-FROM...
. Allow-from is not supported in some browsers, (use frame-ancestors
).
3rd party package validation
Can you trust the CDN you are referencing on your site? They might intentionally or accidentally inject code into an existing resource.
Some packages also ask you to paste code snippets into the root file of your app, (like Google analytics). These often call out to scripts which can be changed by the provider on the fly.
Lock files can be used within projects to lock packages to a specific version, (LTS versions ideally).
Contract tests can be written against specific functions of a 3rd party library used in the app.
To prevent references to CDNs from being changed, you can use Sub Resource Integrity (SRI)
, and set the integrity
attribute on the script tag to a hash of the original resource. This will mean when the browser pulls down the resource, it will compare against the hash, and if it does not match, (i.e. the 3rd party asset has changed), it will not load the resource. Can be used for imported styles as well as scripts.
synk can be used to scan 3rd party assets for your project and report on any known vulnerabilities, (can be added to build pipeline).
Man in the middle attacks
Sit in the middle of client and server and manipulate responses. A lot of devices will remember past network names, periodically search and connect automatically. Devices will announce what networks they are looking for. These can be recorded and fake networks with the same names created. Garbage packets can then be sent between the client and the existing router, to get the client to disconnect. The client will then search for other known networks and automatically connect to the fake network created. All traffic on plain http can then be monitored and responses changed.
Mobile signal also not secure, as you could be connecting to a FemtoCell, (mini phone mast), which could be compromised.
To prevent this, data must be encrypted using SSL/TLS certificates. Enhanced Validation certs just mean more validation about the company is done before a certificate is issued, (a way for cert companies to still be able to make money now that certificates can be obtained for free with Lets Encrypt).
TLS Handshake
- Client: I want to communicate securely via one of these ciphers.
- Server: Lets use X cipher. Here is my cert. Here is my public key.
- Client: Cert verified by browser. Session key is generated, encrypted with public key and sent. Everything from now on will be encrypted with session key.
- Server: Decrypts session key using private key. Everything from now will be encrypted with session key.
HTTPS Downgrading
This is when the site is setup with a permanent redirect, (301), for traffic from port 80, (http), to port 443, (https). The issue with this, is that if the initial request is made over port 80, a man in the middle attack is still possible as you can intercept that initial request before the server is hit and a redirect occurs.
There are tools to do this automatically called sslStrip and sslSplit.
You can then maintain a http conversation with the client, forwarding requests, and maintain a https conversation between yourself and the server.
Downgrading with a bad cert
You can serve up a fake certificate, which will not be valid, (as it has not been authorised against the domain), but can be made to look genuine by adding a company name, address etc… This will produce a browser warning, but users are still generally given the option to continue.
This can be counteracted by setting Strict-Transport-Security
http header. But this will only work for a user after the initial response, not for a user hitting the site for the first time, (the first request).
To counteract the initial request, we can use HSTS, (HTTP Strict Transport Security), HSTS Preload list, which is essentially a big list of domains which the browser vendors query, to see if a http call is allowed. If your domain is in there, you will get a browser error and you will not see the option to continue anyway.