IT박스

자격 증명을 사용하여 트러스트되지 않은 원격 도메인에서 공유 파일 (UNC)에 액세스

itboxs 2020. 6. 16. 20:41
반응형

자격 증명을 사용하여 트러스트되지 않은 원격 도메인에서 공유 파일 (UNC)에 액세스


우리는 해결해야 할 흥미로운 상황에 처해 있었고 검색 결과가 조금씩 바뀌 었습니다. 따라서 SO 커뮤니티에 도움을 요청합니다.

문제는 이것입니다. 도메인에없고 원격 파일 공유 / UNC를 통해 신뢰할 수있는 외부 도메인 내에 있지 않은 공유 파일에 프로그래밍 방식으로 액세스해야합니다. 당연히 원격 시스템에 자격 증명을 제공해야합니다.

일반적으로 다음 두 가지 방법 중 하나로이 문제를 해결합니다.

  1. 파일 공유를 드라이브로 매핑하고 그때 자격 증명을 제공하십시오. 이것은 일반적으로 NET USE명령 또는 Win32 기능을 복제 하여 수행됩니다 NET USE.
  2. 원격 컴퓨터가 도메인에있는 것처럼 UNC 경로로 파일에 액세스하고 프로그램이 실행되는 계정이 원격 컴퓨터에서 로컬 사용자로 복제되었는지 (비밀번호 포함) 확인하십시오. 기본적으로 사용자가 공유 파일에 액세스하려고하면 Windows가 현재 사용자의 자격 증명을 자동으로 제공한다는 사실을 이용하십시오.
  3. 원격 파일 공유를 사용하지 마십시오. FTP (또는 다른 수단)를 사용하여 파일을 전송하고 로컬에서 작업 한 다음 다시 전송하십시오.

다양하고 햇볕이 잘 드는 이유 때문에 보안 / 네트워크 설계자는 처음 두 가지 접근 방식을 거부했습니다. 두 번째 접근법은 분명히 보안 허점입니다. 원격 컴퓨터가 손상된 경우 이제 로컬 컴퓨터가 위험에 노출됩니다. 새로 마운트 된 드라이브는 프로그램이 파일을 액세스하는 동안 로컬 컴퓨터의 다른 프로그램에서 사용할 수있는 공유 리소스이기 때문에 첫 번째 방법은 만족스럽지 않습니다. 이것을 일시적으로 만들 수는 있지만 여전히 의견에는 구멍이 있습니다.

세 번째 옵션을 사용할 수 있지만 원격 네트워크 관리자는 FTPS가 아니라 SFTP를 주장하며 FtpWebRequest는 FTPS 만 지원합니다. SFTP 방화벽에 더 친숙한 옵션이며 해당 접근 방식에 사용할 수있는 몇 가지 라이브러리가 있지만 가능한 경우 종속성을 줄이는 것을 선호합니다.

원격 파일 공유를 사용하는 관리 또는 win32 수단에 대해 MSDN을 검색했지만 유용한 것을 찾지 못했습니다.

그래서 묻습니다. 다른 방법이 있습니까? 내가 원하는 것을 수행하는 비밀 비밀 win32 기능을 놓쳤습니까? 아니면 옵션 3의 변형을 추구해야합니까?


문제를 해결하는 방법은 WNetUseConnection 이라는 Win32 API를 사용하는 것 입니다.
드라이브를 맵핑하지 않고 인증으로 UNC 경로에 연결하려면이 기능을 사용하십시오 .

이렇게하면 동일한 도메인에 있지 않고 다른 사용자 이름과 암호를 사용하더라도 원격 시스템에 연결할 수 있습니다.

WNetUseConnection을 사용한 후에는 동일한 도메인에있는 것처럼 UNC 경로를 통해 파일에 액세스 할 수 있습니다. 가장 좋은 방법은 아마도 관리 내장 주식을 이용하는 것입니다.
예 : \\ computername \ c $ \ program files \ Folder \ file.txt

다음은 WNetUseConnection을 사용하는 샘플 C # 코드입니다 .

NetResource의 경우 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이의 NetworkShareAccesser(! 당신이 충돌되는 앱하더라도)라고, 그렇지 않으면 열려있는 연결 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);
    }
}

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는 DOS 프롬프트에서 net use를 호출하여 원격 컴퓨터에서 인증하는 것과 같습니다.

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에 전달할 수있는 다양한 값을 살펴보고 필요에 가장 적합한 값을 결정하십시오.

이 코드는 확인할 수있는 두 번째 도메인이 없기 때문에 테스트되지 않았지만 희망적으로 올바른 길을 걸어야합니다.


대부분의 SFTP 서버는 SCP를 지원하므로 라이브러리를 찾기가 훨씬 쉽습니다. PuTTY에 포함 된 pscp와 같은 코드에서 기존 클라이언트를 호출 할 수도 있습니다 .

작업중인 파일 유형이 텍스트 또는 XML 파일과 같은 단순한 유형 인 경우 .NET Remoting 또는 웹 서비스와 같은 파일을 사용하여 파일을 조작하기 위해 자체 클라이언트 / 서버 구현을 작성할 수도 있습니다.


JScape 도구 를 사용하여 옵션 3을 매우 간단하게 구현하는 것을 보았습니다 . 시도해 볼 수 있습니다. 무료는 아니지만 업무를 수행합니다.


여기에 모든 부스러기가 제거 된 최소 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\folderw /를 직접 사용할 수 있으므로 사전 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();

참고 URL : https://stackoverflow.com/questions/659013/accessing-a-shared-file-unc-from-a-remote-non-trusted-domain-with-credentials

반응형