Blog

You are filtering on tag 'html'. Remove Filters
RSS

An index of website security features

November 24th, 2022

I recently took a bit of dive into website security. As the developer of a website, of course I'd like my website to be secure, even if it doesn't handle any sensitive user data. It's also just a fun way to learn more about how the web we use every day works. I read about and implemented a number of security features that I gathered from various blogs, lists, and validation tools, and I wanted to collect them in one spot for my own reference and for anyone else who might find them useful.

I'll include a few words about what each of them do, but Google will get you all the in-depth information you need, so this is mostly intended as an index of some features that exist so you can look into them more.

Example code is in PHP.

This list is certainly not exhaustive. Here are a few other sources I referenced:

CSP (Content Security Policy)

The Content Security Policy is an HTTP header that decribes what types of media, scripts, styles, and other content your page will have on it. This allows the user's browser to block any unexpected content that may have somehow been injected by an attacker (maybe in a forum post, or via an XSS attack).

Every HTML page served should have a CSP. My workflow for adding them is to add the following maximally-restrictive CSP, load the page in a browser, check the developer console for CSP errors, and add entries to permit those things. For embedded styles or scripts, the console will report a hash you can add to the CSP.

header("Content-Security-Policy: "
	."default-src 'none';"
	."frame-ancestors 'none';"
	."base-uri 'none';"
	."form-action 'none';");

You cannot use inline styles or Javascript events with a CSP without adding the 'unsafe-inline' item, which defeats much of the purpose of having a CSP.

Mitigates: XSS, clickjacking

SSL (HTTPS)

Most other security measures can be circumvented if an attacker can intercept the communications between a user and the server (such as via ARP poisoning). Acquiring an SSL certificate and using the HTTPS protocol will allow your site to use encrypted communications, making this impossible. Many hosting providers are now providing ways to acquire free certificates

You must also redirect insecure HTTP requests to HTTPS, such as via this htaccess rule:

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Strict-Transport-Security (HSTS)

Now that your site supports HTTPS, add the Strict-Transport-Security: max-age=<expire-time> header to every file served. This directs the user's browser to only use HTTPS when connecting to your site. This performs a slightly different function than a server-side 301 redirect to HTTPS, in that the browser itself will upgrade future requests to HTTPS before ever sending them to the server.

Via htaccess:

Header set Strict-Transport-Security "max-age=31536000"

Mitigates: man-in-the-middle attacks

X-Frame-Options: deny

Add the X-Frame-Options: deny header to every HTML page served. This header instructs the browser to not permit your page to be framed on another page.

Via htaccess:

<filesMatch ".(php|html|htm)$">
	Header set X-Frame-Options "deny"
</filesMatch>

Mitigates: clickjacking

X-Content-Type-Options: nosniff

Add the X-Content-Type-Options: nosniff header to every file served. In some cases, browsers will ignore the MIME type set by the server and try to determine it by looking at the file. This can be a security risk if your site hosts user-provided files, which could have a harmless extension such as .txt but be interpretted by browsers as executable code such as Javascript.

Header set X-Content-Type-Options "nosniff"

Mitigates: MIME confusion

Remove X-Powered-By

Some web technologies, such as PHP, will identify themselves in the X-Powered-By header of responses. This could help an attacker find weaknesses in the site to attack. Remove this header from all responses.

Header unset X-Powered-By

Mitigates: fingerprinting

MySQL Prepared Statements

Always use prepared statements to make dynamic SQL queries. Forming queries by concatenating strings may allow an attacker to perform SQL injection, if user-provided data can find its way into any of the components in the query. The attacker might be able to extract private data from tables, insert malicious data, or vandalize or drop data.

$query = $connection->prepare("INSERT INTO blog (id, date, title, text) VALUES (NULL, NOW(), ?, ?)");
$query->bind_param("ss", $unsafeTitle, $unsafeText);
$query->execute();

Mitigates: SQL injection

Data sanitization

Sanitize user-provided data as necessary before using it in any context. Use a function like PHP's strip_tags for text. If you expect data to be a number or bool, run it through intval, floatval, or boolval. This will prevent an attacker from inserting malicious code into pages (XSS attack).

I like to name any user-provided variable that hasn't been thoroughly sanitized yet with the prefix "unsafe". This helps me remember that that data may need to be sanitized before being used.

This applies equally to data that was previously provided by a user and stored ("second-order attack").

Mitigates: XSS, SQL injection

Hash Passwords

Passwords on the server should always be stored as hashes; the original password should not be present on the server. This will prevent an attacker who finds a way to extract data from the server from logging in with those passwords (without cracking the hashes).

