Можно ли безопасно получить значение SecureString из VB .NET? - PullRequest
5 голосов
/ 13 октября 2009

Я всегда чувствовал, что SecureString был немного странным, но предполагал, что большинство моих проблем с ним связано с проблемами безопасности, которые я не понимаю. Сегодня я решил сесть и научить себя этому, но наткнулся на то, что кажется смертельным загвоздкой.

Сценарий, который я представляю, - «пользователь вводит пароль в текстовое поле, этот пароль хэшируется и сравнивается с сохраненным хэшем». Сначала я волновался, что текстовое поле содержит строку, но потом я понял, что вы можете свернуть пользовательское текстовое поле, которое использует SecureString в качестве хранилища. Здорово. Эта часть «этот пароль хэшируется и сравнивается ...» доставляет мне неприятности.

Мой первый взлом на проблему в VB .NET был наивным и неправильным:

Dim passwordHandle As IntPtr
Dim insecurePassword As String = Nothing
Try
    passwordHandle = Marshal.SecureStringToBSTR(_password)
    insecurePassword = Marshal.PtrToStringBSTR(passwordHandle)
Catch ex As Exception

Finally
    If passwordHandle <> IntPtr.Zero Then
        Marshal.ZeroFreeBSTR(passwordHandle)
    End If
End Try

If insecurePassword <> Nothing Then
    ' Do hash and comparison
End If

Это просто вставляет пароль в обычную строку и в первую очередь лишает смысла использование SecureString. Поэтому я продолжил поиск и нашел пост в блоге , который хорошо решает проблему в C #: строка превращается в BSTR, копируется в закрепленную строку, затем и BSTR, и закрепленная строка обнуляются после использования. Это кажется намного лучшей идеей, потому что минимизирует время, в течение которого небезопасная строка находится в памяти. Тем не менее, не похоже, что есть способ осуществить это в VB .NET. C # использует функцию небезопасного кода для манипулирования указателями, но VB .NET не может этого сделать. Я посмотрел на Marhsall.Copy (), но похоже, что он ориентирован на массивы. Я думал о том, чтобы попытаться привести переменные IntPtr для объекта и BSTR к строкам, но я все еще оставил использование метода, такого как String.Replace (), который создаст новую строку.

Разве это вообще невозможно сделать из VB .NET, или мне чего-то не хватает?

редактировать Я принимаю ответ AMissico только с небольшими оговорками. Метод Marshal.ReadByte () скопирует байт из неуправляемой памяти и создаст байт в неуправляемой памяти. Это дает небольшую вероятность того, что злоумышленник сможет найти отдельные символы пароля. Я думаю, что это намного меньше, чем шансы найти целую строку, но C # в (очевидно, несуществующей) статье, на которую я ссылался, смог использовать небезопасный код, чтобы избежать этого аккуратно. Мысленный процесс состоял в том, что он использовал GCHandle для закрепления строки в памяти, а затем использовал небезопасный код, чтобы обойти неизменность строк .NET. Умный трюк, который кажется невозможным в VB .NET. Я постараюсь вернуться с самим кодом C #.

Ответы [ 3 ]

3 голосов
/ 31 октября 2009

Ссылка на «Маршалинг паролей SecureString в строку» - Марк Николсон "на http://dotnet.org.za/markn/archive/2008/10/04/handling-passwords.aspx наконец отображается.

Итак, ваше утверждение «C # использует небезопасную функцию кода для манипулирования указателями», похоже, решено с помощью «КАК: обернуть UCOMIStream в класс Stream в Visual Basic .NET» на http://support.microsoft.com/kb/321695. (Я искал на "AddrOfPinnedObject".)

Я не прочитал весь ваш вопрос (ссылка на сообщение всегда отключена), но полезны ли эти классы и тестовый код? Пароль никогда не живет как System.String; поэтому вам нужна реализация SecureStringTextBox, как отмечено в вашем вопросе.

Мне не нравится добавлять весь этот код. Дайте мне знать, какой код полезен, и я отредактирую ответ, чтобы оставить только полезные материалы.

Imports System.Security
Imports System.Security.Principal
Imports System.Security.Permissions
Imports System.Runtime.InteropServices

