1

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
          1. => 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

<type name="Magento\Customer\CustomerData\Customer">
    <plugin name="Plugin_CustomerData" type="My\Core\Plugin\Customer\CustomerData" />
</type>

CustomerData.php

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

class CustomerData
{
    /**
     * @var CurrentCustomer
     */
    protected $currentCustomer;

    /**
     * @var AddressRepositoryInterface
     */
    protected $addressRepository;

    /**
     * @var CountryFactory
     */
    protected $countryFactory;
  
    /**
     * @var \Magento\Customer\Api\Data\AddressInterface
     */
    private $billingAddress;
  
    /**
     * @var \Magento\Customer\Api\Data\AddressInterface
     */
    private $shippingAddress;

    /**
     * CustomerData constructor.
     * @param CurrentCustomer $currentCustomer
     * @param AddressRepositoryInterface $addressRepository
     * @param CountryFactory $countryFactory
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function __construct(
        CurrentCustomer $currentCustomer,
        AddressRepositoryInterface $addressRepository,
        CountryFactory $countryFactory
    ) {
        $this->currentCustomer = $currentCustomer;
        $this->addressRepository = $addressRepository;
        $this->countryFactory = $countryFactory;

        if ($this->currentCustomer->getCustomerId()) {
            $this->billingAddress   = $this->addressRepository->getById($this->currentCustomer->getCustomer()->getDefaultBilling());
            $this->shippingAddress  = $this->addressRepository->getById($this->currentCustomer->getCustomer()->getDefaultShipping());
        }
    }

    /**
     * Add additional required attributes to customerData
     *
     * @param \Magento\Customer\CustomerData\Customer $subject
     * @param $result
     * @return mixed
     */
    public function afterGetSectionData(\Magento\Customer\CustomerData\Customer $subject, $result)
    {
        if ($this->currentCustomer->getCustomerId()) {
            $customer = $this->currentCustomer->getCustomer();

            $result["prefix"]                   = __($customer->getPrefix());
            $result["suffix"]                   = $customer->getSuffix();
            $result["lastname"]                 = $customer->getLastname();
            $result["pobox"]                    = $customer->getCustomAttribute('po_box') ? $customer->getCustomAttribute('po_box')->getValue() : '';
            $result["email"]                    = $customer->getEmail();

            $result["billingaddress"]           = $this->getBillingAddress();
            $result["shippingaddress"]          = $this->getShippingAddress();
        }

        return $result;
    }

    /**
     * @return array
     */
    public function getBillingAddress()
    {
        $billingAddress["prefix"]       = __($this->billingAddress->getPrefix());
        $billingAddress["suffix"]       = $this->billingAddress->getSuffix();
        $billingAddress["firstname"]    = $this->billingAddress->getFirstname();
        $billingAddress["lastname"]     = $this->billingAddress->getLastname();
        $billingAddress["company"]      = $this->billingAddress->getCompany();
        $billingAddress["telephone"]    = $this->billingAddress->getTelephone();
        $billingAddress["street"]       = implode(" ", $this->billingAddress->getStreet());
        $billingAddress["postcode"]     = $this->billingAddress->getPostcode();
        $billingAddress["city"]         = $this->shippingAddress->getCity();
        $billingAddress["country"]      = $this->getCountry($this->billingAddress->getCountryId());

        return $billingAddress;
    }

    /**
     * @return array
     */
    public function getShippingAddress()
    {
        $shippingAddress = array();

        $shippingAddress["prefix"]       = __($this->shippingAddress->getPrefix());
        $shippingAddress["suffix"]       = $this->shippingAddress->getSuffix();
        $shippingAddress["firstname"]    = $this->shippingAddress->getFirstname();
        $shippingAddress["lastname"]     = $this->shippingAddress->getLastname();
        $shippingAddress["company"]      = $this->shippingAddress->getCompany();
        $shippingAddress["telephone"]    = $this->shippingAddress->getTelephone();
        $shippingAddress["street"]       = implode(" ", $this->shippingAddress->getStreet());
        $shippingAddress["postcode"]     = $this->shippingAddress->getPostcode();
        $shippingAddress["city"]         = $this->shippingAddress->getCity();
        $shippingAddress["country"]      = $this->getCountry($this->shippingAddress->getCountryId());

        return $shippingAddress;
    }

    /**
     * @param $countryId
     * @return string
     */
    public function getCountry($countryId)
    {
        $country = $this->countryFactory->create()->loadByCode($countryId);
        return $country->getName();
    }
}
```


### 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

2 Answers 2

0

To eliminate Conclusion #3, why not create a new class, copy Magento\Customer\Helper\Session\CurrentCustomer but change it to use the Factory instead, and use this class inside CustomerData?

0

Thanks Dan. This wasn´t the cause of this failure, sadly. We had an old third party module installed, which was not compatible with Magento 2.4.3. Because of this, the route customer/section/load was cached. The JS call customerData.reload(['customer'], true).done( ... ) has sometimes no timestamp parameter attached to the request url, so customers get sometimes the same request url, like : https://www.website.com/customer/section/load?sections=customer&force_new_section_timestamp=true instead of correctly https://www.website.com/customer/section/load?sections=customer&force_new_section_timestamp=true&_=12323523523523

This in combination lead to this behavior. I will take a deeper look at customerData.reload and why sometimes the timestamp is missing.

greetings

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.