scroll it

Exploits Explained: Escalating Privileges With SSRF

Kuldeep Pandya
0% read

Kuldeep Pandya is a member of the Synack Red Team. You can find him on Twitter or his blog.

This year, OWASP released a new list of the top 10 vulnerabilities related to application programming interfaces (APIs). One new addition is server-side request forgery (SSRF), which didn’t previously appear on the OWASP list but now holds the number seven spot.

I’m going to dive into the world of SSRFs and detail how I found a total of four SSRF vulnerabilities on a relatively small attack surface. Three of them were authenticated SSRFs and the last was a fully unauthenticated SSRF.

I woke up to a new API target from Synack, so I prepared the testing environment by loading the Postman collection and Postman environment files into Postman. I then started Burp Suite in order to view and manipulate the requests.

Application Overview

There were different services running on each sub-collection. By manually checking each request, I found out that only five services were actually performing some sort of operation. The other collections are for authentication and other purposes.

The five services that were running included:

  • XXXIntegration
  • AssetManagement
  • Billing
  • CustomerManagement
  • OLS

To access any of these services, it requires a service-specific access token. For example, if you want to access the Billing service, you must have a Billing access token. If you have an AssetManagement access token, it will not work for the Billing service and vice versa.

I obtained an access token for the AssetManagement service. I set the access token in the Postman environment in order for the testing process to work properly, and I started exploring various requests.

Initial Discovery

By manually testing each request one-by-one, I came across the XXXService - /xxxevent request.

The request looked like this:

