The power of Client-Side Path Traversal: How I found and escalated 2 bugs through “../”

We discussed this vulnerability during Episode 233 on 07 January 2024

Client-side traversals as a cool attack class I overlooked for quite awhile. The idea with these is that often an application might take some identifier from the user, and then use it in a further request for other data. It may be possible to include a path traversal in that identifier so that when that further request is made, the URL gets resolved to a different endpoint, take the following example:

  1. You make a request to example.com/project?id=123-456-789
  2. JavaScript on the page loads a JSON blob from example.com/resources/123-456-789.json

If you sent the id as ../uploads/whatever vulnerable JavaScript may try to load the url: example.com/uploads/whatever.json following that traversal and resolving a request to a different endpoint.

This is the sort of issue being exploited here in two different ways.

First, for an on-site request forgery. It starts of with a request to the page https://redacted.com/accept-invitation?userId=6502b3fc-22dd-4f16-a883–36d825aa8ca0&name=Nightbloodz&invitationId=e04cd1f5-e876–4d12-a4e8–9d7e05db0b0b

This page contains an Accept Invitation button, that once clicked will issue a POST request to https://redacted.com/invite/e04cd1f5-e876–4d12-a4e8–9d7e05db0b0b/accept. The problem is that the invitationId could have a traversal in it. Allowing the attacker to trick a victim into making a legitimate POST request to the endpoint of their choice. As the POST body isn’t controlled this isn’t always very useful, but in this case several endpoints had their important data as part of the URL, and just a simple POST with any body was sufficient.

For example an invitationId=blah/../../course/{courseID}/delete could make a post to /course/{courseID}/delete which would delete a course.

The second issue was a means of escalating a self-xss into a general stored XSS. The basic idea of the XSS is that you could create a story which contains a variety of object types which were just stored in a JSON file as an array of {"type":..., "url":...} JSON blobs. One type of object, iframe was only supported in the draft viewer, but the page used to view published stories didn’t support the iframe. However, if you did publish a story with an iframe type object, the JSON would still be there, it just wouldn’t be rendered on the sharable URL. However by using a client-side traversal in the draft viewer, the author could move from a request for a draft (which would need to belong to the same user) to a request for a globally published JSON file. Definitely a nice find to escalate the issue, and if you’re interested give the post a read. My explanation hardly does the attack justice.