It was one of those clean, modern apps — polished UI, fast APIs, nothing obviously broken. The kind where you spend an hour and feel like everything important is already locked down.

I was mostly poking around features.

That's when I saw it.

The Feature That Looked Too Normal

There was a URL preview option.

You paste a link, and the app fetches metadata title, image, description.

Typical request:

POST /api/preview
{
  "url": "https://example.com"
}

Response included:

  • page title
  • description
  • preview image

Standard stuff.

But anything that makes the server fetch a URL… is worth slowing down for.

First Check (Nothing Interesting)

I sent something simple:

"url": "https://google.com"

Worked fine.

Then tried an invalid domain:

"url": "https://nonexistent-test-xyz.com"

It returned a timeout error.

That's good.

It means the server is actually making the request — not just validating format.

The Usual SSRF Test

Next step:

"url": "http://127.0.0.1"

Response:

Request blocked

Okay, expected.

Tried:

"url": "http://localhost"

Same result.

So they were filtering internal addresses.

At this point, it looks "secure enough."

But filters are usually where things break.

Small Observation That Changed Direction

I noticed something weird in the error messages.

When the request failed, it sometimes showed:

"Could not fetch URL"

And sometimes:

"Invalid URL"

That difference matters.

One means validation failed. The other means the request was actually attempted.

So I tried playing with formats.

Bypassing the Filter

Instead of plain localhost, I used decimal encoding:

"url": "http://2130706433"

Same as 127.0.0.1.

This time, the response changed.

No "blocked" message.

Just a generic fetch error.

That's progress.

Confirming SSRF

I pointed it to a server I controlled.

"url": "http://my-server.com"

Got a hit in my logs.

That confirmed:

The server is making outbound requests based on my input

Now it's real SSRF.

Moving Toward Internal Access

Now the goal was simple:

Can I reach internal services?

Tried again with encoded localhost:

"url": "http://2130706433:80"

Response took longer this time.

Different behavior again.

That usually means something is responding internally.

The Risky Step (But Worth It)

Most cloud environments expose metadata on:

http://169.254.169.254

So I tried:

"url": "http://169.254.169.254/latest/meta-data/"

No direct response in the app.

But timing changed again.

So I narrowed it down.

Extracting Something Real

I requested a specific endpoint:

"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"

This time, part of the response actually came back in the preview.

Not clean but enough.

That's when it hit:

I'm pulling data from the instance metadata

Why This Was a Big Deal

Metadata endpoints can expose:

  • IAM roles
  • temporary credentials
  • access tokens

With that, you're not just inside the app.

You're inside their cloud environment.

That's a completely different level of impact.

What Made This Possible

The app had protections.

But they were surface-level:

  • blocking 127.0.0.1
  • blocking localhost

But not:

  • encoded IPs
  • alternative formats
  • deeper validation

Classic case of blacklist-based filtering.

The Report

I didn't just say "SSRF exists."

I showed:

  • external interaction (my server logs)
  • internal access attempts
  • metadata endpoint behavior
  • potential cloud impact

Because SSRF without impact often gets downgraded.

SSRF with cloud exposure doesn't.

𝒯𝒶𝓃𝓋𝒾 ♡