Giter Site home page Giter Site logo

aidantwoods / secureheaders Goto Github PK

View Code? Open in Web Editor NEW
415.0 25.0 23.0 688 KB

A PHP library aiming to make the use of browser security features more accessible.

License: MIT License

PHP 100.00%
secureheaders hsts cookie csp content-security-policy secure headers secure-cookie samesite

secureheaders's Introduction

SecureHeaders Build Status Build Status

A PHP class aiming to make the use of browser security features more accessible.

For full documentation, please see the Wiki.

A demonstration with a sample configuration is also available.

What is a 'secure header'?

Secure headers, are a set of headers that configure browser security features. All of these headers can be used in any web application, and most can be deployed without any, or very minor code changes. However some of the most effective ones do require code changes – especially to implement well.

Features

  • Add/remove and manage headers easily
  • Build a Content Security Policy, or combine multiple together
  • Content Security Policy analysis
  • Easy integeration with arbitrary frameworks (take a look at the HttpAdapter)
  • Protect incorrectly set cookies
  • Strict mode
  • Safe mode prevents accidental long-term self-DOS when using HSTS, or HPKP
  • Receive warnings about missing, or misconfigured security headers

Methodology and Philosophy

Error messages are often a great way for a program to tell the programmer that something is wrong. Whether it's calling a variable that's not yet been assigned, or causing a fatal error by exhausting the memory allocation limit.

Both of these situations can usually be rectified very quickly by the programmer. The effort required to do so is greatly reduced because the program communicated exactly what the problem was, as soon as the programmer introduced the bug. SecureHeaders aims to apply this concept to browser security features.

Utilising the error reporting level set within PHP configuration, SecureHeaders will generate E_USER_WARNING and E_USER_NOTICE level error messages to inform the programmer about either misconfigurations or lack of configuration.

In addition to error reporting, SecureHeaders will make some safe proactive changes to certain headers, or even add new ones if they're missing.

Installation

Via Composer

composer require aidantwoods/secureheaders

Other

Download SecureHeaders.phar, then

require_once('SecureHeaders.phar');

Sounds good, but let's see some of the code...

Here is a good implementation example

$headers = new SecureHeaders();
$headers->hsts();
$headers->csp('default', 'self');
$headers->csp('script', 'https://my.cdn.org');
$headers->apply();

These few lines of code will take an application from a grade F, to a grade A on Scott Helme's https://securityheaders.io/

Woah, that was easy! Tell me what it did...

Let's break down the example above.

'Out of the box', SecureHeaders will already do quite a lot (by running the following code)

$headers = new SecureHeaders();
$headers->apply();

Automatic Headers and Errors

With such code, the following will occur:

  • Warnings will be issued (E_USER_WARNING)

    Warning: Missing security header: 'Strict-Transport-Security'

    Warning: Missing security header: 'Content-Security-Policy'

  • The following headers will be automatically added

    Expect-CT: max-age=0
    Referrer-Policy: no-referrer
    Referrer-Policy: strict-origin-when-cross-origin
    X-Content-Type-Options:nosniff
    X-Frame-Options:Deny
    X-Permitted-Cross-Domain-Policies: none
    X-XSS-Protection:1; mode=block
    
  • The following header will also be removed (SecureHeaders will also attempt to remove the Server header, though it is unlikely this header will be under PHP jurisdiction)

    X-Powered-By
    

Cookies

Additionally, if any cookies have been set (at any time before ->apply() is called) e.g.

setcookie('auth', 'supersecretauthenticationstring');

$headers = new SecureHeaders();
$headers->apply();

Even though in the current PHP configuration, cookie flags Secure and HTTPOnly do not default to on, and despite the fact that PHP does not support the SameSite cookie attribute, the end result of the Set-Cookie header will be

Set-Cookie:auth=supersecretauthenticationstring; Secure; HttpOnly; SameSite=Lax

These flags were inserted by SecureHeaders because the cookie name contained the substring auth. Of course if that was a bad assumption, you can correct SecureHeaders' behaviour, or conversely you can tell SecureHeaders about some of your cookies that have less obvious names – but may need protecting in case of accidental missing flags.

If you enable ->strictMode() then the SameSite setting will be set to strict (you can also upgrade this without using strict mode).

Strict Mode

Strict mode will enable settings that you should be using. It is highly advisable to adjust your application to work with strict mode enabled.

