Redemption Steps

Weak access control is, for the most part, a design-level problem. The best approach to solving design-level problems is to use threat modeling. Carefully consider all of the objects your application creates, both at install time and at run time. One of the best code reviewers at Microsoft claims to find most of his bugs Using notepad.exe and my brainuse your brain!

A somewhat more difficult redemption step is to educate yourself about the platform(s) that you write code for, and understand how the underlying security subsystems really work. You must, we repeat, must understand how access control mechanisms work on your target platforms.

One way to keep out of trouble is to make a distinction between system-wide information and user -level information. If you do this, then setting access controls becomes very simple and you can usually take the defaults.

Now for the big section: remediation of embedded secrets. Remedying this sin is not necessarily easy; if it was easy, we wouldnt have this sin! There are two potential remedies:

  • Use the operating systems security technologies

  • Move the secret data out of harms way

Lets look at each in detail, but before we continue, we want to point out a very important maxim :

Software cannot protect itself.

A malicious user with unbridled access to a computer system, and enough know-how could access all the secrets on the box, especially if the user is admin or root.

Its imperative that you always consider who you are defending the system against, and then determine if the defense is good enough for the sensitivity of the secret you want to store. Defending a private key used to sign documents that could live 20 years is a harder job than protecting a password used to allow access to the Membership section of a web site.

Lets first look at leveraging the OS as a defense.

Use the Operating Systems Security Technologies

At the time of this writing, only Windows and Mac OS X support comprehensive system-wide capabilities to store sensitive data where the OS performs the critical (and difficult) key management. In the case of Windows, you can use the Data Protection API (DPAPI); and Mac OS X supports KeyChain.

DPAPI is very easy to use from any language running atop Windows. Theres a full explanation of how DPAPI works on http://msdn.microsoft.com; a link to this page is in the Other Resources section.

The C/C++ redemption shows how to use native DPAPI, and the C# example shows how to use DPAPI from Managed Code and the .NET Framework Version 2.0.

Note 

There is no class in .NET 1.x to call DPAPI, but there are many wrappers. Refer to Writing Secure Code, Second Edition by Michael Howard and David C. LeBlanc (Microsoft Press, 2002) for an example .

On Windows, you can also use Crypto API (CAPI) to access encryption keys, and rather than using the key directly, you pass a handle to a hidden key around the system. This is also explained in Writing Secure Code, Second Edition by Michael Howard and David C. LeBlanc (Microsoft Press, 2002).

C/C++ Windows 2000 and Later Redemption

The code that follows shows how to set up and call DPAPI in C/C++ on Windows 2000 or later. There are two functions in this code you must implement yourself; one returns a static BYTE* to the secret data, and the other returns a static BYTE* to some optional, extra entropy. At the very end, the code calls SecureZeroMemory to scrub the data from memory. This is used instead of memset or ZeroMemory , which may be optimized out by an optimizing compiler.

 // Data to protect DATA_BLOB blobIn; blobIn.pbData = GetSecretData(); blobIn.cbData = lstrlen(reinterpret_cast<char *>(blobIn.pbData))+1; // Optional entropy via an external function call DATA_BLOB blobEntropy; blobEntropy.pbData = GetOptionalEntropy(); blobEntropy.cbData = lstrlen(reinterpret_cast<char*>(blobEntropy.pbData)); // Encrypt the data. DATA_BLOB blobOut; if(CryptProtectData(&blobIn,  L"Sin#13 Example", // optional comment  &blobEntropy,   NULL,   NULL,   0,  &blobOut)) {  printf("Protection worked.\n"); } else {  printf("Error calling CryptProtectData() -> %x", GetLastError());  exit(-1); } // Decrypt the data. DATA_BLOB blobVerify; if (CryptUnprotectData(&blobOut,  NULL,  &blobEntropy,  NULL,  NULL,   0,  &blobVerify)) {  printf("The decrypted data is: %s\n", blobVerify.pbData); } else {  printf("Error calling CryptUnprotectData() -> %x",   GetLastError());  exit(-1); } if (blobOut.pbData)   LocalFree(blobOut.pbData); if (blobVerify.pbData) {  SecureZeroMemory(blobOut.pbData, blobOut.cbData);  LocalFree(blobVerify.pbData); } 

