MS 365 공격에서 악용된 ConnectWise SkyKick 벤더 자격 증명
Detection stack
- AIDR
- Alert
- ETL
- Query
요약
ConnectWise Cloud Backup, 이전의 SkyKick과 관련된 손상된 Microsoft 365 API 비밀 정보가 위협 행위자에 의해 악용되어 고객 M365 환경에 대한 무단 액세스를 획득했습니다. 공격자는 애플리케이션의 광범위한 권한을 활용하여 Exchange Online, Microsoft Teams 및 SharePoint Online에 액세스했습니다. 이 활동에는 자동화된 토큰 요청과 수동적 사후 착취작업이 모두 포함되었습니다.
조사
Huntress는 Microsoft 365 로그인 로그를 통해 ConnectWise 서비스 주체의 악용을 식별했으며, 여기에는 호스팅 제공업체 IP 주소 144.172.64.128 및 개인 Azure IPv6 공간에서의 무단 액세스가 포함되었습니다. 조사는 공격자가 Global Administrator와 같은 디렉토리 역할을 할당하고 지속성을 위해 다른 신뢰할 수 있는 애플리케이션에 새로운 클라이언트 비밀정보를 추가하기 위해 손상된 자격 증명을 사용한 사실을 밝혀냈습니다. 활동은 또 다른 차이점인 GUID 순서를 스크립트화하여 테넌트를 통해 이동한 것과 여러 가지의 사용자 에이전트 패턴을 보여주었습니다.
완화
조직은 즉시 Microsoft 365 테넌트 내에서 영향을 받은 ConnectWise Cloud Backup 및 ConnectWise Cloud Management 애플리케이션 ID를 비활성화해야 합니다. 애플리케이션을 삭제하는 대신 비활성화하는 것이 동의 관리 보존을 위해 중요합니다. 보안 팀은 모든 서비스 주체 로그인 역시 감사하고 예기치 않은 추가 사항들이 생성된 애플리케이션 자격 증명도 검토해야 하며, 이는 10월 13일 이후로 생성된 것들입니다.
대응
의심스러운 활동이 확인되면, 조직은 Entra 감사 로그를 검토해야 합니다. 역할에 구성원 추가, 애플리케이션 업데이트, 및 서비스 주체 자격증명 추가 이벤트가 있습니다. 보안 팀은 연결된 사용자 에이전트 문자열을 검증하고 kiota-dotnet과 같은 합법적인 벤더 활동을 Python-urllib 및 curl을 포함한 공격자 도구와 구별해야 합니다. 마지막으로 모든 엔터프라이즈 애플리케이션의 클라이언트 비밀과 인증서를 검토하고 무단 자격 증명을 제거해야 합니다.
공격 흐름
이 부분은 아직 업데이트 중입니다.
탐지
사용자를 관리 역할에 추가 (office365 경유)
서비스 주체 계정 자격 증명 업데이트 (azuread 경유)
높은 권한 역할이 할당되었습니다 (azuread 경유)
M365 공격에서 ConnectWise (SkyKick) 벤더 자격 증명 남용을 탐지하기 위한 IOC (SourceIP)
M365 공격에서 ConnectWise (SkyKick) 벤더 자격 증명 남용을 탐지하기 위한 IOC (DestinationIP)
Python-urllib을 통한 M365의 무단 수정 탐지 [Azure 활동 로그]
ConnectWise 애플리케이션을 사용한 M365에서 Microsoft Graph 토큰의 무단 사용 [Azure 감사 로그]
시뮬레이션 실행
-
공격 서사 및 명령: 적은 제한된 권한을 가진 서비스 주체에 성공적으로 액세스했습니다. 영속성과 전체 환경 제어를 달성하기 위해 공격자는 Python 스크립트를 실행합니다. 이 스크립트는 Microsoft Graph API를 대상으로 하여 1) 손상된 서비스 주체를 높은 권한의 디렉토리 역할에 추가합니다 (예: Global Administrator 또는 Privileged Role Administrator) 및 2) 서비스 주체에 새로운 클라이언트 비밀을 추가합니다. 공격자는
Python 3.12에서라이브러리를 사용하여Python-urllib/3.12사용자 에이전트를 기본값으로 제공하며, 이는 탐지 규칙에 필요한 특정 텔레메트리 후크를 제공합니다. -
회귀 테스트 스크립트:
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>