使用凭证从远程,不受信任的域访问共享文件(UNC)

我们遇到了一个需要解决的有趣的情况,我的search变得很糟糕。 因此,我呼吁SO社区寻求帮助。

问题是这样的:我们需要以编程方式访问不在我们域中的共享文件,并且不通过远程文件共享/ UNC而不在可信的外部域中。 当然,我们需要向远程机器提供证书。

通常,解决这个问题的方法有两种:

  1. 将文件共享映射为驱动器,并在此时提供凭据。 这通常使用NET USE命令或复制NET USE的Win32函数完成。
  2. 使用UNCpath访问该文件,就好像远程计算机在域中一样,并确保运行程序的帐户作为本地用户在远程计算机上被复制(包括密码)。 基本上利用Windows将自动提供当前用户的凭据,当用户尝试访问共享文件的事实。
  3. 不要使用远程文件共享。 使用FTP(或其他方式)传输文件,在本地处理,然后传回。

由于各种各样的原因,我们的安全/networking架构师拒绝了前两种方法。 第二种方法显然是一个安全漏洞; 如果远程计算机受到威胁,本地计算机现在处于危险之中。 第一种方法是不令人满意的,因为新安装的驱动器是在程序进行文件访问期间本地计算机上的其他程序可用的共享资源。 尽pipe这是暂时的,但这仍然是一个漏洞。

他们打开第三个选项,但远程networkingpipe理员坚持使用SFTP而不是FTPS,而FtpWebRequest只支持FTPS。 SFTP 防火墙更友好的select,并且有一些库可以用于这种方法,但如果可以的话,我宁愿减less我的依赖关系。

我已经searchMSDN的使用远程文件共享的托pipe或win32手段,但我没有拿出任何有用的东西。

所以我问:还有其他的方法吗? 我错过了一个超级秘密的win32函数吗? 或者我必须追求选项3的一些变体?

解决问题的方法是使用名为WNetUseConnection的Win32 API。
使用此function通过身份validation连接到UNCpath,而不是映射驱动器

这将允许您连接到远程机器,即使它不在同一个域中,即使它有不同的用户名和密码。

一旦你使用了WNetUseConnection,你将能够通过UNCpath访问文件,就好像你在同一个域中一样。 最好的办法大概是通过行政build股。
例如:\\计算机名\ c $ \ program files \ Folder \ file.txt

这里是一些使用WNetUseConnection的示例C#代码 。

请注意,对于NetResource,您应该为lpLocalName和lpProvider传递null。 dwType应该是RESOURCETYPE_DISK。 lpRemoteName应该是\\ ComputerName。

对于寻求快速解决scheme的人,可以使用我最近写的NetworkShareAccesser (基于这个答案 (非常感谢!)):

用法:

 using (NetworkShareAccesser.Access(REMOTE_COMPUTER_NAME, DOMAIN, USER_NAME, PASSWORD)) { File.Copy(@"C:\Some\File\To\copy.txt", @"\\REMOTE-COMPUTER\My\Shared\Target\file.txt"); } 

警告:请确保NetworkShareAccesser Dispose被调用(即使您的应用程序崩溃!),否则打开的连接将保留在Windows上。 您可以通过打开cmd提示符来查看所有打开的连接并inputnet use

