翻译: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
的示例
di.xml
中)。 Magento\Customer\Api\Data\AddressInterface => Magento\Customer\Model\Data\Address
extensionFactory
、 attributeValueFactory
、 metadataService
或 data 字段。例如,如果 extensionFactory
存在,则从步骤 1 开始递归地重复该过程。否则,检索类 ExtensionAttributesFactory
的默认通用值。set
+ value 是类 Address
的方法之一。例如 aaaa
,则方法是 setaaaa
。如果是,则调用它并仅解析参数(从步骤 1 开始处理)问题在于反序列化过于灵活。它将用户数据和系统数据混在一起,没有进行区分。
为了找出所有可能的输入参数,我对程序代码做了一些修改:
以下是输出示例:
{
"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 的管理员访问权限。
有人提议紧急处理 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;
}
11 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!