Adob​​e Commerce 中的严重漏洞 (CVE-2024-34102) 赏金$9000

偶尔,我会参与一些漏洞奖励项目。选择目标时,我通常会考虑其流行度。我曾发现一个名为Magento的热门目标。后来得知,它是Adobe公司的产品。当相关漏洞被修复后,我对其受到的广泛关注感到惊讶,并偶然读到了一篇关于这个话题的[文章](https://sansec.io/research/cosmicsting)。这两个漏洞,CVE-2024-34102和CVE-2024-2961,被合称为CosmicSting。

翻译:https://github.com/spacewasp/public_docs/blob/main/CVE-2024-34102.md

偶尔,我会参与一些漏洞奖励项目。选择目标时,我通常会考虑其流行度。我曾发现一个名为Magento的热门目标。后来得知,它是Adobe公司的产品。当相关漏洞被修复后,我对其受到的广泛关注感到惊讶,并偶然读到了一篇关于这个话题的文章。这两个漏洞,CVE-2024-34102和CVE-2024-2961,被合称为CosmicSting。

本文仅讨论CVE-2024-34102。

环境准备

应从 https://github.com/magento/magento2 下载产品的最新版本。当时最新的版本是 2.4.6-p2 。还有另一种方法可以从 https://marketplace.digitalocean.com/apps/magento-2-open-source 安装它。然而,在我撰写本文时,该版本仍然存在漏洞。

入口点

Magento是一个基于HTTP的PHP服务器应用程序。这类应用程序通常有两个主要的接入点:用户界面和应用程序接口(API)。我选择从REST API入手,因为它可以更快地用Python进行测试,并且没有跨站请求伪造(CSRF)保护等安全机制。Magento支持REST API、GraphQL和SOAP,但本文将重点讨论REST API。这里有一些关于它的摘要笔记

例如,REST API的请求格式如下:

POST /rest/default/V1/carts/mine/estimate-shipping-methods HTTP/1.1
Host: foo.example
Content-Type : application/json
Content-Length: 1402

{
    "address": {
        "region": "incididunt",
        "region_id": -67220712,
        "region_code": "nisi laboris aute in Duis",
        "country_id": "consectetur fugiat ",
        "street": [
            "in non fugiat consequat",
            "fugiat aliqua non commodo"
        ],
        "telephone": "dolor enim nisi culpa",
        "postcode": "ex Excepteur reprehenderit",
        "city": "laborum cupidatat est ut",
        "firstname": "voluptate minim ",
        "lastname": "do exercitation",
        "email": "ut dolore occaecat sunt",
        "company": "eu officia in",
        "custom_attributes": [
            {
                "attribute_code": "anim nulla cupidatat Lorem aute",
                "value": "voluptate Lore"
            },
            {
                "attribute_code": "dolore",
                "value": "tempor proident nostrud"
            }
        ],
        "customer_address_id": 22984299,
        "customer_id": -43661864,
        "extension_attributes": {
            "gift_registry_id": 17076753
        },
        "fax": "adipisicing id",
        "id": 3672246,
        "middlename": "et tempor enim",
        "prefix": "sunt dolor esse eiusmod",
        "same_as_billing": -57436048,
        "save_in_address_book": -15023339,
        "suffix": "anim ipsum proident",
        "vat_id": "est elit Duis "
    }
}

具体操作

第一个挑战是发现 REST API 的所有可能的 URL。配置文件 webapi.xml ,它将 URL 与 PHP 请求处理程序连接起来。这是其中的一部分:

    <route url="/V1/carts/:cartId/estimate-shipping-methods" method="POST">
        <service class="Magento\Quote\Api\ShipmentEstimationInterface" method="estimateByExtendedAddress"/>
        <resources>
            <resource ref="Magento_Cart::manage" />
        </resources>
    </route>

    <route url="/V1/guest-carts/:cartId/collect-totals" method="PUT">
        <service class="Magento\Quote\Api\GuestCartTotalManagementInterface" method="collectTotals"/>
        <resources>
            <resource ref="anonymous"/>
        </resources>
    </route>

重要提示:某些 REST API 无需身份验证即可使用,例如 /V1/guest-carts/:cartId/collect-totals ,因为

    <resources>
        <resource ref="anonymous"/>
    </resources>

我做了一些研究,发现每个接口类在文件 di.xml 中有一个硬编码的实现。这是其中的一部分:

<preference for="Magento\Quote\Api\ShipmentEstimationInterface" type="Magento\Quote\Model\ShippingMethodManagement" />

一旦用户发送 HTTP 请求,就会从类 ShippingMethodManagement 中调用方法 estimateByExtendedAddress 。
我们来看看这个方法:

    public function estimateByExtendedAddress($cartId, AddressInterface $address)
    {
        /** @var Quote $quote */
        $quote = $this->quoteRepository->getActive($cartId);

        // no methods applicable for empty carts or carts with virtual products
        if ($quote->isVirtual() || 0 == $quote->getItemsCount()) {
            return [];
        }
        return $this->getShippingMethods($quote, $address);
    }

有一个问题:HTTP 正文中的纯文本如何转换为 AddressInterface 对象?反序列化过程是什么样的?此外,如果 REST API 是公开的,攻击者可以尝试在没有任何身份验证的情况下反序列化数据。
让我们深入探讨一下。

反序列化

整个反序列化过程的逻辑很棘手。虽然有点简化,但总的来说,这些步骤都是正确的。让我们看一个涉及 AddressInterface $address 的示例

  1. 查找 AddressInterface 的实现(在文件 di.xml 中)。 Magento\Customer\Api\Data\AddressInterface => Magento\Customer\Model\Data\Address
  2. 查找类 Address 的构造函数 ```php
    public function __construct(
    \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory,
    AttributeValueFactory $attributeValueFactory,
    \Magento\Customer\Api\AddressMetadataInterface $metadataService,
    $data = []
    ) {
    ...
    }
  3. 检查 json HTTP 正文是否包含 extensionFactoryattributeValueFactorymetadataService 或 data 字段。例如,如果 extensionFactory 存在,则从步骤 1 开始递归地重复该过程。否则,检索类 ExtensionAttributesFactory 的默认通用值。
  4. 检查是否有 value http body set + value 是类 Address 的方法之一。例如 aaaa ,则方法是 setaaaa 。如果是,则调用它并仅解析参数(从步骤 1 开始处理)

问题在于反序列化过于灵活。它将用户数据和系统数据混在一起,没有进行区分。

找出所有输入参数

为了找出所有可能的输入参数,我对程序代码做了一些修改:

  1. 当JSON数据中包含特定参数(例如,extensionFactory),反序列化器会检测到它,并将该参数的值存入字典中。
  2. 对于集合方法,处理方式相同。
  3. 最后,这个字典被写入到磁盘,作为一个全新的JSON文件。

以下是输出示例:

{
    "method": "POST",
    "uri": "\/rest\/V1\/guest-carts\/:cartId\/gift-message",
    "data": {
        "cartId": "aaaaaaaaaaaaaaaaaaaaaa",
        "giftMessage": {
            "context": {
                "eventDispatcher": {
                    "instanceName": "aaaaaaaaaaaaaaaaaaaaaa",
                    "shared": true
                },
                "appState": {
                    "configScope": {
                        "areaList": {
                            "default": "aaaaaaaaaaaaaaaaaaaaaa"
                        },
                        "defaultScope": "aaaaaaaaaaaaaaaaaaaaaa",
                        "CurrentScope": "aaaaaaaaaaaaaaaaaaaaaa"
                    },
                    "mode": "aaaaaaaaaaaaaaaaaaaaaa",
                    "AreaCode": "aaaaaaaaaaaaaaaaaaaaaa"
                }
            },
            "resource": {
                "context": {
                    "resource": {
                        "resourceConfig": {
                            "instanceName": "aaaaaaaaaaaaaaaaaaaaaa",
                            "shared": true
                        },
                        "tablePrefix": "aaaaaaaaaaaaaaaaaaaaaa"
                    }
                },
                "connectionName": "aaaaaaaaaaaaaaaaaaaaaa"
            },
            "GiftMessageId": 42,
            "CustomerId": 42,
            "Sender": "aaaaaaaaaaaaaaaaaaaaaa",
            "Recipient": "aaaaaaaaaaaaaaaaaaaaaa",
            "Message": "aaaaaaaaaaaaaaaaaaaaaa",
            "ExtensionAttributes": {
                "EntityId": "aaaaaaaaaaaaaaaaaaaaaa",
                "EntityType": "aaaaaaaaaaaaaaaaaaaaaa"
            }
        }
    }
}

使用收集的参数

数据根本没有被过滤。例如,如果攻击者发送带有此正文的 HTTP 请求,就会出现有趣的错误。
像这样
"Class \"aaaaaaaaaaaaaaaaaaaaaa\" does not exist",
或者
"Warning: SessionHandler::read(): open(aaaaaaaaaaaaaaaaaaaaaa\/sess_aaaaaaaaaaaaaaaaaaaaaa, O_RDWR) failed: No such file or directory (2) in ...

漏洞

在参数收集过程中,发现了一个有趣的子参数,并且可以反序列化 SimpleXMLElement 。我们看一下构造函数的参数:

public __construct(
    string $data,
    int $options = 0,
    bool $dataIsURL = false,
    string $namespaceOrPrefix = "",
    bool $isPrefix = false
)

因此,攻击者可以像经典的 XXE 攻击一样在 data 中传递参数,并且他也可以控制 options ,他可以设置 LIBXML_NOENT|LIBXML_PARSEHUGE ,默认情况下禁用。请注意 dataIsURL ,因为文章中存在一个有缺陷的补丁,允许攻击者绕过它。
使用这个XXE盲注进行反向连接,攻击者可以下载 env.php
<!ENTITY file SYSTEM "../app/etc/env.php">
使用 env.php 中的此键

    'crypt' => [
        'key' => '4ba8fa7a14627e3b812858091ed163ec'
    ],

可以创建管理 JSON Web 令牌 JWT

影响

攻击者可以在未经授权的情况下获得对 REST API、GraphQL 或 Soap 的管理员访问权限。

时间线

  1. 2023年12月20日提交至 HackerOne
  2. 2024年01月08日由 Hacker one 提交给Adobe
  3. 2024年05月21日支付赏金
  4. 2024年06月11日发布CVE-2024-34102

关于 SanSec 的紧急修复

有人提议紧急处理 https://sansec.io/research/cosmicsting#emergency-fix,由于 JSON 编码,攻击者很容易绕过它。例如,"test"变成"t\\u0065st"。我也建议在有效负载中捕获 sourceData ,因为可以省略 dataIsURL 。

紧急修复看起来像这样:

if (strpos(json_encode(json_decode(file_get_contents('php://input'))), 'sourceData') !== false) {
    header('HTTP/1.1 503 Service Temporarily Unavailable');
    header('Status: 503 Service Temporarily Unavailable');
    exit;
}
  • 发表于 2024-07-11 10:00:02
  • 阅读 ( 34053 )
  • 分类:漏洞分析

0 条评论

请先 登录 后评论
csallin
csallin

11 篇文章