magento / magento2

Prior to making any Submission(s), you must sign an Adobe Contributor License Agreement, available here at: https://opensource.adobe.com/cla.html. All Submissions you make to Adobe Inc. and its affiliates, assigns and subsidiaries (collectively “Adobe”) are subject to the terms of the Adobe Contributor License Agreement.
http://www.magento.com
Open Software License 3.0
11.4k stars 9.29k forks source link

Customers can see data of other customers in MyAccount #35037

Closed jan-ziehme closed 2 years ago

jan-ziehme commented 2 years ago

Preconditions (*)

  1. Magento 2.4.3, with a lot of custom modules
  2. Session Cache Handler: Redis 6.2.4 (only Master)
    1. compression_threshold = '2048'
    2. compression_library = 'gzip'
    3. max_concurrency = '12'
    4. break_after_frontend = '5'
    5. break_after_adminhtml = '30'
    6. first_lifetime = '600'
    7. bot_first_lifetime = '60'
    8. bot_lifetime = '7200'
    9. disable_locking = '0'
    10. min_lifetime = '60'
    11. max_lifetime = '2592000'
    12. sentinel_master = ''
    13. sentinel_servers = ''
    14. sentinel_connect_retries = '5'
    15. sentinel_verify_master = '0'
  3. disabled varnish
  4. no sessions within local filesystem or database, all sessions are handled within REDIS (checked with redis-cli monitor)
  5. Session Validation is for every attribute true, but the system has empty values for x-forwarded-for, http-via and remote_addr, so the system only checks for User-Agent
    1. this is an architecture and configuration problem (PHP: variables_order), we will solve this today

Steps to reproduce (*)

  1. Currently not reproducible, we work on it, but maybe another user can relate and knows this situation and has some helpful informations
  2. Situations customers report seeing information from other customers:
    1. They see as logged in customer another customer name within the header (Welcome, Firstname Lastname).
      1. This Information is placed with knockout and CustomerData
      2. <div class="text-center margin-bottom-15" data-bind="scope: 'customer'"><span class="text-small text-bold c-navyblue"><?= __('Login'); ?><br/><?= __('MyAccount'); ?></span><span class="c-denimblue"><?= __('logged in as '); ?><span data-bind="text: customer().fullname"></span></span></div>
    2. MyAccount Page shows another customer data
      1. this page is not cached -> first hint of a session hijacking situation
    3. Product Option Formular is prefilled with data of another customer
      1. we implemented a formular autofill feature with reload of CustomerData Section and a plugin which enhance the CustomerData
        1. look at Enhance CustomerData Plugin and Autofill JS
      2. the products have product options, these are autofilled with this feature
      3. this product was added to cart and also ordered
        1. the order was sent to the correct customer, also every other data within the order than the product options are correct
        2. => that gives the hint, that die hijacking of the session is a temporary event and the customer get his own session back

Conclusion

  1. i know it is a very specific problem and maybe not Magento2 core related
  2. we used standard functionality to enhance customer related data (plugin) and private content (CustomerData), which makes me think it could be Magento2 Core related.
  3. when we access customer session we only use SessionFactory, except at the plugin (CustomerData Plugin), there we use the CurrentCustomer Helper which uses the Session. Could this lead to the problem?
  4. the hijack is highly likely temporary (we are doing further checks actually)

Enhance CustomerData Plugin

di.xml

`

`

CustomerData.php