In PHP, you can generate a salted password hash like so (either offline or online depending on your needs):

$passwordHash = password_hash("PASSWORD", PASSWORD_DEFAULT);

And check it like so:

$unsafePassword = $_POST['pword'];
if (password_verify($unsafePassword, 'PASSWORD_HASH'))
{
	// Restricted code
}

Mitigates: password leakage

Restrict MySQL user privileges

Scripts generating pages should use a MySQL user that has only the privileges it needs (for example, only the SELECT privilege on tables containing public data). This will prevent an attacker who finds an injection vulnerability from using it to modify or delete data.

Mitigates: SQL injection

Files on your server are public

Even if there are no links to them, any file on your server that isn't protected by an actual access control mechanism should be considered public. Do not upload .git folders, documents containing passwords, pre-minified source, etc., or an attacker might be able to enumerate and download them. Obscurity is not security.

Mitigates: data leakage, fingerprinting

Use rel="noopener" on external links that open in new tabs

When a user follows a link that opens a new tab, older browsers may provide the new page with the window.opener property, a reference to your page's window. The target page could use this to do certain operations on your page (in particular, navigate to a different URL).

Browsers will restrict this by default since late 2018, but you can be sure it's done by providing rel="noopener" on any external target="_blank" links.

<a href="https://example.com" target="_blank" rel="noopener">Link</a>

Mitigates: phishing attacks

Subresource Integrity

If your site loads resources such as scripts or stylesheets from an external domain such as CloudFlare or Google Fonts, how do you know the provider (or an attacker) hasn't inserted something malicious into them? The answer is to add a hash of the file's expected contents to the integrity parameter on the relevant HTML tag. The browser will refuse to use the content if the hash doesn't match the file served.

<script src="https://example.com/example-framework.js"
	integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
	crossorigin>
</script>

Mitigates: supply-chain compromise

Use captchas before sending emails

This is a weird one that I learned about when it actually happened on my site. If your site accepts e-mail addresses from users, you must perform a captcha on the user before sending any mail to verify they are not a bot.

Why? If an attacker has compromised a user account on some other site (such as a bank) and intends to perform some action that might send the user an e-mail alert, they can have a bot automatically feed that user's e-mail address to any site with a form that will take it. The intent is to flood that inbox with e-mails in the hope that the user will not notice the important one. This is basically a kind of DDoS attack.

This isn't just bad for the target: it could also land your domain on a spam blacklist, which can be difficult to fix.

Google provides a free, low-friction captcha called reCAPTCHA.

Mitigates: email flooding

Serve /.well-known/security.txt

This is a simple text file on your server. It provides, at minimum, an e-mail address for security researchers to contact if they find a security problem with your site. I've received two such reports for my site that were quite helpful.

Contact: example@example.com
Preferred-Languages: en
Canonical: https://example.com/.well-known/security.txt

Mitigates: anything a security researcher might notice


Permalink

Magic Mobile Website Compatibility

January 31st, 2015

I recently started working on making these pages more compatible with mobile devices. One piece of advice I found quickly was to include the following meta tag in my HTML header:

<meta name="viewport" content="width=device-width,initial-scale=1">

It took me a bit longer to figure out exactly what this seemingly magic piece of code actually means for a site being rendered in a mobile browser, and to convince myself that yes, this code can magically make your website display much better on small mobile devices - but only if your website's CSS is already set up in a nice, size-independent way.

By default, mobile browsers assume that most of the internet is not designed to display well in a very small format. For that reason, they will render webpages at a large resolution more comparable to a desktop, and force the user to zoom and pan to see the content. What this code is actually doing is informing the browser that, yes, your content is fine to be displayed in a very small window - you are promising that it will resize itself and nothing will bleed off the page. This causes the browser to disable the extra-large render, freeing users from needing to zoom or scroll content sideways. So, if you've set up all your CSS to use percentages instead of fixed positions, it should result in an instant improvement in user experience as users can now simply scroll vertically through your content.

In my case, there was one other change needed to make this site's content mostly mobile-compatible. I often use screenshot images here, and those images are usually wider than a typical phone display, so I need them to scale down if the window is smaller than they are. This was accomplished easily enough by including the following CSS in the HTML header:

<style>img{max-width:100%;}</style>

Which simply instructs every img element on the page to be no wider than its parent element. Aspect ratio is preserved automatically.

There are plenty of other small UX adjustments that can be made, but now my site renders in a much more convenient format for visitors on phones.


Permalink


Previous Page
2 posts — page 1 of 1
Next Page