ConnectWise SkyKick厂商凭证在Microsoft 365攻击中被滥用
Detection stack
- AIDR
- Alert
- ETL
- Query
总结
与ConnectWise Cloud Backup(前称SkyKick)关联的微软365 API密钥被威胁行为者利用,非法访问客户的M365环境。攻击者利用该应用程序的广泛权限访问Exchange Online、微软Teams和SharePoint Online。该活动包括自动令牌请求和人工后期利用操作。
调查
Huntress通过微软365登录日志识别出对ConnectWise服务主体的滥用,包括从托管服务商IP地址144.172.64.128和私有Azure IPv6空间的未经授权访问。调查发现,攻击者利用被盗凭证分配目录角色如全局管理员,并向其他受信任应用程序添加新的客户端密钥以保持持久性。活动还显示了按GUID顺序在租户之间的脚本化移动和独特的User-Agent模式。
缓解措施
各组织应立即在其微软365租户中禁用受影响的ConnectWise Cloud Backup和ConnectWise Cloud Management应用ID。禁用而非删除应用程序对于保留同意管理很重要。安全团队还应审核所有服务主体登录,并查看自10月13日以来创建的意外应用程序凭证。
响应
当发现可疑活动时,组织应检查Entra审核日志以 添加成员到角色, 更新应用程序,和 添加服务主体凭证 事件。安全团队应验证相关的User-Agent字符串并区分合法的供应商活动,如kiota-dotnet,与攻击者工具如Python-urllib和curl。最后,审查所有企业应用程序中的客户端密钥和证书,并移除任何未经授权的凭证。
攻击流程
我们仍在更新这一部分。
检测
将用户添加到管理员角色(通过office365)
服务主体账户凭证更新(通过azuread)
分配了高权限角色(通过azuread)
IOC(源IP)检测:M365攻击中被滥用的ConnectWise(SkyKick)供应商凭证
IOC(目标IP)检测:M365攻击中被滥用的ConnectWise(SkyKick)供应商凭证
通过Python-urllib检测M365中的未经授权修改[Azure活动日志]
通过ConnectWise应用在M365中未经授权使用微软Graph令牌[Azure审核日志]
模拟执行
-
攻击叙述与命令: 对手已成功访问具有有限权限的服务主体。为了实现持久性和完全环境控制,攻击者执行了一个Python脚本。该脚本针对Microsoft Graph API以:1)将被盗服务主体添加到高权限目录角色(例如,全局管理员或特权角色管理员),并2)向服务主体添加新的客户端密钥。攻击者使用
urllib库在Python 3.12中,这默认提供了所需的Python-urllib/3.12User-Agent,提供检测规则所需的特定遥测钩子。 -
回归测试脚本:
import urllib.request import json # NOTE: This script is a simulation of the API call structure. # In a real environment, a valid OAuth2 Bearer token would be required. # This script is designed to trigger the 'UserAgent' and 'OperationName' detection logic. target_url = "https://graph.microsoft.com/v1.0/directoryRoles" access_token = "SIMULATED_TOKEN" headers = { "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" } # This specific User-Agent is the trigger for the detection rule user_agent = "Python-urllib/3.12" # Simulate "Add member to role" payload = { "roleId": "target-role-guid", "principalId": "attacker-service-principal-guid" } print(f"[*] Simulating unauthorized role modification using {user_agent}...") try: req = urllib.request.Request( target_url, data=json.dumps(payload).encode('utf-8'), headers=headers, method='POST' ) req.add_header('User-Agent', user_agent) # In a real test, this would call the actual API. # For simulation purposes, we assume the API call is successful to generate the log. print("[+] Request sent. Check Azure Audit Logs for 'Add member to role' with User-Agent: Python-urllib/3.12") except Exception as e: print(f"[-] Simulation failed: {e}") -
清理命令:
# 手动清理在M365中是必要的,因为这些是身份级别的更改。 # 1. 移除添加的角色分配: # Remove-MgDirectoryRoleMemberByRef -DirectoryRoleId <ID> -DirectoryRoleMemberId <ID> # 2. 移除添加的服务主体凭证: # Remove-MgServicePrincipalPassword -ServicePrincipalId <ID> -PasswordId <ID>