HexBrain

Logitech · Report #1108418

SSRF allows reading AWS EC2 metadata using "readapi" variable in Streamlabs Cloudbot

Validstatus
mediumseverity
$200bounty
41upvotes
24 Feb 2021disclosed

Summary

Detailed summary is provided by the hacker below.

Summary:

Streamlabs Cloudbot is a customisable chatbot provided by Streamlabs which allows the creation of custom commands along with custom responses. These chat responses can take in "Variables" wrapped in curly brackets as documented in https://streamlabs.com/dashboard#/cloudbot/commands/variables, such as {{user.name}} which translates to the username of the viewer who executed the command in stream chat.

Some of these "Variables" can take in arguments, one of it is the readapi variable, in the form of {readapi.<url_here>}. When the custom command is executed, Cloudbot backend translates these "Variables" in the custom responses template into their respective values, and then sends it on the stream chat. To translate the readapi variable, an HTTPS request is sent to the provided URL, and then returns its response in place of the {readapi.<url_here>} variable. Example:

  • The server's IP is {readapi.https://api.ipify.org} -> The server's IP is xx.xxx.xxx.x.

However, the backend checks to see if the supplied URL is using the https:// protocol. If any other protocol is supplied, including http://, the backend simply replaces the variable with [Https required], as such:

  • The server's IP is {readapi.http://api.ipify.org} -> The server's IP is [Https required].

Other than that, there's other several caveats.

  1. The response has to be a common human-readable Content-Type. For example, having no Content-Type, or having text/plain, text/html, application/json as the Content-Type would return the response in chat successfully. Meanwhile, application/javascript, application/octet-stream or image/png would cause [Invalid Content-Type] to be shown.

  2. The response code must be 2xx, so 404 Not Found pages or 500 Internal Server Error pages are all replaced with [Bad Server Response / Too slow] in the chat.

  3. The timeout value is very low, my estimate is around 3000 ms. If the web server could not respond within this time frame, the connection is dropped and [Bad Server Response / Too slow] will be shown. This makes it hard to differentiate an open, closed, or filtered port when fingerprinting with SSRF port scanning in the backend server.

  4. There appears to be a limit for the content length, I did not test out what is the exact value for this, but anything exceeding this limit will simply not return anything, so abc{readapi.https://superbigsite/}def -> abcdef.

  5. The response data cannot contain any curly brackets { or }, which I believe is because it represents variables. The backend recursively translates variables until there are no variables left, so it may be to prevent infinite loops or at least a high number of requests being sent. Response data containing the symbols will simply not returning anything, same behaviour as mentioned in the point above.

The HTTPS-only requirement prevents me from querying the AWS EC2 metadata service directly, since it can only be requested via HTTP. Fortunately, Cloudbot's backend follows redirects, even if it's to an HTTP URL. I simply hosted a file slpoc.php on my server containing the following contents.

<?php header('Location: http://169.254.169.254/latest/meta-data/'); ?>

Then, a readapi variable is set to send a request to this PHP file. (The prefixing A and suffixing B is used to ensure I would get a reply even if the variable content is empty).

  • A{readapi.https://mydomain/slpoc.php}B -> Aami-id ami-launch-index ami-manifest-path block-device-mapping/ events/ hostname identity-credentials/ instance-action instance-id instance-type local-hostname local-ipv4 mac metrics/ network/ placement/ profile public-hostname public-ipv4 public-keys/ reservation-id security-groups services/B

As can be seen above, the EC2 instance does not have an IAM role assigned to it. This limits me from being able to escalate this further to file disclosure, RCE, or an AWS account takeover. Since Cloudbot runs on multiple different EC2 servers for load balancing, there may be potential misconfigured servers that my Cloudbot isn't running on which allows such an escalation, but not that I could find.

Attempts on redirecting to other protocols (ex. gopher://, ftp://, file://, dict://) have all returned [Bad Server Response / Too slow] with nothing hitting my test servers, which means the request library being used here in the backend is likely exclusively only for web requests.

Note that some AWS endpoints which return JSON data, such as http://169.254.169.254/latest/dynamic/instance-identity/document cannot be retrieved due to caveat number 5 as previous stated, which also further limited my ability to get access keys, even if an IAM role is attached.

The applied fix no longer follows redirects, the behaviour now is that it would simply throw [Bad Server Response / Too slow] for redirects, which should prevent anyone from querying the AWS metadata endpoint, or any HTTP URLs for that matter.

How it was found

The write-up for this report has not been downloaded yet, or the program disclosed it without one. Read it on HackerOne.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original