it-swarm.com.de

Zugriff auf eine freigegebene Datei (UNC) von einer nicht vertrauenswürdigen Remote-Domäne mit Anmeldeinformationen

Wir sind in eine interessante Situation geraten, die gelöst werden muss, und meine Suchanfragen haben sich nicht ergeben. Ich appelliere daher an die SO Community um Hilfe.

Das Problem ist folgendes: Wir müssen programmgesteuert auf eine freigegebene Datei zugreifen, die sich nicht in unserer Domäne befindet, und sich nicht über eine Remote-Dateifreigabe/UNC innerhalb einer vertrauenswürdigen externen Domäne befindet. Natürlich müssen wir dem Remote-Computer Anmeldeinformationen bereitstellen.

Normalerweise löst man dieses Problem auf zwei Arten: 

  1. Ordnen Sie die Dateifreigabe als Laufwerk zu und geben Sie die Anmeldeinformationen zu diesem Zeitpunkt ein. Dies erfolgt normalerweise mithilfe des Befehls Net Use oder der Win32-Funktionen, die Net Use duplizieren. 
  2. Greifen Sie auf die Datei mit einem UNC-Pfad zu, als ob sich der Remotecomputer in der Domäne befände, und stellen Sie sicher, dass das Konto, unter dem das Programm ausgeführt wird, auf dem Remotecomputer als lokaler Benutzer dupliziert ist (einschließlich Kennwort). Nutzen Sie grundsätzlich die Tatsache, dass Windows automatisch die Anmeldeinformationen des aktuellen Benutzers bereitstellt, wenn der Benutzer versucht, auf eine freigegebene Datei zuzugreifen. 
  3. Verwenden Sie keine Remote-Dateifreigabe. Verwenden Sie FTP (oder eine andere Methode), um die Datei zu übertragen, lokal zu bearbeiten und dann zurück zu übertragen. 

Aus verschiedenen Gründen haben unsere Sicherheits-/Netzwerkarchitekten die ersten beiden Ansätze abgelehnt. Der zweite Ansatz ist offensichtlich eine Sicherheitslücke. Wenn der Remote-Computer gefährdet ist, ist jetzt der lokale Computer gefährdet. Der erste Ansatz ist unbefriedigend, da das neu bereitgestellte Laufwerk eine gemeinsam genutzte Ressource ist, die anderen Programmen auf dem lokalen Computer während des Dateizugriffs durch das Programm zur Verfügung steht. Obwohl es durchaus möglich ist, dies temporär zu machen, ist es ihrer Meinung nach immer noch ein Loch.

Sie sind offen für die dritte Option, aber die Administratoren des Remotenetzwerks bestehen statt auf FTPS auf SFTP, und FtpWebRequest unterstützt nur FTPS. SFTP ist die Firewall-freundlichere Option, und es gibt ein paar Bibliotheken, die ich für diesen Ansatz verwenden könnte, aber ich würde es vorziehen, meine Abhängigkeiten zu reduzieren, wenn ich kann. 

Ich habe in MSDN nach einer verwalteten oder einer win32-Methode für die Verwendung der Remote-Dateifreigabe gesucht, aber ich habe nichts Nützliches gefunden. 

Und so frage ich: Gibt es einen anderen Weg? Habe ich eine supergeheime Win32-Funktion verpasst, die macht was ich will? Oder muss ich eine Variante von Option 3 verfolgen?

138
Randolpho

Um das Problem zu lösen, verwenden Sie eine Win32-API namens WNetUseConnection .
Verwenden Sie diese Funktion, um eine Verbindung mit einem UNC-Pfad mit Authentifizierung herzustellen, NICHT um ein Laufwerk zuzuordnen.

Dadurch können Sie eine Verbindung zu einem Remotecomputer herstellen, auch wenn sich dieser nicht in derselben Domäne befindet und auch ein anderer Benutzername und ein anderes Kennwort verwendet werden. 

Nachdem Sie WNetUseConnection verwendet haben, können Sie über einen UNC-Pfad auf die Datei zugreifen, als ob Sie sich in derselben Domäne befänden. Der beste Weg ist wahrscheinlich durch die administrativen eingebauten Freigaben.
Beispiel: \\ computername\c $\program files\Folder\file.txt

Hier ist ein Beispiel für einen C # -Code, der WNetUseConnection verwendet.

Beachten Sie, dass Sie für NetResource für lpLocalName und lpProvider null übergeben müssen. Der DwType sollte RESOURCETYPE_DISK sein. Der lpRemoteName sollte \\ Computername sein.

160
Brian R. Bondy

Für Leute, die nach einer schnellen Lösung suchen, können Sie die NetworkShareAccesser verwenden, die ich kürzlich geschrieben habe (basierend auf dieser Antwort (vielen Dank!)):

