blog.diffense.co.kr/2020/10/06/Zerologon.html

 

Anatomy of Zerologon

Zero To Logon, Domain를 장악할 수 있는 CVE-2020-1472 취약점 파헤치기

blog.diffense.co.kr

Zerologon을 활용한 Exploit

Exploit 과정은 크게 5가지 Step으로 이루어집니다.

1) Spoofing the client credential

NetrServerReqChallenge 함수 호출로 Challenge를 교환한 후에 Client는 Server의 NetrServerAuthenticate3 함수를 호출하여 인증을 시도합니다. NetrServerAuthenticate3에는 ClientCredential이라는 매개 변수가 있으며 이것이 Server에서 비교할 Client Credential 값이 됩니다.

이 매개 변수 값은 임의로 설정이 가능하고 취약 버전에서는 Server 상에서 어떠한 검증이나 잘못된 로그인 시도에 대한 제재가 존재하지 않으므로 인증이 성공할 때까지 계속하여 시도가 가능합니다.

보통 1 단계가 성공하기 위해서 필요한 평균 횟수는 256회이고 실제로는 약 3초정도 밖에 걸리지 않습니다. 이 방법을 사용하면 도메인의 모든 Computer의 Credential을 spoofing할 수 있고 여기에는 Backup Domain Controller와 Domain Controller도 포함됩니다.

2) Disabling signing and sealing

Step1을 수행하며 Client Credential을 Spoofing할 수 있었지만 Session Key 값이 무엇인지는 알 수 없는 상황입니다. 그래서 Netlogon 상에서 Transport Encryption Mechanism(“RPC Signing and Sealing”)이 적용된 상태라면 Subsequence Message들을 Session Key로 암호화해야 하지만 이 Encryption Mechanism은 선택적인 사항이며 NetrServerAuthenticate3을 호출할 때 Flag 값을 통해 비활성화 할 수 있습니다.

3) Spoofing a call

Step2에서 Call Encryption을 비활성화시켰더라도, 모든 RPC Call은 Authenticator value값을 포함하고 있어야 합니다. 이 값은 ComputeNetlogonCredential 함수를 호출할 때 필요한 인자인 ClientStoredCredential을 계산하는 데 사용됩니다.

SET TimeNow = current time; SET ClientAuthenticator.Timestamp = TimeNow; SET ClientStoredCredential = ClientStoredCredential + TimeNow; CALL ComputeNetlogonCredential(ClientStoredCredential, Session-Key, ClientAuthenticator.Credential);

ClientStoredCredential을 위해 필요한 Authenticator값은 Credential과 Timestamp입니다. Credential은 클라이언트에 저장된 값으로써, 핸드세이크를 수행할 때 공격자가 서버에 제공한 ClientCredential과 동일한 값입니다. 공격자는 0으로 구성된 Client Credential 값을 지니고 있음으로 0으로 설정합니다.

ciphertext = b'\x00' * 8 authenticator = nrpc.NETLOGON_AUTHENTICATOR() authenticator['Credential'] = ciphertext authenticator['Timestamp'] = b"\x00" * 4 request = nrpc.NetrServerPasswordSet2() request['PrimaryName'] = NULL request['AccountName'] = target_computer + '$\x00' request['SecureChannelType'] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel request['ComputerName'] = target_computer + '\x00' request["Authenticator"] = authenticator resp = rpc_con.request(request)

TimeStamp는 현재 Posix 시간을 나타내며 서버는 자신의 실제 시간 값과 일치하는 지 검증하지 않으므로 단순히 1970년 1월 1일의 Timestamp 값을 사용하도록 0으로 설정하면 됩니다.

ComputeNetlogCredential(ClientStoredCredential(0), Session-Key(Unknown), ClientAuthenticator.Credential(0,0)) ⇒ 0이므로 인증된 사용자만 호출할 수 있는 함수들을 호출할 수 있습니다.

4) Changing a computer’s AD password

앞의 Step들을 통해 이제 어떤 Computer로든지 인증된 Netlogon Call을 수행할 수 있게 되었습니다. 이제 기존에 설정된 Computer 계정의 AD Password를 바꿔보도록 하겠습니다.

공격하는 데 사용할 함수는 NetrServerPasswordSet2 함수입니다. 이 함수는 Client에서 새 Computer Password를 설정하는 데 사용됩니다. 설정할 암호 자체는 Hash 되어있지 않지만 Session Key로 암호화되어야 합니다. 서버에서 동일한 Session key를 사용하므로 Step1과 같이 0으로 설정하면 됩니다.