代码:

 /// <summary> /// Provides access to a network share. /// </summary> public class NetworkShareAccesser : IDisposable { private string _remoteUncName; private string _remoteComputerName; public string RemoteComputerName { get { return this._remoteComputerName; } set { this._remoteComputerName = value; this._remoteUncName = @"\\" + this._remoteComputerName; } } public string UserName { get; set; } public string Password { get; set; } #region Consts private const int RESOURCE_CONNECTED = 0x00000001; private const int RESOURCE_GLOBALNET = 0x00000002; private const int RESOURCE_REMEMBERED = 0x00000003; private const int RESOURCETYPE_ANY = 0x00000000; private const int RESOURCETYPE_DISK = 0x00000001; private const int RESOURCETYPE_PRINT = 0x00000002; private const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000; private const int RESOURCEDISPLAYTYPE_DOMAIN = 0x00000001; private const int RESOURCEDISPLAYTYPE_SERVER = 0x00000002; private const int RESOURCEDISPLAYTYPE_SHARE = 0x00000003; private const int RESOURCEDISPLAYTYPE_FILE = 0x00000004; private const int RESOURCEDISPLAYTYPE_GROUP = 0x00000005; private const int RESOURCEUSAGE_CONNECTABLE = 0x00000001; private const int RESOURCEUSAGE_CONTAINER = 0x00000002; private const int CONNECT_INTERACTIVE = 0x00000008; private const int CONNECT_PROMPT = 0x00000010; private const int CONNECT_REDIRECT = 0x00000080; private const int CONNECT_UPDATE_PROFILE = 0x00000001; private const int CONNECT_COMMANDLINE = 0x00000800; private const int CONNECT_CMD_SAVECRED = 0x00001000; private const int CONNECT_LOCALDRIVE = 0x00000100; #endregion #region Errors private const int NO_ERROR = 0; private const int ERROR_ACCESS_DENIED = 5; private const int ERROR_ALREADY_ASSIGNED = 85; private const int ERROR_BAD_DEVICE = 1200; private const int ERROR_BAD_NET_NAME = 67; private const int ERROR_BAD_PROVIDER = 1204; private const int ERROR_CANCELLED = 1223; private const int ERROR_EXTENDED_ERROR = 1208; private const int ERROR_INVALID_ADDRESS = 487; private const int ERROR_INVALID_PARAMETER = 87; private const int ERROR_INVALID_PASSWORD = 1216; private const int ERROR_MORE_DATA = 234; private const int ERROR_NO_MORE_ITEMS = 259; private const int ERROR_NO_NET_OR_BAD_PATH = 1203; private const int ERROR_NO_NETWORK = 1222; private const int ERROR_BAD_PROFILE = 1206; private const int ERROR_CANNOT_OPEN_PROFILE = 1205; private const int ERROR_DEVICE_IN_USE = 2404; private const int ERROR_NOT_CONNECTED = 2250; private const int ERROR_OPEN_FILES = 2401; #endregion #region PInvoke Signatures [DllImport("Mpr.dll")] private static extern int WNetUseConnection( IntPtr hwndOwner, NETRESOURCE lpNetResource, string lpPassword, string lpUserID, int dwFlags, string lpAccessName, string lpBufferSize, string lpResult ); [DllImport("Mpr.dll")] private static extern int WNetCancelConnection2( string lpName, int dwFlags, bool fForce ); [StructLayout(LayoutKind.Sequential)] private class NETRESOURCE { public int dwScope = 0; public int dwType = 0; public int dwDisplayType = 0; public int dwUsage = 0; public string lpLocalName = ""; public string lpRemoteName = ""; public string lpComment = ""; public string lpProvider = ""; } #endregion /// <summary> /// Creates a NetworkShareAccesser for the given computer name. The user will be promted to enter credentials /// </summary> /// <param name="remoteComputerName"></param> /// <returns></returns> public static NetworkShareAccesser Access(string remoteComputerName) { return new NetworkShareAccesser(remoteComputerName); } /// <summary> /// Creates a NetworkShareAccesser for the given computer name using the given domain/computer name, username and password /// </summary> /// <param name="remoteComputerName"></param> /// <param name="domainOrComuterName"></param> /// <param name="userName"></param> /// <param name="password"></param> public static NetworkShareAccesser Access(string remoteComputerName, string domainOrComuterName, string userName, string password) { return new NetworkShareAccesser(remoteComputerName, domainOrComuterName + @"\" + userName, password); } /// <summary> /// Creates a NetworkShareAccesser for the given computer name using the given username (format: domainOrComputername\Username) and password /// </summary> /// <param name="remoteComputerName"></param> /// <param name="userName"></param> /// <param name="password"></param> public static NetworkShareAccesser Access(string remoteComputerName, string userName, string password) { return new NetworkShareAccesser(remoteComputerName, userName, password); } private NetworkShareAccesser(string remoteComputerName) { RemoteComputerName = remoteComputerName; this.ConnectToShare(this._remoteUncName, null, null, true); } private NetworkShareAccesser(string remoteComputerName, string userName, string password) { RemoteComputerName = remoteComputerName; UserName = userName; Password = password; this.ConnectToShare(this._remoteUncName, this.UserName, this.Password, false); } private void ConnectToShare(string remoteUnc, string username, string password, bool promptUser) { NETRESOURCE nr = new NETRESOURCE { dwType = RESOURCETYPE_DISK, lpRemoteName = remoteUnc }; int result; if (promptUser) { result = WNetUseConnection(IntPtr.Zero, nr, "", "", CONNECT_INTERACTIVE | CONNECT_PROMPT, null, null, null); } else { result = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, null, null, null); } if (result != NO_ERROR) { throw new Win32Exception(result); } } private void DisconnectFromShare(string remoteUnc) { int result = WNetCancelConnection2(remoteUnc, CONNECT_UPDATE_PROFILE, false); if (result != NO_ERROR) { throw new Win32Exception(result); } } /// <summary> /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// </summary> /// <filterpriority>2</filterpriority> public void Dispose() { this.DisconnectFromShare(this._remoteUncName); } } 