Verwendungszweck:

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

WARNUNG: Stellen Sie unbedingt sicher, dass Dispose der NetworkShareAccesser aufgerufen wird (auch wenn Ihre App abstürzt!), Andernfalls bleibt eine offene Verbindung unter Windows bestehen. Sie können alle offenen Verbindungen anzeigen, indem Sie die Eingabeaufforderung cmd öffnen und Net Use eingeben.

Der 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);
    }
}
107
GameScripting

AFAIK, Sie müssen den UNC-Pfad nicht zu einem Laufwerksbuchstaben zuordnen, um Anmeldeinformationen für einen Server festzulegen. Ich habe regelmäßig Batch-Skripte verwendet:

Net Use \\myserver /user:username password

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

Net Use /delete \\my.server.com

Jedes Programm, das auf demselben Konto wie Ihr Programm läuft, kann jedoch weiterhin auf alles zugreifen, auf das username:password Zugriff hat. Eine mögliche Lösung könnte sein, Ihr Programm in seinem eigenen lokalen Benutzerkonto zu isolieren (der UNC-Zugriff erfolgt lokal für das Konto, das Net Use aufgerufen hat).

Hinweis: Die Verwendung von SMB über Domains ist keine gute Verwendung der IMO-Technologie. Wenn Sicherheit so wichtig ist, ist die Tatsache, dass SMB keine Verschlüsselung aufweist, ein bisschen ein Dämpfer für sich.

15
Jacob

Obwohl ich mich selbst nicht kenne, würde ich sicherlich hoffen, dass # 2 falsch ist. Ich würde gerne glauben, dass Windows meine Anmeldeinformationen (am allerwenigsten mein Passwort!) AUTOMATISCH an keine Maschine verteilt Ganz zu schweigen von einem, das nicht zu meinem Vertrauen gehört.

Egal, haben Sie die Architektur für Identitätswechsel erforscht? Ihr Code wird folgendermaßen aussehen:

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

    context.Undo();
}

In diesem Fall ist die Variable token ein IntPtr. Um einen Wert für diese Variable zu erhalten, müssen Sie die nicht verwaltete Windows-API-Funktion "LogonUser" aufrufen. Eine kurze Reise nach pinvoke.net gibt uns folgende Signatur:

[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
);

Benutzername, Domäne und Kennwort sollten offensichtlich sein. Sehen Sie sich die verschiedenen Werte an, die an dwLogonType und dwLogonProvider übergeben werden können, um zu bestimmen, welcher Wert Ihren Anforderungen am besten entspricht.

Dieser Code wurde nicht getestet, da ich hier keine zweite Domäne habe, in der ich mich verifizieren kann. Dies sollte Sie hoffentlich auf die richtige Spur bringen.

4
Adam Robinson

Anstelle von WNetUseConnection würde ich NetUseAdd empfehlen. WNetUseConnection ist eine ältere Funktion, die von WNetUseConnection2 und WNetUseConnection3 verdrängt wurde. Mit all diesen Funktionen wird jedoch ein in Windows Explorer sichtbares Netzwerkgerät erstellt. NetUseAdd entspricht dem Aufrufen von Net Use in einer DOS-Eingabeaufforderung zur Authentifizierung auf einem Remotecomputer.

Wenn Sie NetUseAdd aufrufen, sollten nachfolgende Zugriffsversuche auf das Verzeichnis erfolgreich sein. 

3
Adam Robinson

Die meisten SFTP-Server unterstützen auch SCP, wodurch es einfacher sein kann, Bibliotheken zu finden. Sie können sogar einen vorhandenen Client von Ihrem Code aus aufrufen, z. B. pscp, das in PuTTY enthalten ist.

Wenn der Dateityp, mit dem Sie arbeiten, etwas wie eine Text- oder XML-Datei ist, können Sie sogar so weit gehen, dass Sie Ihre eigene Client/Server-Implementierung schreiben, um die Datei mithilfe von .NET Remoting oder Webservices zu bearbeiten.

2
Ryan Bolger

Ich habe gesehen, wie Option 3 mit JScape-Tools auf ziemlich unkomplizierte Weise implementiert wurde. Sie könnten es versuchen. Es ist nicht frei, aber es macht seinen Job.

1
DreamSonic

Hier wurde eine minimale POC-Klasse mit allen Krusten entfernt

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

Sie können \\server\share\folder mit/WNetUseConnection direkt verwenden, es ist nicht erforderlich, es vorher auf \\server part zu entfernen.

0
wqw

ich füge meinen vb.net Code basierend auf brian reference hinzu

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

wie man es benutzt

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