-
为什么更新本篇?
-
启动电源;
-
PXE-boot启动维护操作系统;
-
Agent进行格式化硬盘以及通过Windows Deployment Services(WDS)下载Host OS;
-
HostOS采用Sysprep(来进行安全加固配置)等,完成后重新启动;
-
FC Host Agent连接到FC中心;
-
服务开发:Azure的服务通过VisualStudio SaaS版进行开发,Azure自身的代码全部存储在了SaaS上,SaaS版的功能包括仓库管理、Pipeline发布管理、部署管理、代码管理、Issues问题跟踪等;PS:如果有想研究Azure VS SaaS漏洞的可以在这里下载Azure DevOps Server 2020版进行研究(https://go.microsoft.com/fwlink/?LinkId=2132056);
-
服务发布:Azure的服务推送到RDFE(Red Dog Front End),RDFE发送到制定Region的FC,FC存储对应的镜像和部署的服务到对应的虚拟机或者裸金属上;
-
资源:确定需要的资源包括CPU、内存、硬盘、网络资源等信息;
-
准备Nodes节点:创建虚拟机资源、部署Role镜像,启动虚拟机和Roles;
-
配置网络:动态IP地址分配、VIP地址+端口分配、VM和VM流量包过滤、加载负载均衡允许服务的流量;
6、部署FC GuestAgent
-
Guest Agent部署:FC部署Guest Agent(后续会有专题分析FC的全套体系),可以看到Azure的Trust Boundary是通过虚拟机和物理节点,这里可以看到Hyper-V自身存在攻击面,另外Guest Agent和FC Host Agent也存在对应的攻击面;另外看到一些资料说Hyper-V的核心代码不到一万行都是经过形式化验证的;
再具体看一个Process进程的CIM的例子,首先创建了Job任务、任务调用BatchService批处理任务、任务产生了Service服务、服务最终执行了Process进程。整个流程大家清楚之后,我们就看2个点,Service服务执行Process流程,首先看一下Service服务的模型包括,另外Service服务模型调用了Process(模型包括CreationClassName:String名字格式字符型、Priority:unit32运行优先级格式是数字、ExecutionState:unit16进程执行状态)等。
下面看一下Windows CIM的最终实现,Windows的标准实现名是以win32_开头的标准。Windows CIM实现了四个大的类别包括计算系统硬件类、操作系统类、性能计数器类和WMI服务管理类(详情:https://docs.microsoft.com/zh-cn/windows/win32/cimwin32prov/win32-provider)。
具体的我们只看一个操作系统的例子Win32_Process。首先看Win32_Process用来管理整个Windows的进程。第一步先看一下Windows是如何扩展CIM_Process的。从下面可以明显的看到,除了CIM_Process的模型外,扩展了不少的模型定义,包括ExecutablePath运行路径、内存相关一些管理,通过CIM的模型,Windows进行了对应的扩展。
[Dynamic, Provider("CIMWin32"), SupportsCreate, CreateBy("Create"), SupportsDelete, DeleteBy("DeleteInstance"), UUID("{8502C4DC-5FBB-11D2-AAC1-006008C78BC7}"), DisplayName("Processes"), AMENDMENT]
class Win32_Process : CIM_Process
{
string CreationClassName;
string Caption;
string CommandLine;
datetime CreationDate;
string CSCreationClassName;
string CSName;
string Description;
string ExecutablePath;
uint16 ExecutionState;
string Handle;
uint32 HandleCount;
datetime InstallDate;
uint64 KernelModeTime;
uint32 MaximumWorkingSetSize;
uint32 MinimumWorkingSetSize;
string Name;
string OSCreationClassName;
string OSName;
uint64 OtherOperationCount;
uint64 OtherTransferCount;
uint32 PageFaults;
uint32 PageFileUsage;
uint32 ParentProcessId;
uint32 PeakPageFileUsage;
uint64 PeakVirtualSize;
uint32 PeakWorkingSetSize;
uint32 Priority = NULL;
uint64 PrivatePageCount;
uint32 ProcessId;
uint32 QuotaNonPagedPoolUsage;
uint32 QuotaPagedPoolUsage;
uint32 QuotaPeakNonPagedPoolUsage;
uint32 QuotaPeakPagedPoolUsage;
uint64 ReadOperationCount;
uint64 ReadTransferCount;
uint32 SessionId;
string Status;
datetime TerminationDate;
uint32 ThreadCount;
uint64 UserModeTime;
uint64 VirtualSize;
string WindowsVersion;
uint64 WorkingSetSize;
uint64 WriteOperationCount;
uint64 WriteTransferCount;
};
strComputer = "."
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\" & strComputer & "rootcimv2")
Set colProcessList = objWMIService.ExecQuery("SELECT * FROM Win32_Process")
For Each objProcess in colProcessList
Wscript.Echo "Process: " & objProcess.Name
Wscript.Echo "Process ID: " & objProcess.ProcessID
Wscript.Echo "Thread Count: " & objProcess.ThreadCount
Wscript.Echo "Page File Size: " & objProcess.PageFileUsage
Wscript.Echo "Page Faults: " & objProcess.PageFaults
Wscript.Echo "Working Set Size: " & objProcess.WorkingSetSize
Next
5、Microsoft WS-Man详细分析
Windows远程管理Windows Remote Management(WinRM)就是通过WS-Man协议来进行远程管理的实现。该协议为Azure云管理服务器提供了非常方便的方式,通过WinRM在Azure云中进行部署、远程管理等东西。WS-Man采用了标准了SOAP协议来进行管理。
客户端执行WinRM管理:
$UserName = "AzureCloudUser"
$serverpass = "P@ssw0rd"
$Password = ConvertTo-SecureString $serverpass -AsPlainText –Force
$cred = New-Object System.Management.Automation.PSCredential($UserName,$Password)
invoke-command -ComputerName localhost -Credential $cred -ScriptBlock { ipconfig }
服务端接受WS-Man SOAP协议请求
<s:Envelope
xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:wsman="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd">
<s:Header>
<wsa:To>
http://localhost:80/wsman
</wsa:To>
<wsman:ResourceURI s:mustUnderstand="true">
http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd
</wsman:ResourceURI>
<wsa:ReplyTo>
<wsa:Address s:mustUnderstand="true">
http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
</wsa:Address>
</wsa:ReplyTo>
<wsa:Action s:mustUnderstand="true">
http://schemas.xmlsoap.org/ws/2004/09/transfer/Create
</wsa:Action>
<wsman:MaxEnvelopeSize s:mustUnderstand="true">153600</wsman:MaxEnvelopeSize>
<wsa:MessageID>uuid:AF6A2E07-BA33-496E-8AFA-E77D241A2F2F</wsa:MessageID>
<wsman:Locale xml:lang="en-US" s:mustUnderstand="false" />
<wsman:OptionSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<wsman:Option Name="WINRS_NOPROFILE">TRUE</wsman:Option>
<wsman:Option Name="WINRS_CODEPAGE">437</wsman:Option>
</wsman:OptionSet>
<wsman:OperationTimeout>PT60.000S</wsman:OperationTimeout>
</s:Header>
<s:Body>
<rsp:Shell
xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell">
<rsp:Environment>
<rsp:Variable Name="test">1</rsp:Variable>
</rsp:Environment>
<rsp:WorkingDirectory>d:windows</rsp:WorkingDirectory>
<rsp:Lifetime>PT1000.000S</rsp:Lifetime>
<rsp:InputStreams>stdin</rsp:InputStreams>
<rsp:OutputStreams>stdout stderr</rsp:OutputStreams>
</rsp:Shell>
<CommandLine xmlns='http://schemas.microsoft.com/wbem/wsman/1/windows/shell'>
<Command>powershell</Command>
<Arguments>ifconfig</Arguments>
</CommandLine>
</s:Body>
</s:Envelope>
6、Microsoft OMI详细分析
OMI其实简单点说就是DAL的一种实现,微软和OpenGroup组织一起定义OMI管理,大力发展基于标准的管理。微软与Arista和Cisco合作,把OMI开放到他们的交换机上,用来为Azure和云数据中心的统一标准化管理。实现这套标准之后,可以通过标准的工具来管理服务器上的BMC、Windows操作系统和运行OMI的Arista、Cisco的交换机。
其实OMI主要解决的问题就是如今的Azure和数据中心由一系列不同硬件和平台供应商提供的异构设备,需要不同的工具和管理流程来进行管理。而大型企业被迫编写自己的抽象层,或者被Cisco这种厂商锁定,严重限制灵活性。所以微软通过推动行业采用抽象层和标准化的工作来进行管理。
在正式开始详细分析Azure Fabric Controller的技术细节之前,还是先了解下整个Azure数据中心的架构。
Azure数据中心由非常的Region组成,Region通过AZ组成,AZ通过一个一个的Cluster集群、Cluster集群通过Node机架、Node机架通过交换机和服务器组成了庞大的Azure数据中心。
每一个Cluster集群都会通过Fabric Controller来进行管理,Fabric Controller的主要职责和Azure的服务生命周期管理、Azure虚拟机生命周期的管理、Azure数据中心物理服务器的监控状态管理、Azure云进行Scale-Out扩展的时候进行服务器的扩容管理等等、Fabric Controller会管理Azure硬件服务器的部署和使用,并且最终完成Azure服务部署和管理应用生命周期的核心云内核核心。
从底层设计来看Cluster集群上面还有一个Utility Fabric Controller(UFC),UFC是Fabric Controller的Leader,UFC会负责所有的Fabric Controller的管理例如会把服务器Node节点的信息通过XML传递给Fabric Controller,方便其管理资源。另外在Cluster集群分布式管理上还会有很多分布式状态例如etcd,会选举集群Leader,选取的Leader来传递对应的服务器配置信息,通过配置信息去通过Fabric Controller去进行管理。
Fabric Controller进行管理的话,是通过部署在每台Azure服务器上的HostAgent组件进行管理的。另外在Fabric Controller HostAgent管理的虚拟机上,安装GuestAgent,用来跟HostAgent进行通信。
Azure的云产品服务是通过*.cspkg(Cloud Service Package)以及*.cscfg(Cloud Service Configuration)来进行部署。
下面简单讲解一下Azure服务部署的文件内容的作用:
.cspkg:用来封装Azure云产品服务所有的文件,包括执行文件PE EXE和依赖的DLLs文件,如果有Web.config和App.config也会一起部署;
.csdef:用来定义Azure云产品服务所依赖的环境,包括使用的Role(虚拟机的角色)、网络服务、虚拟机的数量等信息;
.cscfg:用来定义Azure云产品服务所依赖的配置信息,包括对应的Storage Account、远程桌面RDP参数等;
截止到现在目前已经讲清楚整个Azure云平台的大的架构了,下面开始讲解一些细节实现。
-
设置Host Agent端口
Set-Variable -Name HostAgentPort -Option Constant -Value 14200
-
设置操作系统防火墙
利用New-NetFirewallRule来创建14200端口的入方向规则,可以看到并没有做一些IP地址的限制等策略;
Set-Variable -Name HostAgentFirewallRuleName -Option Constant -Value "AzsHostAgent"
Set-Variable -Name HostAgentFirewallRulePort -Option Constant -Value 14200
Set-Variable -Name HostAgentFirewallDisplayName -Option Constant -Value "Allow inbound TCP traffic for AzsHostAgent
New-NetFirewallRule -Name $HostAgentFirewallRuleName -DisplayName $HostAgentFirewallDisplayName -Enabled True -Direction Inbound -Action Allow -Protocol TCP -LocalPort $HostAgentFirewallRulePort
-
通过前面讲到的CIM协议来进行部署的账号的账号变更和新服务的创建。通过服务创建,设置自启动的Azure Fabric Controller Host Agent运行服务。
New-Service -Name $ServiceName -DisplayName $DisplayName -BinaryPathName $ExePath -Description "This is the Host Agent Service" -StartupType Automatic
$SvcWmi = Get-CimInstance -ClassName Win32_Service | Where-Object Name -eq $ServiceName
$ret = Invoke-CimMethod -InputObject $SvcWmi -MethodName Change -Arguments @{StartName="$env:USERDOMAINazs-jea-comsa$";StartPassword=""}
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AzureStack.Fabric.HostAgent.HostAgent.Client.Models;
using Microsoft.AzureStack.Fabric.HostAgent.HostAgent.Client.Models.VMs;
[ ]
[ ]
public abstract class HostVirtualMachineController : ControllerBase
{
[ ]
public abstract Task<ActionResult<VmPowerState>> GetVmPowerState(string vmName, bool noCache = true);
[ ]
public abstract Task<ActionResult> PutVirtualMachine(string vmName, VirtualMachine virtualMachine);
[ ]
public abstract Task<ActionResult<VirtualMachine>> GetVirtualMachine(string vmName);
[ ]
public abstract Task<ActionResult> DeleteVirtualMachine(string vmName);
[ ]
public abstract Task<ActionResult<VmThumbnail>> GetVirtualMachineThumbnail(string vmName, ushort widthPixels, ushort heightPixels);
[ ]
public abstract Task<ActionResult<VirtualMachineMemorySettings>> GetVirtualMachineMemorySettings(string vmName);
[ ]
public abstract Task<ActionResult> SetVirtualMachineMemorySettings(string vmName, VirtualMachineMemorySettings virtualMachineMemorySettings);
[ ]
public abstract Task<ActionResult<VirtualMachineMemoryData>> GetVirtualMachineMemoryData(string vmName);
[ ]
public abstract Task<ActionResult> SetVmNotes(string vmName, VmNotes notes);
[ ]
public abstract Task<ActionResult<VmSmBios>> GetVmSmBios(string vmName);
[ ]
public abstract Task<ActionResult> SetVmSmBios(string vmName, VmSmBios smBios);
[ ]
public abstract Task<ActionResult> SetVirtualMachineProcessorSettings(string vmName, VirtualMachineProcessorSettings virtualMachineProcessorSettings);
[ ]
public abstract Task<ActionResult<VirtualMachineProcessorSettings>> GetVirtualMachineProcessorSettings(string vmName);
[ ]
public abstract Task<ActionResult> DoesVirtualMachineExist(string vmName);
[ ]
public abstract Task<ActionResult<VmGeneration>> GetVmGeneration(string vmName);
[ ]
public abstract Task<ActionResult<VmHardwareProfile>> GetVmHardwareProfile(string vmName);
[ ]
public abstract Task<ActionResult> UpdateVmPowerState(string vmName, string action);
[ ]
public abstract Task<ActionResult<Job>> AsyncUpdateVmPowerState(string vmName, string action);
}
using System;
using System.Diagnostics.CodeAnalysis;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Certificate;
using Microsoft.AzureStack.Fabric.HostAgent.HostAgent.Controllers.Contract;
[ ]
public class CertificateAuthenticationHandler : ICertificateAuthenticationHandler
{
private readonly X509Certificate2 peerCertificate;
public CertificateAuthenticationHandler(X509Certificate2 peerCertificate)
{
this.peerCertificate = peerCertificate;
}
public Task OnCertficateValidated(CertificateValidatedContext context)
{
if (IsValidCertificate(context.ClientCertificate))
{
Claim[] claims = new Claim[2]
{
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", context.ClientCertificate.Subject, "http://www.w3.org/2001/XMLSchema#string", context.Options.ClaimsIssuer),
new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", context.ClientCertificate.Subject, "http://www.w3.org/2001/XMLSchema#string", context.Options.ClaimsIssuer)
};
context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name));
context.Success();
return Task.CompletedTask;
}
throw new UnauthorizedAccessException($"The request is unauthorized because the client certificate thumbprint {context.ClientCertificate.Thumbprint} is not authorized");
}
private bool IsValidCertificate(X509Certificate2 clientCertificate)
{
return clientCertificate.Thumbprint == peerCertificate.Thumbprint;
}
}
通过前面的ASP.NET MVC APIController进入对应的处理逻辑。再详细讲解后台调用之前,先看一下Azure Fabric Controller针对虚拟机管理的生命周期包含哪些操作。包括电源管理(开机、关机)、虚拟机创建、虚拟机销毁、内存管理、内存设置、BIOS设置、CPU处理、CPU设置、硬件管理、热迁移、虚拟机硬盘、网络配置、虚拟路由器配置、网络安全组设置等等。
下面针对创建虚拟机这个过程来进行看一下Azure的虚拟机到底是如何创建的?第一步在Azure云产品控制台发起了创建的请求,通过层层调用发送到了Fabric Controller HostAgent 14200端口的API请求[HttpPut("vms/{vmName}")]来进行调用,通过跟踪Controller API的跟进,看到实际的虚拟机操作逻辑如下(是不是非常简单,简洁和具备高可读性):
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AzureStack.Fabric.HostAgent.HostAgent.Client.Models;
using Microsoft.AzureStack.Fabric.HostAgent.HostAgent.Core.Contract;
using Microsoft.AzureStack.Fabric.HostAgent.HostAgent.Core.Models;
public override async Task<ActionResult> PutVirtualMachine(string vmName, Microsoft.AzureStack.Fabric.HostAgent.HostAgent.Client.Models.VirtualMachine virtualMachine)
{
if (!vmName.Equals(virtualMachine.Name, StringComparison.InvariantCultureIgnoreCase))
{
return BadRequest($"Virtual Machine name does not match the model. Uri: {vmName} Model: {virtualMachine.Name}");
}
Func<Task<ActionResult>> createVm = async delegate
{
try
{
await hostFabricManager.GetVirtualMachine(AzureVmId.Parse(vmName));
return Ok();
}
catch (FabricVmNotFoundException)
{
}
Microsoft.AzureStack.Fabric.HostAgent.HostAgent.Core.Models.VirtualMachine coreModel = converter.Convert(virtualMachine);
await hostFabricManager.CreateVirtualMachine(coreModel);
return Ok();
};
Dictionary<string, int> exceptionMapping = new Dictionary<string, int> { ["FabricVirtualMachineNotFound"] = 455 };
return await errorHandler.HandleFabricExceptions(createVm, exceptionMapping);
}
继续跟进关键函数hostFabricManager
await hostFabricManager.CreateVirtualMachine(coreModel);
往下继续调用,发现使用hyperVWmiInvoker这个函数,继续跟进。这里要提一下,从Portal.azure.com控制台来创建虚拟机开始,经过层层的调用,最终还是在Windows Server上利用Hyper-V进行了虚拟机的创建流程。
using System.Threading.Tasks;
using Microsoft.AzureStack.Fabric.HostAgent.HostAgent.Core.Models;
public async Task CreateVirtualMachine(VirtualMachine virtualMachine)
{
string path = await fileSharePathConverter.ConvertPathOnCluster(virtualMachine.Path);
await hyperVWmiInvoker.CreateVirtualMachine(virtualMachine.AzureVmId, path);
}
继续跟可以最终调用Windows 实现CIM模块对Hyper-V进行了调用。CIM的Hyper-V模块是CimInstance("Msvm_VirtualSystemSettingData", "root\virtualization\v2");
root\virtualization\v2的含义是Hyper-V通过WMI CIM模块提供的底层Namespace,再这个Namespace下首先调用了Msvm_VirtualSystemSettingData来进行配置操作,然后调用"DefineSystem"(https://docs.microsoft.com/en-us/windows/win32/hyperv_v2/definesystem-msvm-virtualsystemmanagementservice)最终创建了对应的虚拟机,完成整个虚拟机的操作工作。
using System;
using System.Threading.Tasks;
using Microsoft.AzureStack.Fabric.HostAgent.HostAgent.Core.Contract.Exceptions;
using Microsoft.AzureStack.Fabric.HostAgent.HostAgent.Core.Models;
using Microsoft.AzureStack.Fabric.HostAgent.HostAgent.Core.Source.Trace;
using Microsoft.Management.Infrastructure;
using Microsoft.Management.Infrastructure.Generic;
using Microsoft.Management.Infrastructure.Serialization;
public async Task CreateVirtualMachine(AzureVmId azureVmId, string path)
{
using CimInstance vssdCimInstance = new CimInstance("Msvm_VirtualSystemSettingData", "root\virtualization\v2");
vssdCimInstance.CimInstanceProperties.Add(CimProperty.Create("ElementName", azureVmId.ToString(), CimType.String, CimFlags.Required));
vssdCimInstance.CimInstanceProperties.Add(CimProperty.Create("ConfigurationDataRoot", path, CimType.String, CimFlags.None));
vssdCimInstance.CimInstanceProperties.Add(CimProperty.Create("AutomaticShutdownAction", (ushort)4, CimType.UInt16, CimFlags.None));
byte[] serializedVssdCimInstance = cimSerializer.Serialize(vssdCimInstance, InstanceSerializationOptions.None);
using CimInstance vmmsCimInstance = await virtualSystemManagementServiceProvider.GetVmmsCimInstance();
using CimMethodParametersCollection parameters = new CimMethodParametersCollection { CimMethodParameter.Create("SystemSettings", unicodeEncoding.GetString(serializedVssdCimInstance), CimType.String, CimFlags.In) };
try
{
CimInstance resultingSystem = (CimInstance)(await cimMethodAsyncOperationPoller.InvokeMethodOnCimInstanceAndWaitForCompletion("root\virtualization\v2", "DefineSystem", parameters, vmmsCimInstance, string.Format(Literals.CreatingVirtualMachine, azureVmId.ToString(), path))).OutParameters["ResultingSystem"].Value;
using (CimInstance kvpSettings = await GetSingleKvpExchangeComponentSettingsData(azureVmId))
{
if (kvpSettings != null)
{
HostAgentLogger.DeletePreExistingDuplicateVms(azureVmId);
await TryCleanupVm(vmmsCimInstance, azureVmId);
throw new KvpSettingsForFabricVmAlreadyExistException(azureVmId);
}
await CreateKvpExchangeDataItem(resultingSystem, azureVmId);
}
Action<CimKeyedCollection<CimProperty>> updateAction = delegate(CimKeyedCollection<CimProperty> settings)
{
settings["ElementName"].Value = "SCSI Controller";
settings["VirtualSystemIdentifiers"].Value = new string[1] { "{f8b3781b-1e82-4818-a1c3-63d806ec15bb}" };
};
HypervVmId hypervVmId = HypervVmId.Parse(resultingSystem.CimInstanceProperties["Name"].Value.ToString());
await vmResourceHandler.AddResourceToVm(hypervVmId, "Microsoft:Hyper-V:Synthetic SCSI Controller", "Msvm_ResourceAllocationSettingData", updateAction);
}
catch (KvpSettingsForFabricVmAlreadyExistException e2)
{
HostAgentLogger.KvpSettingsForFabricVmAlreadyExist(azureVmId, e2);
}
catch (Exception e)
{
HostAgentLogger.AttemptVmDeletionAfterVmCreationFailure(azureVmId, e);
await TryCleanupVm(vmmsCimInstance, azureVmId);
throw;
}
}
这里还是要简单介绍一下DefineSystem的定义,可以看到还是调用CIM的标准的数据模型,CIM_VirtualSystemSettingData、CIM_ComputerSystem、CIM_ConcreateJob等模型的,对应上笔者之前说的CIM模型。
uint32 DefineSystem(
[in] string SystemSettings,
[in] string ResourceSettings[],
[in] CIM_VirtualSystemSettingData REF ReferenceConfiguration,
[out] CIM_ComputerSystem REF ResultingSystem,
[out] CIM_ConcreteJob REF Job
)
其他虚拟机管理的流程类似,就不做过多的介绍了。读者可以参考这里的API来看整个Azure虚拟机支持的各种资源操作。(https://docs.microsoft.com/en-us/rest/api/compute/virtual-machines)
备注:这里有个小的备注,Azure虚拟机的生命周期管理中标识虚拟机的唯一标识是分成AzureVMID和HyperVID两种的,这里会有点小误区。其实AzureVMID就是Azure云平台来标识虚拟机的,到物理机上通过HyperVID来标识,这里面需要维护两个ID标识的关系。
5、物理机生命周期管理
介绍完虚拟机的生命周期管理,下面看一下物理机的生命周期管理。例如下面的查询物理机的内存状态,通过之前讲到的CIM的Windows实现来查询可用的内存,如内存不足可以触发对应的虚拟机操作,关于物理机的其他操作大概都是类似的动作,不再赘述:
#查询物理机可用的内存
SELECT AvailableMemory FROM Win32_PerfRawData_BalancerStats_HyperVDynamicMemoryBalancer
#查询物理机有多少对应的虚拟机
SELECT * FROM Msvm_ComputerSystem WHERE Description = 'Microsoft Virtual Machine'
6、Azure云产品服务管理
Azure云产品服务管理较复杂,需打通DevOps的相关流程,再部署侧也比较复杂,前面讲到了,需要部署对应的cspkg、cscfg等文件,后续会再对应的后续文章中详细讲解。
毕竟是讲解云安全的公众号,不讲解一些安全的内容肯定不满足各位读者的要求,下面针对近期出现的CVE-2021-27075漏洞(https://www.intezer.com/blog/cloud-security/cve-2021-27075-microsoft-azure-vulnerability-allows-privilege-escalation-and-leak-of-data/)。
CVE-2021-27075漏洞会让没有特权的用户泄露任何Azure虚拟机扩展的私人数据。VMAccess扩展是Azure官方为协助系统管理员而设计的扩展,与之相配,国外研究者将展示如何利用这个漏洞实现权限升级,并可能实现横向移动。
对应的POC:
Azure VM为开发者和管理员提供了一个集成的插件系统,以便在他们的机器上安装额外的组件。第一方(如微软Azure诊断程序和微软Azure网络观察者)和第三方应用程序(如Datadog代理)都是通过这个机制提供的(例外笔者说一下第二篇的Azure的威胁分析Extension模块也是这样获取的^_^)。
为了管理扩展的安装并保持更新,系统上安装了Microsoft Azure Guest Agent。这个组件是开源的,托管在GitHub上。这个代理,连同Azure扩展,安装在/var/lib/waagent。这个目录对于非root用户来说是无法访问的,因为它包含与扩展相关的秘密漏洞。
扩展程序被安装在这个目录下对应的子目录中,例如,/var/lib/waagent/Microsoft.Azure.NetworkWatcher.NetworkWatcherAgentLinux-1.4.1587.1 许多共享配置被布置在根扩展目录/var/lib/waagent。
当一个扩展被添加到虚拟机中时,扩展的配置文件会在Azure虚拟机管理器(也称为Fabric Controller)中进行后台通信。WAAgent不断地轮询Fabric Controller的这个文件,一旦更新,扩展就被下载和部署。在微软Azure云中,WAAgent与 "Wire Server "进行通信,这是一个属于Fabric Controller的HTTP服务。
WAAgent通过访问一个特殊的IP地址与织物控制器进行通信。168.63.129.16.通过一些测试,国外白帽子发现这个端点与169.254.169.254相同,也就是大家熟知的Azure实例元数据服务IP地址。
WAAgent通过解析'GoalState'来接收扩展配置URL。
目标状态端点请求,HTTP响应如下:
GoalState包含Wire Server可用的所有相关配置的URL。使用GoalState,我们可以自己查询ExtensionsConfig文件。
ExtensionsConfig端点请求。下面是一个关于LinuxDiagnostic扩展配置的例子。
ExtensionsConfig端点响应protectedSettings字段保存敏感的扩展配置,如私钥,并有额外的加密方案保护。protectedSettingsCertThumbprint字段持有用于解密protectedSettings的密钥的文件名。这个密钥存储在 /var/lib/waagent/F54265F38F8D16C35C0E1FD3190882831A6C4384.prv,其证书存储在/var/lib/waagent/F54265F38F8D16C35C0E1FD3190882831A6C4384.crt。
在这个部署中,使用了证书端点。在对WAAgent与Wire Server的通信进行逆向工程后,国外白帽子发现证书端点需要一个通信证书,用于提供F542...扩展密钥。
证书端点请求,服务器返回一个加密形式的扩展密钥,可以通过传输证书的私钥进行解密。
漏洞#1:证书端点没有验证传输证书
攻击者可以创建自己的传输私钥和其相应的传输证书。使用证书端点,攻击者提供他们自己的传输证书,并从对应的服务器接收加密形式的密钥(在我们的例子中,这是F54265F38F8D16C35C0E1FD3190882831A6C4384密钥)。
最后,加密的密钥通过传输密钥被解密,攻击者可以继续解密被保护的设置。
在以根用户开发PoC后,它在非特权用户下无法工作。看来,服务器并没有把指向168.63.129.16的Wire Server终端的数据包发送出去。这是因为有一条iptables规则,它把不是来自用户ID 0(root)的数据包丢弃到端点。
漏洞二:绕过服务器非特权访问防御
如前所述,国外白帽子发现164.254.164.254与168.63.129.16是同一个机器。我们用164.254.164.254替换了对168.63.129.16的每个请求,这使得国外白帽子可以在没有特权用户的情况下与Wire Server进行通信。
此外,这种iptables防御并不适用于在Docker容器中运行的进程(甚至当PoC作为非特权用户运行时),允许容器通过Wire Server泄露其主机信息。这个问题也被MSRC修复了。
结合缺陷
利用这两个缺陷,无权的用户可以泄露任何Azure VM扩展的私人设置。当与处理敏感数据的扩展配对时,这尤其危险。
一个特别严重的例子是VMAccess扩展,这是微软Azure的官方扩展,用于在受控机器上方便地更改密码。Guardicore以前记录过,VMAccess即使在修改完用户的密码,不再需要在磁盘上保留密码,也会在protectedSettings字段中持续保留密码。
结合国外白帽子发现的漏洞,攻击者可以通过泄露VMAccess管理密码将自己提升为更高权限的用户。此外,如果VMAccess密码与其他Azure虚拟机共享(通常是这样),攻击者可以在系统中进行横向移动。
最终的POC(导致物理机的密码泄露,通过普通用户达到提权):
本文针对Azure的云核心内核系统架构进行了详细的讲解,读者肯定会有行云流水般的感觉,因为整个分析的流程都是从顶层架构层层分解到最终实现的。在分析的将近1年多时间内(文章写了不到8个小时),学习到了非常多的Azure云的设计思想。包括统一和屏蔽Azure底层硬件、软件、操作系统复杂性的DAL以及通过WS-Man协议结合CIM来进行Azure整个服务器内部管理的体系,还有Azure Fabric Controller的云核心内核的详细分析。另外再总结一下,第一条线从Azure机器上架->部署Cluster->Node机架->Fabric Controller进行部署服务->构成机房->构成AZ->构成Region->构成Azure云的角度进行了讲解。第二条线是从Azure云平台Portal.azure.com申请账号部署虚拟机->Azure Fabric Controller调用->Hyper-V WMI进行调用->生成虚拟机的全套流程。第三条线就是Azure机器管理和对应的服务部署流程进行了详细讲解。第四条线是对应的漏洞。
过程中遇到了非常多的困难,感谢腾讯云@killer提供高配服务器进行研究、感谢IT小圈子作者Stan的精彩文章让我对Azure的架构有了顶层的认识、感谢某金融客户经常问我一些云的相关问题,让我对Azure的实现有了一些了解、感谢奇安信的"DadayKuan"同学一起愉快的交流,还有很多同学的鼓励才能写下去,另外还有一些同学也非常感谢不再一一列举。行文过程中难免有一些错误和不足,请联系微信@ThreatSource进行沟通交流、指正和好的建议。再次感谢各位读者的耐心阅读!!!
下篇见!!!
原文始发于微信公众号(鸟哥谈云安全):云架构系列之一-Azure云核心内核系统架构详解
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论