AFAIK,您不需要将UNCpath映射到一个驱动器号为了build立服务器的凭据。 我经常使用批处理脚本,如:

 net use \\myserver /user:username password :: do something with \\myserver\the\file\i\want.xml net use /delete \\my.server.com 

但是,与您的程序运行在同一个帐户上的任何程序仍然可以访问username:password有权访问的所有username:password 。 一个可能的解决scheme可能是隔离您的程序在它自己的本地用户帐户(UNC访问本地到称为NET USE的帐户)。

注意:使用跨域的SMB并不是很好的使用IMO的技术。 如果安全性如此重要,那么SMB缺乏encryption的事实本身就是一种阻碍。

虽然我不了解自己,但我当然希望#2是不正确的…我想认为Windows不会自动将我的login信息(至less我的密码!)发送到任何机器更不用说那些不属于我信任的部分了。

无论如何,您是否探索过模拟架构? 你的代码将看起来类似于这样的:

 using (System.Security.Principal.WindowsImpersonationContext context = System.Security.Principal.WindowsIdentity.Impersonate(token)) { // Do network operations here context.Undo(); } 

在这种情况下, tokenvariables是一个IntPtr。 为了得到这个variables的值,你必须调用非托pipe的LogonUser Windows API函数。 pinvoke.net的快速旅程给了我们以下签名:

 [System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError = true)] public static extern bool LogonUser( string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out IntPtr phToken ); 

用户名,域名和密码看起来相当明显。 看看可以传递给dwLogonType和dwLogonProvider的各种值,以确定最适合您的需求的值。

这个代码还没有经过testing,因为我没有第二个领域,我可以validation,但这应该有希望把你放在正确的轨道上。

我会推荐NetUseAdd ,而不是WNetUseConnection 。 WNetUseConnection是由WNetUseConnection2和WNetUseConnection3取代的传统function,但所有这些function都创build了一个在Windows资源pipe理器中可见的networking设备。 NetUseAdd相当于在DOS提示符下调用net use以在远程计算机上进行身份validation。

如果您调用NetUseAdd,则后续尝试访问该目录应该成功。

大多数SFTP服务器也支持SCP,这可以更容易地find库。 您甚至可以从您的代码中调用现有客户端,例如PuTTY附带的pscp。

如果您正在使用的文件types是文本或XML文件,那么甚至可以编写自己的客户端/服务器实现来使用诸如.NET Remoting或Web服务之类的操作文件。

我已经看到选项3与JScape工具以一个非常简单的方式实现。 你可以试试看。 这不是免费的,但它的工作。

我期待MSfind答案。 第一种解决scheme假定运行应用程序进程的用户帐户可以访问共享文件夹或驱动器(同域)。 确保您的DNS已解决或尝试使用IP地址。 只需执行以下操作:

  DirectoryInfo di = new DirectoryInfo(PATH); var files = di.EnumerateFiles("*.*", SearchOption.AllDirectories); 

如果你想跨越不同的领域.NET 2.0与凭据遵循这个模型:

 WebRequest req = FileWebRequest.Create(new Uri(@"\\<server Name>\Dir\test.txt")); req.Credentials = new NetworkCredential(@"<Domain>\<User>", "<Password>"); req.PreAuthenticate = true; WebResponse d = req.GetResponse(); FileStream fs = File.Create("test.txt"); // here you can check that the cast was successful if you want. fs = d.GetResponseStream() as FileStream; fs.Close();