Skip to content

View.prototype.lookup() lacks path containment check (unlike send library for res.sendFile) #7140

@raajheshkannaa

Description

@raajheshkannaa

Description

View.prototype.lookup() in lib/view.js:104-123 uses path.resolve(root, name) without validating that the resolved path stays within the configured views directory. Combined with decodeURIComponent() in route parameter decoding, applications that pass user input to res.render() are exposed to path traversal.

res.sendFile() has path traversal protection via the send library's root containment check. The view system has no equivalent protection.

Reproduction

const express = require('express');
const app = express();
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.get('/page/:name', (req, res) => res.render(req.params.name));
app.listen(3000);
curl http://localhost:3000/page/..%2f..%2f..%2fetc%2fpasswd

Route param decodes to ../../../../etc/passwd. View lookup resolves outside views directory.

Runtime verification on Express 5.2.1:

layer.match('/page/..%2f..%2fetc%2fpasswd')
// => true, params.name = "../../etc/passwd"

Context

This is not a critical vulnerability since it requires the developer to pass unsanitized user input to res.render(). However, given that res.sendFile() already has containment via the send library, adding equivalent protection in View.prototype.lookup() would provide defense-in-depth consistency.

Suggested improvement

// lib/view.js - View.prototype.lookup()
var loc = resolve(root, name);
if (!loc.startsWith(resolve(root) + sep)) continue;

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions