Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 88 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
88
Dung lượng
455,21 KB
Nội dung
Storing Data Securely | 679 { return pwdNode.InnerText; } return ""; } set { XmlNode pwdNode = settingsDoc.SelectSingleNode("Settings/Password"); string hashedPassword = CreateHashedPassword(value,null); if(pwdNode != null) { pwdNode.InnerText = hashedPassword; } else { XmlNode settingsNode = settingsDoc.SelectSingleNode("Settings"); XmlElement pwdElem = settingsDoc.CreateElement("Password"); pwdElem.InnerText=hashedPassword; settingsNode.AppendChild(pwdElem); } } } The CreateHashedPassword method creates the salted and hashed password. The password parameter is the plain text of the password; the existingSalt parameter is the salt to use when creating the salted and hashed version. If no salt exists, such as the first time a password is stored, existingSalt should be passed as null, and a ran- dom salt will be generated. Once you have the salt, it is combined with the plain text password and hashed using the SHA512Managed class. The salt value is then appended to the end of the hashed value and returned. The salt is appended so that when you attempt to vali- date the password, you know what salt was used to create the hashed value. The entire value is then base64-encoded and returned: // Make a hashed password. private string CreateHashedPassword(string password, byte[] existingSalt) { byte [] salt = null; if(existingSalt == null) { // Make a salt of random size. // Create a stronger hash code using RNGCryptoServiceProvider. byte[] random = new byte[1]; RNGCryptoServiceProvider rngSize = new RNGCryptoServiceProvider( ); // Populate with random bytes. rngSize.GetBytes(random); // Convert random bytes to string. 680 | Chapter 17: Security int size = Convert.ToInt32(random); // Create salt array. salt = new byte[size]; // Use the better random number generator to get // bytes for the salt. RNGCryptoServiceProvider rngSalt = new RNGCryptoServiceProvider( ); rngSalt.GetNonZeroBytes(salt); } else salt = existingSalt; // Turn string into bytes. byte[] pwd = Encoding.UTF8.GetBytes(password); // Make storage for both password and salt. byte[] saltedPwd = new byte[pwd.Length + salt.Length]; // Add pwd bytes first. pwd.CopyTo(saltedPwd,0); // now add salt salt.CopyTo(saltedPwd,pwd.Length); // Use SHA512 as the hashing algorithm. byte[] hashWithSalt = null; using (SHA512Managed sha512 = new SHA512Managed( )) { // Get hash of salted password. byte[] hash = sha512.ComputeHash(saltedPwd); // Append salt to hash so we have it. hashWithSalt = new byte[hash.Length + salt.Length]; // Copy in bytes. hash.CopyTo(hashWithSalt,0); salt.CopyTo(hashWithSalt,hash.Length); } // Return base64-encoded hash with salt. return Convert.ToBase64String(hashWithSalt); } To check a given password against the stored value (which is salted and hashed), you call IsPasswordValid and pass in the plain text password to check. First, the stored value is retrieved using the Password property and converted from base64. Since you know you used SHA512, there are 512 bits in the hash. But you need the byte size, so you do the math and get that size in bytes. This allows you to figure out where to get the salt from in the value, so you copy it out of the value and call CreateHashedPassword using that salt and the plain text password parameter. This gives you the hashed value for the password that was passed in to verify. Once you Storing Data Securely | 681 have that, you just compare it to the Password property to see whether you have a match and return true or false as appropriate: // Check the password against our storage. public bool IsPasswordValid(string password) { // Get bytes for password. // This is the hash of the salted password and the salt. byte[] hashWithSalt = Convert.FromBase64String(Password); // We used 512 bits as the hash size (SHA512). int hashSizeInBytes = 512 / 8; // Make holder for original salt. int saltSize = hashWithSalt.Length - hashSizeInBytes; byte[] salt = new byte[saltSize]; // Copy out the salt. Array.Copy(hashWithSalt,hashSizeInBytes,salt,0,saltSize); // Figure out hash for this password. string passwordHash = CreateHashedPassword(password,salt); // If the computed hash matches the specified hash, // the plain text value must be correct. // See if Password (stored) matched password passed in. return (Password == passwordHash); } } Code that uses the UserSettings class is shown here: class IsoApplication { static void Main(string[] args) { if(args.Length > 0) { UserSettings settings = new UserSettings(args[0]); if(settings.IsPasswordValid(args[0])) { Console.WriteLine("Welcome"); return; } } Console.WriteLine("The system could not validate your credentials"); } } The way to use this application is to pass the password on the command line as the first argument. This password is then checked against the UserSettings, which is stored in the isolated storage for this particular user. If the password is correct, the user is welcomed; if not, the user is shown the door. 682 | Chapter 17: Security Discussion Isolated storage allows an application to store data that is unique to the application and the user running it. This storage allows the application to write out state infor- mation that is not visible to other applications or even other users of the same appli- cation. Isolated storage is based on the code identity as determined by the CLR, and it stores the information either directly on the client machine or in isolated stores that can be opened and roam with the user. The storage space available to the appli- cation is directly controllable by the administrator of the machine on which the application operates. The Solution uses isolation by User, AppDomain, and Assembly by calling IsolatedStorageFile.GetUserStoreForDomain. This creates an isolated store that is accessible by only this user in the current assembly in the current AppDomain: // Get the isolated storage. isoStorageFile = IsolatedStorageFile.GetUserStoreForDomain( ); The Storeadm.exe utility will allow you to see which isolated-storage stores have been set up on the machine by running the utility with the /LIST command-line switch. Storeadm.exe is part of the .NET Framework SDK and can be located in your Visual Studio installation directory under the \SDK\v2.0\Bin subdirectory. The output after using the UserSettings class would look like this: C:\>storeadm /LIST Microsoft (R) .NET Framework Store Admin 1.1.4322.573 Copyright (C) Microsoft Corporation 1998-2002. All rights reserved. Record #1 [Domain] <System.Security.Policy.Url version="1"> <Url>file://D:/PRJ32/Book/IsolatedStorage/bin/Debug/IsolatedStorage.exe</ Url> </System.Security.Policy.Url> [Assembly] <System.Security.Policy.Url version="1"> <Url>file://D:/PRJ32/Book/IsolatedStorage/bin/Debug/IsolatedStorage.exe</ Url> </System.Security.Policy.Url> Size : 1024 Passwords should never be stored in plain text, period. It is a bad habit to get into, so in the UserSettings class, you have added the salting and hashing of the password value via the CreateHashedPassword method and verification through the IsPasswordValid method. Adding a salt to the hash helps to strengthen the protec- tion on the value being hashed so that the isolated storage, the hash, and the salt now protect the password you are storing. Making a Security Assert Safe | 683 See Also The “IsolatedStorageFile Class,” “IsolatedStorageStream Class,” “About Isolated Storage,” and “ComputeHash Method” topics in the MSDN documentation. 17.7 Making a Security Assert Safe Problem You want to assert that at a particular point in the call stack, a given permission is available for all subsequent calls. However, doing this can easily open a security hole to allow other malicious code to spoof your code or to create a back door into your component. You want to assert a given security permission, but you want to do so in a secure and efficient manner. Solution In order to make this approach secure, you need to call Demand on the permissions that the subsequent calls need. This makes sure that code that doesn’t have these permis- sions can’t slip by due to the Assert. The Demand is done to ensure that you have indeed been granted this permission before using the Assert to short-circuit the stackwalk. This is demonstrated by the function CallSecureFunctionSafelyAndEfficiently, which performs a Demand and an Assert before calling SecureFunction, which in turn does a Demand for a ReflectionPermission. The code listing for CallSecureFunctionSafelyAndEfficiently is shown in Example 17-10. Example 17-10. CallSecureFunctionSafelyAndEfficiently function public static void CallSecureFunctionSafelyAndEfficiently( ) { // Set up a permission to be able to access nonpublic members // via reflection. ReflectionPermission perm = new ReflectionPermission(ReflectionPermissionFlag.MemberAccess); // Demand the permission set we have compiled before using Assert // to make sure we have the right before we Assert it. We do // the Demand to ensure that we have checked for this permission // before using Assert to short-circuit stackwalking for it, which // helps us stay secure, while performing better. perm.Demand( ); // Assert this right before calling into the function that // would also perform the Demand to short-circuit the stack walk // each call would generate. The Assert helps us to optimize // our use of SecureFunction. 684 | Chapter 17: Security The code listing for SecureFunction is shown here: public static void SecureFunction( ) { // Set up a permission to be able to access nonpublic members // via reflection. ReflectionPermission perm = new ReflectionPermission(ReflectionPermissionFlag.MemberAccess); // Demand the right to do this and cause a stackwalk. perm.Demand( ); // Perform the action here } Discussion In the demonstration function CallSecureFunctionSafelyAndEfficiently, the func- tion you are calling ( SecureFunction) performs a Demand on a ReflectionPermission to ensure that the code can access nonpublic members of classes via reflection. Nor- mally, this would result in a stackwalk for every call to SecureFunction. The Demand in CallSecureFunctionSafelyAndEfficiently is there only to protect against the usage of the Assert in the first place. To make this more efficient, you can use Assert to state that all functions issuing Demands that are called from this one do not have to stack- walk any further. The Assert says stop checking for this permission in the call stack. In order to do this, you need the permission to call Assert. The problem comes in with this Assert, as it opens up a potential luring attack where SecureFunction is called via CallSecureFunctionSafelyAndEfficiently, which calls Assert to stop the Demand stackwalks from SecureFunction. If unauthorized code with- out ReflectionPermission were able to call CallSecureFunctionSafelyAndEfficiently, the Assert would prevent the SecureFunction Demand call from determining that there is some code in the call stack without the proper rights. This is the power of the call stack checking in the CLR when a Demand occurs. In order to protect against this, you issue a Demand for the ReflectionPermission needed by SecureFunction in CallSecureFunctionSafelyAndEfficiently to close this perm.Assert( ); // We call the secure function 100 times but only generate // the stackwalk from the function to this calling function // instead of walking the whole stack 100 times. for(int i=0;i<100;i++) { SecureFunction( ); } } Example 17-10. CallSecureFunctionSafelyAndEfficiently function (continued) Verifying That an Assembly Has Been Granted Specific Permissions | 685 hole before issuing the Assert. The combination of this Demand and the Assert causes you to do one stackwalk instead of the original 100 that would have been caused by the Demand in SecureFunction. Security optimization techniques, such as using Assert in this case (even though it isn’t the primary reason to use Assert), can help class library as well as control devel- opers who are trusted to perform Asserts in order to speed the interaction of their code with the CLR; but if used improperly, these techniques can also open up holes in the security picture. This example shows that you can have both performance and security where secure access is concerned. If you are using Assert, be mindful that stackwalk overrides should never be made in a class constructor. Constructors are not guaranteed to have any particular security context, nor are they guaranteed to execute at a specific point in time. This lack leads to the call stack not being well defined, and Assert used here can produce unex- pected results. One other thing to remember with Assert is that you can have only one active Assert in a function at a given time. If you Assert the same permission twice, a SecurityException is thrown by the CLR. You must revert the original Assert first using RevertAssert. Then, you can declare the second Assert. See Also The “CodeAccessSecurity.Assert Method,” “CodeAccessSecurity.Demand Method,” “CodeAccessSecurity.RevertAssert Method,” and “Overriding Security Checks” top- ics in the MSDN documentation. 17.8 Verifying That an Assembly Has Been Granted Specific Permissions Problem When your assembly requests optional permissions (such as asking for disk access to enable users to export data to disk as a product feature) using the SecurityAction. RequestOptional flag, it might or might not get those permissions. Regardless, your assembly will still load and execute. You need a way to verify whether your assembly actually obtained those permissions. This can help prevent many security exceptions from being thrown. For example, if you optionally requested read/write permissions on the registry but did not receive them, you could disable the user interface con- trols that are used to read and store application settings in the registry. 686 | Chapter 17: Security Solution Check to see if your assembly received the optional permissions using the SecurityManager.IsGranted method like this: using System; using System.Text.RegularExpressions; using System.Web; using System.Net; using System.Security; Regex regex = new Regex(@"http://www\.oreilly\.com/.*"); WebPermission webConnectPerm = new WebPermission(NetworkAccess. Connect,regex); if(SecurityManager.IsGranted(webConnectPerm)) { // Connect to the O'Reilly site. } This code sets up a Regex for the O’Reilly web site and then uses it to create a WebPermission for connecting to that site and all sites containing the string. You then check the WebPermission by calling SecurityManager.IsGranted to see whether you have permission to do this. Discussion The IsGranted method is a lightweight way of determining whether permission is granted for an assembly without first incurring the full stackwalk that a Demand gives you. Note, however, that once you exercise the code that performs the Demand, the full stackwalk will then take place. The drawback to this approach is that the code is still subject to a luring attack if Assert is misused, so you need to consider where the call to IsGranted is being made in the overall scheme of your security. Some of the reasons you might design an assembly to have optional permissions is for deployment in different customer scenarios. In some scenarios (such as desktop applications), it might be acceptable to have an assembly that can perform more robust actions (talk to a database, create network traffic via HTTP, etc.). In other scenarios, you can defer these actions if the customer does not wish to grant enough permissions for these extra services to function. See Also The “WebPermission Class,” “SecurityManager Class,” and “IsGranted Method” topics in the MSDN documentation. Minimizing the Attack Surface of an Assembly | 687 17.9 Minimizing the Attack Surface of an Assembly Problem Someone attacking your assembly will first attempt to find out as many things as possible about your assembly and then use this information in constructing the attack(s). The more surface area you give to attackers, the more they have to work with. You need to minimize what your assembly is allowed to do so that, if an attacker is successful in taking it over, the attacker will not have the necessary privi- leges to do any damage to the system. Solution Use the SecurityAction.RequestRefuse enumeration member to indicate, at an assembly level, the permissions that you do not wish this assembly to have. This will force the CLR to refuse these permissions to your code and will ensure that, even if another part of the system is compromised, your code cannot be used to perform functions that it does not need the rights to do. The following example allows the assembly to perform file I/O as part of its minimal permission set but explicitly refuses to allow this assembly to have permissions to skip verification: [assembly: FileIOPermission(SecurityAction.RequestMinimum,Unrestricted=true)] [assembly: SecurityPermission(SecurityAction.RequestRefuse, SkipVerification=false)] Discussion Once you have determined what permissions your assembly needs as part of your normal security testing, you can use RequestRefuse to lock down your code. If this seems extreme, think of scenarios in which your code could be accessing a data store containing sensitive information, such as social security numbers or salary informa- tion. This proactive step can help you show your customers that you take security seriously and can help defend your interests in case a break-in occurs on a system that your code is part of. One serious consideration with this approach is that the use of RequestRefuse marks your assembly as partially trusted. This in turn prevents it from calling any strong- named assembly that hasn’t been marked with the AllowPartiallyTrustedCallers attribute. See Also Chapter 8 of Microsoft Patterns & Practices Group: http://msdn.microsoft.com/library/ default.asp?url=/library/en-us/dnnetsec/html/THCMCh08.asp; see the “SecurityAction Enumeration” and “Global Attributes” topics in the MSDN documentation. 688 | Chapter 17: Security 17.10 Obtaining Security/Audit Information Problem You need to obtain the security rights and/or audit information for a file or registry key. Solution When obtaining security/audit information for a file, use the static GetAccessControl method of the File class to obtain a System.Security.AccessControl.FileSecurity object. Use the FileSecurity object to access the security and audit information for the file. These steps are demonstrated in Example 17-11. Example 17-11. Obtaining security audit information public static void ViewFileRights( ) { // Get security information from a file. string file = @"c:\FOO.TXT"; FileSecurity fileSec = File.GetAccessControl(file); DisplayFileSecurityInfo(fileSec); } public static void DisplayFileSecurityInfo(FileSecurity fileSec) { Console.WriteLine("GetSecurityDescriptorSddlForm: {0}", fileSec.GetSecurityDescriptorSddlForm(AccessControlSections.All)); foreach (FileSystemAccessRule ace in fileSec.GetAccessRules(true, true, typeof(NTAccount))) { Console.WriteLine("\tIdentityReference.Value: {0}", ace.IdentityReference.Value); Console.WriteLine("\tAccessControlType: {0}", ace.AccessControlType); Console.WriteLine("\tFileSystemRights: {0}", ace.FileSystemRights); Console.WriteLine("\tInheritanceFlags: {0}", ace.InheritanceFlags); Console.WriteLine("\tIsInherited: {0}", ace.IsInherited); Console.WriteLine("\tPropagationFlags: {0}", ace.PropagationFlags); Console.WriteLine(" \r\n\r\n"); } foreach (FileSystemAuditRule ace in fileSec.GetAuditRules(true, true, typeof(NTAccount))) { Console.WriteLine("\tIdentityReference.Value: {0}", ace.IdentityReference.Value); Console.WriteLine("\tAuditFlags: {0}", ace.AuditFlags); Console.WriteLine("\tFileSystemRights: {0}", ace.FileSystemRights); Console.WriteLine("\tInheritanceFlags: {0}", ace.InheritanceFlags); Console.WriteLine("\tIsInherited: {0}", ace.IsInherited); [...]... encrypt data, you can call the EncryptWebConfigData method with the following arguments: EncryptWebConfigData("/WebApplication1", "appSettings", "DataProtectionConfigurationProvider"); The first argument is the virtual path to the web application, the second argument is the section that you want to encrypt, and the last argument is the data protection provider that you want to use to encrypt the data. .. which to send data However, encryption is just one of the security features built into the SslStream object Another feature of SslStream is that it detects malicious or even accidental modification to the data Even though the data is encrypted, it may become modified during transit To determine if this has occurred, the data is signed with a hash before it is sent When it is received, the data is rehashed... webConfigSection.SectionInformation.ProtectSection(dataProtectionProvider); The dataProtectionProvider argument is a string identifying which data protection provider you want to use to encrypt the section information The two available providers are DpapiProtectedConfigurationProvider and RsaProtectedConfigurationProvider The DpapiProtectedConfigurationProvider class makes use of the Data Protection API (DPAPI) to encrypt and decrypt data The... the MSDN documentation 17.14 Encrypting web.config Information Problem You need to encrypt data within a web.config file programmatically Solution To encrypt data within a web.config file section, use the following method: public static void EncryptWebConfigData(string appPath, string protectedSection, string dataProtectionProvider) { System.Configuration.Configuration webConfig = WebConfigurationManager.OpenWebConfiguration(appPath);... for reading data byte[] bytes = new byte[1024]; string clientData = null; using (SslStream sslStream = new SslStream(newClient.GetStream( ))) { sslStream.AuthenticateAsServer(GetServerCert("MyTestCert2"), false, SslProtocols.Default, true); // Loop to receive all the data sent by the client int bytesRead = 0; while ((bytesRead = sslStream.Read(bytes, 0, bytes.Length)) != 0) { // Translate data bytes... string data that you want to keep secret Some of the data you may want to store in a SecureString object would be a social security number, a credit card number, a PIN number, a password, an employee ID, or any other type of sensitive information This string data is automatically encrypted immediately upon being added to the SecureString object, and it is automatically decrypted when the string data. .. encrypt and decrypt data Encrypting web.config Information | 709 The final step to encrypting the section information is to call the Save method of the System.Configuration.Configuration object This saves the changes to the web.config file If this method is not called, the encrypted data will not be saved To decrypt data within a web.config file, you can call the DecryptWebConfigData method with the... following parameters: DecryptWebConfigData("/WebApplication1", "appSettings"); The first argument is the virtual path to the web application; the second argument is the section that you want to encrypt DecryptWebConfigData method operates very similarly to the EncryptWebConfigData method, except that it calls the UnprotectSection method to The decrypt the encrypted data in the web.config file: webConfigSection.SectionInformation.UnprotectSection(... your code Protecting String Data with Secure Strings | 697 to determine if this new SecureString object should be made read-only similarly to its original SecureString object The SecureString object can be used only on Windows 2000 (with Service Pack 3 or greater) or later operating system In this recipe, you create a SecureString object from data read in from a stream This data could also come from... GetSection(protectedSection); if (!webConfigSection.SectionInformation.IsProtected) { webConfigSection.SectionInformation ProtectSection(dataProtectionProvider); webConfig.Save( ); } } To decrypt data within a web.config file section, use the following method: public static void DecryptWebConfigData(string appPath, string protectedSection) { System.Configuration.Configuration webConfig = WebConfigurationManager.OpenWebConfiguration(appPath); . Storing Data Securely | 679 { return pwdNode.InnerText; } return ""; } set { XmlNode. parameter. This gives you the hashed value for the password that was passed in to verify. Once you Storing Data Securely | 681 have that, you just compare it to the Password property to see whether you. String Data with Secure Strings Problem You need to store sensitive information, such as a social security number, in a string. However, you do not want prying eyes to be able to view this data