Chinaunix首页 | 论坛 | 博客
  • 博客访问: 42126
  • 博文数量: 22
  • 博客积分: 1145
  • 博客等级: 少尉
  • 技术积分: 515
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-10 14:28
文章分类
文章存档

2012年(22)

我的朋友
最近访客

分类: 服务器与存储

2012-03-27 13:51:25

我们程序的威胁来自于各个方面.在互联网高度发达的今天, 安全性问题已经是企业软件开发所必须面对的最重要的问题. 从安全学的一般意义上来讲,安全性主要体现在两个方面:

  • 敏感数据的泄露
     
  • 敏感数据的破坏
     
      从具体上来说, .NET 元数据机制的设计, 既方便了反射等强大特性的实现,又同时给代码安全及程序运行时安全带来了巨大的隐患.迄今为止, 还未发现比较有效元数据可见性控制方法. 当然, 这不在本文的讨论范围之内.我还是更愿意在这篇文章在针对.NET的内存分配机制讨论一个更具体的问题: 如何保护在内存中存储的敏感数据?
String的驻留机制带来的安全性问题
      String是代码中使用频率很高的对象类型. 为了提高字符串的处理速度, 节省内存空间, Microsoft为.NET String类设计了驻留机制. 其大概的逻辑模型是, 大部分String存储在一个类似的Hash Table中, string的内容是哈希表的key, 该key对应的value是string的内存地址.这样内容相同的string实际上只是对应内存堆上同一个字符串.之所以说是大部分而不是全部,是因为有一部分动态创建(concat)的string, 是不会进入这样一个虚拟的hash Table中的.本文的最后附上String类的源代码, 有兴趣的同学可以研究研究
      这就带来了最主要的问题, 你无法准确控制或者预测一个特定字符串的生命周期.一个以string形式呈现的敏感数据(比如密码)很有可能在内存中一直存在, 而你却预测它在超出某个特定函数的作用域的时候就被垃圾回收了.这样, 当发生操作系统换页的时候(而这也往往是可能发生的), 这个敏感数据就被保存到本地文件pagefile.sys当中,或者当操作系统休眠的时候, 敏感数据进入hiberfil.sys中.一个可能的敏感数据泄漏过程是:



使用SecureString类
      现在既然String靠不住了,我们能有什么简单的方法来特别的保护我的敏感数据吗? 幸运的是, .NET从Version 2.0开始, 为我们提供了一套基于的解决方法 - SecureString.
      SecureString类具有以下特性:

  • SecureString中的内容是加密之后的,而不是平文;

     
  • 使用windows的加密方案DPAPI ;

     
  • SecureString只能在基于NT的平台上使用

     
C#代码示例
public
void MethodA()
{
    
//using DPAPI to encrpt the sensitive content

     System.Security.SecureString password
=
new System.Security.SecureString();

    
char[] pass = { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' };

    
for (int i =
0; i < pass.Length; i++)
     {
         password.AppendChar(pass);
     }

    password.MakeReadOnly();

    
//pass the encrypted password through memory or file
}

public
void MethodB(System.Security.SecureString password)
{
    
string decryptedPassword =
"";

    
//copy the secure content to a long pointer

    IntPtr ptr
= System.Runtime.InteropServices.Marshal.SecureStringToBSTR(password);

    
try
    {
        
//Convert secure content to string using DPAPI
        decryptedPassword = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr);
        
//using the decrypted password to check
    }
    
finally
    {
        System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr);
        password.Dispose();
    }
}


   这段代码中有几个值得说明的地方:

  • 代码写得有些粗糙, 仅为示意。
     
  • 使用Char数组来保存敏感数据的原始值. 因为Char数组的生命周期是可以预期的, 它在超出自己的作用域之后,就被回收。
     
  • MakeReadOnly方法, 一旦使用了该方法后, SecureString的内容就不能再被修改,从而保证了加密后的数据不能再被修改,否则将引发异常。
     
  • SecureString的解密,是通过将其内容复制到一个长指针中,然后利用DPAPI, 最终获得String.该String不会进入上文所说的那个虚拟Hash Table中。
     
  • ZeroFreeBSTR()方法. 因为使用COM Interop引入了非托管资源,所以一定不能忘记使用ZeroFreeBSTR来释放指针,否则会造成内存泄漏。
     
  • SecureString类重写了基类的ToString()方法,不过该方法不会返回所持有的加密内容, 而总是返回System.Security.SecureString。
     
