diff --git a/config/application.rb b/config/application.rb index 90218a83eb1..8c76e55609f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -186,6 +186,8 @@ module Discourse # supports etags (post 1.7) config.middleware.delete Rack::ETag + require 'middleware/enforce_hostname' + config.middleware.insert_after Rack::MethodOverride, Middleware::EnforceHostname require 'middleware/discourse_public_exceptions' config.exceptions_app = Middleware::DiscoursePublicExceptions.new(Rails.public_path) diff --git a/lib/middleware/enforce_hostname.rb b/lib/middleware/enforce_hostname.rb new file mode 100644 index 00000000000..130cabb87a9 --- /dev/null +++ b/lib/middleware/enforce_hostname.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Middleware + class EnforceHostname + def initialize(app, settings = nil) + @app = app + end + + def call(env) + # enforces hostname to match the hostname of our connection + # this middleware lives after rails multisite so at this point + # Discourse.current_hostname MUST be canonical, enforce it so + # all Rails helpers are guarenteed to use it unconditionally and + # never generate incorrect links + env[Rack::Request::HTTP_X_FORWARDED_HOST] = nil + env[Rack::HTTP_HOST] = Discourse.current_hostname + @app.call(env) + end + end +end diff --git a/spec/requests/application_controller_spec.rb b/spec/requests/application_controller_spec.rb index fb64f91b0b5..12a75006bc9 100644 --- a/spec/requests/application_controller_spec.rb +++ b/spec/requests/application_controller_spec.rb @@ -169,4 +169,18 @@ RSpec.describe ApplicationController do expect(controller.theme_ids).to eq([theme.id]) end end + + describe 'Custom hostname' do + + it 'does not allow arbitrary host injection' do + get("/latest", + headers: { + "X-Forwarded-Host" => "test123.com" + } + ) + + expect(response.body).not_to include("test123") + end + end + end