From 46a4f7bc3662ca2996e93150e73168e3ecdaa81d Mon Sep 17 00:00:00 2001 From: Sam Lord Date: Thu, 5 Sep 2024 17:15:09 +0100 Subject: [PATCH] Added middleware to prevent other sites' content from being served (#20922) ref ONC-294 --------- Co-authored-by: Daniel Lockyer --- ghost/core/core/app.js | 22 ++++ .../e2e-frontend/site_id_middleware.test.js | 109 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 ghost/core/test/e2e-frontend/site_id_middleware.test.js diff --git a/ghost/core/core/app.js b/ghost/core/core/app.js index 5a996f6d34..3f8445623e 100644 --- a/ghost/core/core/app.js +++ b/ghost/core/core/app.js @@ -1,6 +1,7 @@ const sentry = require('./shared/sentry'); const express = require('./shared/express'); const config = require('./shared/config'); +const logging = require('@tryghost/logging'); const urlService = require('./server/services/url'); const fs = require('fs'); @@ -27,12 +28,33 @@ const maintenanceMiddleware = function maintenanceMiddleware(req, res, next) { fs.createReadStream(path.resolve(__dirname, './server/views/maintenance.html')).pipe(res); }; +// Used by Ghost (Pro) to ensure that requests cannot be served by the wrong site +const siteIdMiddleware = function siteIdMiddleware(req, res, next) { + const configSiteId = config.get('hostSettings:siteId'); + const headerSiteId = req.headers['x-site-id']; + + if (`${configSiteId}` === `${headerSiteId}`) { + return next(); + } + + logging.warn(`Mismatched site id (expected ${configSiteId}, got ${headerSiteId})`); + + res.set({ + 'Cache-Control': 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0' + }); + res.writeHead(500); + res.end(); +}; + const rootApp = () => { const app = express('root'); app.use(sentry.requestHandler); if (config.get('sentry')?.tracing?.enabled === true) { app.use(sentry.tracingHandler); } + if (config.get('hostSettings:siteId')) { + app.use(siteIdMiddleware); + } app.enable('maintenance'); app.use(maintenanceMiddleware); diff --git a/ghost/core/test/e2e-frontend/site_id_middleware.test.js b/ghost/core/test/e2e-frontend/site_id_middleware.test.js new file mode 100644 index 0000000000..fc4d9e0431 --- /dev/null +++ b/ghost/core/test/e2e-frontend/site_id_middleware.test.js @@ -0,0 +1,109 @@ +const sinon = require('sinon'); +const supertest = require('supertest'); +const testUtils = require('../utils'); +const configUtils = require('../utils/configUtils'); + +describe('Site id middleware execution', function () { + let request; + + describe('Using site-id middleware', function () { + before(async function () { + configUtils.set('hostSettings:siteId', '123123'); + + // Ensure we do a forced start so that spy is in place when the server starts + await testUtils.startGhost({forceStart: true}); + + request = supertest.agent(configUtils.config.get('url')); + }); + + after(async function () { + sinon.restore(); + + configUtils.restore(); + + await testUtils.stopGhost(); + }); + + it('should allow requests with the correct site id header', function () { + return request.get('/') + .set('x-site-id', '123123') + .expect(200); + }); + + it('should allow requests with numeric site id header', function () { + return request.get('/') + .set('x-site-id', 123123) + .expect(200); + }); + + it('should prevent requests with incorrect numeric site id header', function () { + return request.get('/') + .set('x-site-id', 1231230) + .expect(500); + }); + + it('should allow static asset requests with the correct site id header', function () { + return request.get('/content/images/ghost.png') + .set('x-site-id', '123123') + .expect(404); + }); + + it('should reject static asset requests without a site id header', function () { + return request.get('/content/images/ghost.png') + .expect(500); + }); + + it('should reject requests with an incorrect site id header', function () { + return request.get('/') + .set('x-site-id', '456456') + .expect(500); + }); + + it('should reject requests without a site id header', function () { + return request.get('/') + .expect(500); + }); + + it('should reject requests with an empty site id header', function () { + return request.get('/') + .set('x-site-id', '') + .expect(500); + }); + + it('should reject requests with a malformed site id header', function () { + return request.get('/') + .set('x-ghost-site-id', 'not-a-valid-id') + .expect(500); + }); + }); + + describe('Not using site-id middleware', function () { + before(async function () { + configUtils.set('hostSettings:siteId', undefined); + + // Ensure we do a forced start so that spy is in place when the server starts + await testUtils.startGhost({forceStart: true}); + + request = supertest.agent(configUtils.config.get('url')); + }); + + after(async function () { + sinon.restore(); + + configUtils.restore(); + + await testUtils.stopGhost(); + }); + + it('should allow requests without a site id header', function () { + return request.get('/') + .expect(200); + }); + + it('should allow requests with a site id header', function () { + return request.get('/') + .set('x-site-id', '123123') + .expect(200); + }); + }); +});