Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 53 additions & 10 deletions Parse-Dashboard/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,52 @@ module.exports = function(config, options) {
cookieSessionStore: options.cookieSessionStore
});

// Headers set by reverse proxies to describe the original client. Their
// presence means the request was relayed and did not originate on this host.
// The `x-forwarded-` prefix is matched as a whole because proxies vary in
// which of them they set, and any single one is enough to reveal a relay.
const forwardingHeaderPrefix = 'x-forwarded-';
const forwardingHeaders = ['x-real-ip', 'forwarded'];

/**
* Checks whether a request is from localhost.
* Checks whether the connection was opened from this host.
*
* This describes the connection, not the client: a reverse proxy running on
* the same host also connects over loopback, so a true result does not mean
* the client is local. Use it only to decide whether the connection itself
* needs encrypting, never to grant access.
*/
function isLoopbackPeer(req) {
// The whole of 127.0.0.0/8 is loopback, not just 127.0.0.1. The address is
// absent on a socket that has already been destroyed.
const address = req.socket.remoteAddress;
return typeof address === 'string' && (
address.startsWith('127.') ||
address.startsWith('::ffff:127.') ||
address === '::1'
);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* Checks whether a request originated on this host.
*
* Stricter than `isLoopbackPeer`, because a same-host reverse proxy would
* otherwise make every request it relays look local. A request counts as
* local only when nothing indicates it was relayed.
*/
function isLocalRequest(req) {
return req.connection.remoteAddress === '127.0.0.1' ||
req.connection.remoteAddress === '::ffff:127.0.0.1' ||
req.connection.remoteAddress === '::1';
// A configured proxy relays every request, so no request is local.
if (config.trustProxy) {
return false;
}
// Forwarding headers are never sent by a client on this host.
const isForwarded = Object.keys(req.headers).some(header =>
header.startsWith(forwardingHeaderPrefix) || forwardingHeaders.includes(header)
);
if (isForwarded) {
return false;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
return isLoopbackPeer(req);
}

/**
Expand All @@ -102,11 +141,15 @@ module.exports = function(config, options) {
* - Requires users to be configured for remote access (unless dev mode is enabled)
*/
function enforceRemoteAccessRestrictions(req, res, next) {
if (!options.dev && !isLocalRequest(req)) {
if (!req.secure && !options.allowInsecureHTTP) {
if (!options.dev) {
// Keyed on the connection: a loopback connection cannot be observed off
// this host, so it needs no encryption of its own.
if (!isLoopbackPeer(req) && !req.secure && !options.allowInsecureHTTP) {
return res.status(403).json({ error: 'Parse Dashboard can only be remotely accessed via HTTPS' });
}
if (!users) {
// Keyed on the client: granting access requires knowing where the
// request came from, which a relayed request cannot establish.
if (!isLocalRequest(req) && !users) {
return res.status(401).json({ error: 'Configure a user to access Parse Dashboard remotely' });
}
}
Expand Down Expand Up @@ -135,13 +178,13 @@ module.exports = function(config, options) {
agent: config.agent,
};

if (!options.dev && !isLocalRequest(req)) {
if (!req.secure && !options.allowInsecureHTTP) {
if (!options.dev) {
if (!isLoopbackPeer(req) && !req.secure && !options.allowInsecureHTTP) {
//Disallow HTTP requests except on localhost, to prevent the master key from being transmitted in cleartext
return res.send({ success: false, error: 'Parse Dashboard can only be remotely accessed via HTTPS' });
}

if (!users) {
if (!isLocalRequest(req) && !users) {
//Accessing the dashboard over the internet can only be done with username and password
return res.send({ success: false, error: 'Configure a user to access Parse Dashboard remotely' });
}
Expand Down
Loading
Loading