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
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
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
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.
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))