From 26413e3e06a8454161e6ccfd71705ce5283e61c3 Mon Sep 17 00:00:00 2001 From: Robert Szefner Date: Mon, 18 May 2020 00:07:59 +0200 Subject: [PATCH 1/2] fix parsing X-Forwarded-For header. --- defaults/application.ini | 14 ++++++-- src/tappsettings.cpp | 1 + src/tfnamespace.h | 1 + src/thttprequest.cpp | 72 ++++++++++++++++++++++++---------------- 4 files changed, 57 insertions(+), 31 deletions(-) diff --git a/defaults/application.ini b/defaults/application.ini index f4ad98e75..e75aedd11 100644 --- a/defaults/application.ini +++ b/defaults/application.ini @@ -83,8 +83,18 @@ EnableHttpMethodOverride=false # the client, if true. EnableForwardedForHeader=false -# Specify IP addresses of the proxy servers to work the feature of -# X-Forwarded-For header. +# If false is specified, only the right-most IP address in X-Forwarded-For +# request header is parsed, otherwise all IP addresses in header are parsed +# from right side to left stopping at the last non-trusted IP address. +ParseForwardedForHeaderRecursively=false + +# Specify trusted proxy servers to work the feature of X-Forwarded-For header. +# Set to a quoted semicolon-delimited list of IP addresses or subnets. +# If the special value 'unix:' is specified, an UNIX-domain socket proxy will +# be trusted. +# Example 1: "unix:" +# Example 2: "192.168.1.10; 192.168.1.20" +# Example 3: "10.0.1.0/24; 10.0.2.0/255.255.255.0" TrustedProxyServers= # Sets the timeout in seconds during which a keep-alive HTTP connection diff --git a/src/tappsettings.cpp b/src/tappsettings.cpp index 148f9f191..ebf6c1b19 100644 --- a/src/tappsettings.cpp +++ b/src/tappsettings.cpp @@ -41,6 +41,7 @@ class AttributeMap : public QMap { insert(Tf::EnableCsrfProtectionModule, "EnableCsrfProtectionModule"); insert(Tf::EnableHttpMethodOverride, "EnableHttpMethodOverride"); insert(Tf::EnableForwardedForHeader, "EnableForwardedForHeader"); + insert(Tf::ParseForwardedForHeaderRecursively, "ParseForwardedForHeaderRecursively"); insert(Tf::TrustedProxyServers, "TrustedProxyServers"); insert(Tf::HttpKeepAliveTimeout, "HttpKeepAliveTimeout"); insert(Tf::LDPreload, "LDPreload"); diff --git a/src/tfnamespace.h b/src/tfnamespace.h index c464911c6..8f7702143 100644 --- a/src/tfnamespace.h +++ b/src/tfnamespace.h @@ -203,6 +203,7 @@ enum AppAttribute { SessionCookieSameSite, // EnableForwardedForHeader, + ParseForwardedForHeaderRecursively, TrustedProxyServers, }; diff --git a/src/thttprequest.cpp b/src/thttprequest.cpp index 262e1f092..3b3b3e79c 100644 --- a/src/thttprequest.cpp +++ b/src/thttprequest.cpp @@ -602,6 +602,18 @@ QList THttpRequest::generate(const QByteArray &byteArray, const QH return reqList; } +typedef QPair NetworkSubnet; + +static bool isInAnySubnet(const QHostAddress &ip, const QList &subnets) +{ + for (const auto &subnet : subnets) { + if (ip.isInSubnet(subnet.first, subnet.second)) { + return true; + } + } + return false; +} + /*! Returns a originating IP address of the client by parsing the 'X-Forwarded-For' header of the request. To enable this feature, edit application.ini and @@ -611,44 +623,46 @@ QList THttpRequest::generate(const QByteArray &byteArray, const QH QHostAddress THttpRequest::originatingClientAddress() const { static const bool EnableForwardedForHeader = Tf::appSettings()->value(Tf::EnableForwardedForHeader, false).toBool(); - static const QStringList TrustedProxyServers = []() { // delimiter: comma or space - QStringList servers; - for (auto &s : Tf::appSettings()->value(Tf::TrustedProxyServers).toStringList()) { - servers << s.simplified().split(QLatin1Char(' ')); - } - - QHostAddress ip; - for (QMutableListIterator it(servers); it.hasNext();) { - auto &s = it.next(); - if (!ip.setAddress(s)) { // check IP address - it.remove(); + static const bool ParseForwardedForHeaderRecursively = Tf::appSettings()->value(Tf::ParseForwardedForHeaderRecursively, false).toBool(); + static const bool ListenOnUnixDomainSocket = Tf::appSettings()->value(Tf::ListenPort).toString().trimmed().startsWith(QStringLiteral("unix:")); + static const bool TrustUnixDomainSocketProxy = Tf::appSettings()->value(Tf::TrustedProxyServers).toString().split(QRegularExpression(QStringLiteral("\\s*;\\s*"))).contains(QStringLiteral("unix:")); + static const QList TrustProxyServersInSubnets = []() { + QList subnets; + for (const auto &s : Tf::appSettings()->value(Tf::TrustedProxyServers).toString().split(QRegularExpression(QStringLiteral("\\s*;\\s*")), QString::SkipEmptyParts)) { + if (s != QStringLiteral("unix:")) { + NetworkSubnet subnet = QHostAddress::parseSubnet(s); + if (subnet.first.protocol() != QAbstractSocket::UnknownNetworkLayerProtocol) { + subnets.append(subnet); + } else { + tSystemWarn("Invalid IP address or subnet '%s' in TrustedProxyServers parameter", qPrintable(s)); + } } } - return servers; + return subnets; }(); - QString remoteHost; - if (EnableForwardedForHeader) { - if (TrustedProxyServers.isEmpty()) { - T_ONCE(tWarn("TrustedProxyServers parameter of config is empty!")); - } + QHostAddress remoteAddress = clientAddress(); - auto hosts = QString::fromLatin1(header().rawHeader(QByteArrayLiteral("X-Forwarded-For"))).simplified().split(QRegularExpression("\\s?,\\s?"), QString::SkipEmptyParts); - if (hosts.isEmpty()) { - tWarn("'X-Forwarded-For' header is empty"); - } else { - for (auto &proxy : TrustedProxyServers) { - hosts.removeAll(proxy); - } - - if (!hosts.isEmpty()) { - remoteHost = hosts.last(); + if (EnableForwardedForHeader) { + const QByteArray forwardedForHeader = header().rawHeader(QByteArrayLiteral("X-Forwarded-For")); + if (!forwardedForHeader.isEmpty()) { + QStringList hosts = QString::fromLatin1(forwardedForHeader).trimmed().split(QRegularExpression(QStringLiteral("\\s*,\\s*"))); + + bool trustProxy = (ListenOnUnixDomainSocket && TrustUnixDomainSocketProxy) || isInAnySubnet(remoteAddress, TrustProxyServersInSubnets); + while (trustProxy && !hosts.isEmpty()) { + QHostAddress host; + if (!host.setAddress(hosts.takeLast())) + break; + remoteAddress = host; + if (!ParseForwardedForHeaderRecursively) + break; + trustProxy = isInAnySubnet(remoteAddress, TrustProxyServersInSubnets); } } } - return (remoteHost.isEmpty()) ? clientAddress() : QHostAddress(remoteHost); -} + return remoteAddress; +} QIODevice *THttpRequest::rawBody() { From d37c14fc055ed0a87172d0e36af14b55973ddc48 Mon Sep 17 00:00:00 2001 From: Robert Szefner Date: Sun, 19 Jul 2020 20:28:21 +0200 Subject: [PATCH 2/2] always trust UNIX domain socket proxy, separate IP addresses by comma. --- defaults/application.ini | 8 ++------ src/thttprequest.cpp | 20 +++++++++----------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/defaults/application.ini b/defaults/application.ini index e75aedd11..712f0995b 100644 --- a/defaults/application.ini +++ b/defaults/application.ini @@ -89,12 +89,8 @@ EnableForwardedForHeader=false ParseForwardedForHeaderRecursively=false # Specify trusted proxy servers to work the feature of X-Forwarded-For header. -# Set to a quoted semicolon-delimited list of IP addresses or subnets. -# If the special value 'unix:' is specified, an UNIX-domain socket proxy will -# be trusted. -# Example 1: "unix:" -# Example 2: "192.168.1.10; 192.168.1.20" -# Example 3: "10.0.1.0/24; 10.0.2.0/255.255.255.0" +# Set to a quoted comma-delimited list of IP addresses or subnets, +# for example: "192.168.1.10, 10.0.1.0/24, 10.0.2.0/255.255.255.0" TrustedProxyServers= # Sets the timeout in seconds during which a keep-alive HTTP connection diff --git a/src/thttprequest.cpp b/src/thttprequest.cpp index 3b3b3e79c..f5f251518 100644 --- a/src/thttprequest.cpp +++ b/src/thttprequest.cpp @@ -624,18 +624,16 @@ QHostAddress THttpRequest::originatingClientAddress() const { static const bool EnableForwardedForHeader = Tf::appSettings()->value(Tf::EnableForwardedForHeader, false).toBool(); static const bool ParseForwardedForHeaderRecursively = Tf::appSettings()->value(Tf::ParseForwardedForHeaderRecursively, false).toBool(); - static const bool ListenOnUnixDomainSocket = Tf::appSettings()->value(Tf::ListenPort).toString().trimmed().startsWith(QStringLiteral("unix:")); - static const bool TrustUnixDomainSocketProxy = Tf::appSettings()->value(Tf::TrustedProxyServers).toString().split(QRegularExpression(QStringLiteral("\\s*;\\s*"))).contains(QStringLiteral("unix:")); + static const bool ListeningOnUnixDomainSocket = Tf::appSettings()->value(Tf::ListenPort).toString().trimmed().startsWith(QStringLiteral("unix:")); + static const bool TrustUnixDomainSocketProxy = true; static const QList TrustProxyServersInSubnets = []() { QList subnets; - for (const auto &s : Tf::appSettings()->value(Tf::TrustedProxyServers).toString().split(QRegularExpression(QStringLiteral("\\s*;\\s*")), QString::SkipEmptyParts)) { - if (s != QStringLiteral("unix:")) { - NetworkSubnet subnet = QHostAddress::parseSubnet(s); - if (subnet.first.protocol() != QAbstractSocket::UnknownNetworkLayerProtocol) { - subnets.append(subnet); - } else { - tSystemWarn("Invalid IP address or subnet '%s' in TrustedProxyServers parameter", qPrintable(s)); - } + for (const QString &s : Tf::appSettings()->value(Tf::TrustedProxyServers).toString().split(QRegularExpression(QStringLiteral("\\s*,\\s*")), QString::SkipEmptyParts)) { + NetworkSubnet subnet = QHostAddress::parseSubnet(s); + if (subnet.first.protocol() != QAbstractSocket::UnknownNetworkLayerProtocol) { + subnets.append(subnet); + } else { + tSystemWarn("Invalid IP address or subnet '%s' in TrustedProxyServers parameter", qPrintable(s)); } } return subnets; @@ -648,7 +646,7 @@ QHostAddress THttpRequest::originatingClientAddress() const if (!forwardedForHeader.isEmpty()) { QStringList hosts = QString::fromLatin1(forwardedForHeader).trimmed().split(QRegularExpression(QStringLiteral("\\s*,\\s*"))); - bool trustProxy = (ListenOnUnixDomainSocket && TrustUnixDomainSocketProxy) || isInAnySubnet(remoteAddress, TrustProxyServersInSubnets); + bool trustProxy = (ListeningOnUnixDomainSocket && TrustUnixDomainSocketProxy) || isInAnySubnet(remoteAddress, TrustProxyServersInSubnets); while (trustProxy && !hosts.isEmpty()) { QHostAddress host; if (!host.setAddress(hosts.takeLast()))