Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSP nonce cached on pages when disabling unsafe-inline #38615

Closed
1 of 5 tasks
Dnd-Coch opened this issue Apr 12, 2024 · 26 comments · May be fixed by #38637
Closed
1 of 5 tasks

CSP nonce cached on pages when disabling unsafe-inline #38615

Dnd-Coch opened this issue Apr 12, 2024 · 26 comments · May be fixed by #38637
Assignees
Labels
Area: Framework Component: Csp Issue: Confirmed Gate 3 Passed. Manual verification of the issue completed. Issue is confirmed Priority: P2 A defect with this priority could have functionality issues which are not to expectations. Progress: done Reported on 2.4.7 Indicates original Magento version for the Issue report. Reproduced on 2.4.x The issue has been reproduced on latest 2.4-develop branch Triage: Dev.Experience Issue related to Developer Experience and needs help with Triage to Confirm or Reject it

Comments

@Dnd-Coch
Copy link

Dnd-Coch commented Apr 12, 2024

Preconditions

  • Magento 2.4.7
  • Custom configuration as following in etc/config.xml:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
       <csp>
           <policies>
               <storefront>
                   <scripts>
                       <inline>0</inline>
                   </scripts>
               </storefront>
           </policies>
       </csp>
    </default>
</config>

Steps to reproduce

  1. Add above content in the etc/config.xml of a custom module
  2. Clear the cache
  3. Load the site homepage in your browser
  4. Inspect the Content-Security-Policy-Report-Only HTTP header of the page

Expected result

  • The unsafe-inline expression is not present in the script-src policy
  • Hashes of the inline scripts of the page are present in the script-src policy

Actual result

  • The unsafe-inline expression is not present in the script-src policy
  • A generated nonce is present in the script-src policy instead of hashes
  • The nonce is cached by FPC

As it is mentioned in this architecture document, the nonce approach is incompatible with Magento's page cache.

So the fact that nonce takes precedence over hashes in this method \Magento\Csp\Helper\InlineUtil::processTag is a problem for merchants who want to remove the unsafe-inline expression of their policies to improve overall security.

Nonce should be generated only for non-cacheable pages.

Examples

See \Magento\Csp\Helper\InlineUtil::processTag method.

Proposed solution

No response

Release note

No response

Triage and priority

  • Severity: S0 - Affects critical data or functionality and leaves users without workaround.
  • Severity: S1 - Affects critical data or functionality and forces users to employ a workaround.
  • Severity: S2 - Affects non-critical data or functionality and forces users to employ a workaround.
  • Severity: S3 - Affects non-critical data or functionality and does not force users to employ a workaround.
  • Severity: S4 - Affects aesthetics, professional look and feel, “quality” or “usability”.
@Dnd-Coch Dnd-Coch added the Triage: Dev.Experience Issue related to Developer Experience and needs help with Triage to Confirm or Reject it label Apr 12, 2024
Copy link

m2-assistant bot commented Apr 12, 2024

Hi @Dnd-Coch. Thank you for your report.
To speed up processing of this issue, make sure that the issue is reproducible on the vanilla Magento instance following Steps to reproduce. To deploy vanilla Magento instance on our environment, Add a comment to the issue:


Join Magento Community Engineering Slack and ask your questions in #github channel.
⚠️ According to the Magento Contribution requirements, all issues must go through the Community Contributions Triage process. Community Contributions Triage is a public meeting.
🕙 You can find the schedule on the Magento Community Calendar page.
📞 The triage of issues happens in the queue order. If you want to speed up the delivery of your contribution, join the Community Contributions Triage session to discuss the appropriate ticket.

Copy link

m2-assistant bot commented Apr 15, 2024

Hi @engcom-November. Thank you for working on this issue.
In order to make sure that issue has enough information and ready for development, please read and check the following instruction: 👇

  • 1. Verify that issue has all the required information. (Preconditions, Steps to reproduce, Expected result, Actual result).
  • 2. Verify that issue has a meaningful description and provides enough information to reproduce the issue.
  • 3. Add Area: XXXXX label to the ticket, indicating the functional areas it may be related to.
  • 4. Verify that the issue is reproducible on 2.4-develop branch
    Details- Add the comment @magento give me 2.4-develop instance to deploy test instance on Magento infrastructure.
    - If the issue is reproducible on 2.4-develop branch, please, add the label Reproduced on 2.4.x.
    - If the issue is not reproducible, add your comment that issue is not reproducible and close the issue and stop verification process here!
  • 5. Add label Issue: Confirmed once verification is complete.
  • 6. Make sure that automatic system confirms that report has been added to the backlog.

