Hello,
I’ve been following Lift-off II: Resolvers
course on Odyssey and I noticed that the way that REST API requests are made is insecure and it can lead to developers making mistakes that result in security vulnerabilities in their systems.
What is the potential bug?
resolvers.js
const resolvers = {
Query: {
// returns an array of Tracks that will be used to populate the homepage grid of our web client
tracksForHome: (_, __, { dataSources }) => {
return dataSources.trackAPI.getTracksForHome();
},
},
Track: {
author: ({ authorId }, _, { dataSources }) => {
return dataSources.trackAPI.getAuthor(authorId);
},
},
};
module.exports = resolvers;
track-api.js
const { RESTDataSource } = require('apollo-datasource-rest');
class TrackAPI extends RESTDataSource {
constructor() {
super();
// the Catstronauts catalog is hosted on this server
this.baseURL = 'https://odyssey-lift-off-rest-api.herokuapp.com/';
}
getTracksForHome() {
return this.get('tracks');
}
getAuthor(authorId) {
return this.get(`author/${authorId}`);
}
}
module.exports = TrackAPI;
When we can see that the authorId
parameter comes directly from the user and is not URL-encoded. Thus, the user can pass an authorId
like 1/../../anotherresouce/2
. Then, the application will create this URL:
https://odyssey-lift-off-rest-api.herokuapp.com/author/1/../../anotherresouce/2
which after normalization will become
https://odyssey-lift-off-rest-api.herokuapp.com/anotherresouce/2
It becomes a problem when not every user should be able to access anotherresouce/2
and the authentication part is done on the JavaScript layer, not on the REST API. This bug is called secondary context path traversal and here’s an example of such bug in Starbucks (not in GQL context): Hacking Starbucks and Accessing Nearly 100 Million Customer Records | Sam Curry
When chained with open redirects, it can also lead to SSRFs.
How to fix it?
Obviously, the vulnerability in the example application is not exposing any sensitive data and it’s every developer’s responsibility to make their applications secure. With that said, I think we should do our best to show developers the secure way to create their applications.
I think the Odyssey docs should follow the way that HTTP requests are done in Data sources - Apollo GraphQL Docs
which is using encodeURIComponent(id)
. This is sufficient to prevent this vulnerability.
class MoviesAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = 'https://movies-api.example.com/';
}
// GET
async getMovie(id) {
return this.get(
`movies/${encodeURIComponent(id)}` // path
);
}
}