When enabled, strict mode will:

  • Auto-enable HSTS with a 1 year duration, and the includeSubDomains and preload flags set. Note that this HSTS policy is made as a header proposal, and can thus be removed or modified.

  • The source keyword 'strict-dynamic' will also be added to the first of the following directives that exist: script-src, default-src; only if that directive also contains a nonce or hash source value, and not otherwise.

    This will disable the source whitelist in script-src in CSP3 compliant browsers. The use of whitelists in script-src is considered not to be an ideal practice, because they are often trivial to bypass.

    Don't forget to manually submit your domain to the HSTS preload list if you are using this option.

  • The default SameSite value injected into ->protectedCookie will be changed from SameSite=Lax to SameSite=Strict. See documentation on ->auto to enable/disable injection of SameSite and documentation on ->sameSiteCookies for more on specific behaviour and to explicitly define this value manually, to override the default.

  • Auto-enable Expect-CT with a 1 year duration, and the enforce flag set. Note that this Expect-CT policy is made as a header proposal, and can thus be removed or modified.

Back to the example

Let's take a look at those other three lines, the first of which was

$headers->hsts();

This enabled HSTS (Strict-Transport-Security) on the application for a duration of 1 year.

That sounds like something that might break things – I wouldn't want to accidentally enable that.

Safe Mode

Okay, SecureHeaders has got you covered – use $headers->safeMode(); to prevent headers being sent that will cause lasting effects.

So for example, if the following code was run (safe mode can be called at any point before ->apply() to be effective)

$headers->hsts();
$headers->safeMode();

HSTS would still be enabled (as asked), but would be limited to lasting 24 hours.

SecureHeaders would also generate the following notice

Notice: HSTS settings were overridden because Safe-Mode is enabled. Read about some common mistakes when setting HSTS via copy/paste, and ensure you understand the details and possible side effects of this security feature before using it.

What if I set it via a method not related to SecureHeaders? Can SecureHeaders still enforce safe mode?

Yup! SecureHeaders will look at the names and values of headers independently of its own built in functions that can be used to generate them.

For example, if I use PHPs built in header function to set HSTS for 1 year, for all subdomains, and indicate consent to preload that rule into major browsers, and then (before or after setting that header) enable safe-mode...

header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
$headers->safeMode();

The same above notice will be generated, max-age will be modified to 1 day, and the preload and includesubdomains flags will be removed.

Content Security Policy

The final two lines to cover from the initial example are as follows

$headers->csp('default', 'self');
$headers->csp('script', 'https://my.cdn.org');

These tell SecureHeaders that it should build a CSP (Content Security Policy) that allows default assets to be loaded from the current domain (self), and that scripts should be allowed from https://my.cdn.org.

Note that if we had said http://my.cdn.org instead, then the following would have been generated

Warning: Content Security Policy contains the insecure protocol HTTP in a source value http://my.cdn.org; this can allow anyone to insert elements covered by the script-src directive into the page.

Similarly, if wildcards such as 'unsafe-inline', https:, or * are included – SecureHeaders will generate warnings to highlight these CSP bypasses.

Note that the ->csp function is very diverse in what it will accept, to see some more on that take a look at Using CSP

Sending the headers

In order to apply anything added through SecureHeaders, you'll need to call ->apply(). By design, SecureHeaders doesn't have a construct function – so everything up until ->apply() is called is just configuration. However, if you don't want to have to remember to call this function, you can call ->applyOnOutput() instead, at any time. This will utilise PHP's ob_start() function to start output buffering. This lets SecureHeaders attatch itself to the first instance of any piece of code that generates output – and prior to actually sending that output to the user, make sure all headers are sent, by calling ->apply() for you.

Because SecureHeaders doesn't have a construct function, you can easily implement your own, via a simple class extension, e.g.

class CustomSecureHeaders extends SecureHeaders{
    public function __construct()
    {
        $this->applyOnOutput();
        $this->hsts();
        $this->csp('default', 'self');
        $this->csp('script', 'https://my.cdn.org');
    }
}

The above would implement the example discussed above, and would automatically apply to any page that ran just one line of code

$headers = new CustomSecureHeaders();

Of course, pages could add additional configuration too, and headers would only be applied when the page started generating output.

Another Example