@engcom-November
Copy link

Hello @Dnd-Coch,

Thank you for the report and collaboration!

This issue is not reproducible in 2.4-develop.
Added the config file with inline as 0, but in the Content-Security-Policy-Report-Only header we were able to see the hash instead of the generated nonce.

Please take a look at the screenshot :
image

Please let us know if we are missing anything.

Thank you.

@engcom-November engcom-November added the Issue: needs update Additional information is require, waiting for response label Apr 15, 2024
@m2-community-project m2-community-project bot moved this from Ready for Confirmation to Needs Update in Issue Confirmation and Triage Board Apr 15, 2024
@engcom-November engcom-November added the Reported on 2.4.7 Indicates original Magento version for the Issue report. label Apr 15, 2024
@hostep
Copy link
Contributor

hostep commented Apr 15, 2024

@engcom-November: you can't test this yet on 2.4-develop, some code which was released in 2.4.7 isn't merged yet into 2.4-develop, you should either test with 2.4.7, or wait until the merge has happened.
Here's an example of changes that haven't been merged in 2.4-develop that might be relevant to this issue: 25cf7f0

@engcom-November
Copy link

Hello @Dnd-Coch,

Verified in 2.4.7, as suggested by @hostep,
We are able to see a nonce in Content-Security-Policy-Report-Only header along with the hashes.
As nonce is incompatible with Magento's page cache, this issue can be confirmed.

Please take a look at the screenshot:
image

Thank you.

@engcom-November engcom-November added Component: Csp Area: Framework Issue: Confirmed Gate 3 Passed. Manual verification of the issue completed. Issue is confirmed Reproduced on 2.4.x The issue has been reproduced on latest 2.4-develop branch labels Apr 16, 2024
@m2-community-project m2-community-project bot moved this from Needs Update to Confirmed in Issue Confirmation and Triage Board Apr 17, 2024
@m2-community-project m2-community-project bot removed the Issue: Confirmed Gate 3 Passed. Manual verification of the issue completed. Issue is confirmed label Apr 17, 2024
@github-jira-sync-bot
Copy link

✅ Jira issue https://jira.corp.adobe.com/browse/AC-11805 is successfully created for this GitHub issue.

@m2-community-project m2-community-project bot added Issue: Confirmed Gate 3 Passed. Manual verification of the issue completed. Issue is confirmed and removed Issue: needs update Additional information is require, waiting for response labels Apr 17, 2024
Copy link

m2-assistant bot commented Apr 17, 2024

✅ Confirmed by @engcom-November. Thank you for verifying the issue.
Issue Available: @engcom-November, You will be automatically unassigned. Contributors/Maintainers can claim this issue to continue. To reclaim and continue work, reassign the ticket to yourself.

@github-jira-sync-bot
Copy link

❌ You don't have permission to export this issue.

@engcom-Hotel engcom-Hotel added the Priority: P2 A defect with this priority could have functionality issues which are not to expectations. label Apr 17, 2024
@m2-community-project m2-community-project bot added this to Ready for Development in High Priority Backlog Apr 17, 2024
@digitalrisedorset
Copy link

@magento I am working on this

@digitalrisedorset
Copy link

digitalrisedorset commented Apr 17, 2024

I have tested on my local Magento 2.4.7. I modified the method \Magento\Csp\Helper\InlineUtil::processTag as per the recommendations in this issue. The simple swap of nonce and hash in this method seems to make the nonce dissapearing and many more hash to appear.
I understand this is all there is to it. I am not deeply knowledgeable of this particular issue.

@Dnd-Coch although doing accurately what you told us to seems to resolve the issue, I'd like to understand if this recommendation is just an example amongst others that I may have to discover?