Heres the implementation of SecureZeroMemory used in Windows:

 FORCEINLINE PVOID SecureZeroMemory(void *ptr, size_t cnt) {  volatile char *vptr = (volatile char *)ptr;  while (cnt) {  *vptr = 0;  vptr++;  cnt;  }  return ptr; } 

Or, as suggested by David Wheeler (see the Other Resources section):

 void *guaranteed_memset(void *v, int c, size_t n)  { volatile char *p=v; while (n) *p++=c; return v;} 

ASP.NET 1.1 and Later Redemption

This solution applies to web applications written using ASP.NET 1.1 and later. Because many web applications are database driven, the ASP.NET team made it very easy to securely store sensitive data, such as SQL connection strings, in a web.config file. Refer to Knowledgebase Article Q329290 for more information. (For the link, see the Other Resources section.) This tool uses DPAPI under the covers.

You can also use the HashPasswordForStoringInConfigFile method to store passwords in a configuration file.

C# .NET Framework 2.0 Redemption

The first example shows how to gather a password, and then write the protected password to a file. Note that DPAPI allows you to protect data so it is accessible only to the current user, or is accessible to all applications on the current machine. Your threat model should dictate which is most appropriate for your application.

 byte[] sensitiveData = Encoding.UTF8.GetBytes(GetPassword()); byte[] protectedData = ProtectedData.Protect(sensitiveData, null,  DataProtectionScope.CurrentUser); FileStream fs = new FileStream(filename, FileMode.Truncate); fs.Write(protectedData, 0, protectedData.Length); fs.Close(); 

The next example shows the reverse process of opening a file and accessing the secret data inside:

 FileStream fs = new FileStream(filename, FileMode.Open); byte[] protectedData = new byte[512]; fs.Read(protectedData, 0, protectedData.Length); byte[] unprotectedBytes = ProtectedData.Unprotect(protectedData, null,  DataProtectionScope.CurrentUser); fs.Close(); 
Note 

If you are using passwords in .NET Framework String classes, you should consider using the SecureString class instead. Refer to "Making Strings More Secure" in the "Other Resources" section.

C/C++ Mac OS X v10.2 and Later Redemption

There is sample code at http://darwinsource.opendarwin.org/10.3/SecurityTool-7/keychain_add.c showing how to add a password or key to the Apple Keychain. The core functions are as follows: SecKeychainAddGenericPassword and SecKeychainFindGenericPassword :

 // Set password SecKeychainRef keychain = NULL; // Users default keychain OSStatus status= SecKeychainAddGenericPassword(keychain,   strlen(serviceName), serviceName,   strlen(accountName), accountName,  strlen(passwordData), passwordData,   NULL); if (status == noErr) {  // cool! } // Get password char *password = NULL; u_int_32_t passwordLen = 0; status = SecKeychainFindGenericPassword(keychain,   strlen(serviceName), serviceName,  strlen(accountName), accountName,  &passwordLen, &password,  NULL); if (status == noErr) {  // Cool! Use pwd  ...  // Now Cleanup  guaranteed_memset(password,42,passwordLen);  SecKeychainItemFreeContent(NULL, (void*)password); } 

Redemption with No Operating System Help (or Keeping Secrets Out of Harms Way)

This is a little hard to do, and is certainly not as good as having the operating system perform all the heavy work, but if the operating system youre targeting doesnt support the capability to hide secret data for you, then you need to create your own mechanism. The simplest way is to store the secret data out of the line of fire.

Remember we said earlier that you should always consider who you are defending against, and the value of the data being defended? If you have a web app protecting some sensitive data, you should always store the sensitive data outside the web space. In other words, if the application resides in c:\inetpub\wwwroot\myapp, store the sensitive data in c:\webconfig, or, better yet, d:\webconfig, as these directories are not in the line of fire. However, the wwwroot directory (and below) can be accessed by a remote web browser. Sure, your web server may not serve up text-based config files (such as web.config, app.config, and global.asa on IIS; and httpd.conf and .htaccess in Apache), but all it takes is a bug in the web application or the web server and the attacker could potentially read the sensitive data.

In a Windows system, you can also use the registry, which means a remote attacker has to get code running on the box to read the registry value.

