171 - Tailscale RCE, an SQLi in PAM360, and Exploiting Backstage
A number of bugs in Tailscale leading to an RCE chain.
So many bugs its hard to know where to start on this one. There are a few APIs of interest here:
LocalAPIon Windows this runs on localhost, on Linux its a unix socket. Can do a number of things from here including disclosing the user’s tailscale private key (can be used for impersonation), but for this chain, the important thing is that you can change the current user’s control plane url. Pointing its control plane to an attacker controller server if an attacker could gain access to this API.
- Quad100 (100.100.100.100) doesn’t do much but it discloses some user information like username, and their “tailnet” ip address, allowing one to determine the IP of their
- PeerAPI - This API is running on the “tailnet” and should be exposed. It has an endpoint that can be used to share a file with the client. The fail can be
PUTusing the exposed
PeerAPIbut then is downloaded by the local GUI using the
So with that background now a couple chains, first both the
PeerAPI could be reachable through DNS rebinding. There are some constraints on reaching the
LocalAPI since it is on a private IP when the victim is using Chrome but other than the browser protections it is vulnerable to be accessed through a rebinding attack. So as the first RCE chain, an attacker could have their website, rebind to access
LocalAPI from there change the control plane url, enable TailDrop to share a binary, upload a binary onto the victim using TailDrop and have trigger that binary to be executed from the control plane server.
The second chain is a bit more complex, but starts with with rebinding to access Quad100, using that to find where to rebind again to access the
PeerAPI. With access to
PeerAPI they could cause an XSS in the
LocalAPI context. Recall how the GUI would fetch the uploaded file through the
LocalAPI, in fetching the file from the
LocalAPI it would be served with
Content-Type: text/html. So if the attacker side included the
LocalAPI context, allowing for the core attack of the first chain to be followed. There was a catch though, the GUI would fetch the file and delete it from its temprary position that was served by the
LocalAPI. However this was defeated by having multiple requests upload the same file, in their racing to control the file, some would fail and the file would never get deleted.
An SQLi in Password Manager Pro, which is bundled with Manage Engine’s Privileged Access Management 360 (PAM360) and Access Manager Plus. In the password manager, there’s a concept of “resources” which can be added or edited, which internally submits a multipart form request to the
AddResourceType.ve endpoint. The resource names are used by the
AutoLogonHelperUtil class to construct partial SQL statements, but they’re not sanitized against SQLi. It’s possible to add or edit a resource with a malicious SQL payload, and click something that triggers that resource name to be used in a query to get SQL code exec, via something like the connections menu. This would require authentication to exploit.
This blogpost is essentially using a previous sandbox escape they discovered against Backstage, which is Spotify’s incubated solution for managing infrastructure and microservices and such. Backstage includes software templates, which can contain ` message` parameter that gets rendered in Nunjucks (a JS templating engine). The environment is sandboxed via the vm2 JS sandbox library.
The idea behind the sandbox escape is the fact that you can access global Node objects outside the sandbox by overriding the
prepareStackTrace() method of the
Error object. The intent is to allow you to customize the callstack, and so the
prepareStackTrace() method will be invoked with an array of
CallSite objects that represent stack frames. Some of those stack frames may have objects that are outside the sandbox. Knowing this, the vm2 maintainers tried to prevent this strategy by wrapping the
Error object with their own implementation that would prevent overriding
prepareStackTrace(). The problem is, an attacker can then just override the
Error object again to implement it.
In the case of Backstage, there were some challenges due to their use of strict mode functions. Functions that are in strict mode cannot access their receivers. They iterated the call stack and found the earliest strict mode function was
renderString2(). What this does isn’t really important, what is important is that this method can be overridden to force it to run in non-strict mode. They discovered
renderString2() was also called when any error was thrown. By overriding it and intentionally triggering an error, they can then resume the
Error object override attack to escape the sandbox and achieve RCE.