If the following CSP is created (note this probably isn't the best way to define a CSP of this size, see the array syntax that is available in the section on Using CSP)

$headers->csp('default', '*');
$headers->csp('script', 'unsafe-inline');
$headers->csp('script', 'http://insecure.cdn.org');
$headers->csp('style', 'https:');
$headers->csp('style', '*');
$headers->csp('report', 'https://valid-enforced-url.org');
$headers->cspro('report', 'whatisthis');
Content-Security-Policy:default-src *; script-src 'unsafe-inline'
http://insecure.cdn.org; style-src https: *; report-uri
https://valid-enforced-url.org;

Content-Security-Policy-Report-Only:report-uri whatisthis;

The following messages will be issued with regard to CSP: (level E_USER_WARNING and level E_USER_NOTICE)

  • The default-src directive contains a wildcard (so is a CSP bypass)

    Warning: Content Security Policy contains a wildcard * as a source value in default-src; this can allow anyone to insert elements covered by the default-src directive into the page.

  • The script-src directive contains an a flag that allows inline script (so is a CSP bypass)

    Warning: Content Security Policy contains the 'unsafe-inline' keyword in script-src, which prevents CSP protecting against the injection of arbitrary code into the page.

  • The script-src directive contains an insecure resource as a source value (HTTP responses can be trivially spoofed – spoofing allows a bypass)

    Warning: Content Security Policy contains the insecure protocol HTTP in a source value http://insecure.cdn.org; this can allow anyone to insert elements covered by the script-src directive into the page.

  • The style-src directive contains two wildcards (so is a CSP bypass) – both wildcards are listed

    Warning: Content Security Policy contains the following wildcards https:, * as a source value in style-src; this can allow anyone to insert elements covered by the style-src directive into the page.

  • The report only header was sent, but no/an invalid reporting address was given – preventing the report only header from doing anything useful in the wild

    Notice: Content Security Policy Report Only header was sent, but an invalid, or no reporting address was given. This header will not enforce violations, and with no reporting address specified, the browser can only report them locally in its console. Consider adding a reporting address to make full use of this header.

Using CSP

If you're new to Content-Security-Policy then running your proposed policy through Google's CSP Evaluator may be a good idea.

Let's take a look at a few ways of declaring the following CSP (or parts of it). Newlines and indentation added here for readability

Content-Security-Policy:
    default-src 'self';
    script-src 'self' https://my.cdn.org https://scripts.cdn.net https://other.cdn.com;
    img-src https://images.cdn.xyz;
    style-src https://amazingstylesheets.cdn.pizza;
    base-uri 'self';
    form-action 'none';
    upgrade-insecure-requests;
    block-all-mixed-content;

CSP as an array

$myCSP = array(
    'default-src' => [
        "'self'"
    ],
    'script-src' => [
        'self',
        'https://my.cdn.org',
        'https://scripts.cdn.net',
        'https://other.cdn.com'
    ],
    'img-src' => ['https://images.cdn.xyz'],
    'style-src' => 'https://amazingstylesheets.cdn.pizza',
    'base' => 'self',
    'form' => 'none',
    'upgrade-insecure-requests' => null,
    'block-all-mixed-content'
);

$headers->csp($myCSP);

In the above, we've specified the policy using an array in the way it makes the most sense (bar some slight variation to demonstrate supported syntax). We then passed our policy array to the csp function.

Within the array, take a look at default-src. This is the full directive name (the key of the array), and its source list is specified as an array containing source values. In this case, the directive only has one source value, 'self', which is spelled out in full (note the single quotes within the string).

In this case, we've actually written a lot more than necessary – see the directive base for comparison. The actual CSP directive here is base-uri, but base is a supported shorthand by SecureHeaders. Secondly, we've omitted the array syntax from the descending source list entirely – we only wanted to declare one valid source, so SecureHeaders supports foregoing the array structure if its not useful. Additionally, we've made use of a shorthand within the source value too – omitting the single quotes from the string's value (i.e. self is a shorthand for 'self').

There are two CSP 'flags' included also in this policy, namely upgrade-insecure-requests and block-all-mixed-content. These do not hold any source values (and would not be valid in CSP if they did). You can specify these by either giving a source value of null (either as above, or an array containing only null as a source), or forgoing any mention of decedents entirely (as shown in block-all-mixed-content, which is written as-is). Once a flag has been set, no sources may be added. Similarly once a directive has been set, it may not become a flag. (This to prevent accidental loss of the entire source list).

The csp function also supports combining these CSP arrays, so the following would combine the csp defined in $myCSP, and $myOtherCSP. You can combine as many csp arrays as you like by adding additional arguments.

$headers->csp($myCSP, $myOtherCSP);

CSP as ordered pairs

Using the same csp function as above, you can add sources to directives as follows

$headers->csp('default', 'self');
$headers->csp('script', 'self');
$headers->csp('script', 'https://my.cdn.org');

or if you prefer to do this all in one line

$headers->csp('default', 'self', 'script', 'self', 'script', 'https://my.cdn.org');

Note that directives and sources are specified as ordered pairs here.

If you wanted to add a CSP flag in this way, simply use one of the following.

$headers->csp('upgrade-insecure-requests');
$headers->csp('block-all-mixed-content', null);

Note that the second way is necessary if embedded in a list of ordered pairs – otherwise SecureHeaders can't tell what is a directive name or a source value. e.g. this would set block-all-mixed-content as a CSP flag, and https://my.cdn.org as a script-src source value.

$headers->csp('block-all-mixed-content', null, 'script', 'https://my.cdn.org');

However, the csp function also supports mixing these ordered pairs with the array structure, and a string without a source at the end of the argument list will also be treated as a flag. You could, in perhaps an abuse of notation, use the following to set two CSP flags and the policy contained in the $csp array structure.

$headers->csp('block-all-mixed-content', $csp, 'upgrade-insecure-requests');

CSP as, uhh..

The CSP function aims to be as tolerant as possible, a CSP should be able to be communicated in whatever way is easiest to you.

That said, please use responsibly – the following is quite hard to read

$myCSP = array(
    'default-src' => [
        "'self'"
    ],
    'script-src' => [
        "'self'",
        'https://my.cdn.org'
    ],
    'script' => [
        'https://scripts.cdn.net'
    ],
);

$myotherCSP = array(
    'base' => 'self'
);

$whoopsIforgotThisCSP = array(
    'form' => 'none'
);

$headers->csp(
    $myCSP, 'script', 'https://other.cdn.com',
    ['block-all-mixed-content'], 'img',
    'https://images.cdn.xyz', $myotherCSP
);
$headers->csp(
    'style', 'https://amazingstylesheets.cdn.pizza',
    $whoopsIforgotThisCSP, 'upgrade-insecure-requests'
);

Behaviour when a CSP header has already been set

header("Content-Security-Policy: default-src 'self'; script-src https://cdn.org 'self'");
$headers->csp('script', 'https://another.domain.example.com');

The above code will perform a merge the set CSP header, and the additional script-src value set in the final line. Producing the following merged CSP header

Content-Security-Policy: script-src https://another.domain.example.com https://cdn.org 'self'; default-src 'self'

Content-Security-Policy-Report-Only

All of the above is applicable to report only policies in exactly the same way. To tell SecureHeaders that you're creating a report only policy, simply use ->cspro in place of ->csp.

As an alternate method, you can also include the boolean true, or a non zero integer (loosely compares to true) in the regular ->csp function's argument list. The boolean false or the integer zero will signify enforced CSP (already the default). The left-most of these booleans or intgers will be taken as the mode. So to force enforced CSP (in-case you are unsure of the eventual variable types in the CSP argument list), use ->csp(false, arg1[, arg2[, ...]]) etc... or use zero in place of false. Similarly, to force report-only (in-case you are unsure of the eventual variable types in the CSP argument list) you can use either ->cspro(arg1[, arg2[, ...]]) or ->csp(true, arg1[, arg2[, ...]]).

Note that while ->csp supports having its mode changed to report-only, ->cspro does not (since is an alias for ->csp with report-only forced on). ->csp and ->cspro are identical in their interpretation of the various structures a Content-Security-Policy can be communicated in.

More on Usage

For full documentation, please see the Wiki

Versioning

The SecureHeaders project will follow Semantic Versioning 2, with the following declared public API:

Any method baring the @api phpdoc tag.

Roughtly speaking

  • Every public method in Aidantwoods\SecureHeaders\SecureHeaders (except Aidantwoods\SecureHeaders\SecureHeaders::returnBuffer)
  • Every public method in Aidantwoods\SecureHeaders\Http
  • Every public method in Aidantwoods\SecureHeaders\HeaderBag

This allows the main SecureHeaders class to be used as expected by semver, and also the HttpAdapter interface/implementation (for integration with anything) to be used as expected by semver.

All other methods and properties are therefore non-public for the purposes of semver. That means that, e.g. methods with public visibility that are not in the above scope are subject to change in a backwards incompatible way, without a major version bump.

ChangeLog

The SecureHeaders project will follow Keep a CHANGELOG principles

Check out the ChangeLogs/ folder, to see these.

secureheaders's People

Contributors

aidantwoods avatar carusogabriel avatar cebe avatar franzliedke avatar lucasmichot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

secureheaders's Issues

Formatting, usage concerns

While I love the idea of SecureHeaders being adoptable everywhere, you're not using PSR-1/PSR-4, you've not got this package on composer, and the vast majority of PHP code that is going to be updated will not be able to simply just include it.

In addition, it'd need to be modified so that all the major frameworks currently in use (wordpress/laravel/symfony2/drupal) can actually include it at the correct stages of their lifecycles without needing to modify core code. That one's far more tricky.

A few things you could do to cut down on work:

  • Use StyleCI (free for open source) for automatic formatting
    • This is great for moving to PSR-2 (or at least PSR-1, but modern PHP is leaning towards namespaces + PascalCase classes + camelCase methods, etc)
  • Autoload files via PSR-4 using Composer
  • Add your package to Packagist so people can instantly use it.

People haven't really done the download package / require files / use library approach in several years for newer projects at this point, and it'd be a shame for this package to just die from lack of use.

Support 'strict-dynamic' by default

It would be really cool if you could add support for 'strict-dynamic' (W3C) by default.

Google is offering Google Patch Rewards for integrating CSP in popular frameworks and facilitating adoption (as you already did, so please apply!).

The most recent research on CSP demonstrated that the whitelist-based approach is intrinsically flawed for XSS mitigation, and that a nonce/hash + 'strict-dynamic' policy, similar to the ones already served by important Google products, is in the quasi-totality of cases the only working solution.

There are a lot of misconceptions about CSP, and getting rid of things that are proven not to work is good - the worst thing you can have is a false sense of security.

If you could also add a link to the CSP Evaluator to your README, to let users evaluate the quality of their policy, it would be awesome.

Please do not hesitate to get back to me if you need any additional information or have doubts.

More resources

Increase Test Coverage

Add tests for each component of the library.


This is the final item on the agenda for 2.0 release, hence:

Closes #27
Closes #3

Allow set X-Frame-Options directive

Is usual to use an iframe to show content from facebook stream, or twitter, for set an examples.

I think a directive of ->csp(), some like
$headers->csp('frame', 'sameorigin');
$headers->csp('frame', ['sameorigin', 'http://www.facebook.com']);

Thanks for consider.

Throw exceptions instead of user warnings/errors

As discussed in #60 (comment), I would like the library is avoid of triggering php errors, but instead throw SPL Exceptions, which were created for this case.

At the moment, I'm tired, and I can't describe more things why I want to catch exceptions instead of errors.

Conditional Intent to Deprecate and Remove: Public Key Pinning

HPKP is dead.

Okay, well not quite yet 😉, but the end looks to be nigh unless something changes to fix it.

HPKP was a standard developed in hope of solving the problem of certificate mis-issuance by allowing a domain owner to "pin" a select set of certificates.
HPKP unfortunately makes it very easy to shoot yourself in the foot by rotating pins too fast, losing access to your backup, locking yourself to a CA (if you pin the CA) and mess up rotation windows, misconfiguration etc... mostly caused by having no mechanism to recover from misconfiguration (other than waiting for the enforcement window to expire).
Unfortunately this also opens up the door to malicious pinning: HPKP based ransom (where an attacker may utilise HPKP along with temporarily gained access to your webserver to set up HPKP with pins to a certificate you do not control – and holding you to ransom, or face users unable to visit your site).

For a comprehensive overview see this post from last year by Ivan Ristic.

HPKP has been part of SecureHeaders since the beginning, but was (deliberately) never really explicitly encouraged (by virtue of not triggering the usual warnings that omitting other security headers will cause).
It was also one of the driving factors for introducing safe mode – in which (if safe mode is enabled) it carries the heaviest neutering value of being capped at 10 seconds of enforcement with policy propagation to subdomains disabled.

Fortunately the problem of certificate mis-issuance is now being tackled by certificate transparency and the Expect-CT.

I'll just grab a quote from the first link about it:

To defend against certificate misissuance, web developers should use the Expect-CT header, including its reporting function. Expect-CT is safer than HPKP due to the flexibility it gives site operators to recover from any configuration errors, and due to the built-in support offered by a number of CAs. Site operators can generally deploy Expect-CT on a domain without needing to take any additional steps when obtaining certificates for the domain. Even if the CT log ecosystem substantially changes during the validity period of the certificate, site operators can provide updated SCTs in the form of OCSP responses (if their CA supports it) or via a TLS extension (if they wish for greater control). The combination of these mitigations substantially reduces the risk of DoS (either accidental or hostile) via Expect-CT deployment. By combining Expect-CT with active monitoring for relevant domains, which a growing number of CAs and third-parties now provide, site operators can proactively detect misissuance in a way that HPKP does not achieve, while also reducing the risk of misconfiguration and avoiding the risk of hostile pinning.

You can set up and send the Expect-CT header using the ->expectCT method, and incase you want some more reading on it – take a look at this post by Scott Helme, or the latest draft spec.


So that leaves the issue of HPKP in SecureHeaders. The proposal linked first plans to remove support in Chrome by 29th May 2018. I would suggest that if there are no objections to deprecate the following parts of the public API:

SecureHeaders::hpkp
SecureHeaders::hpkpro
SecureHeaders::hpkpSubdomains
SecureHeaders::hpkproSubdomains

and then subsequently removing support in a major release that occurs sometime around (but preferably before) the Chrome removal date.

All this hinges on Chrome's intent to deprecate and remove being actioned, and we should also wait to see if Firefox follows suit. This means no date for even the deprecation on the public API, but I'll update this issue when more information is available.

This issue serves as a conditional intent to deprecate and remove, subject to outcome of browser decisions.

`'strict-dynamic'` isn't injected into CSP Report-Only

When enabling strict mode, 'strict-dynamic' is opportunistically injected into CSP but not 'strict-dynamic'. There's no documentation that indicates this is only for enforced policies (and seems to go against the idea of ->cspro behaving like ->csp – in a different mode). Therefore I see no reason for BC break (from intended behaviour), and it's not really a new feature either – hence can probably be a bugfix release.

Should probably add a config for disabling/enabling this opportunistic injection in strict mode too in each header (one might want to deploy slightly different policies in each header to trial run a CSP in report mode before using enforce).


Conscious of a potential "configuration overload" approaching here, we're building up quite a few configuration options. Will open a separate issue to discuss possibly cleaning some of this up, see #57.

Rethink cookie upgrades

In future versions we should rework how cookie upgrades are handled.

Sites which care about security related headers being reliably delivered should already be using HTTPS on all their pages – which means that HTTP cookies shouldn't be a thing. Adding Secure to all cookies seems like a pretty damn easy win. (this would replace our current approach which is only slightly better than a whitelist). For cookies that must be sent over HTTP, we can permit cookies to be manually deselected from this upgrade.

I think this approach may be valuable for all settings though: it adopts a "permissions" view of the world where everything is disallowed by default, and the developer must actively opt-in to less protection. i.e. instead of having to remember to think about "we must disallow JavaScript from accessing a cookie", developers should instead think "we should grant JavaScript access to this cookie".

My current thinking is the following:

  • All cookies receive the Secure flag by default.

  • All cookies receive the httpOnly flag by default.

  • All cookies receive the SameSite=Lax flag by default.

  • Allow manual exceptions to be written for all of the above based on cookie name, i.e.

    • Explicit opt-in for cookies to be allowed to be sent over HTTP

    • Explicit opt-in for JavaScript to be allowed access to a cookie

    • Explicit opt-in for cookies to be allowed to be sent when not using top-level cross-origin navigations

  • Allow specific cookies to be marked as "write cookies", which will upgrade the SameSite setting to SameSite=Strict. These are cookies which (if your site can be built to allow for it) should be reserved for granting write permissions to a particular user. SameSite=Lax cookies are perfectly okay for where the user need only read information.

    There is a section in the SameSite cookie spec detailing a scenario where lax would be used for read permissions, and strict would be used for write permissions. Scott Helme has also done a nice explainer on using different cookies for read/write permissions in CSRF is dead.

    If you're developing a new app – this is something you should definitely look into in addition to current CRSF mechanisms (when all browsers have added support for SameSite it might be possible to ditch CSRF form tokens, but that is way in the future!).

`strict-origin-when-cross-origin` doesn't seem to be supported by Chrome

Using the default config:

$headers = new SecureHeaders();
$headers->apply()

I get the following console error in Chrome:

Failed to set referrer policy: The value 'strict-origin-when-cross-origin' is not one of 'no-referrer', 'no-referrer-when-downgrade', 'origin', 'origin-when-cross-origin', or 'unsafe-url'. The referrer policy has been left unchanged.

Not sure if it's just Chrome but this doesn't happen in Firefox.

I've had this problem with CORS stuff before in Chrome and there's usually an extension that someone has used to intercept the URLs, not sure if you know of one?

Report missing CSP directives

base-uri must be defined to have blocking behaviour.
If default-src is not defined many directives will have no fallback (and so will operate as if * was specified if they too are undefined by the CSP).
Some key directives that should not be emitted include:

  • default-src (obviously)
  • object-src
  • script-src
  • style-src

SecureHeaders should emit a warning if any directive that falls back to default-src is absent from CSP and default-src is also absent.

We should also enumerate things that do not fallback to default-src (like base-uri) and warn about these separately (regardless of whether default-src is present).

Add hashes and nonces as friendly directive

I know that the package is meant to not use them directly, but sometimes it could be useful to be able to insert nonces and hashes directly instead of relying on the given API methods.
It's actually already doable via csp() method, but with a somehow hacky notation:

'\'sha256-K28TraF0hDSDYoxDYfyCCb5cCVAhDUyT0P1E3a+hKyQ=\''
"'sha256-K28TraF0hDSDYoxDYfyCCb5cCVAhDUyT0P1E3a+hKyQ='"
'\'nonce-xxxxxxxx\''
"'nonce-xxxxxxxx'"

I've not tested those on your library directly but I use them in laravel-securityheaders and they work.

My request, if possible, is to add two more "friendly directive":

  • one for nonces, which checks if the string start with a "nonce-" and automatically adds the single quotation marks, while preserving the actual nonce value
  • and one for hashes, which checks if the string starts with a "sha[acceptableVersionOfSHA]-" and automatically adds the single quotation marks, while preserving the actual encoded hash value.

More intuitive config

Some stuff is really easy and hard to guess a way of using it wrong (see: https://github.com/aidantwoods/SecureHeaders/wiki/csp).

Other configuration might be a little harder to remember off hand (see: https://github.com/aidantwoods/SecureHeaders/wiki/auto).

This issue to to discuss whether we can do the "toggle like" configuration a bit better. Policies should stay as-is IMO (like CSP), but for configuring behaviour like in auto – we might be able to do better.

Should we create some kind of standardised config object or methodology that we could use to at least sub-category some of the stuff going on in auto (and probably being added to strict mode RE 'strict-dynamic' injection, see #56).

Or should we create a new function to configure (like https://github.com/aidantwoods/SecureHeaders/wiki/sameSiteCookies for SameSite's variable default override).

Drop PHP 5.x

SecureHeaders was originally written in PHP 7.

However, I wanted as many people as possible to be able to use these browser security features easily, and didn't want it to be unusable by someone just because they were stuck with a lazy hosting provider.

So I backported the codebase all the way back to PHP 5.3 (and I took scalar type exceptions back with me! 😉)
PHP 5.6 initial backport: 4565833
PHP 5.4: 2192058
PHP 5.3: cdab04a

A year(-ish) later and two major versions out, the minimum version has increased to PHP 5.4. I think that's where I'm comfortable drawing the line though. It certainly doesn't make sense to continue supporting versions of PHP that the PHP team themselves don't.
As far as official support goes, PHP 5.6 is the only version on 5.x not to be end of life, and it will no longer receive updates unless they are security related. It'll be that way for a while longer, so it might make sense to still support that. We'd even gain the ... operator for type-hinting collections of objects.
However, we're still missing proper language enforced type safety for scalars, return type hints, and strict mode to disable "type coercion". For these features, I feel it will be worth dropping 5.6 too.

If you really have to use PHP 5.x, 2.x isn't going anywhere. 2.x will likely enjoy quite a few more updates too. This just forewarning that when 3.0 finally rolls around, it'll be modern PHP only.


Edit: Starting a checklist of sub-tasks/issues in this meta-issue, that'll need to be completed when the transition is underway (don't worry, still not yet).

  • Type Safety
    • Scalar type hints
    • Return type hints
    • strict_types=1
    • Type hint collections when possible with ... operator
  • Move away from OpenSSL for randomness (could perhaps do this sooner in 2.x too)

2.0 Planned Changes

There's a lot of work going on under-the-hood, so to speak, to help make SecureHeaders a lot more usable within frameworks. (This thanks to folks like @lucasmichot and @franzliedke).

Since there's going to be a major version bump at some point, I wanted to open up this thread to discuss some feature changes that I'm planning on wrapping into the 2.0 version. This mainly so that there is the opportunity for concerns to be raised that I may have neglected to consider.

  1. Better cookie upgrades:
    Specifically incorporating the SameSite cookie attribute. The plan is for SameSite=Lax to be added in alongside the HttpOnly and Secure flags to sensitive looking cookies by default, and for this to be upgraded to SameSite=Strict if operating in strictMode.

    SameSite=Lax already offers pretty good CSRF protection by preventing non GET requests or non top-level requests from sending cookies when coming from a cross-origin source. In other words cookies will not be sent when the request originates from a cross-origin unless request can be seen in the URL bar and any request data is also in the URL bar.

    Obviously this isn't a complete solution, so for those that want it stricter, I'll include the upgrade to SameSite=Strict (or just SameSite according to spec). This should be able to be configured granularly similarly to how HttpOnly and Secure are currently configured or just by enabling strictMode to use SameSite=Strict (as said above).

  2. Add a new header by default:
    The new header being X-Permitted-Cross-Domain-Policies: none. As with other automatic headers, this will be done via a header proposal – so this can be explicitly removed or modified as you prefer if the default is not desired.

  3. I need some feedback on what SecureHeaders should do if you pass it an argument of the wrong type.
    Since generating an exception produces output and forces headers to be sent immediately (preventing already configured ones from being added to PHPs list), there may be a case to do something slightly differently. At present (in 0.0 through to 1.0.1) I've utilised a custom exception class that will trigger the SecureHeaders done method on your behalf when an exception is uncaught. There has been some discussion on this perhaps not being the best behaviour because it is unexpected. It would be good to get some feedback on what to do when raising exceptions. Whether that be: "no, don't auto send the headers"; "yes, auto send the headers"; or "it should be user configurable, and the default should be [x]".

  4. Add a new header by default: Referrer-Policy: strict-origin-when-cross-origin with a fallback policy of no-referrer.
    I've made no-referrer the fallback because is the only policy value (currently) supported by both Chrome and FF which guarantees that the full query string will remain private on cross-origin requests, and that no URL is leaked over the network on insecure requests (to the same origin).
    At time of writing latest stable on both Chrome and FF do not understand strict-origin-when-cross-origin and will use the fallback. Firefox is due to add support in v52. Chrome has a bug report open.
    Safari and other browsers appear to offer no support. This is unfortunate. Hopefully there will be progress.

  5. Add a new header by default: Expect-CT: max-age=0.
    Spec here.
    This defaults to reporting mode, but will be configurable to operate in enforce mode, or just reporting with some report-uri specified.

Thanks for reading! I'll update this issue if I add anything else to the planned changes list 😄

allow method chaining

Personally, I'd like to chain my settings, so instead of this:

// init SecureHeaders
$headers = new SecureHeaders;

$headers->auto();
$headers->hsts();
$headers->csp([
    'default-src' => [
        'none'
    ],
    'script-src' => [
        'self'
    ],
    'style-src' => [
        'self',
        'https://fonts.googleapis.com'
    ],
    'img-src' => [
        'https://www.gravatar.com'
    ]
]);
$headers->apply();

I could do

// init SecureHeaders
$headers = new SecureHeaders;

$headers
    ->auto()
    ->hsts()
    ->csp([
        'default-src' => [
            'none'
        ],
        'script-src' => [
            'self'
        ],
        'style-src' => [
            'self',
            'https://fonts.googleapis.com'
        ],
        'img-src' => [
            'https://www.gravatar.com'
        ]
    ])
    ->apply();

Option to manually disable warnings

I with you that having Warnings and Notices is a wonderful thing while developing, but it seems that it's not possible to manually disable them in any way.

Use cases:

  • we want to support Edge pre-15, so we must put the "unsafe-inline". Our CSP is build in order to leverage CSP3 and 2 where possible and fallback to CSP1 only where really needed, but actually we find ourselves filled with warnings about a decision we know we did for a reason;
  • we are enhancing security step-by-step and for this reason we preferred to disable HSTS for now. We know we did, we are going to enable it later, still we apparently have no way to disable those errors.

If there were some way to selectively disable some warnings (of course they must be enabled by default), it would be great

Discuss finally releasing 2.0

So everything is #19 is now complete. The documentation can now mostly be very nicely auto-generated from phpdoc. And we now have nice adapters to SecureHeaders can be used in arbitrary frameworks with very minor implementation code.

I'll make some additions to the test suit this weekend just to make sure everything is running nicely and as expected, but other than that I can't think of anything major that needs doing.

@franzliedke @lucasmichot feel free to jump in here if I've forgotten something that needs doing 😜

Proposal: Move most documentation to PhpDoc blocks

At least for the method documentation, this can be added directly to the code, which can then be used for automatically generating up-to-date documentation, e.g. with https://www.phpdoc.org/.

Example for removeHeader():

/**
 * Queue a header for removal
 *
 * Upon calling {@see apply}, the header will be removed. This function can
 * be used to prevent automatic headers from being sent.
 *
 * @param string $name Case insensitive name of the header to remove
 * @return void
 */
public function removeHeader($name)
{
    // ...
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.