diff --git a/.symfony.bundle.yaml b/.symfony.bundle.yaml new file mode 100644 index 0000000..1dc8b47 --- /dev/null +++ b/.symfony.bundle.yaml @@ -0,0 +1,8 @@ +branches: + - master +maintained_branches: + - master +current_branch: master +dev_branch: master +doc_dir: src/Resources/doc/ +dev_branch_alias: 3.x diff --git a/README.md b/README.md index d535d76..1f2d0af 100644 --- a/README.md +++ b/README.md @@ -6,830 +6,58 @@ The NelmioSecurityBundle provides additional security features for your Symfony ## Installation -Require the `nelmio/security-bundle` package in your composer.json and update your dependencies. +Require the `nelmio/security-bundle` package in your composer.json and update your dependencies: $ composer require nelmio/security-bundle -The bundle should be automatically enabled by Flex. In case you don't use Flex, you'll need to manually enable the -bundle by adding the following line in the config/bundles.php file of your project: -```php - ['all' => true], - // ... -]; -``` - -If you don't have a `config/bundles.php` file in your project, chances are that you're using an older Symfony version. -In this case, you should have an `app/AppKernel.php` file instead. Edit such file: - -```php -HTTPS), - # and send no header to a less secure destination (HTTPS->HTTP). - # If `strict-origin-when-cross-origin` is not supported, use `no-referrer` policy, - # no referrer information is sent along with requests. - referrer_policy: - enabled: true - policies: - - 'no-referrer' - - 'strict-origin-when-cross-origin' - - # forces HTTPS handling, don't combine with flexible mode - # and make sure you have SSL working on your site before enabling this -# forced_ssl: -# hsts_max_age: 2592000 # 30 days -# hsts_subdomains: true -# redirect_status_code: 302 # default, switch to 301 for permanent redirects - - # flexible HTTPS handling, read the detailed config info - # and make sure you have SSL working on your site before enabling this -# flexible_ssl: -# cookie_name: auth -# unsecured_logout: false -``` - -## Configuration Detail - -### Content Security Policy: - -Using CSP you can set a policy which modern browsers understand and will honor. The policy contains many different -directives; `default-src`, `script-src`, `object-src`, `style-src`, `img-src`, `media-src`, `frame-src`, -`font-src`, `connect-src`, `base-uri`, `child-src`, `form-action`, `frame-ancestors`, `plugin-types`, -`block-all-mixed-content`, `upgrade-insecure-requests`, `report-uri`, `manifest-src`. - -You can provide an array of directives per content type, except for `block-all-mixed-content` and -`upgrade-insecure-requests` that only accept boolean values. Empty content -types will inherit from `default-src`, specified content types will never inherit from `default-src`. Please see -the [Content Security Policy 1.0](https://www.w3.org/TR/2012/CR-CSP-20121115/) and -[Content Security Policy 2.0](https://www.w3.org/TR/2015/CR-CSP2-20150721/) specifications for details. - -Each directive should be a domain, URI or keyword. The keyword `'self'` will allow content from the same origin as -the page. If you need to allow inline scripts or `eval()` you can use `'unsafe-inline'` and `'unsafe-eval'`. - -**WARNING:** By using `'unsafe-inline'` or `'unsafe-eval'` you're effectively disabling the XSS protection -mechanism of CSP. - -Apart from content types, the policy also accepts `report-uri` which should be a URI where a browser can POST a -[JSON payload](https://developer.mozilla.org/en-US/docs/Security/CSP/Using_CSP_violation_reports#Sample_violation_report) -to whenever a policy directive is violated. - -An optional `content_types` key lets you restrict the Content Security Policy headers only on some HTTP -response given their content type. - -Finally, an optional `hosts` key lets you configure which hostnames (e.g. `foo.example.org`) -the CSP rule should be enforced on. If the list is empty (it is by default), all -hostnames will use the CSP rule. - -```yaml -nelmio_security: - csp: - enabled: true - report_logger_service: logger - hosts: [] - content_types: [] - enforce: - # see full description below - level1_fallback: true - # only send directives supported by the browser, defaults to false - # this is a port of https://github.com/twitter/secureheaders/blob/83a564a235c8be1a8a3901373dbc769da32f6ed7/lib/secure_headers/headers/policy_management.rb#L97 - browser_adaptive: - enabled: false - report-uri: '%router.request_context.base_url%/nelmio/csp/report' - default-src: [ 'self' ] - frame-src: [ 'https://www.youtube.com' ] - script-src: - - 'self' - - 'unsafe-inline' - img-src: - - 'self' - - facebook.com - - flickr.com - block-all-mixed-content: true # defaults to false, blocks HTTP content over HTTPS transport - # upgrade-insecure-requests: true # defaults to false, upgrades HTTP requests to HTTPS transport - report: - # see full description below - level1_fallback: true - # only send directives supported by the browser, defaults to false - # this is a port of https://github.com/twitter/secureheaders/blob/83a564a235c8be1a8a3901373dbc769da32f6ed7/lib/secure_headers/headers/policy_management.rb#L97 - browser_adaptive: - enabled: true - report-uri: '%router.request_context.base_url%/nelmio/csp/report' - script-src: - - 'self' -``` - -The above configuration would enforce the following policy: - -* Default is to allow from same origin as the page -* Frames only from secure youtube connections -* JavaScript from same origin and from inline ` -{% endcspscript %} - -// ... - -{% cspstyle %} - -{% endcspstyle %} -``` - -If you're not using Twig, you can use message digest with the `ContentSecurityPolicyListener`, it will automatically -compute the message digest and add it to the response CSP header: - -```php - -$listener->addScript(""); - - -$listener->addStyle(""); - -``` - -#### Nonce for inline script handling - -Content-Security-Policy specification also proposes a nonce implementation for inlining. Nelmio Security Bundle -comes out of the box with nonce functionality. Twig is natively supported. - - -In your Twig template use the `csp_nonce` function to access the nonce for the current request and add it to the response -CSP header. If you do not request a nonce, nonce will not be generated. - -```twig - - -// ... - - -``` - -If you're not using Twig, you can use nonce functionality with the `ContentSecurityPolicyListener`: - -```php -// generates a nonce at first time, returns the same nonce once generated -$listener->getNonce('script'); -// or -$listener->getNonce('style'); -``` - -#### Reporting: - -Using the `report-uri` you can easily collect violation using the `ContentSecurityPolicyController`. -Here's an configuration example using `routing.yml`: - -```yaml -csp_report: - path: /csp/report - methods: [POST] - defaults: { _controller: nelmio_security.csp_reporter_controller::indexAction } -``` - -This part of the configuration helps to filter noise collected by this endpoint: - -```yaml -nelmio_security: - csp: - report_endpoint: - log_level: "notice" # Use the appropriate log_level - log_formatter: ~ # Declare a service name that must implement Nelmio\SecurityBundle\ContentSecurityPolicy\Violation\Log\LogFormatterInterface - log_channel: ~ # Declare the channel to use with the logger - filters: - # Filter false positive reports given a domain list - domains: true - # Filter false positive reports given a scheme list - schemes: true - # Filter false positive reports given known browser bugs - browser_bugs: true - # Filter false positive reports given known injected scripts - injected_scripts: true - # You can add you custom filter rules by implementing Nelmio\SecurityBundle\ContentSecurityPolicy\Violation\Filter\NoiseDetectorInterface - # and tag the service with "nelmio_security.csp_report_filter" - dismiss: - # A list of key-values that should be dismissed - # A key is either a domain or a regular expression - # A value is a source or an array of source. The '*' wilcard is accepted - '/^data:/': 'script-src' - '/^https?:\/\/\d+\.\d+\.\d+\.\d+(:\d+)*/': '*' - 'maxcdn.bootstrapcdn.com': '*' - 'www.gstatic.com': ['media-src', 'img-src'] -``` - -### **Signed Cookies**: - -Ideally you should explicitly specify which cookies to sign. The reason for this is simple. -Cookies are sent with each request. Signatures are often longer than the cookie values themselves, -so signing everything would just needlessly slow down your app and increase bandwidth usage for -your users. - -```yaml -nelmio_security: - signed_cookie: - names: [test1, test2] -``` - -However, for simplicity reasons, and to start with a high security and optimize later, you can -specify '*' as a cookie name to have all cookies signed automatically. - -```yaml -nelmio_security: - signed_cookie: - names: ['*'] -``` - -Additional, optional configuration settings: - -```yaml -nelmio_security: - signed_cookie: - secret: this_is_very_secret # defaults to global %secret% parameter - hash_algo: sha512 # defaults to sha256, see `hash_algos()` for available algorithms -``` - -### **Clickjacking Protection**: - -Most websites do not use frames and do not need to be frame-able. This is a common attack vector -for which all current browsers (IE8+, Opera10.5+, Safari4+, Chrome4+ and Firefox3.7+) have a -solution. An extra header sent by your site will tell the browser that it can not be displayed in -a frame. Browsers react by showing a short explanation instead of the content, or a blank page. - -The valid values for the `X-Frame-Options` header are `DENY` (prevent framing from all pages) and -`SAMEORIGIN` (prevent framing from all pages not on the same domain). Additionally this bundle -supports the `ALLOW` option which skips the creation of the header for the matched URLs, if you -want to allow a few URLs and then DENY everything else. - -One more option, as of yet [not well supported](https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options), -is to use `ALLOW-FROM uri` where `uri` can be any origin URL, from -`example.org` to `https://example.org:123/sub/path`. This lets you specify -exactly which domain can embed your site, in case you have a multi-domain setup. - -Default configuration (deny everything): - -```yaml -nelmio_security: - clickjacking: - paths: - '^/.*': DENY - content_types: [] - hosts: [] -``` - -Allow list configuration (deny all but a few URLs): - -```yaml -nelmio_security: - clickjacking: - paths: - '^/iframes/': ALLOW - '^/business/': 'ALLOW-FROM https://biz.example.org' - '^/local/': SAMEORIGIN - '^/.*': DENY - content_types: [] - hosts: [] -``` - -Apply to certain hosts: - -```yaml -nelmio_security: - clickjacking: - paths: - '^/iframes/': ALLOW - '^/.*': DENY - content_types: [] - hosts: - - '^foo\.com$' - - '\.example\.org$' -``` - -You can also of course only deny a few critical URLs, while leaving the rest alone: - -```yaml -nelmio_security: - clickjacking: - paths: - '^/message/write': DENY - content_types: [] - hosts: [] -``` - -An optional `content_types` key lets you restrict the X-Frame-Options header only on some HTTP -response given their content type. - -### **External Redirects Detection**: - -This feature helps you detect and prevent redirects to external sites. This can easily happen -by accident if you carelessly take query parameters as redirection target. - -You can log those (it's logged at warning level) by turning on logging: - -```yaml -nelmio_security: - external_redirects: - log: true -``` - -You can abort (they are replaced by a 403 response) the redirects: - -```yaml -nelmio_security: - external_redirects: - abort: true -``` - -Or you can override them, replacing the redirect's `Location` header by a route name or -another URL: - -```yaml -# redirect to the 'home' route -nelmio_security: - external_redirects: - override: home -``` -```yaml -# redirect to another URL -nelmio_security: - external_redirects: - override: /foo -``` - -If you want to display the URL that was blocked on the overriding page you can -specify the `forward_as` parameter, which defines which query parameter will -receive the URL. For example using the config below, doing a redirect to -`http://example.org/` will be overridden to `/external-redirect?redirUrl=http://example.org/`. - -```yaml -# redirect and forward the overridden URL -nelmio_security: - external_redirects: - override: /external-redirect - forward_as: redirUrl -``` - -Since it's quite common to have to redirect outside the website for legit reasons, -typically OAuth logins and such, you can allow a few domain names. All their subdomains -will be allowed as well, so you can allow your own website's subdomains -if needed. - -```yaml -nelmio_security: - external_redirects: - abort: true - allow_list: - - twitter.com - - facebook.com -``` - -### **Forced HTTPS/SSL Handling**: - -By default, this option forces your entire site to use SSL, always. It redirect all users -reaching the site with a http:// URL to a https:// URL with a 302 response. - -The base configuration for this is the following: - -```yaml -nelmio_security: - forced_ssl: ~ -``` - -If you turn this option on, it's recommended to also set your session cookie to be secure, -and all other cookies you send for that matter. You can do the former using: - -```yaml -framework: - session: - cookie_secure: true -``` - -To keep a few URLs from being force-redirected to SSL you can define an allowed list of regular -expressions: - -```yaml -nelmio_security: - forced_ssl: - enabled: true - allow_list: - - ^/unsecure/ -``` - -To restrict the force-redirects to some hostnames only you can define a list of hostnames -as regular expressions: - -```yaml -nelmio_security: - forced_ssl: - enabled: true - hosts: - - ^\.example\.org$ -``` - -To change the way the redirect is done to a permanent redirect for example, you can set: - -```yaml -nelmio_security: - forced_ssl: - enabled: true - redirect_status_code: 301 -``` - -Then if you want to push it further, you can enable -[HTTP Strict Transport Security (HSTS)](http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02). -This is basically sending a header to tell the browser that your site must always be -accessed using SSL. If a user enters a http:// URL, the browser will convert it to https:// -automatically, and will do so before making any request, which prevents man-in-the-middle -attacks. - -The browser will cache the value for as long as the specified `hsts_max_age` (in seconds), and if -you turn on the `hsts_subdomains` option, the behavior will be applied to all subdomains as well. - -```yaml -nelmio_security: - forced_ssl: - hsts_max_age: 2592000 # 30 days - hsts_subdomains: true -``` - -You can also tell the browser to add your site to the list of known HSTS sites, by enabling -`hsts_preload`. Once your site has appeared in the Chrome and Firefox preload lists, then new -users who come to your site will already be redirected to HTTPS URLs. - -```yaml -nelmio_security: - forced_ssl: - hsts_max_age: 31536000 # 1 year - hsts_preload: true -``` - -> **Note:** A value of at least 1 year is currently required by [Chrome](https://hstspreload.org/) -> and [Firefox](https://blog.mozilla.org/security/2012/11/01/preloading-hsts/). -> `hsts_subdomains` must also be enabled for preloading to work. - -You can speed up the inclusion process by submitting your site to the [HSTS Preload List](https://hstspreload.org/). - -A small word of caution: While HSTS is great for security, it means that if the browser -can not establish your SSL certificate is valid, it will not allow the user to query your site. -That just means you should be careful and renew your certificate in due time. - -Note: HSTS presently (Feb. 2018) works in Firefox 4+, Chrome 4+, Opera 12+, IE 11+, Edge 12+ and Safari 7+. - Check [caniuse](http://caniuse.com/#feat=stricttransportsecurity) for HSTS support in other browsers. - -### **Flexible HTTPS/SSL Handling**: - -The best way to handle SSL securely is to enable it for your entire site. - -However in some cases this is not desirable, be it for caching or performance reasons, -or simply because most visitors of your site are anonymous and don't benefit much from the -added privacy and security of SSL. - -If you don't want to enable SSL across the board, you need to avoid that people on insecure -networks (typically open Wi-Fi) get their session cookie stolen by sending it non-encrypted. -The way to achieve this is to set your session cookie to be secure as such - but don't do -it just yet, keep reading to the end. - -```yaml -framework: - session: - cookie_secure: true -``` - -If you use the remember-me functionality, you would also mark that one as secure: - -```yaml -security: - firewalls: - somename: - remember_me: - secure: true -``` - -Now if you do this, you have two problems. First, insecure pages will not be able to use -the session anymore, which can be inconvenient. Second, if a logged in user gets to a -non-HTTPS page of your site, it is seen as anonymous since his browser will not send the -session cookie. To fix this, this bundle sets a new insecure cookie -(`flexible_ssl.cookie_name`, defaults to `auth`) once a user logs in. That way, if any page -is accessed insecurely by a logged in user, he is redirected to the secure version of the -page, and his session is then visible to the framework. - -Enabling the `flexible_ssl` option of the NelmioSecurityBundle will make sure that -logged-in users are always seeing secure pages, and it will make sure their session cookie -is secure, but anonymous users will still be able to have an insecure session, if you need -to use it to store non critical data like language settings and whatnot. The remember-me -cookie will also be made always secure, even if you leave the setting to false. - -```yaml -nelmio_security: - flexible_ssl: - cookie_name: auth - unsecured_logout: false -``` - -You have to configure one more thing in your security configuration though: every firewall -should have our logout listener added, so that the special `auth` cookie can be cleared when -users log out. You can do it as such: - -```yaml -security: - firewalls: - somename: - # ... - logout: - handlers: - - nelmio_security.flexible_ssl_listener -``` - -On logout, if you would like users to be redirected to an unsecure page set ``unsecured_logout`` -to true. - -### Content Type Sniffing - -Disables the content type sniffing for script resources. Forces the browser to only execute script files with valid -content type headers. This is a non-standard header from Microsoft, more information can be found in -[their documentation at MSDN](http://msdn.microsoft.com/en-us/library/ie/gg622941.aspx). - -```yaml -nelmio_security: - content_type: - nosniff: true -``` - -### XSS Protection - -Enables or disables Microsoft XSS Protection on compatible browsers. -This is a non-standard header from Microsoft, more information can be found in -[their documentation at MSDN](http://blogs.msdn.com/b/ieinternals/archive/2011/01/31/controlling-the-internet-explorer-xss-filter-with-the-x-xss-protection-http-header.aspx). - -```yaml -nelmio_security: - xss_protection: - enabled: true - mode_block: true - report_uri: '%router.request_context.base_url%/nelmio/xss/report' -``` - -### Referrer Policy - -Adds `Referrer-Policy` header to control the `Referer` header that is added -to requests made from your site, and for navigations away from your site by browsers. - -You can specify multiple [referrer policies](https://www.w3.org/TR/referrer-policy/#referrer-policies). -The order of the policies is important. Browser will choose only the last policy they understand. -For example older browsers don’t understand the `strict-origin-when-cross-origin` policy. -A site can specify a `no-referrer` policy followed by a `strict-origin-when-cross-origin` policy: -older browsers will ignore the unknown `strict-origin-when-cross-origin` value and use `no-referrer`, -while newer browsers will use `strict-origin-when-cross-origin` because it is the last to be processed. - -A referrer policy is: - * [`no-referrer`](https://www.w3.org/TR/referrer-policy/#referrer-policy-no-referrer), - * [`no-referrer-when-downgrade`](https://www.w3.org/TR/referrer-policy/#referrer-policy-no-referrer-when-downgrade), - * [`same-origin`](https://www.w3.org/TR/referrer-policy/#referrer-policy-same-origin), - * [`origin`](https://www.w3.org/TR/referrer-policy/#referrer-policy-origin), - * [`strict-origin`](https://www.w3.org/TR/referrer-policy/#referrer-policy-strict-origin), - * [`origin-when-cross-origin`](https://www.w3.org/TR/referrer-policy/#referrer-policy-origin-when-cross-origin), - * [`strict-origin-when-cross-origin`](https://www.w3.org/TR/referrer-policy/#referrer-policy-strict-origin-when-cross-origin), - * [`unsafe-url`](https://www.w3.org/TR/referrer-policy/#referrer-policy-unsafe-url), - * [the empty string](https://www.w3.org/TR/referrer-policy/#referrer-policy-empty-string). - -For better security of your site please use `no-referrer`, `same-origin`, `strict-origin` or `strict-origin-when-cross-origin`. - -```yaml -nelmio_security: - referrer_policy: - enabled: true - policies: - - 'no-referrer' - - 'strict-origin-when-cross-origin' -``` - ## License Released under the MIT License, see LICENSE. + +[1]: https://symfony.com/doc/current/setup/flex.html +[2]: src/Resources/doc/index.rst diff --git a/src/Resources/doc/index.rst b/src/Resources/doc/index.rst new file mode 100644 index 0000000..54ff28f --- /dev/null +++ b/src/Resources/doc/index.rst @@ -0,0 +1,912 @@ +NelmioSecurityBundle +==================== + +The NelmioSecurityBundle provides additional security features for your Symfony application. + +Installation +------------ + +Require the ``nelmio/security-bundle`` package in your composer.json and update +your dependencies: + +.. code-block:: terminal + + $ composer require nelmio/security-bundle + +The bundle should be automatically enabled by `Symfony Flex`_. If you don't use +Flex, you'll need to manually enable the bundle by adding the following line in +the ``config/bundles.php`` file of your project:: + + ['all' => true], + // ... + ]; + +If you don't have a ``config/bundles.php`` file in your project, chances are that +you're using an older Symfony version. In this case, you should have an +``app/AppKernel.php`` file instead. Edit such file: + + HTTPS), + # and send no header to a less secure destination (HTTPS->HTTP). + # If ``strict-origin-when-cross-origin`` is not supported, use ``no-referrer`` policy, + # no referrer information is sent along with requests. + referrer_policy: + enabled: true + policies: + - 'no-referrer' + - 'strict-origin-when-cross-origin' + + # forces HTTPS handling, don't combine with flexible mode + # and make sure you have SSL working on your site before enabling this + # forced_ssl: + # hsts_max_age: 2592000 # 30 days + # hsts_subdomains: true + # redirect_status_code: 302 # default, switch to 301 for permanent redirects + + # flexible HTTPS handling, read the detailed config info + # and make sure you have SSL working on your site before enabling this + # flexible_ssl: + # cookie_name: auth + # unsecured_logout: false + +Content Security Policy +----------------------- + +Using CSP you can set a policy which modern browsers understand and will honor. +The policy contains many different directives; ``default-src``, ``script-src``, +``object-src``, ``style-src``, ``img-src``, ``media-src``, ``frame-src``, +``font-src``, ``connect-src``, ``base-uri``, ``child-src``, ``form-action``, +``frame-ancestors``, ``plugin-types``, ``block-all-mixed-content``, +``upgrade-insecure-requests``, ``report-uri``, ``manifest-src``. + +You can provide an array of directives per content type, except for ``block-all-mixed-content`` +and ``upgrade-insecure-requests`` that only accept boolean values. Empty content +types will inherit from ``default-src``, specified content types will never inherit +from ``default-src``. Please see the `Content Security Policy 1.0`_ and +`Content Security Policy 2.0`_ specifications for details. + +Each directive should be a domain, URI or keyword. The keyword ``'self'`` will +allow content from the same origin as the page. If you need to allow inline +scripts or ``eval()`` you can use ``'unsafe-inline'`` and ``'unsafe-eval'``. + +.. caution:: + + By using ``'unsafe-inline'`` or ``'unsafe-eval'`` you're effectively + disabling the XSS protection mechanism of CSP. + +Apart from content types, the policy also accepts ``report-uri`` which should be +a URI where a browser can POST a `JSON payload`_ to whenever a policy directive +is violated. + +An optional ``content_types`` key lets you restrict the Content Security Policy +headers only on some HTTP response given their content type. + +Finally, an optional ``hosts`` key lets you configure which hostnames (e.g. ``foo.example.org``) +the CSP rule should be enforced on. If the list is empty (it is by default), all +hostnames will use the CSP rule. + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + csp: + enabled: true + report_logger_service: logger + hosts: [] + content_types: [] + enforce: + # see full description below + level1_fallback: true + # only send directives supported by the browser, defaults to false + # this is a port of https://github.com/twitter/secureheaders/blob/83a564a235c8be1a8a3901373dbc769da32f6ed7/lib/secure_headers/headers/policy_management.rb#L97 + browser_adaptive: + enabled: false + report-uri: '%router.request_context.base_url%/nelmio/csp/report' + default-src: [ 'self' ] + frame-src: [ 'https://www.youtube.com' ] + script-src: + - 'self' + - 'unsafe-inline' + img-src: + - 'self' + - facebook.com + - flickr.com + block-all-mixed-content: true # defaults to false, blocks HTTP content over HTTPS transport + # upgrade-insecure-requests: true # defaults to false, upgrades HTTP requests to HTTPS transport + report: + # see full description below + level1_fallback: true + # only send directives supported by the browser, defaults to false + # this is a port of https://github.com/twitter/secureheaders/blob/83a564a235c8be1a8a3901373dbc769da32f6ed7/lib/secure_headers/headers/policy_management.rb#L97 + browser_adaptive: + enabled: true + report-uri: '%router.request_context.base_url%/nelmio/csp/report' + script-src: + - 'self' + +The above configuration would enforce the following policy: + +* Default is to allow from same origin as the page +* Frames only from secure YouTube connections +* JavaScript from same origin and from inline `` + {% endcspscript %} + + {# ... #} + + {% cspstyle %} + + {% endcspstyle %} + +If you're not using Twig, you can use message digest with the +``ContentSecurityPolicyListener``, it will automatically compute the message +digest and add it to the response CSP header:: + + $listener->addScript(""); + + + $listener->addStyle(""); + +Nonce for inline script handling +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Content-Security-Policy specification also proposes a nonce implementation for +inlining. Nelmio Security Bundle comes out of the box with nonce functionality. +Twig is natively supported. + +In your Twig template use the ``csp_nonce`` function to access the nonce for the +current request and add it to the response CSP header. If you do not request a +nonce, nonce will not be generated. + +.. code-block:: html+twig + + + + {# ... #} + + + +If you're not using Twig, you can use nonce functionality with the ``ContentSecurityPolicyListener``:: + + // generates a nonce at first time, returns the same nonce once generated + $listener->getNonce('script'); + // or + $listener->getNonce('style'); + +Reporting +~~~~~~~~~ + +Using the ``report-uri`` you can easily collect violation using the ``ContentSecurityPolicyController``. +Here's an configuration example using ``routing.yml``: + +.. code-block:: yaml + + # config/routes.yaml + csp_report: + path: /csp/report + methods: [POST] + defaults: { _controller: nelmio_security.csp_reporter_controller::indexAction } + +This part of the configuration helps to filter noise collected by this endpoint: + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + csp: + report_endpoint: + log_level: "notice" # Use the appropriate log_level + log_formatter: ~ # Declare a service name that must implement Nelmio\SecurityBundle\ContentSecurityPolicy\Violation\Log\LogFormatterInterface + log_channel: ~ # Declare the channel to use with the logger + filters: + # Filter false positive reports given a domain list + domains: true + # Filter false positive reports given a scheme list + schemes: true + # Filter false positive reports given known browser bugs + browser_bugs: true + # Filter false positive reports given known injected scripts + injected_scripts: true + # You can add you custom filter rules by implementing Nelmio\SecurityBundle\ContentSecurityPolicy\Violation\Filter\NoiseDetectorInterface + # and tag the service with "nelmio_security.csp_report_filter" + dismiss: + # A list of key-values that should be dismissed + # A key is either a domain or a regular expression + # A value is a source or an array of source. The '*' wilcard is accepted + '/^data:/': 'script-src' + '/^https?:\/\/\d+\.\d+\.\d+\.\d+(:\d+)*/': '*' + 'maxcdn.bootstrapcdn.com': '*' + 'www.gstatic.com': ['media-src', 'img-src'] + +Signed Cookies +-------------- + +Ideally you should explicitly specify which cookies to sign. The reason for this +is simple. Cookies are sent with each request. Signatures are often longer than +the cookie values themselves, so signing everything would just needlessly slow +down your app and increase bandwidth usage for your users. + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + signed_cookie: + names: [test1, test2] + +However, for simplicity reasons, and to start with a high security and optimize +later, you can specify ``*`` as a cookie name to have all cookies signed automatically. + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + signed_cookie: + names: ['*'] + +Additional, optional configuration settings: + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + signed_cookie: + secret: this_is_very_secret # defaults to global %secret% parameter + hash_algo: sha512 # defaults to sha256, see ``hash_algos()`` for available algorithms + +Clickjacking Protection +----------------------- + +Most websites do not use frames and do not need to be frame-able. This is a +common attack vector for which all current browsers (IE8+, Opera10.5+, +Safari4+, Chrome4+ and Firefox3.7+) have a solution. An extra header sent by +your site will tell the browser that it can not be displayed in a frame. +Browsers react by showing a short explanation instead of the content, or a blank page. + +The valid values for the ``X-Frame-Options`` header are ``DENY``(prevent framing +from all pages) and ``SAMEORIGIN`` (prevent framing from all pages not on the +same domain). Additionally this bundle supports the ``ALLOW`` option which +skips the creation of the header for the matched URLs, if you want to allow a +few URLs and then DENY everything else. + +One more option, as of yet `not well supported`_, is to use ``ALLOW-FROM uri`` +where ``uri`` can be any origin URL, from ``example.org`` to +``https://example.org:123/sub/path``. This lets you specify exactly which domain +can embed your site, in case you have a multi-domain setup. + +Default configuration (deny everything): + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + clickjacking: + paths: + '^/.*': DENY + content_types: [] + hosts: [] + +Allow list configuration (deny all but a few URLs): + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + clickjacking: + paths: + '^/iframes/': ALLOW + '^/business/': 'ALLOW-FROM https://biz.example.org' + '^/local/': SAMEORIGIN + '^/.*': DENY + content_types: [] + hosts: [] + +Apply to certain hosts: + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + clickjacking: + paths: + '^/iframes/': ALLOW + '^/.*': DENY + content_types: [] + hosts: + - '^foo\.com$' + - '\.example\.org$' + +You can also of course only deny a few critical URLs, while leaving the rest alone: + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + clickjacking: + paths: + '^/message/write': DENY + content_types: [] + hosts: [] + +An optional ``content_types`` key lets you restrict the X-Frame-Options header +only on some HTTP response given their content type. + +External Redirects Detection +---------------------------- + +This feature helps you detect and prevent redirects to external sites. This can +easily happen by accident if you carelessly take query parameters as redirection target. + +You can log those (it's logged at warning level) by turning on logging: + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + external_redirects: + log: true + +You can abort (they are replaced by a 403 response) the redirects: + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + external_redirects: + abort: true + +Or you can override them, replacing the redirect's ``Location`` header by a +route name or another URL: + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + external_redirects: + # redirect to the 'home' route + override: home + # use this to redirect to another URL + # override: /foo + +If you want to display the URL that was blocked on the overriding page you can +specify the ``forward_as`` parameter, which defines which query parameter will +receive the URL. For example using the config below, doing a redirect to +``http://example.org/`` will be overridden to ``/external-redirect?redirUrl=http://example.org/``. + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + external_redirects: + # redirect and forward the overridden URL + override: /external-redirect + forward_as: redirUrl + +Since it's quite common to have to redirect outside the website for legit +reasons, typically OAuth logins and such, you can allow a few domain names. All +their subdomains will be allowed as well, so you can allow your own website's +subdomains if needed. + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + external_redirects: + abort: true + allow_list: + - twitter.com + - facebook.com + +Forced HTTPS/SSL Handling +------------------------- + +By default, this option forces your entire site to use SSL, always. It redirect +all users reaching the site with a http:// URL to a https:// URL with a 302 response. + +The base configuration for this is the following: + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + forced_ssl: ~ + +If you turn this option on, it's recommended to also set your session cookie to +be secure, and all other cookies you send for that matter. You can do the former using: + +.. code-block:: yaml + + # config/packages/framework.yaml + framework: + session: + cookie_secure: true + +To keep a few URLs from being force-redirected to SSL you can define an allowed +list of regular expressions: + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + forced_ssl: + enabled: true + allow_list: + - ^/unsecure/ + +To restrict the force-redirects to some hostnames only you can define a list of +hostnames as regular expressions: + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + forced_ssl: + enabled: true + hosts: + - ^\.example\.org$ + +To change the way the redirect is done to a permanent redirect for example, you can set: + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + forced_ssl: + enabled: true + redirect_status_code: 301 + +Then if you want to push it further, you can enable `HTTP Strict Transport Security (HSTS)`. +This is basically sending a header to tell the browser that your site must always +be accessed using SSL. If a user enters a http:// URL, the browser will convert it +to https:// automatically, and will do so before making any request, which prevents +man-in-the-middle attacks. + +The browser will cache the value for as long as the specified ``hsts_max_age`` +(in seconds), and if you turn on the ``hsts_subdomains`` option, the behavior +will be applied to all subdomains as well. + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + forced_ssl: + hsts_max_age: 2592000 # 30 days + hsts_subdomains: true + +You can also tell the browser to add your site to the list of known HSTS sites, +by enabling ``hsts_preload``. Once your site has appeared in the Chrome and +Firefox preload lists, then new users who come to your site will already be +redirected to HTTPS URLs. + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + forced_ssl: + hsts_max_age: 31536000 # 1 year + hsts_preload: true + +.. note:: + + A value of at least 1 year is currently `required by Chrome`_ and + `also required by Firefox`_. ``hsts_subdomains`` must also be enabled for + preloading to work. + +You can speed up the inclusion process by submitting your site to the `HSTS Preload List`_. + +A small word of caution: While HSTS is great for security, it means that if the +browser can not establish your SSL certificate is valid, it will not allow the +user to query your site. That just means you should be careful and renew your +certificate in due time. + +.. tip:: + + Check `Can I use HSTS?`_ for the full information about its support in browsers. + +Flexible HTTPS/SSL Handling +--------------------------- + +The best way to handle SSL securely is to enable it for your entire site. + +However in some cases this is not desirable, be it for caching or performance +reasons, or simply because most visitors of your site are anonymous and don't +benefit much from the added privacy and security of SSL. + +If you don't want to enable SSL across the board, you need to avoid that people +on insecure networks (typically open Wi-Fi) get their session cookie stolen by +sending it non-encrypted. The way to achieve this is to set your session cookie +to be secure as such - but don't do it just yet, keep reading to the end. + +.. code-block:: yaml + + # config/packages/framework.yaml + framework: + session: + cookie_secure: true + +If you use the remember-me functionality, you would also mark that one as secure: + +.. code-block:: yaml + + # config/packages/security.yaml + security: + firewalls: + somename: + remember_me: + secure: true + +Now if you do this, you have two problems. First, insecure pages will not be +able to use the session anymore, which can be inconvenient. Second, if a logged +in user gets to a non-HTTPS page of your site, it is seen as anonymous since +his browser will not send the session cookie. To fix this, this bundle sets a +new insecure cookie(``flexible_ssl.cookie_name``, defaults to ``auth``) once a +user logs in. That way, if any page is accessed insecurely by a logged in user, +he is redirected to the secure version of the page, and his session is then +visible to the framework. + +Enabling the ``flexible_ssl`` option of the NelmioSecurityBundle will make sure +that logged-in users are always seeing secure pages, and it will make sure +their session cookie is secure, but anonymous users will still be able to have +an insecure session, if you need to use it to store non critical data like +language settings and whatnot. The remember-me cookie will also be made always +secure, even if you leave the setting to false. + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + flexible_ssl: + cookie_name: auth + unsecured_logout: false + +You have to configure one more thing in your security configuration though: +every firewall should have our logout listener added, so that the special +``auth`` cookie can be cleared when users log out. You can do it as such: + +.. code-block:: yaml + + # config/packages/security.yaml + security: + firewalls: + somename: + # ... + logout: + handlers: + - nelmio_security.flexible_ssl_listener + +On logout, if you would like users to be redirected to an unsecure page set +``unsecured_logout`` to true. + +Content Type Sniffing +--------------------- + +Disables the content type sniffing for script resources. Forces the browser to only execute script files with valid +content type headers. This requires using `a non-standard nosniff header from Microsoft`_. + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + content_type: + nosniff: true + +XSS Protection +-------------- + +Enables or disables Microsoft XSS Protection on compatible browsers. +This requires using `a non-standard X-XSS-Protection header from Microsoft`_. + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + xss_protection: + enabled: true + mode_block: true + report_uri: '%router.request_context.base_url%/nelmio/xss/report' + +Referrer Policy +--------------- + +Adds ``Referrer-Policy`` header to control the ``Referer`` header that is added +to requests made from your site, and for navigations away from your site by browsers. + +You can specify multiple `referrer policies`_. The order of the policies is +important. Browser will choose only the last policy they understand. For +example older browsers don't understand the ``strict-origin-when-cross-origin`` +policy. A site can specify a ``no-referrer`` policy followed by a +``strict-origin-when-cross-origin`` policy: older browsers will ignore the +unknown ``strict-origin-when-cross-origin`` value and use ``no-referrer``, +while newer browsers will use ``strict-origin-when-cross-origin`` because it is +the last to be processed. + +These are the valid referrer policies: + +* `no-referrer `_ +* `no-referrer-when-downgrade `_ +* `same-origin `_ +* `origin `_ +* `strict-origin `_ +* `origin-when-cross-origin `_ +* `strict-origin-when-cross-origin `_ +* `unsafe-url `_ +* `an empty string `_ + +For better security of your site please use ``no-referrer``, ``same-origin``, +``strict-origin`` or ``strict-origin-when-cross-origin``. + +.. code-block:: yaml + + # config/packages/nelmio_security.yaml + nelmio_security: + referrer_policy: + enabled: true + policies: + - 'no-referrer' + - 'strict-origin-when-cross-origin' + +.. _`Symfony Flex`: https://symfony.com/doc/current/setup/flex.html +.. _`Content Security Policy 1.0`: https://www.w3.org/TR/2012/CR-CSP-20121115/ +.. _`Content Security Policy 2.0`: https://www.w3.org/TR/2015/CR-CSP2-20150721/ +.. _`JSON payload`: https://developer.mozilla.org/en-US/docs/Security/CSP/Using_CSP_violation_reports#Sample_violation_report +.. _`Twitter SecureHeaders library`: https://github.com/twitter/secureheaders +.. _`not well supported`: https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options +.. _`HTTP Strict Transport Security (HSTS)`: http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02 +.. _`required by Chrome`: https://hstspreload.org/ +.. _`also required by Firefox`: https://blog.mozilla.org/security/2012/11/01/preloading-hsts/ +.. _`HSTS Preload List`: https://hstspreload.org/ +.. _`Can I use HSTS?`: http://caniuse.com/#feat=stricttransportsecurity +.. _`a non-standard nosniff header from Microsoft`: http://msdn.microsoft.com/en-us/library/ie/gg622941.aspx +.. _`a non-standard X-XSS-Protection header from Microsoft`: http://blogs.msdn.com/b/ieinternals/archive/2011/01/31/controlling-the-internet-explorer-xss-filter-with-the-x-xss-protection-http-header.aspx +.. _`referrer policies`: https://www.w3.org/TR/referrer-policy/#referrer-policies