首页 文章

从具有凭据的远程非受信任域访问共享文件(UNC)

提问于
浏览
138

我们遇到了一个需要解决的有趣情况,我的搜索已经发现了 . 因此,我呼吁SO社区寻求帮助 .

问题是:我们需要以编程方式访问不在我们域中的共享文件,并且不在远程文件共享/ UNC的可信外部域中 . 当然,我们需要为远程计算机提供凭据 .

通常,可以通过以下两种方式之一解决此问题:

  • 将文件共享映射为驱动器并在此时提供凭据 . 这通常使用 NET USE 命令或复制 NET USE 的Win32函数来完成 .

  • 使用UNC路径访问文件,就好像远程计算机位于域上一样,并确保作为本地用户在远程计算机上复制运行程序的帐户(包括密码) . 基本上利用了当用户尝试访问共享文件时Windows将自动提供当前用户凭据的事实 .

  • 不要使用远程文件共享 . 使用FTP(或其他方法)传输文件,在本地处理它,然后将其传回 .

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

他们对第三种选择持开放态度,但远程网络管理员坚持使用SFTP而不是FTPS,而FtpWebRequest仅支持FTPS . SFTP is 更适合防火墙的选项,我可以使用几个库来实现这种方法,但如果可以的话,我宁愿减少我的依赖项 .

我在MSDN上搜索了使用远程文件共享的托管或win32方法,但我没有提出任何有用的东西 .

所以我问:还有另外一种方法吗?我是否错过了一个超级秘密的win32功能,可以满足我的需求?或者我必须追求选项3的一些变体?

9 回答

  • 104

    对于寻求快速解决方案的人来说,你可以使用我最近写的 NetworkShareAccesser (基于this answer(非常感谢!)):

    Usage:

    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");
    }
    

    WARNING: 请确保 DisposeDispose 被调用(即使你的应用程序崩溃了!),否则Windows上将保留打开的连接 . 您可以通过打开 cmd 提示并输入 net use 来查看所有打开的连接 .

    The Code:

    /// <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);
        }
    }
    
  • 4

    解决问题的方法是使用名为WNetUseConnection的Win32 API .
    Use this function to connect to a UNC path with authentication, NOT to map a drive .

    这将允许您连接到远程计算机,即使它不在同一个域上,即使它具有不同的用户名和密码 .

    使用WNetUseConnection后,您将能够通过UNC路径访问该文件,就像您在同一个域中一样 . 最好的方法可能是通过行政内置股票 .
    示例:\ computername \ c $ \ program files \ Folder \ file.txt

    Here is some sample C# code that uses WNetUseConnection .

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

  • 15

    我已经看到选项3以JScape tools以非常简单的方式实现了 . 你可以尝试一下 . 它不是免费的,但它确实起作用 .

  • 158

    而不是WNetUseConnection,我会推荐NetUseAdd . WNetUseConnection是一个遗留功能,在Windows资源管理器中可见_303972 . NetUseAdd相当于在DOS提示符下调用net use以在远程计算机上进行身份验证 .

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

  • 0

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

    如果您正在使用的文件类型简单,如文本或XML文件,您甚至可以编写自己的客户端/服务器实现来使用.NET Remoting或Web服务等操作文件 .

  • -2

    我找了MS找到答案 . 第一种解决方案假定运行应用程序进程的用户帐户可以访问共享文件夹或驱动器(同一域) . 确保您的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();
    
  • 1

    AFAIK,您不需要_C3962_为驱动器号的UNC路径,以便为服务器 Build 凭据 . 我经常使用批处理脚本,如:

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

    但是,与您的程序在同一帐户上运行的任何程序仍然可以访问 username:password 具有的所有内容进入 . 一种可能的解决方案是将您的程序隔离在自己的本地用户帐户中(UNC访问权限是名为 NET USE 的帐户的本地访问) .

    Note: 使用SMB跨域并不是很好地利用IMO这项技术 . 如果安全性非常重要,那么SMB缺乏加密这一事实本身就是一种阻碍 .

  • 2

    虽然我不了解自己,但我当然希望#2不正确......我想我认为Windows不会自动将我的登录信息(至少我的密码!)发给任何一台机器,更不用说不属于我信任的一个 .

    无论如何,你有没有探索过模仿架构?您的代码看起来与此类似:

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

    在这种情况下, token 变量是IntPtr . 为了获得此变量的值,您必须调用非托管的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的各种值,以确定最适合您需求的值 .

    此代码尚未经过测试,因为我在此处没有第二个域可以验证,但这应该会让您走上正确的轨道 .

  • 3

    这里有一个最小的POC类w /所有被删除的

    using System;
    using System.ComponentModel;
    using System.Runtime.InteropServices;
    
    public class UncShareWithCredentials : IDisposable
    {
        private string _uncShare;
    
        public UncShareWithCredentials(string uncShare, string userName, string password)
        {
            var nr = new Native.NETRESOURCE
            {
                dwType = Native.RESOURCETYPE_DISK,
                lpRemoteName = uncShare
            };
    
            int result = Native.WNetUseConnection(IntPtr.Zero, nr, password, userName, 0, null, null, null);
            if (result != Native.NO_ERROR)
            {
                throw new Win32Exception(result);
            }
            _uncShare = uncShare;
        }
    
        public void Dispose()
        {
            if (!string.IsNullOrEmpty(_uncShare))
            {
                Native.WNetCancelConnection2(_uncShare, Native.CONNECT_UPDATE_PROFILE, false);
                _uncShare = null;
            }
        }
    
        private class Native
        {
            public const int RESOURCETYPE_DISK = 0x00000001;
            public const int CONNECT_UPDATE_PROFILE = 0x00000001;
            public const int NO_ERROR = 0;
    
            [DllImport("mpr.dll")]
            public static extern int WNetUseConnection(IntPtr hwndOwner, NETRESOURCE lpNetResource, string lpPassword, string lpUserID,
                int dwFlags, string lpAccessName, string lpBufferSize, string lpResult);
    
            [DllImport("mpr.dll")]
            public static extern int WNetCancelConnection2(string lpName, int dwFlags, bool fForce);
    
            [StructLayout(LayoutKind.Sequential)]
            public class NETRESOURCE
            {
                public int dwScope;
                public int dwType;
                public int dwDisplayType;
                public int dwUsage;
                public string lpLocalName;
                public string lpRemoteName;
                public string lpComment;
                public string lpProvider;
            }
        }
    }
    

    你可以直接使用 \\server\share\folder w / WNetUseConnection ,不需要事先将它剥离到 \\server 部分 .

相关问题