Search code examples
.netvb.netconnection-stringpassword-protectionconfigurationmanager

How to secure a connection string in memory after loading it from app.config in a Winforms application


I have a legacy application that uses a SQL Server authentication connection string to connect to a local or intranet based SQL Server instance. It currently uses the System.Configuration.ConfigurationManager to get the connection string from the app.config file. However, once that connection string is read from the app.config file, its value is loaded into memory and can be exposed using a tool like Process Hacker to view the applications memory. I currently have module with a method that returns the value of the connection string stored in a SecureString object. The connection string value is loaded into memory on the creation of the ConnectionStringSection Object. The app.config connection string xml is encrypted via instructions given from the microsoft documentation

https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/connection-strings-and-configuration-files

I understand that it is best practice to use Integrated Security, in this case our connection must use SQL Server Authentication. Is there a way eliminate or minimize the exposure of the connection string in the applications memory?

Public Function GetConnectionString() As SecureString
    Dim fileMap As ExeConfigurationFileMap = New ExeConfigurationFileMap
    fileMap.ExeConfigFilename = Environment.CurrentDirectory + "\app.config"
    Dim config As Configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None)
    Dim section As ConnectionStringsSection = TryCast(config.GetSection("connectionStrings"), ConnectionStringsSection)

    Dim secureString As New SecureString

    For Each character As Char In section.ConnectionStrings("ConString").ConnectionString.ToCharArray
        secureString.AppendChar(character)
    Next

    Return secureString
End Function

memory dump after first call to get connection string


Solution

  • Here is the workflow I used to keep the password out of memory in plain text.

    1) Encrypt the password in the connection string itself using symmetric encryption so when it is loaded into memory via the call

    Dim section As ConnectionStringsSection = TryCast(config.GetSection("connectionStrings"), ConnectionStringsSection)
    

    the value that is exposed is the encrypted value and not plain text.

    https://learn.microsoft.com/en-us/dotnet/standard/security/encrypting-data

    2) decrypt the password and append it one character at a time to a SecureString object for storage.

    https://learn.microsoft.com/en-us/dotnet/api/system.security.securestring?view=netframework-4.8

    Public Shared Function DecryptString(ByVal srcString As String) As SecureString
    
            Dim p As Byte() = Convert.FromBase64String(srcString)
            Dim rv As RijndaelManaged = New RijndaelManaged
            Dim ms As MemoryStream = New MemoryStream(p)
            Dim cs As CryptoStream = New CryptoStream(ms, rv.CreateDecryptor(keyb, ivb), CryptoStreamMode.Read)
            Dim secureString As New SecureString
            Try
                Do
                    Dim character As Integer = cs.ReadByte()
                    If character = -1 Then
                        Exit Do
                    End If
                    secureString.AppendChar(Chr(character))
                Loop
            Finally
                ms.Close()
                ms.Dispose()
                cs.Close()
                cs.Dispose()
            End Try
            secureString.MakeReadOnly()
            Return secureString
        End Function
    

    3) Use the SecureString object and username to construct a SqlCredential object where "GetUserName()" gets the username out of the connection string, and "GetPassword()" gets the SecureString password

    https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcredential.-ctor?view=netframework-4.8

    Dim SqlCredential = New SqlCredential(GetUserName(), GetPassword())
    

    4) From here you can construct a SQLConnection object using a connection string that only contains the "Initial Catalog=;Data Source=;" part of the connection string and the SQLCredential object. "GetConnectionString()" would return the aforementioned connection string.

    https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.-ctor?view=netframework-4.8#System_Data_SqlClient_SqlConnection__ctor_System_String_System_Data_SqlClient_SqlCredential_

    Dim connection = New SqlConnection(GetConnectionString(), SqlCredential)
    

    5) If you are using Entity Framework and need to pass that connection to your DBContext object you can use this constructor.

    https://learn.microsoft.com/en-us/ef/ef6/fundamentals/connection-management

    Dim myDbContext = New DBContext(New SqlConnection(ConnectionString, SqlCredential))