선을 넘는 호스트의 휴식 방해
핵심 요약
- Akamai 연구원 벤 바르니아(Ben Barnea)는 Microsoft Windows RPC 서비스에서 기본 점수 7.7로 CVE-2022-37998 및 CVE-2022-37973으로, 기본 점수는 7.7입니다.
- 이 취약점은 Local Session Manager RPC 인터페이스의 여러 버그를 이용합니다.
- 이 취약점으로 인해 컨테이너 및 세션 서비스(예: Microsoft Defender Application Guard, Sandbox, Docker 및 Windows Terminal Server)가 작동하지 않도록 하는 DoS 공격이 발생합니다.
- 이 취약점은 패치되지 않은 Windows 10, Windows 11 및 Windows Server 2022 컴퓨터에 존재합니다.
- 이는 Microsoft에 보고되어 2022년 10월 패치 화요일(Patch Tuesday)에서 처리됐습니다.
- Akamai는 취약점 개념 증명(PoC)을 연구 리포지토리에 제공합니다.
서론
Akamai Security Intelligence Group은 지난해 심층적인 MS-RPC 리서치에 착수했습니다. 많은 기능을 수행하는 프로토콜임에도 MS-RPC는 제대로 된 연구가 이루어지지 않았으며 이는 실질적인 영향을 미칠 수 있습니다. 이러한 영향 중 하나는 RPC 인터페이스의 취약점이 노출된다는 것입니다. 이 블로그 게시물에서는 LSM(Local Session Manager) RPC 인터페이스 내의 취약점에 대해 중점적으로 다루고 있습니다.
LSM은 Session Manager Subsystem의 일부입니다. Windows 시스템의 터미널 서버 세션과 관련된 로컬 세션 관리를 담당합니다. Winlogon 및 Csrss와 같은 다른 관련 Windows 구성요소와 통신합니다.
LSM은 lsm.dll에서 구현되며 클라이언트 및 서버 로직이 모두 포함되어 있습니다. LSM을 통해 여러 RPC 인터페이스가 드러나는데, 그중 하나가 Hyper-V 가상 머신 내에서 실행되는 컨테이너 세션 관리와 관련이 있는 흥미로운 인터페이스입니다. 그리고 취약점은 바로 이 인터페이스 내에 있습니다.
이 인터페이스는 무엇일까요?
새 RPC 인터페이스에는 UUID c938b419-5092-4385-8360-7cdc9625976a가 할당되어 있습니다. 이 인터페이스는 정확히 두 가지 기능을 제공합니다. ContainerCom_AskForSession 및 ContainerCom_SessionLoggedOff입니다. 이 인터페이스는 항상 RPC_S_OK를 반환하는 보안 콜백에도 등록되므로 모든 사용자가 접속할 수 있습니다. LSM 서버는 Hyper-V 컨테이너에서만 접속할 수 있는 Hyper-V 소켓(hvsocket) 엔드포인트를 등록합니다.
그림 1: hvsocket을 통해 RPC 연결을 설정하는 클라이언트 vs. hvsocket 엔드포인트를 설정하는 서버
컨테이너 내부에서 세션이 생성되면(예: RDP 연결 생성으로 인해) LSM 클라이언트는 먼저 RpcGetRequestForWinlogon을 컨테이너의 LSM 내에서 호출합니다. 이 함수는 세션 생성을 중재하고, 컨테이너 내부에서 실행될 때 먼저 호스트에 권한을 요청합니다. 이 작업은 상위에 대한 hvsocket을 사용하여 호스트에 ContainerCom_AskForSession RPC 호출을 수행하면서 이루어집니다. RPC 인터페이스는 컨테이너에 대한 세션 수를 제한합니다. 방법은 새로 생성된 세션을 추적하는 것입니다.
LSM은 세션을 어떻게 추적하나요?
그 답은 간단합니다. 두 개의 변수를 보유하는 ContainerSessionServer라는 전역 오브젝트를 통해 세션을 추적하는 것입니다.
- 생성된 총 세션 수의 카운터는 1로 제한되며, 특정 시점에 1개의 세션만 허용됨을 의미합니다.
- 컨테이너의 GUID와 해당 컨테이너의 세션 수 간의 맵은 각 컨테이너에 대해 2로 제한됩니다.
컨테이너가 세션을 요청할 때마다 ContainerSessionServer::AskForSession에서 먼저 총 세션 카운터가 1 미만인지 확인합니다. 이 경우 총 세션 수가 증가하고 맵에서 컨테이너의 세션 카운터가 증가합니다.
(컨테이너 종료 시 또는 RPC 호출로 직접) ContainerSessionServer::OnSessionLoggedOff가 호출되면 이 함수는 총 세션 수와 컨테이너의 세션 수를 모두 1씩 감소시킵니다.
RPC 함수의 취약점 감사
이 인터페이스는 간단하고 구현하기 쉬운 듯하지만 4개의 버그가 발견되었고, 이를 2개의 취약점으로 연결했습니다.
1번 체인 - 중요 섹션을 통한 DoS - CVE-2022-37998
1번 버그 - 중요 섹션 종료 실패
ContainerSessionServer::AskForSession은 중요 섹션을 사용해 글로벌 오브젝트 ContainerSessionServer에 대한 접속을 동기화합니다.
그림 2: 디컴파일된 취약점 코드. 함수는 중요 섹션을 해제하지 않고 종료됩니다.
위에서 볼 수 있듯이 112행에 중요 섹션이 입력됩니다. 이후 114~116행에서 컨테이너의 세션 카운터가 제한(2) 범위 안에 있는지 확인합니다. 이 경우 LSM은 이 세션을 추적하지 않고 함수를 즉시 종료합니다(125행). 안타깝게도 코드로는 시작된 중요 섹션이 종료되지 않습니다. 따라서 이 인터페이스에 대한 추가 호출은 이 중요 섹션이 해제될 때까지 중단됩니다.
하지만 총 세션 카운터의 제한은 1입니다. 그렇다면 컨테이너의 세션 카운터가 2인 지점을 어떻게 찾을 수 있을까요? 논리적으로는 불가능합니다. 그리고 이 지점에서 두 번째 버그가 발생합니다!
2번 버그 - 카운터의 잘못된 추적
컨테이너에 대한 세션이 로그오프되면 호스트의 ContainerSessionServer::OnSessionLoggedOff에 RPC 호출이 이루어집니다. 이 함수는 먼저 DecreaseTotalSessionCount를 호출함으로써 전체 세션 카운터를 줄입니다. 이 작업은 컨테이너가 추적 중인지 여부와는 무관하게 실시됩니다. 컨테이너가 추적 중이지 않은 것이 확인되면 총 세션 카운터를 다시 증가시키지 않고 종료됩니다.
이 경우 총 세션 카운터에서 부호 있는 정수로 인해 음수 값을 가질 수 있습니다. 간단하게 많은 OnSessionLoggedOff 요청을 AskForSession 요청을 보내기 전에 보내므로, 총 세션 카운터는 임의의 음수로 계속 감소합니다.
첫 번째 버그와 두 번째 버그 연결
2번 버그를 사용하여 총 세션 카운터를 음수가 될 때까지 몇 차례 줄일 수 있습니다. 그런 다음 2개의 요청을 AskForSession으로 보냄으로써 계속 버그를 활용할 수 있습니다. 이 함수가 두 번째로 호출되면 총 세션 카운터가 1 미만인지 확인하고, 두 번째 버그로 인해 이 카운터는 1 미만이 됩니다. 그러면 컨테이너 세션 카운터가 2인 것을 알 수 있으며, 중요 섹션을 종료하지 않고 반환됩니다.
그림 3: 악용 프로세스의 개요
DoS는 새로 들어오는 RPC 호출이 중요 섹션 교착 상태를 만든 스레드에 디스패치되는지 여부에 따라 달라집니다. RPC 런타임이 동일한 스레드에 새 호출을 디스패치한 경우 DoS가 발생하지 않는데, EnterCriticalSection이 중첩 소유권을 허용하고, 동일한 스레드가 EnterCriticalSection을 두 번 호출할 수 있기 때문입니다. RPC 호출이 중요 섹션을 보유한 스레드가 아닌 다른 스레드로 디스패치된 경우 계속 대기합니다.
2번 체인 - 메모리 유출을 통한 DoS - CVE-2022-37973
3번 버그 - 메모리 유출
ContainerSessionServer::AskForSession은 컨테이너 종료, 일시 중지, 재시작과 같은 컨테이너 이벤트도 추적합니다. 이는 HcsOpenComputeSystem을 컨테이너의 GUID를 사용하여 호출하고 콜백을 HcsRegisterComputeSystemCallback으로 등록함으로써 가능합니다.
등록된 콜백은 컨텍스트 오브젝트를 받습니다. 컨텍스트는 ContainerSessionServer::AskForSession내부에 할당됩니다. 하지만 오류가 발생하는 많은 경우 컨텍스트에 할당된 메모리가 해제되지 않고 함수가 종료됩니다. 이로 인해 공격자가 여러 번 트리거할 수 있는 메모리 유출이 발생합니다. 충분한 호출 이후 LSM 프로세스에 대한 메모리 고갈이 발생하고 프로세스가 충돌합니다.
테스트에서 무한 루프로 RPC 요청을 보내면 초당 약 3MB의 할당이 이루어졌습니다. 이 경우 24GB의 메모리가 할당된 후 LSM 서비스가 충돌했습니다. 24GB를 소진하는 데 걸린 시간은 약 2시간입니다. 이 서비스는 자동으로 다시 작동하지 않습니다.
4번 버그 - 원격 접속
MS-RPC의 엔드포인트는 멀티플렉싱됩니다. 서버가 여러 인터페이스와 여러 엔드포인트를 등록하면 각 엔드포인트를 통해 각 인터페이스에 접속할 수 있습니다. 엔드포인트 및 인터페이스는 서로 종속되지 않습니다.
이 인터페이스는 hvsocket을 통해서만 컨테이너에 접속할 수 있어야 합니다. 이 경우 LSM은 원격으로 접속할 수 있는 명명된 파이프 엔드포인트인 '\pipe\LSM_API_service'를 등록합니다. 엔드포인트 멀티플렉싱으로 인해 원격 공격자가 명명된 파이프 엔드포인트에 연결하여 컨테이너 인터페이스에 요청을 보낼 수 있습니다. 해결 방법은 간단합니다. 보안 콜백은 클라이언트가 사용한 엔드포인트를 확인해야 하며, hvsocket이 아닌 경우 접속을 거부해야 합니다.
세 번째와 네 번째 버그 연결
컨테이너 추적 기능은 클라이언트 식별자 속성을 기반으로 합니다. 즉, hvsocket의 경우 클라이언트 식별자는 컨테이너의 GUID가 됩니다. 명명된 파이프의 경우 클라이언트의 컴퓨터 이름이 됩니다.
첫 번째 악용을 트리거하려면 클라이언트에 실행 중인 컨테이너의 실제 GUID인 클라이언트 식별자가 있어야 합니다. 따라서 실행 중인 컨테이너의 GUID를 확인하고 컴퓨터 이름을 변경하지 않는 한 원격 클라이언트는 이러한 버그를 트리거할 수 없습니다.
하지만 세 번째 버그(메모리 유출)는 요청된 컨테이너에서 검사를 수행하기 전에 오브젝트를 할당합니다. 즉, 원격 공격자(4번 버그 사용)가 원격으로 메모리 유출을 트리거할 수 있습니다. 공격자가 여러 번 호출함으로써 메모리 고갈과 프로세스 충돌을 일으킬 수 있습니다.
영향
이 취약점은 DoS(서비스 거부) 취약점으로 분류되지만 공격자가 보안 기능을 우회할 수 있으므로 보안에 영향을 미칩니다.
첫 번째 악용(중요 섹션)에서는 특정 새 인터페이스에 대한 DoS가 있습니다. 이 문제로 인해 새 샌드박스 인스턴스가 생성되지 않습니다.
두 번째 악용의 경우 원격으로는 물론 컨테이너에서 모두 트리거될 수 있으며, 전체 프로세스가 충돌합니다. 따라서 LSM에 대한 모든 의존성이 작동하지 않습니다. Microsoft Defender Application Guard 및 Sandbox와 같은 보안 기능도 작동하지 않습니다. RDP 및 Docker도 그렇습니다.
그림 4: Microsoft Edge에 표시된 MDAG 오류
요약
이러한 취약점은 단순하거나 중요하지 않은 것으로 보이는 것이 실제로는 부정적인 영향을 미칠 수 있음을 보여주는 좋은 예입니다. 이 인터페이스는 사소한 것처럼 보일 수 있습니다. 트리거하기 쉽고 흥미로운 영향을 미치는 몇 가지 버그만이 있었지만,
실제로는 공격자가 이런 공격 체인을 활용하고 있습니다. 중요하지 않은 것으로 보일수록 무시되기 쉽고, 이는 악용하기 쉬운 수단이 됩니다.
RPC에서 수행하는 지속적인 작업과 함께 다른 연구원들이 다른 RPC 인터페이스에서 유사한 버그를 찾도록 권장하고 있습니다.
이와 같은 RPC 연구 주제에 관심이 있는 경우 RPC 툴킷에서 더 많은 기록과 툴을 확인해 보시기 바랍니다. 또한 Twitter에서 Akamai를 팔로우하여 이 연구는 물론 진행 중인 다른 연구에 대한 실시간 업데이트도 확인하실 수 있습니다.