On Linux, Mac OS X, or UNIX using Apache, you probably wouldnt want to store sensitive config data in the directory DocumentRoot points to (defined in httpd.conf). For example, on RedHat or Fedora Core, this is /var/www/html. The same applies to cgi-bin.

The following examples show how to read secret data from a resource outside the web line of fire.

Read from the File System Using PHP on Linux

 <?php  $filename = "/home/apache/config","r";  $fh = fopen($filename);  $data = fread($fh,filesize($filename));  fclose($fh); ?> 

Read from the File System Using ASP.NET (C#)

This code loads the filename from the app.config file that points to the file containing a SQL connection string. The app.config file looks like this:

 <?xml version="1.0" encoding="utf-8" ?> <configuration>  <appSettings>  <add key="connectFile" value="c:\webapps\config\sqlconn.config" />  </appSettings> </configuration> 

And the C# code to read this setting, and then get the connection string from the file, is

 static string GetSQLConnectionString() {  NameValueCollection settings = ConfigurationSettings.AppSettings;  string filename = settings.Get("connectFile");  (filename == null  filename.Length ==0)  throw new IOException();  FileStream f = new FileStream(filename, FileMode.Open);  StreamReader reader = new StreamReader(f, Encoding.ASCII);  string connection = reader.ReadLine();  reader.Close();  f.Close();  return connection; } 

You can also use the aspnet_setreg tool to store and protect configuration information.

Note that in the .NET Framework 2.0, ConfigurationSettings is replaced with ConfigurationManager .

Read from the File System Using ASP (VBScript)

This is a little less sophisticated than the preceding code because ASP doesnt use a config file. However, you could copy the name of the file into a variable in the global.asa file (ASP and IIS will not serve up global.asa by default), like so:

 Sub Application_OnStart  Application("connectFile") = "c:\webapps\config\sqlconn.txt" End Sub 

And then read it into your application when you need the SQL connection string:

 Dim fso, file, pwd Set fso = CreateObject("Scripting.FileSystemObject") Set file = fso.OpentextFile(Application("connectFile")) connection = file.ReadLine file.Close 

Read from the Registry Using ASP.NET (VB.Net)

Rather than reading from a file, this code reads from the Windows registry:

 With My.Computer.Registry  Dim connection As String =  .GetValue("HKEY_LOCAL_MACHINE\Software\" + _  "MyCompany\WebApp", "connectString", 0) End With 

A Note on Java and the Java KeyStore

The JDK 1.2 and later provides a key management class named KeyStore (java.security.KeyStore) that can be used to store X.509 certificates, private keys, and, in some derived classes, symmetric keys. KeyStore does not, however, provide any key management facility to protect the keystore. So if you want to access a key from code, you should read the key used to encrypt and decrypt the store from somewhere out of the line of sight, such as a file outside the applications domain or outside the web space; and then use that key to decrypt the store, get the private key held within, and then use it.

You can place keys in a KeyStore using the keytool application that comes with the JDK, and then use code like this to extract the key:

 // Get password used to unlock the keystore private static char [] getPasswordFromFile()  {  try   {      BufferedReader pwdFile = new BufferedReader  (newFileReader("c:\webapps\config\pwd.txt"));  String pwdString = pwdFile.readLine();  pwdFile.close();  char [] pwd = new char[pwdString.length()];  pwdString.getChars(0,pwdString.length(),pwd,0);  return pwd;  }  catch (Exception e) { return null; } } private static String getKeyStoreName()  {  return "<location of keyfile name>"; } public static void main(String args[]) {  try {  KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());    // get user password and file input stream  FileInputStream fis = new FileInputStream(getKeyStoreName());  char[] password = getPasswordFromFile();  ks.load(fis, password);  fis.close();  Key key = ks.getKey("mykey",password);    // Use key for other cryptographic operations  ks.close();  } catch(Exception e) { String s = e.getMessage(); } } 

This is by no means a great solution, but at least the key can be managed using keytool, and most importantly, the key is not in the code itself. The code also includes a common error noted in Sin 6, catching all exceptions.



19 Deadly Sins of Software Security. Programming Flaws and How to Fix Them
Writing Secure Code
ISBN: 71626751
EAN: 2147483647
Year: 2003
Pages: 239

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net