''' <summary>
''' Helper class to programmatically impersonate a user, load and unload a user's profile, and perform other maintenance-related tasks for impersonating a user.
''' </summary>
Public Class ImpersonationHelper
    Implements IDisposable

#Region " IDisposable Implementaton "

    Private _disposed As Boolean

    Protected Overrides Sub Finalize()
        Dispose(False)
        MyBase.Finalize()
    End Sub

    ''' <summary>
    ''' Implementation of the <b>IDisposable</b> interface.
    ''' </summary>
    ''' <remarks>This method calls <see>Undo</see> if impersonation is still being performed. This method calls the common language runtime version of the Dispose method.</remarks>
    Public Overloads Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)
        System.GC.SuppressFinalize(Me)
    End Sub

    ''' <summary>
    ''' Implementation of the <b>IDisposable</b> interface.
    ''' </summary>
    ''' <param name="disposing">If <b>true</b>, the object to be disposed is finalized and collected by the garbage collector; otherwise, <b>false</b>.</param>
    ''' <remarks>This method calls Undo if impersonation is still being performed. This method calls the common language runtime version of the Dispose method.</remarks>
    Protected Overloads Sub Dispose(ByVal disposing As Boolean)
        If Not _disposed Then
            If disposing Then
                If Not IsNothing(_impersonationContext) Then
                    _impersonationContext.Undo()
                    _impersonationContext.Dispose()
                End If
            End If
            _impersonationContext = Nothing
        End If
        _disposed = True
    End Sub

#End Region

    '2009.02.12 AMJ
    '   Modified From:
    '       How to implement impersonation in an ASP.NET application (KB306158)
    '       http://support.microsoft.com/kb/306158
    '   Implemented IDisposable based on ImpersonationHelper class of
    '       Namespace: Microsoft.Office.Excel.Server.Addins.ComputeCluster.Security
    '       Assembly: Microsoft.Office.Excel.Server.Addins.ComputeCluster (in microsoft.office.excel.server.addins.computecluster.dll)

    Const LOGON32_LOGON_INTERACTIVE As Integer = 2
    Const LOGON32_LOGON_BATCH As Integer = 4
    Const LOGON32_LOGON_SERVICE As Integer = 5

    Const LOGON32_PROVIDER_DEFAULT As Integer = 0
    Const LOGON32_PROVIDER_WINNT35 As Integer = 1

    Private Enum SECURITY_IMPERSONATION_LEVEL
        SecurityAnonymous = 0
        SecurityIdentification = 1
        SecurityImpersonation = 2
        SecurityDelegation = 3
    End Enum

    Private Declare Auto Function LogonUser Lib "advapi32.dll" ( _
        ByVal username As String, _
        ByVal domain As String, _
        ByVal password As IntPtr, _
        ByVal logonType As Integer, _
        ByVal logonProvider As Integer, _
        ByRef token As IntPtr) As Boolean

    Private Declare Auto Function DuplicateToken Lib "advapi32.dll" ( _
        ByVal ExistingTokenHandle As IntPtr, _
        ByVal ImpersonationLevel As SECURITY_IMPERSONATION_LEVEL, _
        ByRef DuplicateTokenHandle As IntPtr) As Integer

    Private Declare Auto Function RevertToSelf Lib "advapi32.dll" () As Long
    Private Declare Auto Function CloseHandle Lib "kernel32.dll" (ByVal handle As IntPtr) As Long

    Dim _impersonationContext As WindowsImpersonationContext
    Dim _domain As String
    Dim _login As String
    Dim _password As SecureString

