Skip to content

Commit

Permalink
Item14380: backported UseForwardedHeaders
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelDaum committed Feb 27, 2023
1 parent 2f74e0f commit 15b795a
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 25 deletions.
4 changes: 2 additions & 2 deletions core/data/System/ReleaseNotes02x01.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%META:TOPICINFO{author="ProjectContributor" date="1675684512" format="1.1" version="1"}%
%META:TOPICINFO{author="ProjectContributor" date="1677500294" format="1.1" version="1"}%
%META:TOPICPARENT{name="ReleaseHistory"}%
---+!! Release Notes 2.1.x

Expand Down Expand Up @@ -157,7 +157,7 @@ version.

---+++ Additional support for Proxy configurations.

Foswiki has a new option under bin/configure -> Security and Authentication -> Proxies: ={PROXY}{UseForwardedForHeader}=. Enable this setting
Foswiki has a new option under bin/configure -> Security and Authentication -> Proxies: ={PROXY}{UseForwardedHeaders}=. Enable this setting
if the Foswiki is accessed through a reverse proxy. Foswiki will the use the =X-Forwarded-For= header to determine the Client IP address. This has several effects:
* Foswiki will log the real Client IP address instead of the address of the reverse proxy server.
* Session IP matching will use the real client IP when determining if the CGI Session is for the correct client.
Expand Down
17 changes: 17 additions & 0 deletions core/lib/Foswiki.spec
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,23 @@ $Foswiki::cfg{AccessibleHeaders} = ['Accept-Language', 'User-Agent'];
# http://username:password@proxy.your.company:8080.
$Foswiki::cfg{PROXY}{HOST} = undef;

# **BOOLEAN LABEL="Forwarded Headers" **
# Use the =Forwarded-*= headers to determine the URL Protocol, Hostname and Port.
# Foswiki normally uses the local server information for identifying the connection information.
# A reverse proxy will hide the URL used by the client.
# <p/>
# Enable this setting to make use of the Proxy headers provided by the Client or intermediate devices:
# * =X-Forwarded-For= _Identifies the client IP, overrides REMOTE_ADDRESS variable._
# * =X-Forwarded-Host= _Captures the hostname used by the client in it's initial request._
# * =X-Forwarded-Proto= _Specifies if the client used an HTTP or HTTPS secure connection._
# * =X-Forwarded-Port= _Specifies the original port used by the client._
# <p/>
# *Caution:* These headers are easily spoofed. Only enable this flag if you are certain that
# a proxy server exists and that you trust the Proxy server. If all users are behind the same
# proxy server, the preferred configuration is to enable {ForceDefaultURLHost} instead of using these
# headers for dynamic resolution.
$Foswiki::cfg{PROXY}{UseForwardedHeaders} = $FALSE;

#---++ Anti-spam
# Foswiki incorporates some simple anti-spam measures to protect
# e-mail addresses and control the activities of benign robots, which
Expand Down
32 changes: 25 additions & 7 deletions core/lib/Foswiki/Configure/Checkers/ForceDefaultUrlHost.pm
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,34 @@ use strict;
use warnings;

use Foswiki::Configure::Checkers::URL ();
our @ISA = ('Foswiki::Configure::Checkers::URL');
our @ISA = ('Foswiki::Configure::Checker');