敏感数据已经足够安全了吗?
      这个问题的答案很让我们沮丧, 不是. 有两个问题:

  • 用户的输入往往先被处理成string, 然后才能传递到我们的处理函数, 比如command line parameters, 或者textbox.
     
  • .NET Framework的很多函数都要求string参数, 而非SecureString, 比如ADO.NET的Connect函数.
     
幸运的是, 对于这两个问题,我们除了祈祷Microsoft尽快更新Framework以外, 在当前条件下还有些办法来处理.

  • 针对第一个问题,重写Command Line或者Textbox,添加对SecureString的支持(不详述).
     
  • 针对第二个问题,利用GC特性来处理.
     

      第二个问题的主要安全隐患是来自于string的特性, 即不可变性(immutable). 为了防止GC的自作聪明处理我们的数据, 从而造成敏感数据泄漏, 我们需要对GC做一些处理, 此时上面代码的MethodB就应该修改成如下:
C#代码示例
public
unsafe
void MethodB(System.Security.SecureString password)
{
    
int pwdLength = password.Length;

    IntPtr passwordPtr
= IntPtr.Zero;

    
//allocate a pinned memory to store the password in string form

string decryptedPassword =
new
string('\0', pwdLength);

    GCHandle gch
= GCHandle.Alloc(decryptedPassword, GCHandleType.Pinned);

    
try
    {
        
//copy the secure content to a long pointer    
        passwordPtr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(password);

        var pPassword
= (char*)passwordPtr;

        var pDecryptedPassword
= (char*)gch.AddrOfPinnedObject();

        
for (int index =
0; index < pwdLength; index++)
        {
            pDecryptedPassword[index]
= pPassword[index];

        }
    }
    
finally
    {
        
if (IntPtr.Zero != passwordPtr)
        {
            System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(passwordPtr);       }   }}




      我们用GCHandleType.Pinned标志, 申请了一块固定位置的内存来存储密码, 这段明文密码是独立于string类的虚拟hash table的.这可以在一定程度上减少因不当权限访问造成的敏感数据泄露.

到这里,string是可以用了, 但是换页的问题还没有解决啊?
       是的, 你可能已经觉得麻烦了.我们不得已而为之, 实在是因为.NETFramework对于SecureString的支持还不够完善, 或者说是部分的. 上面虽然解决了String的不可变特性造成的问题,但是重新引入系统换页的问题. 怎么办?

       在这种情况下,我们只能求助于Windows API. Windows API对于页的操作为我们提供了2个接口:AllocateuserPhysicalPages 和 VirtualLock,这两个函数可以将我们在上例中所取得的密码存储地址pDecryptedPassword 锁定在内存中,强制不换页. 不过这么做要万分小心,因为一旦pDecryptedPassword 所指向的密码内容被强制不换页, 那该程序的整个workset都会一直被强制在内存中,一直到程序结束. 这可能给系统的其他程序带来糟糕的体验.

      关于使用VirtualLock来强制Page In的修改, 就不再讨论了.
 

总结
  事物总是两面性的, .NET给我们带来了快速实现, 关注业务的好处, 却缺少了譬如C++般精确操作内存这样的灵活性,因而在安全性方面如果Framework不够完善, 我们就会多多少少有些掣肘. 总之, 在现有条件下, 尽力实现系统安全性, 是我们的目标.本文没有讨论系统设计的安全性考虑等这些概念性理论性的东西, 而是从最具体的String类入手讨论, 希望对您有一些启发.
下载:
声明: 本文为作者Shining Sun原创, 禁止用于任何商务用途! 如需转载, 请务必注明作者及作品出处。

阅读(653) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~