GOAD(Game Of Active Directory) 구축 과정
2021년에 다니던 회사에서 Active Directory를 정말 잘 운영하는 가까운 형으로부터 자극을 받아 꼭 AD 침투테스트에 대한 연구를 및 테스트를 진행해야겠다 마음먹었었다.
하지만 바쁜 업무로 시도 조차 하지못하고있다가 (핑계) 새로운 직장으로 이직을하여 침투테스트 업무를 진행하면서 DevOps, CICD, Active Directory에 대한 침투테스트 스킬이 많이 부족함을 느끼고 방황하던 중 TryHackMe와 HackTheBox를 알게되었다.
그렇게 현재는 HackTheBox의 다양한 환경의 머신들을 풀어가면서 CTF같은 환경은 실제 리얼 월드와 거리가 멀다고 생각했던 나에게 새로운 시야를 트이게 만들어주고있다. 실제로 엔터프라이즈 환경에서 사용할 수 있는 오픈소스나 프레임워크에 대한 내용 및 DevOps 환경과 같은 다양한 구성을 만날 수 있었고 포럼과 공식 디스코드 채널에 수 많은 해커들에게 피드백을 받을 수 있어 이런 플랫폼을 멀리했던 나를 반성했다.
나는 현재 HackTheBox에 VIP+ 정기 결제를 신청하여 월 20유로가 결제되고있다. 일반 VIP 플랜의 경우 월 14유로가 청구되며 VIP+와의 차이점은 Hack The Box에서 시간이 지나 폐기된 머신들에 접근할 수 있는 권한이 주어지고, VIP 플랜은 생성한 머신에 여러 사람들이 동시에 붙어 공격을 진행하지만 VIP+ 플랜은 전용 인스턴스를 제공하여 좀 더 안정적인 환경에서 머신을 체험할 수 있다😀
그러는 과정에서 자연스럽게 Active Directory와 관련된 머신들을 만나게되었고 기존 웹, 모바일, 게임 위주의 모의해킹 기술에 국한되어있던 나에게 새로운 자극거리가 되었다!
HackTheBox를 통해 AD 환경에서의 침투 테스트 기술을 습득하던 중 지식의 우선 순위가 꼬였음을 알게되었고 기초부터 단단하게하기위해 Game Of Active Directory(GOAD) v2를 알게되어 이를 구축하고 직접 공격하고 더 나아가 보안 사항을 적용하여 공격이 불가능하도록하는 계획을 갖게됐다.
결과적으로 홈랩을 구축하는데 최종 목적을 두고 GOAD 구축을 시도한다. 아래는 내가 생각하는 홈랩 구축 순서이다.
GOAD 구축 -> 각 시나리오를 전부 테스트 -> 보안 사항 적용 -> Elastic SIEM 구축 -> Elastic EDR 구축 -> 시간이 지나 새로운 CVE나 공격 방식이 나올 경우 관련된 내용을 테스트
어찌됐건 이번 포스팅은 GOAD를 구축하는 과정을 다룰 예정이다. 생각보다 삽질을 많이하게됐고 vagrant와 ansible, windows 환경에 대한 얕은 지식으로 더 삽질을 했던것같.
GOAD (Game Of Active Directory)
GOAD는 취약한 AD 환경을 갖는 Active Directory LAB 프로젝트이다. 침투테스터에게 공격 기술에 대한 연습을 하는데 목적을 두고있어 실제로 외부 환경에 노출될 시 매우 위험하다.
LAB
GOAD는 크게 3가지의 랩이 존재한다.
- GOAD (VM 5개, Forest 2개, Domain 3개로 구성됨)
- GOAD-Light (VM 3개, Forest 1개, Domain 2개로 구성됨)
- NHA (VM 5개, Domain 2개로 구성됨)
Ninja Hacker Academy(NHA)는 가장 많은 취약점을 다루고 있는 랩 환경으로 소개되고있다.
Installation
위에서 3가지 종류의 랩 환경을 확인할 수 있다. 나는 GOAD 환경을 구축할 예정이다. 설치를 위한 환경은 크게 4가지(VirtualBox, VMWare, Proxmox, Azure)로 구분되며 기회가 된다면 Azure 환경에서도 진행하고 싶지만 일단 VirtualBox 환경에서 구축을 시작한다.
일반적으로 블로그 게시글이나 유튜브 영상을 찾아보면 Ubuntu를 호스트로 두고 GOAD 인스턴스를 생성 후 프로비저닝을 진행한다. 하지만 나의 경우 Windows 환경에서 VirtualBox로 Ubuntu를 생성하고 그 안에서 가상머신을 생성하는 과정 중 잦은 가상머신의 수많은 에러가 발생하여 결국 Windows를 호스트로 두기로했고 u21h2의 블로그 게시글을 참고하여 구축을 진행했다.
환경
현재 사용중인 Windows 데스크탑의 사양은 다음과 같다. GOAD 구축 시 약 115GB의 저장 공간이 필요하다고하고 다양한 게시글을 확인해보니 메모리는 24GB 이상 필요한것같다.
제공하는 가상머신을 인스턴스화 하기위해 Vagrant를 사용하는데 현재 내가 사용한 버전은 Vagrant 2.3.4
이다. (설치 파일)
위에서 언급한것과 같이 가상머신 플랫폼은 VirtualBox를 사용한다. 사용하는 VirtualBox 버전은 VirtualBox 6.1.21
이나 최신 버전을 사용해도 무방한것 같다.
또 GOAD는 설치 과정이 은근 복잡해서 수 많은 Issue가 존재하는데 개발자가 지속적으로 코드를 수정하고있다. 어찌됐건 이번 포스팅을 작성하면서 구축했던 GOAD 레포지토리의 최신 커밋 ID는 b8933e2bdf7ef22ac070aea13453e098b5367284
이다.
이 포스팅을 보고 구축을 하는 사람이 있다면 해당 커밋으로 체크아웃하고 가상 머신 설치 및 프로비저닝을 진행할 것을 권고한다.
마지막으로 Windows 환경에서 vagrant 사용을 위해 VAGRANT_HOME
환경 변수를 설정해준다. 환경변수의 경로는 아래와 같으며 vagrant를 설치할 때 사용자 디렉터리에 생성되며 해당 경로를 등록한다.
1
2
setx VAGRANT_HOME "/C:\Users\{USER}]\.vagrant.d"
setx VAGRANT_HOME "C:\Users\{USER}\.vagrant.d" /M
Providing
이제 가상머신을 인스턴스화하기 위해 vagrant up
명령을 사용할 수 있다. 해당 명령을 실행하면 Vagrantfile
을 해석해서 해당 가산머신 이미지에 맞는 파일이 존재하는지 여부를 파악하고 이미지 파일이 존재하지 않는다면 지정된 이미지를 다운로드하여 인스턴스화 한다.
과정에서 다운로드한 이미지 파일은 C:\Users\{USER}\.vagrant.d\boxes
에 저장된다.
나는 여러 시행착오를 겪어 아래와 같이 이미지 파일을 먼저 다운로드하였다.
1
2
3
4
> vagrant box add StefanScherer/windows_2019 --box-version 2021.05.15
> vagrant box add StefanScherer/windows_2019 --box-version 2020.07.17
> vagrant box add StefanScherer/windows_2016 --box-version 2017.12.14
> vagrant box add StefanScherer/windows_2016 --box-version 2019.02.14
다운로드하는 이미지 파일은 위에서 언급한것처럼 Vagrantfile
에 정의되어있다.
1
2
3
4
5
6
7
8
9
10
11
boxes = [
{ :name => "GOAD-DC01", :ip => "192.168.56.10", :box => "StefanScherer/windows_2019", :box_version => "2021.05.15", :os => "windows"},
# windows server 2019
{ :name => "GOAD-DC02", :ip => "192.168.56.11", :box => "StefanScherer/windows_2019", :box_version => "2021.05.15", :os => "windows"},
# windows server 2016
{ :name => "GOAD-DC03", :ip => "192.168.56.12", :box => "StefanScherer/windows_2016", :box_version => "2017.12.14", :os => "windows"},
# windows server 2019
{ :name => "GOAD-SRV02", :ip => "192.168.56.22", :box => "StefanScherer/windows_2019", :box_version => "2020.07.17", :os => "windows"},
# windows server 2016
{ :name => "GOAD-SRV03", :ip => "192.168.56.23", :box => "StefanScherer/windows_2016", :box_version => "2019.02.14", :os => "windows"}
]
이미지 파일이 전부 다운로드가 완료되고 vagrant up
명령을 통해 인스턴스화한다. 정상적으로 인스턴스가 생성되면 아래와 같이 5개의 가상머신이 실행 중인 상태로 확인할 수 있다.(vagrant up 명령을 실행하기 위해선 Vagrantfile이 존재하는 경로에서 실행해야함)
구추 과정에서 예상하지 못한 에러가 발생할 수 있으니 초기 상태인 인스턴스들에 대한 스냅샷을 기록한다. 스냅샷을 찍기 전에 가상머신을 안전하게 종료하고 각 가상머신에 대한 스냅샷을 생성한다.
1
2
3
4
5
6
7
> vagrant halt
> vagrant snapshot save GOAD-DC01 GOAD-DC01-INIT
> vagrant snapshot save GOAD-DC02 GOAD-DC02-INIT
> vagrant snapshot save GOAD-DC03 GOAD-DC03-INIT
> vagrant snapshot save GOAD-SRV02 GOAD-SRV02-INIT
> vagrant snapshot save GOAD-SRV03 GOAD-SRV03-INIT
Provisioning
가상머신이 성공적으로 생성됐다면 이제 각 가상머신에 대한 설정 및 서비스 실행 등의 작업을 진행해야한다. GOAD는 프로비저닝을 위해 docker를 통한 프로비저닝과 ansible을 통한 프로비저닝을 지원한다.
나의 구축과정에서는 직접 ansible playbook을 실행하여 프로비저닝하는 방식을 선택했고 프로비저닝을 위해 Ububtu 22.04
가상 머신을 따로 생성하였다.
생성한 Ubuntu 가상 머신에서 프로비저닝을 위한 셋팅을 진행한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ sudo apt update
$ sudo apt upgrade
$ sudo apt install python3-pip
$ sudo apt install python3-venv
$ sudo apt-get install git-all
$ git clone https://github.com/Orange-Cyberdefense/GOAD.git
$ python3 -m venv venvGOAD
$ source ~/venvGOAD/bin/activate
$ pip install ansible-core
$ pip install pywinrm
$ cd GOAD/ansible
$ ansible-galaxy install -r requirements.yml
모든 준비가 완료되어 아래와 같은 명령을 통해 생성한 가상머신들에 대한 프로비저닝을 진행한다.
1
$ ansible-playbook -i ../ad/GOAD/data/inventory -i ../ad/GOAD/providers/virtualbox/inventory main.yml
Troubleshooting
dnscmd 관련 에러
Ansible Playbook을 실행하는 과정에서 아래와 같은 에러가 발생하며 종료되었다. 에러 내용을 보면 DC02에서 enable the Ethernet 2 interface (local) for DNS client requests
작업을 실행하는 과정에서 dnscmd.exe
명령어가 존재하지 않아 발생한 에러로 확인된다.
1
2
3
4
5
6
7
8
9
TASK [child_domain : enable the Ethernet 2 interface (local) for DNS client requests] **********************************************
fatal: [dc02]: FAILED! => {"changed": true, "cmd": "dnscmd . /resetlistenaddresses 192.168.56.11", "delta": "0:00:00.689721", "end": "2023-12-27 10:08:22.510558", "msg": "non-zero return code", "rc": 1, "start": "2023-12-27 10:08:21.820837", "stderr": "dnscmd : The term 'dnscmd' is not recognized as the name of a cmdlet, function, script file, or operable program. \r\nCheck the spelling of the name, or if a path was included, verify that the path is correct and try again.\r\nAt line:1 char:65\r\n+ ... ::InputEncoding = New-Object Text.UTF8Encoding $false; dnscmd . /rese ...\r\n+ ~~~~~~\r\n + CategoryInfo : ObjectNotFound: (dnscmd:String) [], CommandNotFoundException\r\n + FullyQualifiedErrorId : CommandNotFoundException", "stderr_lines": ["dnscmd : The term 'dnscmd' is not recognized as the name of a cmdlet, function, script file, or operable program. ", "Check the spelling of the name, or if a path was included, verify that the path is correct and try again.", "At line:1 char:65", "+ ... ::InputEncoding = New-Object Text.UTF8Encoding $false; dnscmd . /rese ...", "+ ~~~~~~", " + CategoryInfo : ObjectNotFound: (dnscmd:String) [], CommandNotFoundException", " + FullyQualifiedErrorId : CommandNotFoundException"], "stdout": "", "stdout_lines": []}
PLAY RECAP *************************************************************************************************************************
dc01 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
dc02 : ok=9 changed=5 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
dc03 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
srv02 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
srv03 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
이를 해결하기위해 playbook을 수정하려다 이후 다른 문제로 확장될 것을 우려하여 DC02 서버에 직접 접근하여 dnscmd를 설치한다. VirtualBox에서 직접 접근을 위해 사용되는 계정정보는 Vagrant:vagrant
이다.
이후 해당 서버에서 Server Manager - Dashboard - Add rolse and features
에 접근 후 Server Roles
탭에서 DNS Server
를 추가, Features
탭에서 Remote Server Admin Tools - Role Admin Tools - DNS server tools
를 추가하여 dnscmd를 사용할 수 있는 상태로 만들었다.
SRV02 서버 도메인 조인 문제
위 에러를 해결하고 다시 main.yml
playbook을 실행할 경우 다음과 같은 또 다른 에러를 확인할 수 있다. 내용을 확인하면 SRV02(castelblack)가 north.sevenkingdoms.local
도메인에 조인하는 과정에서 해당 도메인을 찾을 수 없어 발생한 에러로 확인된다.
1
2
3
TASK [member_server : Add member server] ***************************************************************************************************
fatal: [srv02]: FAILED! => {"changed": true, "msg": "failed to join domain: Computer 'castelblack' failed to join domain 'north.sevenkingdoms.local' from its current workgroup 'WORKGROUP' with following error message: The specified domain either does not exist or could not be contacted.", "reboot_required": false}
changed: [srv03]
여러 케이스들을 참고하여 첫번째로 vagrant reload
를 통해 인스턴스들을 재실행하여 다시 프로비저닝을 시도했지만 동일하게 같은 에러가 발생했다.
두번째로 SRV02 서버에 직접 접근하여 nslookup을 통해 해당 도메인을 조회하니 외부 IP가 조회되는것으로 보아 DNS 서버 설정이 문제로 예상되어 아래와 같은 조치를 진행했다.
- hosts 파일에 north.sevenkingdoms.local(192.168.56.11) 추가
- SRV02 가상머신의 NAT 네트워크 어댑터의 DNS Server를 127.0.0.1로 변경
이후 다시 Ansible Playbook을 실행하면 해당 에러는 발생하지 않으며 아래와 같이 SRV02(castelblack) 서버가 도메인에 조인된것을 확인할 수 있다.
모든 프로비저닝이 완료됐다. 이제는 공격자 서버를 셋팅하여 구축한 GOAD에 대한 공격을 진행하는데, 해당 과정에서 가상머신들에 대한 설정 또는 서비스 상태 등이 변경되었을 때를 대비하여 처음 가상머신을 인스턴스화 했을때와 같이 현재 상태를 스냅샷으로 기록해둔다.
1
2
3
4
5
> vagrant snapshot save GOAD-DC01 GOAD-DC01-OK
> vagrant snapshot save GOAD-DC02 GOAD-DC02-OK
> vagrant snapshot save GOAD-DC03 GOAD-DC03-OK
> vagrant snapshot save GOAD-SRV02 GOAD-SRV02-OK
> vagrant snapshot save GOAD-SRV03 GOAD-SRV03-OK
이렇게 GOAD에 대한 구축을 마무리한다😀