web-dev-qa-db-ja.com

資格情報を使用してリモートの信頼されていないドメインから共有ファイル(UNC)にアクセスする

私たちは解決する必要がある興味深い状況に遭遇しました、そして、私の検索はゼロになりました。したがって、私はSOコミュニティに助けを求めます。

問題はこれです。ドメイン内になく、リモートファイル共有/ UNCを介して信頼できる外部ドメイン内にない共有ファイルにプログラムでアクセスする必要があります。当然、リモートマシンに資格情報を提供する必要があります。

通常、次の2つの方法のいずれかでこの問題を解決します。

  1. ファイル共有をドライブとしてマップし、その時点で資格情報を提供します。これは通常、Net UseコマンドまたはNet Useを複製するWin32関数を使用して行われます。
  2. リモートコンピューターがドメイン上にあるかのようにUNCパスでファイルにアクセスし、ローカルユーザーとしてリモートコンピューター上でプログラムを実行するアカウント(パスワードを含む)が複製されていることを確認します。基本的に、ユーザーが共有ファイルにアクセスしようとすると、Windowsが現在のユーザーの資格情報を自動的に提供するという事実を活用します。
  3. リモートファイル共有を使用しないでください。 FTP(またはその他の手段)を使用してファイルを転送し、ローカルで作業してから、元に戻します。

さまざまな理由から、セキュリティ/ネットワークアーキテクトは最初の2つのアプローチを拒否しました。 2番目のアプローチは明らかにセキュリティホールです。リモートコンピューターが侵害された場合、ローカルコンピューターが危険にさらされます。新しくマウントされたドライブは、プログラムによるファイルアクセス中にローカルコンピューター上の他のプログラムが使用できる共有リソースであるため、最初のアプローチは不十分です。これを一時的にすることはかなり可能ですが、彼らの意見にはまだ穴があります。

3番目のオプションは公開されていますが、リモートネットワーク管理者はFTPSではなくSFTPを要求し、FtpWebRequestはFTPSのみをサポートします。 SFTP isよりファイアウォールに優しいオプションであり、そのアプローチに使用できるライブラリがいくつかありますが、可能な場合は依存関係を減らしたいと思います。

リモートファイル共有を使用するマネージド手段またはwin32手段についてMSDNを検索しましたが、有用なものを思い付くことはできませんでした。

だから私は尋ねる:別の方法はありますか?私がしたいことをする極秘のwin32関数を見逃しましたか?または、オプション3のバリエーションを追求する必要がありますか?

142
Randolpho

問題を解決する方法は、 WNetUseConnection と呼ばれるWin32 APIを使用することです。
この関数を使用して、ドライブをマップするのではなく、認証付きでUNCパスに接続します

これにより、同じドメイン上にない場合や、ユーザー名とパスワードが異なる場合でも、リモートマシンに接続できます。

WNetUseConnectionを使用すると、同じドメインにいるかのようにUNCパスを介してファイルにアクセスできるようになります。最善の方法は、おそらく管理ビルトイン共有を使用することです。
例:\\ computername\c $\program files\Folder\file.txt

WNetUseConnectionを使用するC#コードのサンプル

NetResourceの場合、lpLocalNameおよびlpProviderにnullを渡す必要があります。 dwTypeはRESOURCETYPE_DISKである必要があります。 lpRemoteNameは\\ ComputerNameである必要があります。

164
Brian R. Bondy

迅速な解決策を探している人のために、最近書​​いた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");
}

警告: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);
    }
}
115
GameScripting

知る限り、サーバーの資格情報を確立するためにドライブ文字へのUNCパスをmapする必要はありません。私は定期的に次のようなバッチスクリプトを使用しました。

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が暗号化を欠いているという事実は、それ自体が少しダンパーです。

16
Jacob

私は自分自身を知りませんが、私は確かに#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に渡すことができるさまざまな値を見て、ニーズに最適な値を決定してください。

確認できる2番目のドメインがないので、このコードはテストされていませんが、うまくいけば正しい軌道に乗れるはずです。

4
Adam Robinson

WNetUseConnectionではなく、 NetUseAdd をお勧めします。 WNetUseConnectionは、WNetUseConnection2とWNetUseConnection3に取って代わられたレガシー関数ですが、これらの関数はすべて、Windowsエクスプローラーに表示されるネットワークデバイスを作成します。 NetUseAddは、DOSプロンプトでNet Useを呼び出してリモートコンピューターで認証するのと同じです。

NetUseAddを呼び出すと、その後のディレクトリへのアクセス試行は成功するはずです。

4
Adam Robinson

ほとんどのSFTPサーバーもSCPをサポートしているため、ライブラリの検索がはるかに簡単になります。 PuTTY に含まれるpscpのようなコードから既存のクライアントを呼び出すことさえできます。

作業しているファイルの種類がテキストファイルやXMLファイルのような単純なものである場合、.NET RemotingやWebサービスなどを使用してファイルを操作する独自のクライアント/サーバー実装を作成することさえできます。

2
Ryan Bolger

JScape tools で実装されたオプション3は、非常に簡単な方法で見ました。試してみてください。それは無料ではありませんが、その仕事をします。

1
DreamSonic

ここでは、最小限の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部分にストリップする必要はありません。

0
wqw

imvb.net codebrian リファレンスに基づいて添付

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
0
roy.d