CVE-2021-3331 (C++, published 2021-01-27T21:15Z)
Description: value: WinSCP before 5.17.10 allows remote attackers to execute arbitrary programs when the URL handler encounters a crafted URL that loads session settings. (For example, this is exploitable in a default installation in which WinSCP is the handler for sftp:// URLs.)
Severity: HIGH (CVSS v3 base score: 9.8)
Weakness: NVD-CWE-Other \u2014 Other
NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
Repository: https://github.com/winscp/winscp
Files changed:
source/Console.cbproj
source/DragExt.cbproj
source/WinSCP.cbproj
source/core/SessionData.cpp
source/core/SessionData.h
source/forms/Custom.cpp
source/windows/ConsoleRunner.cpp
source/windows/WinMain.cpp
Commit message: Bug 1943: Prevent loading session settings that can lead to remote code execution from handled URLs

https://winscp.net/tracker/1943
(cherry picked from commit ec584f5189a856cd79509f754722a6898045c5e0)

Source commit: 0f4be408b3f01132b00682da72d925d6c4ee649b


Vulnerable code:
		<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=Console interface for WinSCP;FileVersion=5.0.1.0;InternalName=console;LegalCopyright=(c) 2000-2020 Martin Prikryl;LegalTrademarks=;OriginalFilename=winscp.com;ProductName=WinSCP;ProductVersion=5.17.10.0;ReleaseType=stable;WWW=https://winscp.net/</VerInfo_Keys>
		<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=Drag&amp;Drop shell extension for WinSCP ($(Platform));FileVersion=2.0.0.0;InternalName=dragext;LegalCopyright=(c) 2000-2020 Martin Prikryl;LegalTrademarks=;OriginalFilename=dragext.dll;ProductName=WinSCP;ProductVersion=5.17.10.0;ReleaseType=stable;WWW=https://winscp.net/</VerInfo_Keys>
		<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=WinSCP: SFTP, FTP, WebDAV, S3 and SCP client;FileVersion=5.17.10.0;InternalName=winscp;LegalCopyright=(c) 2000-2020 Martin Prikryl;LegalTrademarks=;OriginalFilename=winscp.exe;ProductName=WinSCP;ProductVersion=5.17.10.0;ReleaseType=stable;WWW=https://winscp.net/</VerInfo_Keys>
void __fastcall TSessionData::DoLoad(THierarchicalStorage * Storage, bool PuttyImport, bool & RewritePassword)
  GssLibList = Storage->ReadString(L"GSSLibs", GssLibList);
  Shell = Storage->ReadString(L"Shell", Shell);
  ListingCommand = Storage->ReadString(L"ListingCommand",
    Storage->ReadBool(L"AliasGroupList", false) ? UnicodeString(L"ls -gla") : ListingCommand);
  DeleteToRecycleBin = Storage->ReadBool(L"DeleteToRecycleBin", DeleteToRecycleBin);
  OverwrittenToRecycleBin = Storage->ReadBool(L"OverwrittenToRecycleBin", OverwrittenToRecycleBin);
  RecycleBinPath = Storage->ReadString(L"RecycleBinPath", RecycleBinPath);
  PostLoginCommands = Storage->ReadString(L"PostLoginCommands", PostLoginCommands);
  ReturnVar = Storage->ReadString(L"ReturnVar", ReturnVar);
  if (ProxyMethod == pmCmd)
    ProxyLocalCommand = Storage->ReadStringRaw(L"ProxyTelnetCommand", ProxyLocalCommand);
  }
  else
  {
    ProxyTelnetCommand = Storage->ReadStringRaw(L"ProxyTelnetCommand", ProxyTelnetCommand);
  SftpServer = Storage->ReadString(L"SftpServer", SftpServer);
    DoLoad(Storage, PuttyImport, RewritePassword);
        ApplyRawSettings(RawSettings.get());
        ApplyRawSettings(RawSettings.get());
void __fastcall TSessionData::ApplyRawSettings(TStrings * RawSettings)
  ApplyRawSettings(OptionsStorage.get());
void __fastcall TSessionData::ApplyRawSettings(THierarchicalStorage * Storage)
  DoLoad(Storage, false, Dummy);
  void __fastcall DoLoad(THierarchicalStorage * Storage, bool PuttyImport, bool & RewritePassword);
  void __fastcall ApplyRawSettings(TStrings * RawSettings);
  void __fastcall ApplyRawSettings(THierarchicalStorage * Storage);
    Data->ApplyRawSettings(SettingsMemo->Lines);
            Data->ApplyRawSettings(OptionsStorage.get());
          GetLoginData(AutoStartSession, Params, DataList.get(), DownloadFile, NeedSession, NULL, pufAllowStoredSiteWithProtocol);

Fixed code:
		<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=Console interface for WinSCP;FileVersion=5.0.1.0;InternalName=console;LegalCopyright=(c) 2000-2021 Martin Prikryl;LegalTrademarks=;OriginalFilename=winscp.com;ProductName=WinSCP;ProductVersion=5.17.10.0;ReleaseType=stable;WWW=https://winscp.net/</VerInfo_Keys>
		<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=Drag&amp;Drop shell extension for WinSCP ($(Platform));FileVersion=2.0.0.0;InternalName=dragext;LegalCopyright=(c) 2000-2021 Martin Prikryl;LegalTrademarks=;OriginalFilename=dragext.dll;ProductName=WinSCP;ProductVersion=5.17.10.0;ReleaseType=stable;WWW=https://winscp.net/</VerInfo_Keys>
		<VerInfo_Keys>CompanyName=Martin Prikryl;FileDescription=WinSCP: SFTP, FTP, WebDAV, S3 and SCP client;FileVersion=5.17.10.0;InternalName=winscp;LegalCopyright=(c) 2000-2021 Martin Prikryl;LegalTrademarks=;OriginalFilename=winscp.exe;ProductName=WinSCP;ProductVersion=5.17.10.0;ReleaseType=stable;WWW=https://winscp.net/</VerInfo_Keys>
void __fastcall TSessionData::DoLoad(THierarchicalStorage * Storage, bool PuttyImport, bool & RewritePassword, bool Unsafe)
  if (!Unsafe)
  {
    GssLibList = Storage->ReadString(L"GSSLibs", GssLibList);
  }
  if (!Unsafe)
  {
    Shell = Storage->ReadString(L"Shell", Shell);
  }
  if (!Unsafe)
  {
    ListingCommand = Storage->ReadString(L"ListingCommand",
      Storage->ReadBool(L"AliasGroupList", false) ? UnicodeString(L"ls -gla") : ListingCommand);
  }
  if (!Unsafe)
  {
    DeleteToRecycleBin = Storage->ReadBool(L"DeleteToRecycleBin", DeleteToRecycleBin);
    OverwrittenToRecycleBin = Storage->ReadBool(L"OverwrittenToRecycleBin", OverwrittenToRecycleBin);
    RecycleBinPath = Storage->ReadString(L"RecycleBinPath", RecycleBinPath);
    PostLoginCommands = Storage->ReadString(L"PostLoginCommands", PostLoginCommands);
    ReturnVar = Storage->ReadString(L"ReturnVar", ReturnVar);
  }
  if (!Unsafe)
    if (ProxyMethod == pmCmd)
    {
      ProxyLocalCommand = Storage->ReadStringRaw(L"ProxyTelnetCommand", ProxyLocalCommand);
    }
    else
    {
      ProxyTelnetCommand = Storage->ReadStringRaw(L"ProxyTelnetCommand", ProxyTelnetCommand);
    }
  if (!Unsafe)
  {
    SftpServer = Storage->ReadString(L"SftpServer", SftpServer);
  }
    DoLoad(Storage, PuttyImport, RewritePassword, false);
  bool Unsafe = FLAGSET(Flags, pufUnsafe);
        ApplyRawSettings(RawSettings.get(), FLAGSET(Flags, pufUnsafe));
        ApplyRawSettings(RawSettings.get(), Unsafe);
void __fastcall TSessionData::ApplyRawSettings(TStrings * RawSettings, bool Unsafe)
  ApplyRawSettings(OptionsStorage.get(), Unsafe);
void __fastcall TSessionData::ApplyRawSettings(THierarchicalStorage * Storage, bool Unsafe)
  DoLoad(Storage, false, Dummy, Unsafe);
  pufUnsafe = 0x02,
  void __fastcall DoLoad(THierarchicalStorage * Storage, bool PuttyImport, bool & RewritePassword, bool Unsafe);
  void __fastcall ApplyRawSettings(TStrings * RawSettings, bool Unsafe);
  void __fastcall ApplyRawSettings(THierarchicalStorage * Storage, bool Unsafe);
    Data->ApplyRawSettings(SettingsMemo->Lines, false);
            Data->ApplyRawSettings(OptionsStorage.get(), false);
          int Flags =
            pufAllowStoredSiteWithProtocol |
            FLAGMASK(!CheckSafe(Params), pufUnsafe);
          GetLoginData(AutoStartSession, Params, DataList.get(), DownloadFile, NeedSession, NULL, Flags);