Netlogon 프로토콜의 Plain Text Password 구조는 516 바이트 크기로 구성됩니다. 마지막 4 바이트는 Password의 길이(바이트)를 나타냅니다. 길이를 제외한 나머지 바이트들은 패딩으로 간주되며 임의의 값으로 설정하여도 됩니다. 516 바이트를 모두 0으로 채우면, 길이가 0인 Password, 즉 Empty Password로 취급됩니다. Computer에 빈 암호(Empty Password)를 설정하는 것은 금지되어있지 않음으로 도메인의 모든 Computer에 Empty Password를 설정할 수 있습니다.

이제 Password를 변경한 후에는 패스워드가 Empty Password란 것을 알고 있음으로 공격을 시도할 필요 없이 정상적인 사용자로서 권한있는 작업을 수행할 수 있습니다.

다만 이러한 방식으로 Computer 암호를 변경하면 AD(Active Directory) 상에서만 Computer 암호가 변경됩니다. 대상 시스템 자체에서는 암호를 로컬로 저장하고 있음으로 더 이상 도메인에 인증할 수 없으며 이 시점에서 도메인의 모든 장치에 대한 DOS 공격이 될 수 있습니다.

5) From password change to domain admin

앞의 과정을 통해 Domain Controller의 PW를 변경하면 AD에 저장된 DC PW와 시스템 로컬 레지스트리(HKLM\SECURITY\Policy\Secret\$machine.ACC)에 저장된 PW와 달라지는 현상이 발생합니다. 이로 인해 DC의 특정 서비스(DNS Resolver등)등이 멈추는 등 다양한 오류가 발생합니다. 이를 방지하기 위해 AD의 PW와 로컬 레지스트리를 동기화해주는 작업이 필요합니다. 이러한 작업을 위해서 DC에 새롭게 설정된 Password를 사용하여 로그인하여야 합니다.

새롭게 설정한 PW로 impacket의 ‘secretsdump’ script를 실행하면 DRS(Domain Replication Service)프로토콜을 통해 도메인의 모든 사용자 Hash를 성공적으로 추출할 수 있습니다. 여기에는 GoldenTicket을 만드는 데 사용할 수 있는 krbtgt 계정의 Hash 또한 포함됩니다. 이 Hash 값을 이용하여 DC에 로그인한 뒤 DC의 로컬 레지스트리에 저장된 computer password를 업데이트 할 수 있습니다.



Zerologon을 활용한 Exploit

Exploit 과정은 크게 5가지 Step으로 이루어집니다.

1) Spoofing the client credential

NetrServerReqChallenge 함수 호출로 Challenge를 교환한 후에 Client는 Server의 NetrServerAuthenticate3 함수를 호출하여 인증을 시도합니다. NetrServerAuthenticate3에는 ClientCredential이라는 매개 변수가 있으며 이것이 Server에서 비교할 Client Credential 값이 됩니다.

이 매개 변수 값은 임의로 설정이 가능하고 취약 버전에서는 Server 상에서 어떠한 검증이나 잘못된 로그인 시도에 대한 제재가 존재하지 않으므로 인증이 성공할 때까지 계속하여 시도가 가능합니다.

보통 1 단계가 성공하기 위해서 필요한 평균 횟수는 256회이고 실제로는 약 3초정도 밖에 걸리지 않습니다. 이 방법을 사용하면 도메인의 모든 Computer의 Credential을 spoofing할 수 있고 여기에는 Backup Domain Controller와 Domain Controller도 포함됩니다.

2) Disabling signing and sealing

Step1을 수행하며 Client Credential을 Spoofing할 수 있었지만 Session Key 값이 무엇인지는 알 수 없는 상황입니다. 그래서 Netlogon 상에서 Transport Encryption Mechanism(“RPC Signing and Sealing”)이 적용된 상태라면 Subsequence Message들을 Session Key로 암호화해야 하지만 이 Encryption Mechanism은 선택적인 사항이며 NetrServerAuthenticate3을 호출할 때 Flag 값을 통해 비활성화 할 수 있습니다.

3) Spoofing a call

Step2에서 Call Encryption을 비활성화시켰더라도, 모든 RPC Call은 Authenticator value값을 포함하고 있어야 합니다. 이 값은 ComputeNetlogonCredential 함수를 호출할 때 필요한 인자인 ClientStoredCredential을 계산하는 데 사용됩니다.

SET TimeNow = current time; SET ClientAuthenticator.Timestamp = TimeNow; SET ClientStoredCredential = ClientStoredCredential + TimeNow; CALL ComputeNetlogonCredential(ClientStoredCredential, Session-Key, ClientAuthenticator.Credential);