The question from now is: is there a way to perform all the use cases to test this change better?
For instance, is there a way to validate the hashes are ok even with FPC enabled?

@digitalrisedorset
Copy link

digitalrisedorset commented Apr 18, 2024

Hi, I think I have the nonce replaced by the sha256 number: the change is as simple as swapping the nonce generation code for the sha one.
To test it all, I have added in a template an inline script

<script type="application/x-javascript">
    require(['jquery','domReady!'], function($){
        console.log('hello world');
    })
</script>

Whilst the csp config is in report mode and the inline javascript is not allowed (see config below), I could see an error in my console once the cache was cleared

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <csp>
            <policies>
                <storefront>
                    <scripts>
                        <inline>0</inline>
                    </scripts>
                </storefront>
            </policies>
            <mode>
                <storefront>
                    <report_only>1</report_only>
                </storefront>
            </mode>
        </csp>
    </default>
</config>

Then to remove the error, I have 3 solutions:

    1. using the method $csp->renderTag for the script will remove the error by dynamically allocating the sha policy for this specific script
<?php $csp->renderTag('script', [],
    <<<script
require(['jquery','domReady!'], function($){
        console.log('hello world');
    })
script
) ?>
    1. In a whiltelist.xml file in a custom module, we can add the sha number that is in the console error message and set it whitelisted like below:
<?xml version="1.0" encoding="UTF-8"?>
<csp_whitelist xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Csp:etc/csp_whitelist.xsd">
    <policies>
        <policy id="script-src">
            <values>
                <value id="layered-navigation" type="hash" algorithm="sha256">OIrv6L30QCGPwetl1dc9xrNsnTrCZCF0bTiMujXOSOI=</value>
            </values>
        </policy>
    </policies>
</csp_whitelist>

The above above consists in leaving the inline javascript as it is and adding the whitelist rule manually. It is convenient for legacy code

    1. the last method is to enable the inline script either globally (highly discouraged) or disabled for one page only. The config snippet enables inline javascript for category landing page
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <csp>
            <policies>
                <storefront>
                    <scripts>
                        <inline>0</inline>
                    </scripts>
                </storefront>
                <storefront_catalog_category_view>
                    <scripts>
                        <inline>1</inline>
                    </scripts>
                </storefront_catalog_category_view>
            </policies>
            <mode>
                <storefront>
                    <report_only>1</report_only>
                </storefront>
            </mode>
        </csp>
    </default>
</config>

@digitalrisedorset
Copy link

I put the fix in a separate comment here:
the method \Magento\Csp\Helper\InlineUtil::processTag has to be changed from

if ($tagData->getTag() === 'script') {
                    $nonce = $this->nonceProvider->generateNonce();
                    $tagAttributes = $tagData->getAttributes();
                    $tagAttributes['nonce'] = $nonce;
                    $newTagData = new TagData(
                        $tagData->getTag(),
                        $tagAttributes,
                        $tagData->getContent(),
                        $tagData->isTextContent()
                    );

                    $tagData = $newTagData;
                } else {
                    $this->dynamicCollector->add(
                        new FetchPolicy(
                            $policyId,
                            false,
                            [],
                            [],
                            false,
                            false,
                            false,
                            [],
                            $this->generateHashValue($tagData->getContent())
                        )
                    );
                }

to

if ($tagData->getTag() === 'script') {
                    $this->dynamicCollector->add(
                        new FetchPolicy(
                            $policyId,
                            false,
                            [],
                            [],
                            false,
                            false,
                            false,
                            [],
                            $this->generateHashValue($tagData->getContent())
                        )
                    );
                } else {
                    $nonce = $this->nonceProvider->generateNonce();
                    $tagAttributes = $tagData->getAttributes();
                    $tagAttributes['nonce'] = $nonce;
                    $newTagData = new TagData(
                        $tagData->getTag(),
                        $tagAttributes,
                        $tagData->getContent(),
                        $tagData->isTextContent()
                    );

                    $tagData = $newTagData;
                }

@Dnd-Coch
Copy link
Author

Hello @digitalrisedorset,

Swapping nonce and hash logic won't work because the nonce approach must be used in the checkout page.