#Region " Standard Constructor & Properties "

    ''' <summary>
    ''' Initializes a new instance of the ImpersonationHelper class.
    ''' </summary>
    ''' <param name="domain">The domain or computer name of the user to impersonate.</param>
    ''' <param name="userName">The user name of the user to impersonate.</param>
    ''' <param name="password">The secure string password of UserName. For more information about secure strings, see the <see cref="System.Security.SecureString">SecureString</see> class.</param>
    <DebuggerNonUserCode()> _
    Public Sub New(ByVal domain As String, ByVal userName As String, ByVal password As SecureString)
        Me.Domain = domain
        Me.Login = userName
        Me.Password = password
    End Sub

    ''' <summary>
    ''' Do not allow a new instance of the ImpersonationHelper class without credentials.
    ''' </summary>
    Private Sub New()

    End Sub

    ''' <summary>
    ''' Gets or sets the domain of the user to impersonate.
    ''' </summary>
    ''' <value>The domain of the user.</value>
    <DebuggerNonUserCode()> _
    Public Property Domain() As String
        Get
            Return _domain
        End Get
        Set(ByVal value As String)
            _domain = value
        End Set
    End Property

    ''' <summary>
    ''' Gets or sets the user name of the user to impersonate.
    ''' </summary>
    ''' <value>The user name.</value>
    <DebuggerNonUserCode()> _
    Public Property Login() As String
        Get
            Return _login
        End Get
        Set(ByVal value As String)
            _login = value
        End Set
    End Property

    ''' <summary>
    ''' Sets the encrypted password of the user to impersonate. 
    ''' </summary>
    ''' <value>The encrypted password.</value>
    <DebuggerNonUserCode()> _
    Public WriteOnly Property Password() As SecureString
        Set(ByVal value As SecureString)
            _password = value
        End Set
    End Property

#End Region

    ''' <summary>
    ''' Performs the impersonation of the user based on the parameters provided in the constructor. 
    ''' </summary>
    ''' <remarks>
    ''' <para>If logon fails using the supplied credentials, an exception is thrown. The exception is thrown because this method is unable to duplicate the logged-on user's token for purposes of impersonation or is unable to create a Windows identity from the user's impersonated token.</para>
    ''' <para>For details about the direct cause of the impersonation failure, you can inspect the inner exception.</para>
    ''' </remarks>
    <PermissionSetAttribute(SecurityAction.Demand, Name:="FullTrust")> _
    Public Sub ImpersonateUser()

        Dim fResult As Boolean = False  'assume impersonation failed

        Dim hPassword As IntPtr = IntPtr.Zero
        Dim hToken As IntPtr = IntPtr.Zero
        Dim hTokenDuplicate As IntPtr = IntPtr.Zero
        Dim oException As ImpersonationException = Nothing

        If RevertToSelf <> 0 Then

            hPassword = Marshal.SecureStringToGlobalAllocUnicode(_password)

            If LogonUser(Me.Login, Me.Domain, hPassword, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, hToken) Then
                If DuplicateToken(hToken, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, hTokenDuplicate) <> 0 Then
                    _impersonationContext = New WindowsIdentity(hTokenDuplicate).Impersonate()
                    If Not _impersonationContext Is Nothing Then
                        fResult = True
                    End If
                End If
            Else
                oException = New ImpersonationException(Me.Login, Me.Domain)
            End If

            If hPassword.Equals(IntPtr.Zero) = False Then
                Marshal.ZeroFreeGlobalAllocUnicode(hPassword)
            End If

        End If

        If Not hTokenDuplicate.Equals(IntPtr.Zero) Then
            CloseHandle(hTokenDuplicate)
        End If

        If Not hToken.Equals(IntPtr.Zero) Then
            CloseHandle(hToken)
        End If

        If Not (oException Is Nothing) Then
            Throw oException
        End If

    End Sub

    ''' <summary>
    ''' Undoes the impersonation of the user, if it is impersonated.
    ''' </summary>
    ''' <remarks>Use this method to free the objects associated with impersonation.</remarks>
    <PermissionSetAttribute(SecurityAction.Demand, Name:="FullTrust")> _
    <DebuggerNonUserCode()> _
    Public Sub Undo()
        _impersonationContext.Undo()
        _impersonationContext = Nothing
    End Sub

    Public Shared Function InvokeAsUser(ByVal userName As String, ByVal domain As String, ByVal password As SecureString, ByVal methodToCall As [Delegate], ByVal ParamArray parameters() As Object) As Object
        Dim oResult As Object = Nothing

        Using oImpersonation As New ImpersonationHelper(domain, userName, password)

            oImpersonation.ImpersonateUser()

            oResult = methodToCall.DynamicInvoke(parameters)

        End Using

        Return oResult
    End Function

End Class

Public Class ImpersonationException
    Inherits System.Exception

    Public ReadOnly Login As String
    Public ReadOnly Domain As String

    Public Sub New(ByVal userName As String, ByVal domain As String)
        MyBase.New(String.Format("Impersonation failure: {1}\{0}", userName, domain), New System.ComponentModel.Win32Exception)
    End Sub