ClientStoredCredential을 위해 필요한 Authenticator값은 Credential과 Timestamp입니다. Credential은 클라이언트에 저장된 값으로써, 핸드세이크를 수행할 때 공격자가 서버에 제공한 ClientCredential과 동일한 값입니다. 공격자는 0으로 구성된 Client Credential 값을 지니고 있음으로 0으로 설정합니다.

ciphertext = b'\x00' * 8 authenticator = nrpc.NETLOGON_AUTHENTICATOR() authenticator['Credential'] = ciphertext authenticator['Timestamp'] = b"\x00" * 4 request = nrpc.NetrServerPasswordSet2() request['PrimaryName'] = NULL request['AccountName'] = target_computer + '$\x00' request['SecureChannelType'] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel request['ComputerName'] = target_computer + '\x00' request["Authenticator"] = authenticator resp = rpc_con.request(request)

TimeStamp는 현재 Posix 시간을 나타내며 서버는 자신의 실제 시간 값과 일치하는 지 검증하지 않으므로 단순히 1970년 1월 1일의 Timestamp 값을 사용하도록 0으로 설정하면 됩니다.

ComputeNetlogCredential(ClientStoredCredential(0), Session-Key(Unknown), ClientAuthenticator.Credential(0,0)) ⇒ 0이므로 인증된 사용자만 호출할 수 있는 함수들을 호출할 수 있습니다.

4) Changing a computer’s AD password

앞의 Step들을 통해 이제 어떤 Computer로든지 인증된 Netlogon Call을 수행할 수 있게 되었습니다. 이제 기존에 설정된 Computer 계정의 AD Password를 바꿔보도록 하겠습니다.

공격하는 데 사용할 함수는 NetrServerPasswordSet2 함수입니다. 이 함수는 Client에서 새 Computer Password를 설정하는 데 사용됩니다. 설정할 암호 자체는 Hash 되어있지 않지만 Session Key로 암호화되어야 합니다. 서버에서 동일한 Session key를 사용하므로 Step1과 같이 0으로 설정하면 됩니다.

Netlogon 프로토콜의 Plain Text Password 구조는 516 바이트 크기로 구성됩니다. 마지막 4 바이트는 Password의 길이(바이트)를 나타냅니다. 길이를 제외한 나머지 바이트들은 패딩으로 간주되며 임의의 값으로 설정하여도 됩니다. 516 바이트를 모두 0으로 채우면, 길이가 0인 Password, 즉 Empty Password로 취급됩니다. Computer에 빈 암호(Empty Password)를 설정하는 것은 금지되어있지 않음으로 도메인의 모든 Computer에 Empty Password를 설정할 수 있습니다.

이제 Password를 변경한 후에는 패스워드가 Empty Password란 것을 알고 있음으로 공격을 시도할 필요 없이 정상적인 사용자로서 권한있는 작업을 수행할 수 있습니다.

다만 이러한 방식으로 Computer 암호를 변경하면 AD(Active Directory) 상에서만 Computer 암호가 변경됩니다. 대상 시스템 자체에서는 암호를 로컬로 저장하고 있음으로 더 이상 도메인에 인증할 수 없으며 이 시점에서 도메인의 모든 장치에 대한 DOS 공격이 될 수 있습니다.

5) From password change to domain admin

앞의 과정을 통해 Domain Controller의 PW를 변경하면 AD에 저장된 DC PW와 시스템 로컬 레지스트리(HKLM\SECURITY\Policy\Secret\$machine.ACC)에 저장된 PW와 달라지는 현상이 발생합니다. 이로 인해 DC의 특정 서비스(DNS Resolver등)등이 멈추는 등 다양한 오류가 발생합니다. 이를 방지하기 위해 AD의 PW와 로컬 레지스트리를 동기화해주는 작업이 필요합니다. 이러한 작업을 위해서 DC에 새롭게 설정된 Password를 사용하여 로그인하여야 합니다.

새롭게 설정한 PW로 impacket의 ‘secretsdump’ script를 실행하면 DRS(Domain Replication Service)프로토콜을 통해 도메인의 모든 사용자 Hash를 성공적으로 추출할 수 있습니다. 여기에는 GoldenTicket을 만드는 데 사용할 수 있는 krbtgt 계정의 Hash 또한 포함됩니다. 이 Hash 값을 이용하여 DC에 로그인한 뒤 DC의 로컬 레지스트리에 저장된 computer password를 업데이트 할 수 있습니다.

 

 

 

 

'CVE' 카테고리의 다른 글

trendmicro UAF  (0) 2020.11.19
SMB Ghost 취약점 분석(KISA)  (0) 2020.10.14
Bitcoin Remote Dos CVE 2018  (0) 2020.09.06
Word press plugin 0day  (0) 2020.09.06
POODLE Attack  (0) 2020.08.09
블로그 이미지

wtdsoul

,