POST /api/xxx/xxxevent HTTP/1.1
Content-Type: application/json
Authorization: Bearer redacted
Host: AssetManagement-service-host
Content-Length: 507
"event_id": "redacted",
"event_type": "redacted",
"event_time": "2022-07-06T14:55:00.00Z",
"correlation_id" : "redacted",
"payload": {
"urls": [
        "url_type": "XXXIntegration",
        "url": "https://XXXIntegration-service-host/api/xxxhistory/xxx/1234"

Here is the explanation for each parameter:

event_idThis is a UUID for a particular event.
event_typeSet to “public” by default in the collection. Most probably, this identifies if the event is public or private.
event_timeDescribes the time of the event.
url_typeThis is set to XXXIntegration showing that the XXXIntegration service is being requested.
urlThis is the parameter of utmost interest, as this specifies an XXXIntegration service URL to fetch data. A user can modify this parameter to hold any arbitrary URL and the API will send requests to that URL.

The response to this request showed no interesting behavior as it was just a 500 Internal Server Error page without any verbose errors.

I started testing this request by providing it with a Synack Burp Collaborator URL. And, to my surprise, it actually sent me an HTTP request.

Initial Assumption and Limitations

Just like you, I also noticed the authorization token in the request. But at the time of testing, I assumed this was my own authorization token. I believed the server forwarded parts of my request to the URL provided as the url parameter.

This kind of behavior has almost no impact on its own because stealing our own bearer token yields nothing. It must be chained with some other attack like a cross-site request forgery (CSRF) in order to exploit other users. For example, it can be exploited when you make the victim send a request to an attacker-controlled domain.

Bypassing The Blacklist to Achieve a Partial SSRF

I ignored this behavior and started checking if I can do a local port scan to qualifiy for a partial SSRF. I tried to make the API send a request to localhost but the API had a blacklist in place for such payloads. I tried a handful of payloads like:

  • localhost
  • 127.1

This API was allowing requests to arbitrary domains so I thought to try a domain name that resolved to So, I tried it again with the domain and it successfully bypassed the blacklist. I confirmed that it bypassed the blacklist by the 500 Internal Server Error response. Normally, if the API filtered the payload, it sent a 403 Forbidden response.

API normally:

API when I use

Now, all I had to do was create a local port scan proof of concept and submit it as a partial SSRF. However, I decided to escalate this issue in order for a better payout. I kept this vulnerability aside and started checking other functionalities that could potentially be used to exploit the SSRF.

Attempting To Understand JWT

At random, a thought clicked in my brain. I wanted to confirm if the bearer token that I received in the collaborator indeed belonged to me. I later confirmed that the bearer token in the collaborator was different from the one that I had in my request.

I used to decode both the tokens and compared them side by side. And this comparison further confirmed my belief that both the tokens are different. I will not show a screenshot of this for obvious reasons.

Even after confirming that the token is from a different user/service, I still had no idea where this token was being used. I thought to fuzz all API endpoints of all services with this bearer token to see if any of them respond with a 200 OK or even anything apart from 401 Unauthorized.

Finding Services to Use The JWT

As usual, I got lazy and started looking for alternatives to fuzzing. Also, fuzzing must be the last resort in this situation because there were endpoints that performed different CRUD operations. Any wrong request can break the API.

After checking each request manually for a while, another thought randomly clicked in my brain. Let’s revisit our vulnerable request:

"url_type": "XXXIntegration",
"url": "https://XXXIntegration-service-host/api/xxxhistory/xxx/1234"

Here, XXXIntegration specifies that we are requesting the XXXIntegration service. If you notice, the XXXIntegration service is one of the five services that I listed at the start. And the XXXIntegration-service-host is a host for the same service.

So, my hypothesis was that if the original request was being sent to the XXXIntegration-service-host host, then this access token must also belong to the same service.

To confirm this theory, I copied the authorization token received by the collaborator and pasted it into the health check endpoint of the XXXIntegration-service-host host. The health check endpoint was the perfect test for this. The reason is that it returned a 401 Unauthorized response if the credentials weren’t valid and a 200 OK response if the credentials were valid.

After setting the authorization token, I successfully received a 200 OK. This confirmed that the token that was leaked belonged to the XXXIntegration service.

This proved that the authorization token can be used to interact with the XXXIntegration service. However, just to make sure that it can not be accessed with any valid access token, I sent a request to the health check endpoint of the XXXIntegration service with an access token for the AssetManagement service. This failed because the application had proper access control checks in place.

Also, I later confirmed that we can exfiltrate an access token of ANY service by specifying the service name in the url_type parameter. If you replace XXXIntegration with Billing in the vulnerable request, the collaborator request will yield a Billing service access token.

Now we have everything we need to craft a full SSRF report.

  1. A request to an arbitrary URL
  2. API leaking access token of another service
  3. Privilege escalation using the access token

Finding Even More (Scarier) SSRFs

I started writing a report on this issue. But I accidentally closed all my tabs in Postman. I used the “filter” option in Postman to search for the “event” keyword hoping to find the vulnerable endpoint. But instead, I was greeted with 9 such endpoints that ended with “event”. I checked all of them and found out that all of them were vulnerable.

Now, instead of writing one report, I had to write 4 different reports. I wrote the first three reports. And then moved on to the next and final report. I was so shocked that I could not believe what I was seeing.

The last endpoint was accessible without any sort of authentication. The request to it looked like this:

POST /api/xxx/xxxevent HTTP/1.1
Content-Type: application/json
Host: CustomerManagement-service-host
Content-Length: 622

"event_id": "redacted",
"event_type": "redacted",
"event_time": "2022-08-30T09:00:00.0000000Z",
"correlation_id": "redacted",
"payload": {
"urls": [
   "url_type": "CustomerManagement",
   "url": "https://CustomerManagement-service-host/api/xxx/customer/1234"
"url_type": "XXXIntegration",


Any form of authentication was not required to access this endpoint. So, it allowed a remote unauthenticated attacker to obtain valid authorization tokens for different services. All an attacker has to do is tell the server about the service they want to interact with and a URL to send the authenticated access token. The server will send the credentials without asking for anything. It’s that simple—and pretty scary! 


  1. Manually check for small details/anomalies. This may or may not lead to vulnerabilities. But it can certainly help you escalate the severity of the issue.
  2. Always try to increase the severity of your finding. Never settle for a lower severity vulnerability. Take the help of your fellow hackers if you need to.

I work full-time as a bug bounty hunter, mostly hacking in Synack Red Team. If you’re interested in becoming a part of the Synack Red Team, feel free to connect with me! I’m always happy to offer guidance to fellow cybersecurity enthusiasts.