1
0
mirror of https://github.com/winscp/winscp.git synced 2026-10-24 02:28:55 +03:00

Issue 2447 – Do not allow WebDAV redirects to other hosts by default + Issue 2448 – Do not allow WebDAV redirects to an unencrypted URL by default

https://winscp.net/tracker/2447
https://winscp.net/tracker/2448

Source commit: c777084ca5737c4283f256eb8cdf5c2fec5902af
This commit is contained in:
Martin Prikryl
2026-06-12 13:39:56 +00:00
parent 879635216f
commit dc56db76b1
12 changed files with 96 additions and 10 deletions

View File

@@ -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)

View File

@@ -50,6 +50,11 @@ bool TNeonUri::IsTls() const
return SameText(StrFromNeon(scheme), HttpsProtocol);
}
//---------------------------------------------------------------------------
UnicodeString TNeonUri::GetHost() const
{
return StrFromNeon(host);
}
//---------------------------------------------------------------------------
struct TProxyAuthData
{
UnicodeString UserName;

View File

@@ -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);

View File

@@ -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)

View File

@@ -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 };

View File

@@ -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)
{

View File

@@ -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<TSessionContext> 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<int>(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;
}
//---------------------------------------------------------------------------

View File

@@ -423,6 +423,7 @@ void __fastcall TSiteAdvancedDialog::LoadSession()
// webdav page
WebDavLiberalEscapingCheck->Checked = FSessionData->WebDavLiberalEscaping;
WebDavCrossDomainRedirectsCheck->Checked = FSessionData->WebDavCrossDomainRedirects;
// color
FColor = static_cast<TColor>(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;

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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?"