End Class

Imports Missico.Personal

Imports System.Security

Imports Microsoft.VisualStudio.TestTools.UnitTesting

<TestClass()> _
Public Class ImpersonationHelperTest

    Private testContextInstance As TestContext

    Public Property TestContext() As TestContext
        Get
            Return testContextInstance
        End Get
        Set(ByVal value As TestContext)
            testContextInstance = value
        End Set
    End Property

    <TestMethod()> _
    Public Sub ImpersonationHelperTest()

        'testing only, never initialize the characters of the password in this fashion
        'replace with valid password

        Dim oPassword As New System.Security.SecureString

        oPassword.AppendChar("o"c)
        oPassword.AppendChar("o"c)
        oPassword.AppendChar("p"c)
        oPassword.AppendChar("s"c)
        oPassword.AppendChar("!"c)
        oPassword.AppendChar(" "c)
        oPassword.AppendChar("n"c)
        oPassword.AppendChar("o"c)
        oPassword.AppendChar(" "c)
        oPassword.AppendChar("p"c)
        oPassword.AppendChar("a"c)
        oPassword.AppendChar("s"c)
        oPassword.AppendChar("s"c)
        oPassword.AppendChar("w"c)
        oPassword.AppendChar("o"c)
        oPassword.AppendChar("r"c)
        oPassword.AppendChar("d"c)

        Using oImpersonation As New ImpersonationHelper("ANTHONY", "amissico", oPassword)

            oImpersonation.ImpersonateUser()

            '...

        End Using

        Try

            Using oImpersonation As New ImpersonationHelper("INVALID", "amissico", oPassword)

                oImpersonation.ImpersonateUser()

                '...

            End Using

        Catch ex As ImpersonationException
            'expected
            '   due to invalid domain
        End Try


        Try

            Using oImpersonation As New ImpersonationHelper("ANTHONY", "INVALID", oPassword)

                oImpersonation.ImpersonateUser()

                '...

            End Using

        Catch ex As ImpersonationException
            'expected
            '   due to invalid user

        End Try

        Try

            oPassword.AppendChar(" "c) 'invalidate password

            Using oImpersonation As New ImpersonationHelper("ANTHONY", "amissico", oPassword)

                oImpersonation.ImpersonateUser()

                '...

            End Using

        Catch ex As ImpersonationException
            'expected
            '   due to invalid password

        End Try


    End Sub

End Class

Imports System.Security
Imports System.Runtime.InteropServices
Imports System.Runtime.CompilerServices