`use Magento\Customer\Api\AddressRepositoryInterface; use Magento\Customer\Helper\Session\CurrentCustomer; use Magento\Directory\Model\CountryFactory;

class CustomerData { /**

Autofill JS

` define([ 'uiComponent', 'Magento_Customer/js/customer-data', 'jquery' ], function (Component, customerData, $) { 'use strict';

return Component.extend({
    /** @inheritdoc */
    initialize: function () {
        this._super();
        customerData.reload(['customer'], true);
        this.customer = customerData.get('customer'); //pass your custom section name

        let self = this;
        $(window).on('autofill', function () {
            self.autofill();
        });
        $(window).trigger('autofill');
    },
    autofill: function () {
        let self = this;

        jQuery('[data-autofillid^=customer]').each(function (field) {
            let name = jQuery(this).data('autofillid');
            let attributeNames = name.split("_");

            if (attributeNames.length === 3) {
                if (self.customer().hasOwnProperty(attributeNames[1])) {
                    if (self.customer()[attributeNames[1]].hasOwnProperty(attributeNames[2])) {
                        jQuery(this).attr('data-value', self.customer()[attributeNames[1]][attributeNames[2]]);
                        jQuery(this).val(self.customer()[attributeNames[1]][attributeNames[2]]);

                        if (jQuery(this).prop('nodeName') === 'SPAN') {
                            jQuery(this).html(self.customer()[attributeNames[1]][attributeNames[2]]);
                        }
                    }
                }

            } else if (attributeNames.length === 2) {
                if (self.customer().hasOwnProperty(attributeNames[1])) {
                    jQuery(this).attr('data-value', self.customer()[attributeNames[1]]);
                    jQuery(this).val(self.customer()[attributeNames[1]]);

                    if (jQuery(this).prop('nodeName') === 'SPAN') {
                        jQuery(this).html(self.customer()[attributeNames[1]]);
                    }
                }
            }
        });

        let event = document.createEvent('Event');
        event.initEvent('rerender', true, true); //can bubble, and is cancellable
        window.dispatchEvent(event);

        /**
         * fetch parameters from url
         */
        let getUrlParameter = function getUrlParameter(sParam) {
            let sPageURL = window.location.search.substring(1),
                sURLVariables = sPageURL.split('&'),
                sParameterName,
                i;

            for (i = 0; i < sURLVariables.length; i++) {
                sParameterName = sURLVariables[i].split('=');

                if (sParameterName[0] === sParam) {
                    return sParameterName[1] === undefined ? true : decodeURIComponent(sParameterName[1].replace(/\+/g, " "));
                }
            }
        };

        /**
         * for typo3 created autofills we have to use a global variable
         */
        if (window.hasOwnProperty('autofill')) {
            let autofills = window.autofill;
            autofills.forEach(function (autofill) {
                let elements = $('[data-autofillid=' + autofill.key + ']');

                if (autofill.key === 'seminar_dates') {
                    /**
                     * remove all existing options
                     */
                    jQuery(elements).find('option').remove();

                    /**
                     * add options to select
                     */
                    let options = typeof autofill.value === 'string' ? JSON.parse(autofill.value) : autofill.value;
                    options.forEach(function (option) {
                        jQuery(elements).append('<option value="'+option.value+'">'+option.label+'</option>');
                    });
                } else {
                    elements.each(function (index, el) {
                        $(el).val(decodeURIComponent(autofill.value.replace(/\+/g, " ")));
                        $(el).html(decodeURIComponent(autofill.value.replace(/\+/g, " ")));
                    });
                }
            });
        }

        let parameters = getUrlParameter('autofill');
        if (parameters) {
            parameters.split("|").forEach(function (parameter) {
                let keyValue = parameter.split(":");
                if (keyValue.length > 1) {

                    jQuery('[data-autofillid=' + keyValue[0] + ']').each(function () {
                        if (keyValue[0] === 'profitcenter' && $(this).val().length) {
                            return;
                        }

                        let value = [];
                        for (let i = 1; i < keyValue.length; i++) {
                            value.push(keyValue[i]);
                        }

                        value = decodeURIComponent(value.join(':'));

                        if (jQuery(this).prop('nodeName') === 'SPAN') {
                            jQuery(this).html(value);
                        } else {
                            jQuery(this).val(value);
                        }
                    });
                }
            });
        }
    }
});

}); `

Expected result (*)

  1. Customers see only their information

Actual result (*)

  1. Customers can see data of other customers, also with private content (CustomerData) and not cached pages like the myaccount dashboard

Please provide Severity assessment for the Issue as Reporter. This information will help during Confirmation and Issue triage processes.

m2-assistant[bot] commented 2 years ago

Hi @jan-ziehme. Thank you for your report. To speed up processing of this issue, make sure that you provided the following information:

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:

@magento give me 2.4-develop instance - upcoming 2.4.x release

For more details, review the Magento Contributor Assistant documentation.

Add a comment to assign the issue: @magento I am working on this

To learn more about issue processing workflow, refer to the Code Contributions.


:clock10: You can find the schedule on the Magento Community Calendar page.

:telephone_receiver: 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.

:pencil2: Feel free to post questions/proposals/feedback related to the Community Contributions Triage process to the corresponding Slack Channel

jan-ziehme commented 2 years ago

Problem resulted from an old and "incompatible" third party extension. Because of this, the route customer/section/load was cached in FPC. This in combination with a maybe customer-data.js Bug (url was missing the url parameter _={timestamp}) the customers get user data from another users. The JS Call was customerData.reload(['customers', true]).done( ... ).

I close this issue.