Actually, nonce takes precedence over hashes for scripts once unsafe-inline is disabled. Ideally, we should have the option to choose between nonces and hashes.

This could be addressed by adding a configuration to enable nonce usage, something like:

<policies>
    <storefront_checkout_index_index>
        <scripts>
            <inline>0</inline>
            <event_handlers>1</event_handlers>
            <nonce>1</nonce>
        </scripts>
    </storefront_checkout_index_index>
</policies>

This config would be used to conditionally apply nonce logic:

if ($tagData->getTag() === 'script'
     && $this->isNonceEnabled(self::$tagMeta[$tagData->getTag()]['id'])
) {

The ideal scenario would be to have a switch based on page cacheability instead of a configuration, but I don't know if it is even possible:

  • Cacheable pages => hashes
  • Non-cacheable pages => nonce

@nathanjosiah
Copy link
Contributor

We have no choice but to use nonces in some cases. For example the Paypal JS SDK requires a nonce or it can't be used with CSP. This is true for several other libraries as well. We are looking into this.

@nathanjosiah
Copy link
Contributor

After some review including the old architecture document from a previous member of my team, I'm not understanding the impact. The nonce is cached but it's not clear how that is impacting anybody. Hackers would need to know what the random nonce will be in order for their malicious script to be cached with the correct nonce. Is there another problem as a result of the cached nonce?

@digitalrisedorset
Copy link

digitalrisedorset commented Apr 19, 2024

Thank you both, I have now added the cacheable logic, it starts now to make a lot of sense. The implementation I have come up with was on Magento 2.4.7. I understand we are not ready to add it on develop. Are you able to guide me on the next steps?

However, I have added a PR on the develop branch (see #38637). Please let me know if this PR is fine to go or should I close it for now?

@Dnd-Coch
Copy link
Author

After some review including the old architecture document from a previous member of my team, I'm not understanding the impact. The nonce is cached but it's not clear how that is impacting anybody. Hackers would need to know what the random nonce will be in order for their malicious script to be cached with the correct nonce. Is there another problem as a result of the cached nonce?

Nonces are intended to be unique per request. Caching a nonce defeats this purpose, as it becomes shared across multiple users on FPC enabled pages.

While it's certainly true that exploiting a cached nonce would be much more difficult for hackers compared to no nonce at all, there's still a potential vulnerability. Nonces are designed to be one-time-use values, eliminating the risk of reuse altogether. Having them cached introduces a small window of opportunity for a sophisticated hacker with significant knowledge about the caching mechanism.

You may find some technical details in this study.

@nathanjosiah
Copy link
Contributor

I cannot find any information including in that study that shows that caching a unique nonce per-page presents any notable security risk. If something causes the underlying page content to be refreshed (like adding a new script) which invalidates the FPC, a new nonce is generated and cached again. All of the CSP studies I can find talk about not caching them across a session and multiple pages. In fact, in some cases it's required for example a SPA has to have the same none for the entire loaded application.

@Dnd-Coch
Copy link
Author

In my understanding, some attacks do not necessary need the page content to be refreshed. For example, with DOM-based XSS, the HTTP response of the page itself does not change but the client side code contained in the page executes differently due to the malicious modifications that have occurred in the DOM environment.
So, the study says that by exploiting DOM-based XSS vulnerabilities, in a "one cached nonce per page" context, attackers could still dynamically load untrusted data containing the stolen nonce into the victim's browser.

When nonce reuse is only due to a cache, it is harder for an attacker to bypass the CSP, as they have to find a way to inject the malicious payload in the cached web page dynamically, for example exploiting DOM XSS vulnerabilities. If instead, the server-side code issues the same nonce for multiple responses, an attacker can simply use a stolen nonce to craft a payload that includes it to bypass the CSP.

@nathanjosiah
Copy link
Contributor

This just explains the principle that the hacker would need some way to inject the payload into the page for other users without causing the FPC to be invalidated. Fundamentally that's true but doesn't explain how that would work in the specific product.
Regardless, have several axiomatic truths in play:

  • We can't disable the FPC
  • We currently have to use nonces for some things (like PayPayl SDK)
  • Causing the FPC to be constantly invalidated to cause the nonces to be generated more frequently defeats the purpose of the FPC

@digitalrisedorset
Copy link

There is no automated tests at this time but the fix was added in the 2.4-develop branch and I verified both 2.4.7 and 2.4-develop had this issue.

@yuriy-boyko
Copy link

  • I installed a fresh Magento 2.4.7
  • installed sample data
  • then i added a script to Content->Configuration->Footer-> Miscellaneous HTML
<script>
 console.log('test CSP');
</script>

Now i get the same CSP error as you guys

image

I expect scripts added from Magento admin to work like in 2.4.6
You should be able to add any script, for example tracking scripts and they should work fine in the checkout.
Also many vendor modules can add tracking scripts or payment scripts into the checkout, or simple inline scripts when the module uses default.xml so the script is added anywhere.

Upgrading from 2.4.6 to 2.4.7 a website with many vendor module and many tracking script will cause multiple CSP errors in the checkout.

A temporary fix can be overriding this file in your custom module vendor\magento\module-checkout\etc\config.xml
Anyway inline scripts were disabled only in 2.4.7 as i see from this commit 25cf7f0

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <csp>
            <policies>
                <storefront_checkout_index_index>
                    <scripts>
                        <inline>1</inline>
                    </scripts>
                </storefront_checkout_index_index>
            </policies>
        </csp>
    </default>
</config>

@nathanjosiah
Copy link
Contributor

Yes, upgrading is going to be difficult but we do not have a choice for this change.

This is a new PCI 4 requirement going into effect March of 2025. You will have to comply with this or risk failing the audit moving forward. Please check with your auditor about PCI 4 requirement 6.4.3 for more information.

You can change the configuration but to be clear it will be in violation of PCI.

Since this thread is turning into a complaint about the feature and not a discussion of the specific issue of nonce caching, I'm going to close this issue unless somebody can raise a clear issue that addresses my points from previous comments.

@m2-community-project m2-community-project bot moved this from Pull Request In Progress to Done in High Priority Backlog Apr 23, 2024
@digitalrisedorset
Copy link

digitalrisedorset commented Apr 23, 2024

@nathanjosiah there is a PR for this requirement. As far as I see it, what you have asked for is part of the PR changes. These discussions did appear to be making noises rather than anything. Can we not just get the PR changes reviewed before we close this issue?

@Dnd-Coch
Copy link
Author

This just explains the principle that the hacker would need some way to inject the payload into the page for other users without causing the FPC to be invalidated. Fundamentally that's true but doesn't explain how that would work in the specific product.
Regardless, have several axiomatic truths in play:

  • We can't disable the FPC
  • We currently have to use nonces for some things (like PayPayl SDK)
  • Causing the FPC to be constantly invalidated to cause the nonces to be generated more frequently defeats the purpose of the FPC

@nathanjosiah Failing to explain how that would work on the specific product, we simply propose to prevent it from happening.

Disabling the FPC was never an option. According to @digitalrisedorset's PR, it seems possible to make sure the page is not cacheable before generating a nonce. If the page is cacheable, hashes are used instead.

I don't understand why this issue has been closed, just because a comment was off-topic...

@nathanjosiah
Copy link
Contributor

nathanjosiah commented Apr 25, 2024

If the page is cacheable, hashes are used instead.

This is the issue we have. This doesn't work for cases where a nonce is required. PayPal SDK is used on every page and is not possible to use hashes because of how they implemented it. The same is true for several other libraries like Adobe Runtime, Google Tag Manager, Payment Services, and more.

Also, we cannot simply use hashes for everything. It causes the header to be too large and it crashes most reasonable implementations of Varnish and also the application itself. We have also had floods of complaints about our header size from the Magento Tags header and that's before the CSP changes which forces hashes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Framework Component: Csp Issue: Confirmed Gate 3 Passed. Manual verification of the issue completed. Issue is confirmed Priority: P2 A defect with this priority could have functionality issues which are not to expectations. Progress: done Reported on 2.4.7 Indicates original Magento version for the Issue report. Reproduced on 2.4.x The issue has been reproduced on latest 2.4-develop branch Triage: Dev.Experience Issue related to Developer Experience and needs help with Triage to Confirm or Reject it
Projects
8 participants