Доступ к общему файлу (UNC) из удаленного ненадежного домена с учетными данными

StackOverflow https://stackoverflow.com/questions/659013

  •  20-08-2019
  •  | 
  •  

Вопрос

Мы столкнулись с интересной ситуацией, которая требует разрешения, и мои поиски закончились ничем. Поэтому я обращаюсь к сообществу SO за помощью.

Проблема заключается в следующем: нам необходим программный доступ к общему файлу, который находится не в нашем домене и не находится в доверенном внешнем домене через удаленный общий доступ к файлам / UNC. Естественно, нам нужно предоставить учетные данные на удаленный компьютер.

Как правило, эту проблему можно решить одним из двух способов:

<Ол>
  • Сопоставьте общий файловый ресурс как диск и укажите учетные данные. Обычно это делается с помощью команды NET USE или функций Win32, которые дублируют <=>.
  • Получите доступ к файлу с UNC-путем, как если бы удаленный компьютер находился в домене, и убедитесь, что учетная запись, под которой запускается программа, дублируется (включая пароль) на удаленном компьютере как локальный пользователь. В основном используется тот факт, что Windows автоматически предоставит учетные данные текущего пользователя, когда пользователь попытается получить доступ к общему файлу.
  • Не используйте удаленный обмен файлами. Используйте FTP (или другие средства) для передачи файла, работайте с ним локально, а затем передайте его обратно.
  • По разным и разным причинам наши архитекторы безопасности / сети отвергли первые два подхода. Второй подход, очевидно, является дырой в безопасности; если удаленный компьютер скомпрометирован, локальный компьютер теперь находится в опасности. Первый подход неудовлетворителен, поскольку вновь смонтированный диск является общим ресурсом, доступным для других программ на локальном компьютере во время доступа программы к файлам. Несмотря на то, что это вполне возможно сделать временным, это все еще дыра в их мнении.

    Они открыты для третьего варианта, но администраторы удаленных сетей настаивают на SFTP, а не на FTPS, а FtpWebRequest поддерживает только FTPS. SFTP является более подходящим для межсетевого экрана вариантом, и есть несколько библиотек, которые я мог бы использовать для этого подхода, но я бы предпочел уменьшить свои зависимости, если смогу.

    Я искал в MSDN управляемый или win32 способ использования удаленного обмена файлами, но мне не удалось найти что-нибудь полезное.

    И вот я спрашиваю: есть ли другой способ? Я пропустил сверхсекретную функцию win32, которая делает то, что я хочу? Или я должен использовать какой-то вариант 3?

    Это было полезно?

    Решение

    Способ решения вашей проблемы - использовать Win32 API с именем Вот пример кода C #, который использует WNetUseConnection .

    Обратите внимание, что для NetResource вы должны передать null для lpLocalName и lpProvider. Тип dwType должен быть RESOURCETYPE_DISK. LpRemoteName должно быть \\ ComputerName.

    Другие советы

    Для людей, которые ищут быстрое решение, вы можете использовать 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");
    }
    

    ВНИМАНИЕ: . Убедитесь, что Dispose из cmd вызывается (даже если ваше приложение вылетает!), в противном случае в Windows останется открытое соединение. Вы можете увидеть все открытые соединения, открыв приглашение 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);
        }
    }
    

    AFAIK, вам не нужно сопоставлять путь UNC с буквой диска, чтобы установить учетные данные для сервера. Я регулярно использовал пакетные сценарии, такие как:

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

    Примечание. Использование SMB для разных доменов - не совсем удачное применение технологии, IMO. Если безопасность так важна, тот факт, что SMB не имеет шифрования, сам по себе является чем-то вроде демпфера.

    Вместо WNetUseConnection я бы порекомендовал NetUseAdd . WNetUseConnection - это устаревшая функция, которая была заменена WNetUseConnection2 и WNetUseConnection3, но все эти функции создают сетевое устройство, которое отображается в проводнике Windows. NetUseAdd является эквивалентом вызова net use в командной строке DOS для аутентификации на удаленном компьютере.

    Если вы вызываете NetUseAdd, то последующие попытки доступа к каталогу должны быть успешными.

    Хотя я сам не знаю, я, конечно, надеюсь, что # 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, реализованный с помощью инструментов JScape довольно простым способом. Вы можете попробовать. Это не бесплатно, но делает свою работу.

    Здесь минимальный класс 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;
            }
        }
    }
    

    Вы можете напрямую использовать \\server\share\folder w / WNetUseConnection, нет необходимости разбивать его на \\server часть только заранее.

    я прикрепляю свой код vb.net на основе ссылки Брайана

    Imports System.ComponentModel
    
    Imports System.Runtime.InteropServices
    
    Public Class PinvokeWindowsNetworking
    
    Const NO_ERROR As Integer = 0
    
    
    
    Private Structure ErrorClass
    
        Public num As Integer
    
        Public message As String
    
    
    
        Public Sub New(ByVal num As Integer, ByVal message As String)
    
            Me.num = num
    
            Me.message = message
    
        End Sub
    
    End Structure
    
    
    
    Private Shared ERROR_LIST As ErrorClass() = New ErrorClass() {
    
        New ErrorClass(5, "Error: Access Denied"),
    
        New ErrorClass(85, "Error: Already Assigned"),
    
        New ErrorClass(1200, "Error: Bad Device"),
    
        New ErrorClass(67, "Error: Bad Net Name"),
    
        New ErrorClass(1204, "Error: Bad Provider"),
    
        New ErrorClass(1223, "Error: Cancelled"),
    
        New ErrorClass(1208, "Error: Extended Error"),
    
        New ErrorClass(487, "Error: Invalid Address"),
    
        New ErrorClass(87, "Error: Invalid Parameter"),
    
        New ErrorClass(1216, "Error: Invalid Password"),
    
        New ErrorClass(234, "Error: More Data"),
    
        New ErrorClass(259, "Error: No More Items"),
    
        New ErrorClass(1203, "Error: No Net Or Bad Path"),
    
        New ErrorClass(1222, "Error: No Network"),
    
        New ErrorClass(1206, "Error: Bad Profile"),
    
        New ErrorClass(1205, "Error: Cannot Open Profile"),
    
        New ErrorClass(2404, "Error: Device In Use"),
    
        New ErrorClass(2250, "Error: Not Connected"),
    
        New ErrorClass(2401, "Error: Open Files")}
    
    
    
    Private Shared Function getErrorForNumber(ByVal errNum As Integer) As String
    
        For Each er As ErrorClass In ERROR_LIST
    
            If er.num = errNum Then Return er.message
    
        Next
    
    
    
        Try
    
            Throw New Win32Exception(errNum)
    
        Catch ex As Exception
    
            Return "Error: Unknown, " & errNum & " " & ex.Message
    
        End Try
    
    
    
        Return "Error: Unknown, " & errNum
    
    End Function
    
    
    
    <DllImport("Mpr.dll")>
    
    Private Shared Function WNetUseConnection(ByVal hwndOwner As IntPtr, ByVal lpNetResource As NETRESOURCE, ByVal lpPassword As String, ByVal lpUserID As String, ByVal dwFlags As Integer, ByVal lpAccessName As String, ByVal lpBufferSize As String, ByVal lpResult As String) As Integer
    
    End Function
    
    
    
    <DllImport("Mpr.dll")>
    
    Private Shared Function WNetCancelConnection2(ByVal lpName As String, ByVal dwFlags As Integer, ByVal fForce As Boolean) As Integer
    
    End Function
    
    
    
    <StructLayout(LayoutKind.Sequential)>
    
    Private Class NETRESOURCE
    
        Public dwScope As Integer = 0
    
        Public dwType As Integer = 0
    
        Public dwDisplayType As Integer = 0
    
        Public dwUsage As Integer = 0
    
        Public lpLocalName As String = ""
    
        Public lpRemoteName As String = ""
    
        Public lpComment As String = ""
    
        Public lpProvider As String = ""
    
    End Class
    
    
    
    Public Shared Function connectToRemote(ByVal remoteUNC As String, ByVal username As String, ByVal password As String) As String
    
        Return connectToRemote(remoteUNC, username, password, False)
    
    End Function
    
    
    
    Public Shared Function connectToRemote(ByVal remoteUNC As String, ByVal username As String, ByVal password As String, ByVal promptUser As Boolean) As String
    
        Dim nr As NETRESOURCE = New NETRESOURCE()
    
        nr.dwType = ResourceTypes.Disk
    
        nr.lpRemoteName = remoteUNC
    
        Dim ret As Integer
    
    
    
        If promptUser Then
    
            ret = WNetUseConnection(IntPtr.Zero, nr, "", "", Connects.Interactive Or Connects.Prompt, Nothing, Nothing, Nothing)
    
        Else
    
            ret = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, Nothing, Nothing, Nothing)
    
        End If
    
    
    
        If ret = NO_ERROR Then Return Nothing
    
        Return getErrorForNumber(ret)
    
    End Function
    
    
    
    Public Shared Function disconnectRemote(ByVal remoteUNC As String) As String
    
        Dim ret As Integer = WNetCancelConnection2(remoteUNC, Connects.UpdateProfile, False)
    
        If ret = NO_ERROR Then Return Nothing
    
        Return getErrorForNumber(ret)
    
    End Function
    
    
    Enum Resources As Integer
    
        Connected = &H1
    
        GlobalNet = &H2
    
        Remembered = &H3
    
    End Enum
    
    
    Enum ResourceTypes As Integer
    
        Any = &H0
    
        Disk = &H1
    
        Print = &H2
    
    End Enum
    
    
    Enum ResourceDisplayTypes As Integer
    
        Generic = &H0
    
        Domain = &H1
    
        Server = &H2
    
        Share = &H3
    
        File = &H4
    
        Group = &H5
    
    End Enum
    
    
    Enum ResourceUsages As Integer
    
        Connectable = &H1
    
        Container = &H2
    
    End Enum
    
    
    Enum Connects As Integer
    
        Interactive = &H8
    
        Prompt = &H10
    
        Redirect = &H80
    
        UpdateProfile = &H1
    
        CommandLine = &H800
    
        CmdSaveCred = &H1000
    
        LocalDrive = &H100
    
    End Enum
    
    
    End Class
    

    как его использовать

    Dim login = PinvokeWindowsNetworking.connectToRemote("\\ComputerName", "ComputerName\UserName", "Password")
    
        If IsNothing(login) Then
    
    
    
            'do your thing on the shared folder
    
    
    
           PinvokeWindowsNetworking.disconnectRemote("\\ComputerName")
    
        End If
    

    Я посмотрел в 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();
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top