sub check_current_value {
my ( $this, $reporter ) = @_;

if ( $ENV{HTTP_X_FORWARDED_HOST} ) {
unless ( $Foswiki::cfg{ForceDefaultUrlHost} ) {
$reporter->WARN(
'It appears you may be running from behind a proxy. =ForceDefaultUrlHost= should be enabled.'
);
if ( defined $Foswiki::cfg{Engine}
&& substr( $Foswiki::cfg{Engine}, -3 ) eq 'CLI' )
{

# Probe the connection in bootstrap mode:
my ( $client, $protocol, $host, $port, $proxy ) =
Foswiki::Engine::_getConnectionData(1);

if ($proxy) {
if ( !$Foswiki::cfg{PROXY}{UseForwardedHeaders}
&& !$Foswiki::cfg{ForceDefaultUrlHost} )
{
$reporter->WARN(
'It appears you may be running from behind a proxy. =ForceDefaultUrlHost= is the recommended setting.'
);
}
elsif ($Foswiki::cfg{PROXY}{UseForwardedHeaders}
&& $Foswiki::cfg{ForceDefaultUrlHost} )
{
$reporter->WARN(
'Both ={PROXY}{UseForwardedHeaders}= and ={ForceDefaultUrlHost}= are enabled. ={ForceDefaultUrlHost}= will override any Forwarded headers'
);
}
}
}
}
Expand All @@ -23,7 +41,7 @@ sub check_current_value {
__END__
Foswiki - The Free and Open Source Wiki, http://foswiki.org/
Copyright (C) 2016 Foswiki Contributors. Foswiki Contributors
Copyright (C) 2016-2018 Foswiki Contributors. Foswiki Contributors
are listed in the AUTHORS file in the root of this distribution.
NOTE: Please extend that file, not this notice.
Expand Down
93 changes: 93 additions & 0 deletions core/lib/Foswiki/Configure/Checkers/PROXY/UseForwardedHeaders.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# See bottom of file for license and copyright information
package Foswiki::Configure::Checkers::PROXY::UseForwardedHeaders;

use strict;
use warnings;

use Foswiki::Configure::Checker ();
our @ISA = ('Foswiki::Configure::Checker');

sub check_current_value {
my ( $this, $reporter ) = @_;

if ( defined $Foswiki::cfg{Engine}
&& substr( $Foswiki::cfg{Engine}, -3 ) eq 'CLI' )
{
my ( $client, $protocol, $host, $port, $proxy ) =
Foswiki::Engine::_getConnectionData(1);

if ($proxy) {

if ( $Foswiki::cfg{PROXY}{UseForwardedHeaders} ) {
$reporter->WARN(
"Be sure you trust the proxy server. Clients can use this header to spoof their IP addresses."
);
}
else {
$reporter->WARN(
"Proxy detected, Enable this switch if Foswiki should use the =X-Forwarded-For= header to obtain the real client IP address."
);
}
$reporter->NOTE(
"Remote Address is $ENV{REMOTE_ADDR}, Real client IP is =$client=."
);

$reporter->NOTE(
"Proxy server detected. Proxy URL is $protocol://$host:$port. Local server name is $ENV{HTTP_HOST}"
);

if ( $Foswiki::cfg{PROXY}{UseForwardedHeaders} ) {
$reporter->WARN(
"Note that =ForceDefaultUrlHost= is a more secure setting for supporting a reverse proxy. "
);
}
else {
$reporter->NOTE(
'This setting should be enabled if there are multiple proxy servers or there is a mix of proxied and non-proxied clients.'
);
}
}
else {
if ( $Foswiki::cfg{PROXY}{UseForwardedHeaders} ) {
$reporter->WARN(
'You have enabled this setting, but no proxy is detected. Be sure this is what you want to do.'
);
}
}

if ( $Foswiki::cfg{ForceDefaultUrlHost}
&& $Foswiki::cfg{PROXY}{UseForwardedHeaders} )
{
$reporter->ERROR(
'Both ={ForceDefaultUrlHost}= and ={PROXY}{UseForwardedHeaders}= are enabled. ={PROXY}{UseForwardedHeaders}= will be ignored.'
);
}
}
}

1;
__END__
Foswiki - The Free and Open Source Wiki, http://foswiki.org/
Copyright (C) 2008-2023 Foswiki Contributors. Foswiki Contributors
are listed in the AUTHORS file in the root of this distribution.
NOTE: Please extend that file, not this notice.
Additional copyrights apply to some or all of the code in this
file as follows:
Copyright (C) 2000-2006 TWiki Contributors. All Rights Reserved.
TWiki Contributors are listed in the AUTHORS file in the root
of this distribution. NOTE: Please extend that file, not this notice.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version. For
more details read LICENSE in the root of this distribution.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
As per the GPL, removal of this notice is prohibited.
130 changes: 130 additions & 0 deletions core/lib/Foswiki/Engine.pm
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,136 @@ sub write {
ASSERT('Pure virtual method - should never be called');
}

=begin TML
---++ _getConnectionData($detectProxy) = ( $client, $proto, $host, $port, $proxy )
Utility routine obtains the connection information from the headers.
If $Foswiki::cfg{PROXY}{UseForwardedHeaders} is true, or if the $detectProxy flag is set
this routine will return the connection data from the X-Forwarded-* or Forwarded: headers.
Headers:
* HTTP_X_FORWARDED_HOST
* HTTP_X_FORWARDED_PORT
* HTTP_X_FORWARDED_FOR
* HTTP_X_FORWARDED_PROTO
=cut

sub _getConnectionData {

my $detectProxy = shift;

# $detectProxy = 'b'; #Force debug tracing (ie bootstrap mode)

my ( $client, $proto, $host, $port, $proxy );

# These are the defaults populated in a conventional server, no proxy
$client = $ENV{REMOTE_ADDR} || '';
$proto =
( $ENV{HTTPS} && ( uc( $ENV{HTTPS} ) eq 'ON' || $ENV{HTTPS} eq '1' ) )
? 'https'
: 'http';

# HTTP_HOST is the host targeted by the client, SERVER_NAME is the configured name on the server.
$host = $ENV{HTTP_HOST} || $ENV{SERVER_NAME};
if ($host) {
( $host, $port ) = split( /:/, $host );
}
unless ($host) {
if ( defined $ENV{SCRIPT_URI}
&& $ENV{SCRIPT_URI} =~ m#^(https?)://([^/]+)#i )
{
$proto = $1;
$host = $2;
}
elsif ( defined $Foswiki::cfg{DefaultUrlHost} ) {
($host) = $Foswiki::cfg{DefaultUrlHost} =~ m#https?://([^:/]+)#i;
}
}

#SMELL: Give up - no obvious hostname in the request and no default.
unless ($host) {
$host = 'localhost';
}

# $port may have been detected from the HTTP_HOST variable
$port = $port || $ENV{SERVER_PORT} || 80;
$proxy = '';

if ( $detectProxy || $Foswiki::cfg{PROXY}{UseForwardedHeaders} ) {
my ( $fwdClient, $fwdProto, $fwdHost, $fwdPort, $hostport );

if ( my $hdr = $ENV{HTTP_X_FORWARDED_FOR} ) {
my $ip = ( split /\s?,\s?/, $hdr )[0];
if ( defined $ip ) {
$fwdClient = $ip;
print STDERR
"AUTOCONFIG: Client IP detected from Proxy header ($hdr): $ip\n"
if ( $detectProxy && $detectProxy eq 'b' );
$proxy = 1;
}
}

$client = $fwdClient if $fwdClient;
if ( my $hdr = $ENV{HTTP_X_FORWARDED_HOST} ) {
my $first = ( split /\s?,\s?/, $hdr )[0];
if ( defined $first ) {
if ( $first =~ s/:(\d+)$// ) {
$hostport = $1 if ($1);
}
$fwdHost = $first;
print STDERR
"AUTOCONFIG: Hostname detected from Proxy header ($hdr): $fwdHost\n"
if ( $detectProxy && $detectProxy eq 'b' );
$proxy = 1;
}
}

if ( my $hdr = $ENV{HTTP_X_FORWARDED_PROTO} ) {
my $first = ( split /\s?,\s?/, $hdr )[0];
if ( defined $first ) {
$fwdProto = $first;
print STDERR
"AUTOCONFIG: Protocol detected from Proxy header ($hdr): $fwdProto\n"
if ( $detectProxy && $detectProxy eq 'b' );
$proxy = 1;
}
}

if ( my $hdr = $ENV{HTTP_X_FORWARDED_PORT} ) {
my $first = ( split /\s?,\s?/, $hdr )[0];
if ( defined $first ) {
$fwdPort = $first;
print STDERR
"AUTOCONFIG: Port detected from Proxy header ($hdr): $fwdPort\n"
if ( $detectProxy && $detectProxy eq 'b' );
$proxy = 1;
}
}
elsif ($hostport) {
$fwdPort = $hostport;
print STDERR
"AUTOCONFIG: Port recovered from Proxy FOWARDED_HOST header: $fwdPort\n"
if ( $detectProxy && $detectProxy eq 'b' );
}

$host = $fwdHost if $fwdHost;
$port = $fwdPort if $fwdPort;

if ( !defined $fwdProto && defined $fwdPort && $fwdPort == 443 ) {
$fwdProto = 'https';
print STDERR
"AUTOCONFIG: proto overridden to https due to port 443 detected\n"
if ( $detectProxy && $detectProxy eq 'b' );
}
$proto = $fwdProto if $fwdProto;
}

return ( $client, $proto, $host, $port, $proxy );
}

1;
__END__
Foswiki - The Free and Open Source Wiki, http://foswiki.org/
Expand Down
16 changes: 8 additions & 8 deletions core/lib/Foswiki/Engine/CGI.pm
Original file line number Diff line number Diff line change
Expand Up @@ -105,17 +105,17 @@ sub run {
sub prepareConnection {
my ( $this, $req ) = @_;

$req->remoteAddress( $ENV{REMOTE_ADDR} );
$req->method( $ENV{REQUEST_METHOD} );
my ( $client, $protocol, $host, $port ) =
Foswiki::Engine::_getConnectionData();

if ( $ENV{HTTPS} && uc( $ENV{HTTPS} ) eq 'ON' ) {
$req->secure(1);
}

if ( $ENV{SERVER_PORT} && $ENV{SERVER_PORT} == 443 ) {
$req->remoteAddress($client);
$req->serverPort($port);
$req->header( -name => 'Host', -value => $host );
$req->method( $ENV{REQUEST_METHOD} );
if ( $protocol eq 'https' ) {
$req->secure(1);
}
$req->serverPort( $ENV{SERVER_PORT} );
$req->serverPort($port);
}

sub prepareQueryParameters {
Expand Down
12 changes: 4 additions & 8 deletions core/lib/Foswiki/Request.pm
Original file line number Diff line number Diff line change
Expand Up @@ -298,16 +298,12 @@ sub url {
$url = $Foswiki::cfg{DefaultUrlHost};
}
else {
my $host;
if ( $this->header('X-Forwarded-Host') ) {
$host = ( split /[, ]+/, $this->header('X-Forwarded-Host') )[0];
}
else {
$host = $this->header('Host');
}
my ( $client, $protocol, $host, $port ) =
Foswiki::Engine::_getConnectionData();
$port = ( $port && $port != 80 && $port != 443 ) ? ":$port" : '';
$url =
$host
? $this->protocol . '://' . $host
? $protocol . '://' . $host . $port
: $Foswiki::cfg{DefaultUrlHost};
}
return $url if $base;
Expand Down

0 comments on commit 15b795a

Please sign in to comment.