Analyzing customer environments is always a detective task, but seldom do we find structural flaws in a service provider, which — of course — is a cause for real concern. The presence of a Server Side Request Forgery (SSRF) attack vector can most definitely be alarming, as a successful execution can result in an attacker abusing the functionality of a server to read or update internal resources. Below, I describe how I found an instance of SSRF present on Oracle’s server.
It was just a regular day when I started looking for security weaknesses in Acme services, a new onboarding account. Acme is a podcast hosting company with a very large customer base. Like many companies that work on a large scale, Acme uses the services of Apiary to enable its users to manage their favorite podcasts both fast and securely. Acquired by Oracle in early 2017, Apiary provides a toolset for REST API development, testing, and management. The significance of Apiary being part of Oracle Cloud Services will become apparent.
After a short while of analyzing the customer’s environment and getting a better understanding of how Apiary interacts with the customer’s website, I decided to investigate a bit deeper as API services can be prone to weaknesses, and vulnerable services could affect Orca’s clients as well.
First, I signed up for Acme Services. I was welcomed with the following page:
The above screenshot shows the Apiary REST API page with a template. The left side of the screen should show a list of available endpoints (of course each client will have a list of their own chosen endpoints). Depending on the purpose of the endpoint, each will have a different selected request method (typically GET, POST, or PUT). The middle of the screen should show information regarding each endpoint, and on the right we can see the REST API console with the various options to modify the request such as URI Parameters, Request Headers, and the Request Body. In our case, we will use our test account which we called “SSRFtest” that was created for the demonstration of this Proof of Concept (PoC).
Focusing on the right side of the screen, we are given an example endpoint, which we can only send GET requests to:
https://polls.apiblueprint.org/questions
From the name of the endpoint, we can assume that sending a GET request will return a list of questions related to polls. I used the UI console to send the request without modifying the parameters. What we should get is a response with an “OK” status code (200) containing the response data or, in our case, a list of questions:
However, this response was dissatisfying.
I decided to tamper with the request a bit, thinking that maybe I’d get a different output that would lead me down a more interesting path.
To do so, let’s have a look behind the scenes of this GET request and the various parameters the end user (such as myself) is actually sending to the endpoint. Below I use Burpsuite, to help me intercept the various requests I sent to the server:
Below is the subsequent response:
In the first of the two screenshots above, we notice that the GET request we just sent via the UI is sent via another Apiary endpoint:
https://jsapi.apiary.io/apis/ssrftest/http-transactions/
This means that the various parameters we just sent via the UI are actually sent as a POST Request (instead of the GET request shown in the UI).
As for the Response (Figure 4), as expected, we’re given data that we should have access to:
- The response StatusCode – “200” which indicates that the request has succeeded.
- Similarly to what we received in the UI, the “body” parameter is now populated with the “questions” response (can be seen at the bottom of the response in the above screenshot).
But, wait. Among the response parameters, there’s one that clearly stands out: the url parameter. As we can see in Figure 4, Apiary seems to be creating a mock endpoint (in this case, a randomly generated url).
http://private-anon-c0d9ee8cdd-megaphoneapi.apiary-mock.com/networks/731851fc-9fad-11e6-a338-072240a555ac/podcasts
From my experience, when encountering these types of endpoints (in which the current environment is explicit; i.e., “apiary-mock”) there’s usually more than one environment (i.e., Staging, Development, QA, Testing, Production, etc.). Hence, there are probably a few other types of environments I could attempt to access.
My assumption proved correct: modifying the POST Request “destination” parameter to a random string such as “test” would yield the following message: “A Destination header can be one of the following: mock, production, live, proxy.”
The modified POST Request:
And the response:
Ok, so now that we have verified that changing the destination header can result in a different output, my next move is to change the destination of the request. I replaced the original value, which was “/questions,” with “https://jsapi.apiary.io”.
Changing the POST request with the above modified parameters yields a response containing the main Apiary website, which basically proves that by using the Apiary endpoint, we can retrieve any website we’d like. So eventually, our requests workflow would look something like the following:
- Accessing https://app.apiary.io/ssrftest/ (Main Website) →
- Send a GET Request to https://private-amnesiac-8a57a6-ssrftest.apiary-proxy.com/ (Mock Endpoint) →
- Send a POST Request to https://jsapi.apiary.io/ (Intercepted Endpoint via Burpsuite) →
- Modifying the URI Parameter and sending a GET Request to https://jsapi.apiary.io (or basically any desired endpoint).
Getting to any endpoint is fun and all, but what about trying to access the Instance Metadata endpoint?
Now, before sending a crafted request for retrieving the Instance Metadata, I would like to briefly explain its role in the cloud.
Various cloud providers such as AWS, Microsoft Azure, and Google Cloud use a cloud server meta-data REST interface on http://169.254.169.254.
The instance metadata service (IMDS) provides information about a running instance as well as a variety of details about the instance itself, including its attached virtual network interface cards (VNICs), its attached multipath-enabled volume attachments, and any custom metadata that the end user can define.
By sending requests to the metadata endpoint, we can retrieve different kinds of information.
Now, as mentioned, Apiary is part of Oracle Cloud, which means I probably should look for the specific Oracle Cloud IMDS endpoints.
Below is the current Oracle Cloud IMDSv2 and IMDSv1 endpoint mapping:
Oracle Cloud is currently running two versions of Instance Metadata Versions:
http://169.254.169.254/opc/v2/ and http://169.254.169.254/opc/v1/ (deprecated)
Remember: the original POST request contained the following headers:
“body”, “destination”, “headers”, “method” and “uri”.
We also know that the “destination” header could be modified to be one of the following – proxy, production, live and mock. I tried to change the destination header to “production” and the “uri” to:
http://169.254.169.254/opc/v1/identity
And lo and behold we get the following response:
We received the “identity” data, which contained the value of three main certificates files:
cert.pem
Intermediate.pem
key.pem
All three files are responsible for the 509 certificate-signing process. The next step is to replicate the same process locally, so we can work directly via the Oracle Call Interface (OCI).
In order for us to leverage the findings above via the CLI, I set up the following:
- I’ll set up A local server by using Python’s Flask module. That way, when we send the request to the actual, our local server will fetch all three certificate files via the custom localhost I’ll set in the next step. In that way, we can authenticate via the certificate files as a valid user.
- Reset the localhost address to be the same as the IMDSv1 address (169.254.169.254) so when sending OCI commands to the server, it will look up the three certificate files on the IMDSv1 server. In order to do so, I use the following command: sudo /sbin/ifconfig lo0 169.254.169.254 netmask 255.255.255.0 up
Once my local environment is set, we can now use the OCI CLI to run various commands such as:
oci –auth instance_principal iam compartment list –compartment-id ocid1.tenancy.oc1..aaaaaaaat6du5rqytqh7vxfxh3fxbij7abcdefghiklmnopqrstuvwxyz
We can see the three files being grabbed, as expected:
In the above command, we sent a request for the server to list the compartments, which is a collection of related resources (such as cloud networks, compute instances, or block volumes) that can be accessed only by those groups that have been given permission by an administrator in your organization:
As shown and as expected, we managed to leverage the certificate files to extract sensitive data of various Oracle environments via the Apiary service, by using SSRF. The above command is just one example of what a remote attacker might be able to perform against the server.
Vulnerability Disclosure
Following the above findings, we decided that we should report this vulnerability to Oracle’s Security Vulnerability Disclosure. We emailed Oracle the findings above with a detailed POC. Oracle thanked us for the effort and recognized it by adding my name in their Oracle Critical Patch Advisory.
Summary: Preventing an SSRF Attack
It seems that a misconfigured service (one that is still being set with the deprecated IMDSv1 endpoint) can play a crucial role in facilitating an SSRF attack, like in the case with Apiary.
By misusing the Apiary web service, a remote attacker is able to retrieve very sensitive information from various endpoints (such as http://169.254.169.254/opc/v1/identity) and use it to gain even more access and sensitive data of other hosts in the same environment.
We also created a CLI Tool to enumerate services in Oracle Cloud Infrastructure that we hope will prove useful. The tool is available both on Github here and as a Python package on PyPI here.
Stay tuned for the second part of our journey to discover how we used different lateral movement techniques to advance through an Acme Oracle Cloud account!