From dc56db76b1c3628a1fdc6a5c31d0d057b2c4136d Mon Sep 17 00:00:00 2001 From: Martin Prikryl Date: Fri, 12 Jun 2026 13:39:56 +0000 Subject: [PATCH] =?UTF-8?q?Issue=202447=20=E2=80=93=20Do=20not=20allow=20W?= =?UTF-8?q?ebDAV=20redirects=20to=20other=20hosts=20by=20default=20+=20Iss?= =?UTF-8?q?ue=202448=20=E2=80=93=20Do=20not=20allow=20WebDAV=20redirects?= =?UTF-8?q?=20to=20an=20unencrypted=20URL=20by=20default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://winscp.net/tracker/2447 https://winscp.net/tracker/2448 Source commit: c777084ca5737c4283f256eb8cdf5c2fec5902af --- source/core/Http.cpp | 2 +- source/core/NeonIntf.cpp | 5 +++++ source/core/NeonIntf.h | 1 + source/core/SessionData.cpp | 22 ++++++++++++++++++ source/core/SessionData.h | 6 +++++ source/core/SessionInfo.cpp | 13 ++++++++++- source/core/WebDAVFileSystem.cpp | 38 +++++++++++++++++++++++++++----- source/forms/SiteAdvanced.cpp | 2 ++ source/forms/SiteAdvanced.dfm | 14 ++++++++++-- source/forms/SiteAdvanced.h | 1 + source/resource/TextsCore.h | 1 + source/resource/TextsCore1.rc | 1 + 12 files changed, 96 insertions(+), 10 deletions(-) diff --git a/source/core/Http.cpp b/source/core/Http.cpp index 05e15a26f..34ecd8fc2 100644 --- a/source/core/Http.cpp +++ b/source/core/Http.cpp @@ -57,7 +57,7 @@ void THttp::SendRequest(const char * Method, const UnicodeString & Request) } } - FHostName = StrFromNeon(Uri.host); + FHostName = Uri.GetHost(); UnicodeString UriPath = StrFromNeon(Uri.path); if (Uri.query != NULL) diff --git a/source/core/NeonIntf.cpp b/source/core/NeonIntf.cpp index 4e70064dc..a0b900122 100644 --- a/source/core/NeonIntf.cpp +++ b/source/core/NeonIntf.cpp @@ -50,6 +50,11 @@ bool TNeonUri::IsTls() const return SameText(StrFromNeon(scheme), HttpsProtocol); } //--------------------------------------------------------------------------- +UnicodeString TNeonUri::GetHost() const +{ + return StrFromNeon(host); +} +//--------------------------------------------------------------------------- struct TProxyAuthData { UnicodeString UserName; diff --git a/source/core/NeonIntf.h b/source/core/NeonIntf.h index dba05ce04..896359d20 100644 --- a/source/core/NeonIntf.h +++ b/source/core/NeonIntf.h @@ -34,6 +34,7 @@ struct TNeonUri : public ne_uri TNeonUri(const UnicodeString & Url); ~TNeonUri(); bool IsTls() const; + UnicodeString GetHost() const; }; //--------------------------------------------------------------------------- ne_session * CreateNeonSession(const TNeonUri & Uri); diff --git a/source/core/SessionData.cpp b/source/core/SessionData.cpp index 3bc14dd1e..6ab9a1b9c 100644 --- a/source/core/SessionData.cpp +++ b/source/core/SessionData.cpp @@ -224,6 +224,8 @@ void __fastcall TSessionData::DefaultSettings() WebDavLiberalEscaping = false; WebDavAuthLegacy = false; + WebDavCrossDomainRedirects = false; + WebDavUnencryptedRedirects = false; DefaultProxy(); ProxyTelnetCommand = L"connect %host %port\\n"; @@ -536,6 +538,8 @@ void __fastcall TSessionData::NonPersistent() \ PROPERTY(WebDavLiberalEscaping); \ PROPERTY(WebDavAuthLegacy); \ + PROPERTY(WebDavCrossDomainRedirects); \ + PROPERTY(WebDavUnencryptedRedirects); \ \ PROPERTY(PuttySettings); \ \ @@ -964,6 +968,12 @@ void TSessionData::DoLoad( WebDavLiberalEscaping = Storage->ReadBool(L"WebDavLiberalEscaping", WebDavLiberalEscaping); WebDavAuthLegacy = Storage->ReadBool(L"WebDavAuthLegacy", WebDavAuthLegacy); + if (!Unsafe) + { + LOADING_UNSAFE; + WebDavCrossDomainRedirects = Storage->ReadBool(L"WebDavCrossDomainRedirects", WebDavCrossDomainRedirects); + WebDavUnencryptedRedirects = Storage->ReadBool(L"WebDavUnencryptedRedirects", WebDavUnencryptedRedirects); + } IsWorkspace = Storage->ReadBool(L"IsWorkspace", IsWorkspace); Link = Storage->ReadString(L"Link", Link); @@ -1275,6 +1285,8 @@ void __fastcall TSessionData::DoSave(THierarchicalStorage * Storage, WRITE_DATA(Bool, WebDavLiberalEscaping); WRITE_DATA(Bool, WebDavAuthLegacy); + WRITE_DATA(Bool, WebDavCrossDomainRedirects); + WRITE_DATA(Bool, WebDavUnencryptedRedirects); WRITE_DATA(Bool, IsWorkspace); WRITE_DATA(String, Link); @@ -4699,6 +4711,16 @@ void __fastcall TSessionData::SetWebDavAuthLegacy(bool value) SET_SESSION_PROPERTY(WebDavAuthLegacy); } //--------------------------------------------------------------------- +void TSessionData::SetWebDavCrossDomainRedirects(bool value) +{ + SET_SESSION_PROPERTY(WebDavCrossDomainRedirects); +} +//--------------------------------------------------------------------- +void TSessionData::SetWebDavUnencryptedRedirects(bool value) +{ + SET_SESSION_PROPERTY(WebDavUnencryptedRedirects); +} +//--------------------------------------------------------------------- UnicodeString __fastcall TSessionData::GetInfoTip() { if (UsesSsh) diff --git a/source/core/SessionData.h b/source/core/SessionData.h index fbb9b46d5..ec4aba1fb 100644 --- a/source/core/SessionData.h +++ b/source/core/SessionData.h @@ -259,6 +259,8 @@ private: RawByteString FEncryptKey; bool FWebDavLiberalEscaping; bool FWebDavAuthLegacy; + bool FWebDavCrossDomainRedirects; + bool FWebDavUnencryptedRedirects; UnicodeString FOrigHostName; int FOrigPortNumber; @@ -457,6 +459,8 @@ private: void __fastcall SetEncryptKey(UnicodeString value); void __fastcall SetWebDavLiberalEscaping(bool value); void __fastcall SetWebDavAuthLegacy(bool value); + void SetWebDavCrossDomainRedirects(bool value); + void SetWebDavUnencryptedRedirects(bool value); TDateTime __fastcall GetTimeoutDT(); void __fastcall SavePasswords(THierarchicalStorage * Storage, bool PuttyExport, bool DoNotEncryptPasswords, bool SaveAll); @@ -750,6 +754,8 @@ public: __property UnicodeString EncryptKey = { read = GetEncryptKey, write = SetEncryptKey }; __property bool WebDavLiberalEscaping = { read = FWebDavLiberalEscaping, write = SetWebDavLiberalEscaping }; __property bool WebDavAuthLegacy = { read = FWebDavAuthLegacy, write = SetWebDavAuthLegacy }; + __property bool WebDavCrossDomainRedirects = { read = FWebDavCrossDomainRedirects, write = SetWebDavCrossDomainRedirects }; + __property bool WebDavUnencryptedRedirects = { read = FWebDavUnencryptedRedirects, write = SetWebDavUnencryptedRedirects }; __property UnicodeString StorageKey = { read = GetStorageKey }; __property UnicodeString SiteKey = { read = GetSiteKey }; diff --git a/source/core/SessionInfo.cpp b/source/core/SessionInfo.cpp index 7b0ba92b2..4c9620a71 100644 --- a/source/core/SessionInfo.cpp +++ b/source/core/SessionInfo.cpp @@ -1396,7 +1396,18 @@ void __fastcall TSessionLog::DoAddStartupInfo(TSessionData * Data) FtpsOn = (Data->Ftps != ftpsNone); ADF(L"HTTPS: %s [Client certificate: %s]", (BooleanToEngStr(FtpsOn), LogSensitive(Data->TlsCertificateFile))); - ADF(L"WebDAV: Tolerate non-encoded: %s", (BooleanToEngStr(Data->WebDavLiberalEscaping))); + if (Data->WebDavLiberalEscaping) + { + ADF(L"WebDAV: Tolerate non-encoded: %s", (BooleanToEngStr(Data->WebDavLiberalEscaping))); + } + if (Data->WebDavCrossDomainRedirects) + { + ADF(L"WebDAV: Cross-domain redirects: %s", (BooleanToEngStr(Data->WebDavCrossDomainRedirects))); + } + if (Data->WebDavUnencryptedRedirects) + { + ADF(L"WebDAV: Unencrypted redirects: %s", (BooleanToEngStr(Data->WebDavUnencryptedRedirects))); + } } if (Data->FSProtocol == fsS3) { diff --git a/source/core/WebDAVFileSystem.cpp b/source/core/WebDAVFileSystem.cpp index f595825e8..1f2954f8e 100644 --- a/source/core/WebDAVFileSystem.cpp +++ b/source/core/WebDAVFileSystem.cpp @@ -271,11 +271,6 @@ void TWebDAVFileSystem::NeonClientOpenSessionInternal(UnicodeString & CorrectedU bool Ssl = IsTlsSession(FSessionContext->NeonSession); FSessionInfo.SecurityProtocolName = Ssl ? LoadStr(FTPS_IMPLICIT) : EmptyStr; - if (Ssl != (FTerminal->SessionData->Ftps != ftpsNone)) - { - FTerminal->LogEvent(FORMAT(L"Warning: %s", (LoadStr(UNENCRYPTED_REDIRECT)))); - } - { CorrectedUrl = EmptyStr; TAutoFlag Flag(FInitialHandshake); @@ -327,7 +322,7 @@ TWebDAVFileSystem::TSessionContext * TWebDAVFileSystem::NeonOpen(const UnicodeSt std::unique_ptr Result(new TSessionContext()); Result->FileSystem = this; - Result->HostName = StrFromNeon(Uri.host); + Result->HostName = Uri.GetHost(); Result->PortNumber = Uri.port; Result->NtlmAuthenticationFailed = false; @@ -395,6 +390,37 @@ UnicodeString __fastcall TWebDAVFileSystem::GetRedirectUrl() { UnicodeString Result = GetNeonRedirectUrl(FSessionContext->NeonSession); FTerminal->LogEvent(FORMAT(L"Redirected to \"%s\".", (Result))); + + TNeonUri RedirectUri(Result); + + if ((FTerminal->SessionData->Ftps != ftpsNone) && !RedirectUri.IsTls()) + { + UnicodeString Message = LoadStr(UNENCRYPTED_REDIRECT); + if (FTerminal->SessionData->WebDavUnencryptedRedirects) + { + FTerminal->LogEvent(FORMAT(L"Warning: %s", (Message))); + } + else + { + throw Exception(Message); + } + } + + int PortNumber = static_cast(RedirectUri.port); + if (!SameText(RedirectUri.GetHost(), FSessionContext->HostName) || + (PortNumber != FSessionContext->PortNumber)) + { + UnicodeString OtherHost = RedirectUri.GetHost(); + if (RedirectUri.port != ne_uri_defaultport(RedirectUri.scheme)) + { + OtherHost = FORMAT(L"%s:%d", (OtherHost, PortNumber)); + } + if (!FTerminal->SessionData->WebDavCrossDomainRedirects) + { + throw Exception(FMTLOAD(WEBDAV_CROSS_DOMAIN_REDIR, (OtherHost))); + } + } + return Result; } //--------------------------------------------------------------------------- diff --git a/source/forms/SiteAdvanced.cpp b/source/forms/SiteAdvanced.cpp index 5f85c8f15..ea26b1f6f 100644 --- a/source/forms/SiteAdvanced.cpp +++ b/source/forms/SiteAdvanced.cpp @@ -423,6 +423,7 @@ void __fastcall TSiteAdvancedDialog::LoadSession() // webdav page WebDavLiberalEscapingCheck->Checked = FSessionData->WebDavLiberalEscaping; + WebDavCrossDomainRedirectsCheck->Checked = FSessionData->WebDavCrossDomainRedirects; // color FColor = static_cast(FSessionData->Color); @@ -716,6 +717,7 @@ void __fastcall TSiteAdvancedDialog::SaveSession(TSessionData * SessionData) // webdav page SessionData->WebDavLiberalEscaping = WebDavLiberalEscapingCheck->Checked; + SessionData->WebDavCrossDomainRedirects = WebDavCrossDomainRedirectsCheck->Checked; // color SessionData->Color = FColor; diff --git a/source/forms/SiteAdvanced.dfm b/source/forms/SiteAdvanced.dfm index 0f8af597f..7887229c2 100644 --- a/source/forms/SiteAdvanced.dfm +++ b/source/forms/SiteAdvanced.dfm @@ -1190,13 +1190,13 @@ object SiteAdvancedDialog: TSiteAdvancedDialog Left = 8 Top = 2 Width = 438 - Height = 49 + Height = 72 Anchors = [akLeft, akTop, akRight] Caption = 'Protocol options' TabOrder = 0 DesignSize = ( 438 - 49) + 72) object WebDavLiberalEscapingCheck: TCheckBox Left = 11 Top = 22 @@ -1207,6 +1207,16 @@ object SiteAdvancedDialog: TSiteAdvancedDialog TabOrder = 0 OnClick = DataChange end + object WebDavCrossDomainRedirectsCheck: TCheckBox + Left = 11 + Top = 45 + Width = 418 + Height = 17 + Anchors = [akLeft, akTop, akRight] + Caption = '&Allow redirects to other hosts' + TabOrder = 1 + OnClick = DataChange + end end end object ConnSheet: TTabSheet diff --git a/source/forms/SiteAdvanced.h b/source/forms/SiteAdvanced.h index 68c8fa972..d21883ffe 100644 --- a/source/forms/SiteAdvanced.h +++ b/source/forms/SiteAdvanced.h @@ -286,6 +286,7 @@ __published: TLabel *S3RoleArnLabel; TEdit *S3RoleArnEdit; TPanel *NavigationPanel; + TCheckBox *WebDavCrossDomainRedirectsCheck; void __fastcall DataChange(TObject *Sender); void __fastcall FormShow(TObject *Sender); void __fastcall PageControlChange(TObject *Sender); diff --git a/source/resource/TextsCore.h b/source/resource/TextsCore.h index bb8d261e7..73b5b2313 100644 --- a/source/resource/TextsCore.h +++ b/source/resource/TextsCore.h @@ -295,6 +295,7 @@ #define OPENSSL_INIT_ERROR 784 #define NO_PRIVATE_KEY 785 #define ANCESTOR_TARGET_ERROR 786 +#define WEBDAV_CROSS_DOMAIN_REDIR 787 #define CORE_CONFIRMATION_STRINGS 300 #define CONFIRM_PROLONG_TIMEOUT3 301 diff --git a/source/resource/TextsCore1.rc b/source/resource/TextsCore1.rc index 2d47b7274..0661020d1 100644 --- a/source/resource/TextsCore1.rc +++ b/source/resource/TextsCore1.rc @@ -271,6 +271,7 @@ BEGIN OPENSSL_INIT_ERROR, "OpenSSL initialization failed" NO_PRIVATE_KEY, "No private key found in %s file." ANCESTOR_TARGET_ERROR, "Target of copy or move cannot be an ancestor directory of source." + WEBDAV_CROSS_DOMAIN_REDIR, "Redirect to other host encountered. If you trust the target host %s, please allow redirects to other hosts in session settings." CORE_CONFIRMATION_STRINGS, "CORE_CONFIRMATION" CONFIRM_PROLONG_TIMEOUT3, "Host is not communicating for %d seconds.\n\nWait for another %0:d seconds?"