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

Ran*_*pho 144 c# windows file-sharing unc

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

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

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

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

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

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

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

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

Bri*_*ndy 167

解决问题的方法是使用名为WNetUseConnection的Win32 API .
使用此功能通过身份验证连接到UNC路径,而不是映射驱动器.

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

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

以下是一些使用WNetUseConnection的示例C#代码.

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

  • 嗨布莱恩。您链接的文档说您可以为用户名和密码传递 NULL 以使用当前凭据。我会做一些测试看看这是否有效。 (2认同)

Gam*_*ing 116

对于寻求快速解决方案的人,您可以使用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");
}
Run Code Online (Sandbox Code Playgroud)

警告:请绝对确保,那DisposeNetworkShareAccesser被称为(即使你的应用程序崩溃!),否则一个开放的连接将保持在Windows上.您可以通过打开cmd提示并输入来查看所有打开的连接net 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 你还需要`使用System.Runtime.InteropServices;`和`使用System.ComponentModel;`用于`DllImport`和`Win32Exception` (2认同)
  • 我尝试将您的解决方案与远程计算机上的本地用户帐户一起使用,但我不断收到访问被拒绝的错误。您的解决方案仅适用于网络帐户吗? (2认同)
  • 注意:处置对象似乎并不会清除系统中的凭据(Windows 10);连接“取消”后,我可以访问远程计算机上的文件。重新登录我的用户帐户或重新启动计算机似乎清除了此内部缓存。 (2认同)

Jac*_*cob 16

AFAIK,您不需要将UNC路径映射到驱动器号以便为服务器建立凭据.我经常使用批处理脚本,如:

net use \\myserver /user:username password

:: do something with \\myserver\the\file\i\want.xml

net use /delete \\my.server.com
Run Code Online (Sandbox Code Playgroud)

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

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


wqw*_*wqw 5

这里是一个最小的 POC 类,所有的废品都被删除了

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以直接使用\\server\share\folderw/ WNetUseConnection,无需\\server事先将其剥离。