https://blog.vnaik.com/posts/web-attacks.html
Vnaik.com
CSRF, CORS, and HTTP Security headers Demystified
With an increasing number of breaches, intrusions, and data thefts,
securing a web application is extremely important.
On the other hand, programmers often do not have a strong grasp of
how attacks work and how to mitigate them. This post attempts to
close that gap a little.
CSRF
Cross-Site Request Forgery is an attack where a third party forces a
user to execute actions against a site where they are currently
logged in.
Here is an example illustrating how this works:
1. You visit evil.com
2. evil.com has a hidden form that submits on page load to
mybank.com/transfer-funds. Since you are logged in to mybank.com,
this request is made with your mybank.com cookies and will
silently initiate a money transfer out of your account.
3. Since 'evil.com' and 'mybank.com' are different origins, the
browser refuses to provide the response to evil.com (because of
CORS), but the attacker doesn't care, the money's already been
transferred.
Now if mybank.com implements CSRF protection correctly:
1. Each time mybank.com serves a form to a user, it generates a CSRF
token and inserts it into a hidden field in the form
2. If a POST request is received, it checks the CSRF token against
its database - if this is present and valid then the request goes
through. If the CSRF token is missing or incorrect it is
rejected.
CSRF attacks target state-changing requests and not the direct theft
of data because the attacker does not see the response of the forged
request. The CSRF token provides security by being a stateless token
that is never stored in the cookies or permanent storage - doing so
would defeat the purpose of the token.
CSRF protection support needs to be added to your application code
and cannot be added at the proxy server layer (eg. in Nginx). A
detailed look at CSRF from OWASP
It is good practice to always use the SameSite directive with cookies
as this provides protection against CSRF attacks.
CORS
Cross-Origin Request Sharing only applies in a browser context and is
a security mechanism to allow one origin to make a request to another
origin. All browsers follow the Single Origin Policy, meaning by
default scripts cannot make requests to other origins - but if the
server provides properly configured CORS headers this policy can be
selectively relaxed. Thus CORS is a way of selectively loosening
security and not of tightening it.
When a website makes an XHR request to another origin, the browser
initiates a preflight OPTIONS request first - and the original
request is only made if the server responds to this preflight with a
list of allowed origins, and this list contains the origin of the
current page.
Note that CORS preflight requests are not made for GET HEAD POST
requests with default headers.
Some key headers sent as a response to an OPTIONS request:
* access-control-allow-credentials: If set, cookies are sent by the
browser
* access-control-allow-origin: The list of origins allowed to make
requests, or '*' to allow anyone to make requests. If
access-control-allow-credentials is set then this cannot be set
to '*' or the browser will reject the request anyway
* access-control-allow-methods: The list of HTTP methods allowed to
communicate - POST, PUT, etc.
The reason access-control-allow-origin cannot be '*' when
access-control-allow-credentials is set is to prevent developers
taking the shortcut of adding a * and then forgetting about it
altogether - this behaviour forces developers to think about how
their API is going to be consumed.
CORS is often a code smell of sorts - for example, a common use of
CORS is to allow multiple properties access to one API, like
mybank.com being accessed by mybankpersonal.com and
mybankbusiness.com - and in these cases, CORS can be avoided
altogether with the use of an API gateway. If all web pages are
served from the same domain (mybank.com), no CORS headers need to be
set and the browser can apply the full Same Origin Policy and provide
maximum security. After all, CORS only serves to selectively decrease
security not increase it.
On the other hand, using CORS is unavoidable if you are providing an
API for third parties to consume from a browser (eg. after going
through an Oauth process).
Of course, CORS is irrelevant for requests made outside the web
browser, eg. with Curl or with server-to-server communication.
XSS
Cross-Site Scripting is an attack where an attacker injects a
client-side script into a web page. XSS can be used to bypass both
the same-origin policy and CSRF protections.
This is done either by compromising the server (eg. by using a known
exploit) or by leveraging poorly-sanitised user inputs to add a
script to the website, which is triggered whenever a user visits the
website.
This is the most commonly exploited vulnerability and can be fixed
with careful sanitisation and encoding for display of all user input
- even things like email addresses could be vectors for script
injection. For instance.,
">"@example.org
is a valid email address by RFC 5321!
XSS is best sanitised on the output side, this way raw data is stored
in the database as input by the user and is then escaped to an
appropriate format at the time it is sent out again - for example, if
the result is to be sent as part of HTML page the escaping will be
different from if the result is to be sent as JSON or included in a
script tag in the HTML page. The escaping should be appropriate to
the output format. Additionally, if content is escaped before insert
into the database it won't be secure against future changes to
business requirements - for example, if the business decides to serve
data through a REST API when it was previously only used in a HTML
page, the encoding would now be irrelevant.
Care must be taken to ensure the sanitisation is done safely - it may
be possible to easily bypass sanitisation and several such attacks
exist. For instance, it may be possible to simply add text such as:
ipt>alert(1)
where the sanitisation filter strips out the script tag, but in doing
so causes the split script tag to come together and the attack to
succeed.
OWASP has a comprehensive section on XSS sanitisation bypasses.
The OWASP cheat sheet on XSS prevention is a good starting point for
securing an application against XSS.
CSP
Content Security Policy is an added layer of security to detect and
mitigate XSS attacks. By specifying the domains that should be
considered valid sources for scripts, a web browser will only execute
scripts loaded from these white listed domains. CSP can specify
allowed origins for all dynamic origins, including scripts,
stylesheets, images, fonts, objects, media (audio, video), frames,
and even form-actions.
CSP is set through the Content-Security-Policy HTTP header.
The difference from CORS is that CORS prevents a third party from
accessing a server, while CSP prevents a website itself from loading
content from a third party, as a defence against XSS.
CSP is not a silver bullet against XSS but it helps. Ultimately XSS
needs to be prevented with careful application design and effective
user input sanitisation, both on the frontend and the backend.
Of all the mitigations on this page, CSP is the most complicated
because of the depth of its options and also likely the hardest to
implement because every time a front-end dev adds a new CDN - for
fonts, scripts, whatever - that CDN needs to be added to the CSP
header. Without a well-designed test setup, this could work fine in
dev but break when pushed to production.
CSP is complicated enough to require more than a few paragraphs - a
full page would do it more justice.
The Content Security Policy web site describes how to add CSP support
to your web server and Google's handy test tool can help test these
policies.
HSTS
HTTP Strict Transport Security protects against protocol downgrade
attacks and cookie hijacking by allowing a server to declare that
browsers should only interact with it using HTTPS connections and
never HTTP; and the browser should always convert all attempts to
access the site using HTTP to HTTPS instead, eg. by rewriting all
links to use HTTPS instead of HTTP.
This guards against eavesdropping and man-in-the-middle attacks that
work by transparently converting a https connection into plain http.
For example, you might connect to a free wifi service at the airport
to access your bank website, but the access point is actually a
hacker's laptop that performs a MITM attack and redirects you to a
clone of the bank's site. HSTS provides security in this scenario as
long as you have accessed your bank's web site at least once over
HTTPS (and the bank uses HSTS), the browser will reject this request
outright.
This page has details on configuring HSTS for Nginx.
HPKP
HTTP Public Key Pinning allows HTTPS websites to resist impersonation
using fraudulently obtained SSL certificates by providing the browser
a set of public keys which are the only ones to be trusted for future
connections to the same origin.
This provides security against a compromised certificate authority as
has happened in the past with Comodo.
In the airport scenario above, if the hacker also had a fraudulently
obtained certificate for the bank, HSTS alone would not protect you -
but with HPKP, even this attack can be mitigated. This is a
Trust-on-first-use technique.
HPKP will eventually be superseded by the Expect-CT header. More on
HPKP.
Set-Cookie
This is a fairly standard header and by far the oldest on the page,
yet is one of the most important (and easiest) to configure
correctly.
This can be done by setting a few directives.
* The SameSite: Strict directive protects against CSRF attacks by
stripping cookies from all cross-domain requests.
* The Secure directive ensures the cookies are only ever used in a
https connection - thus providing additional security over what
is provided by HPKP and HSTS.
* The HttpOnly directive ensures JavaScript cannot access the
cookie.
You will as a rule want to set all three. More on Set-Cookie
Other Headers
These are generally less important to set but good to be aware of.
* X-XSS Protection: This header is outdated and possibly harmful
and CSP does a better job of providing protections. The main job
of this in Chrome is to trigger the XSS auditor which has been
deprecated for being insecure.
* X-Frame-Options: This header indicates whether the site should be
allowed to be displayed within an iFrame. This has been
superseded by CSP's frame-ancestors option which is better
supported in modern browsers.
* X-Content-Type-Options: This prevents the browser from attempting
to guess the type of content if the Content-Type header is not
set. This can prevent XSS exploits for websites that allow users
to upload and share files.
* X-Download Options: Again useful in scenarios involving user file
uploads, when set to 'noopen' this forces the browser to download
a file instead of attempting to display it in the browser.
* Referrer-Policy: When this is set to 'no-referrer' the browser
does not send the referrer when making a request and is useful
for privacy. More on referrer-policy.
---------------------------------------------------------------------
Overall, as the web grows in terms of features and complexity, the
attack surface also grows correspondingly large. A properly
configured web server and a well designed application with effective
use of HTTPS (eg. with Let's Encrypt) can provide a good degree of
protection.
As an added bonus, many of the mitigations on this page can be
applied at the proxy server (CSP, HSTS, HPKP) or network level
(better server proxying to remove the need for CORS), and only the
CSRF and XSS protections really need to be added to the application.
of course, the mitigations on this page are only a small subset of
what is possible.
Some useful resources to implement these:
* Test your site's security headers
* Adding security headers automatically with Cloudflare workers
* CSP evaluator to test policies
That said, the application is likely the biggest source of security
threats anyway, most of the OWASP top ten have to deal with
application programming bugs - highlighting the importance of good
design and application architecture.
<