Public Module SecureStringExtensions

    ''' <summary>
    ''' Determines whether the specified <see cref="System.Security.SecureString">System.Security.SecureString</see> instances are considered equal.
    ''' </summary>
    ''' <param name="valueA">The first <see cref="System.Security.SecureString">System.Security.SecureString</see> to compare.</param>
    ''' <param name="valueB">The second <see cref="System.Security.SecureString">System.Security.SecureString</see> to compare.</param>
    ''' <returns>True if valueA is equal to valueB; otherwise, False.</returns>
    <Extension()> _
    Public Function Equals(ByVal valueA As SecureString, ByVal valueB As SecureString) As Boolean
        Return IsEqual(valueA, valueB)
    End Function

    ''' <summary>
    ''' Determines whether the specified <see cref="System.Security.SecureString">System.Security.SecureString</see> instances are considered equal.
    ''' </summary>
    ''' <param name="valueA">The first <see cref="System.Security.SecureString">System.Security.SecureString</see> to compare.</param>
    ''' <param name="valueB">The second <see cref="System.Security.SecureString">System.Security.SecureString</see> to compare.</param>
    ''' <returns>True if valueA is equal to valueB; otherwise, False.</returns>
    ''' <remarks>Comparison loop based on Microsoft souce code for String.EqualsHelper method.</remarks>
    <Extension()> _
    Public Function IsEqual(ByVal valueA As SecureString, ByVal valueB As SecureString) As Boolean
        Dim fResult As Boolean = False  'assume failure

        'short-circuit if lengths are not the same

        If valueA.Length <> valueB.Length Then
            'cannot be the same value
            Return False
        End If

        Using oCopyA As SecureString = valueA.Copy, oCopyB As SecureString = valueB.Copy

            Dim iLength As Integer = oCopyA.Length

            Dim oPtrA As IntPtr = Marshal.SecureStringToBSTR(oCopyA)
            Dim oPtrB As IntPtr = Marshal.SecureStringToBSTR(oCopyB)

            Try

                Do While (iLength > 0)

                    If Marshal.ReadByte(oPtrA, iLength) <> Marshal.ReadByte(oPtrB, iLength) Then
                        Exit Do
                    End If

                    iLength -= 1

                Loop

                fResult = (iLength <= 0)

            Finally
                Marshal.ZeroFreeBSTR(oPtrA)
                Marshal.ZeroFreeBSTR(oPtrA)

            End Try

        End Using

        Return fResult
    End Function

End Module

Imports System.Security
Imports System.Diagnostics

Imports Microsoft.VisualStudio.TestTools.UnitTesting

Imports Missico.Security.SecureStringExtensions

<TestClass()> _
Public Class SecureStringExtensionsFixture

#Region " TestContext "

    Private testContextInstance As TestContext

    Public Property TestContext() As TestContext
        Get
            Return testContextInstance
        End Get
        Set(ByVal value As TestContext)
            testContextInstance = value
        End Set
    End Property

#End Region

    <TestMethod()> _
    Public Sub EqualsTest()

        Dim oValueA As New SecureString
        Dim oValueB As New SecureString

        oValueA.AppendChar("p"c)
        oValueA.AppendChar("a"c)
        oValueA.AppendChar("s"c)
        oValueA.AppendChar("s"c)
        oValueA.AppendChar("w"c)
        oValueA.AppendChar("o"c)
        oValueA.AppendChar("r"c)
        oValueA.AppendChar("d"c)

        oValueB.AppendChar("p"c)
        oValueB.AppendChar("a"c)
        oValueB.AppendChar("s"c)
        oValueB.AppendChar("s"c)
        oValueB.AppendChar("w"c)
        oValueB.AppendChar("o"c)
        oValueB.AppendChar("r"c)
        oValueB.AppendChar("d"c)


        'The Object.Equal method does not work because you cannot compare to secure strings.

        If oValueA.Equals(oValueB) Then
            'expected, but does not work
            'you cannot compare two secure strings
        Else
            'always fails
        End If


        'Using the fully-qualified path to the Equal extension method.

        If Missico.Security.SecureStringExtensions.Equals(oValueA, oValueB) Then
            'expected
        Else
            Assert.Fail("SecureString values are not equal, which is not expected.")
        End If


        'Using the IsEqual extension method that does not conflict with the Object.Equal method.

        If oValueA.IsEqual(oValueB) Then
            'expected
        Else
            Assert.Fail("SecureString values are not equal, which is not expected.")
        End If


        'change the second value

        oValueB.AppendChar(" "c)

        If oValueA.IsEqual(oValueB) Then
            Assert.Fail("SecureString values are equal, which is not expected.")
        Else
            'expected
        End If

    End Sub

End Class
0 голосов
/ 18 июня 2014

Только исправление к коду выше:

линия

   Dim iLength As Integer = oCopyA.Length

неверно. Как написано, функция IsEqual будет проверять только половину + 1 символов в строках.

Правильная строка должна быть:

   Dim iLength As Integer = oCopyA.Length*2-1

, поскольку BSTR использует 2 байта на символ, а индекс начинается с нуля, как обычно. Длина задает количество символов, а не байтов.

В противном случае, это похоже на работу.

cignaciob

Привет

0 голосов
/ 27 июля 2012

Все примеры, которые я видел, не учитывают тот факт, что тип входа в систему не подходит для всех решений.

Например, это будет работать только в том случае, если пользователь, который вы олицетворяете, имеет разрешение на вход в целевую систему. Не всегда так при доступе к удаленному серверу SQL Server. LOGON32_LOGON_INTERACTIVE

NetworkClearText - единственный, который работает последовательно для использования с соединениями SQL Server. - Отсутствие открытого текста не означает, что учетные данные передаются небезопасным образом.

Когда в рабочей группе вам нужно выдать себя за пользователя домена, то работает NewCredentials. (не проверено с подключениями к SQL Server)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...