上周 hBifTs在折腾他的文件映射封装类的时候,碰到了不能在 ASP.NET 中直接打开由桌面程序创建的内核对象的问题。

内存映射文件与用户权限

他当时是的方法是修改 ASP.NET 配置文件,让 ASP.NET 扮演系统管理员帐号运行来访问对象。我在水木上回帖说这是非常不好的编程习惯,因为这样一来会是的 ASP.NET 页面运行在失控的安全上下文中。一旦有恶意代码攻击此页面,将具有系统管理员权限,这是非常危险的。而 IIS 5/6 之所以要提出各种隔离模型,正式为了减少以前 IIS 4/5 缺省模型下类似的问题,将页面被攻击后的损失降到最低。

hBifTs 在接受了改进意见后,采用了另外一种危害较小但也是不应推荐的方式,通过建立 NULL DACL 方式解决问题。

创建一个EveryOne SECURITY_ATTRIBUTES

这种方式可以说已经非常接近正确解决方法了,呵呵,虽然也有一定的安全隐患,但比前一种来说已经有个质的突破。

我当时曾答应写一篇文章介绍一下解决办法,可惜因为工作和其他一些原因最近一直很忙,拖到现在 :P
    而从  hBifTs 摸索的过程,以及很多朋友的回帖来看,对 DACL 以及相关的权限控制很多人都非常陌生。下面就简要地介绍一下这方面的基础性知识和一些基本概念,让大家在用的时候,能够知其然也知其所以然 :P

话说开天辟地之初,NT Loader 载入内核,系统开始启动各种系统进程。各个进程在此时大都由系统支配,说同一种语言,彼此之间也没什么隔阂。可坏就怀在 WinLogon 此进程不老实,非要提供登陆界面让外人登陆到系统,进而衍生出 Explorer, CStrike 等七七八八的进程,说不同的语言干不同的事,对文件网络等资源胡乱访问。NT 系统岂是象 9x 之辈那么好欺负的:“想在我地盘上混,就先报上名来,能干什么不能干什么也得由我说了算,威胁的事情还得打个报告留个底先”。
    于是用户登陆系统之时必须提供凭证,至于是缺省的密码还是认证令牌、指纹、虹膜之类由 WinLogon 的小兄弟 GINA 说了算。但如果每次访问对象都得重新认证,敲敲密码按按手印,实在是太麻烦了。于是自登陆到注销期间,开始每个帐号发一个临时的良民证,这个就是 Session 的由来。为了统一管理,NT 系统对计算机自身的进程、服务进程等等也都建立了相应的 Session,每个帐号每次每种登陆建立一个。平时你看着好像只有一个人用一个帐号登陆到系统,但实际上后台还有一堆 Session 的一堆进程在跑。
    一个典型的系统运行时 Session 情况如下,此工具的开发以及字段的详细解释我会随后写一篇文章单独介绍。

KeSession - 1.0.0.0 - Show Kernel Sessions @ Jul 12 2004
(C) 1999-2004 NSFOCUS Corporation. All rights Reserved

Logon session 00000000:000003e7[999]
  User name    : SKY\FLIER$
  Auth package : NTLM
  Logon type   : (none)
  Session      : 0
  SID          : S-1-5-18
  Account Name : NT AUTHORITY\SYSTEM
  Logon time   : 2004-7-14 11:12:20
  Logon server :
  DNS Domain   :
  UPN          :

Process    File Name
     388 : smss.exe
     460 : winlogon.exe
     504 : services.exe
     516 : lsass.exe
     736 : svchost.exe
     ...
     3624 : inetinfo.exe

Logon session 00000000:0000cda3[52643]
  User name    : NT AUTHORITY\ANONYMOUS LOGON
  Auth package : NTLM
  Logon type   : Network
  Session      : 0
  SID          : S-1-5-7
  Account Name : NT AUTHORITY\ANONYMOUS LOGON
  Logon time   : 2004-7-14 11:12:32
  Logon server :
  DNS Domain   :
  UPN          :

Logon session 00000000:0000cfc1[53185]
  User name    : FLIER\Administrator
  Auth package : NTLM
  Logon type   : Interactive
  Session      : 0
  SID          : S-1-5-21-3978760259-2706837669-145014315-500
  Account Name : FLIER\Administrator
  Logon time   : 2004-7-14 11:12:32
  Logon server : FLIER
  DNS Domain   :
  UPN          :

Process    File Name
    1628 : explorer.exe
    ...
    1612 : KeSession.exe

Logon session 00000000:0000a56a[42346]
  User name    :
  Auth package : NTLM
  Logon type   : (none)
  Session      : 0
  SID          :
  Account Name :
  Logon time   : 2004-7-14 11:12:20
  Logon server :
  DNS Domain   :
  UPN          :

可以看到第一个登陆会话 999 就是用户名 SKY\FLIER$ 以帐号 NT AUTHORITY\SYSTEM 登陆到系统,也就是我的计算机的帐号。以前 NT 系统里面是没有计算机这个实体概念的,2000 开始计算机本身也可以作为独立实体存在,特别是在 AD 中体现得最明显。而计算机本身的帐号就是我们熟悉的 SYSTEM 帐号了。而系统进程 smss, winlogon 等等也大多再次会话中执行。另一个则是用户登陆的帐号 FLIER\Administrator,其登陆类型 Interactive 表示其是通过鼠标键盘在控制台登陆的。此外还有有些其他会话,如网络登陆的 NT AUTHORITY\ANONYMOUS LOGON 是用于匿名登陆获取信息的。以前 NT4 不支持计算机实体登陆的时候,只能通过匿名连接这种变通的方式获取其他机器信息。
    另外一个值得注意的是每个会话都有一个 SID 用来标示用户帐号,有固定的 S-1-5-18, S-1-5-7 这种 well-known SID,也有 S-1-5-21-3978760259-2706837669-145014315-500 这种机器相关 SID,就不详细解释了,不然就话长了,呵呵

每个帐号在登陆系统时,会获取它本身的权限相关信息,也就是能访问什么不能访问什么。但这样似乎还不够,就好像 ASP.NET 进程某个页面有时需要扮演其他帐号来访问特殊资源,或者某个进程在允许范围内把自己的权限调整调整。如果帐号权限保存在会话一级,则就会出现一个进程要把权限改大,另一个又要改小的扯皮问题(竞争情况)。要调解纠纷,最后只能各大三个大板,每个进程发一个良民证副本(Token),各取所需自己去改吧,呵呵。进程中的线程一级也是有类似问题的,只是缺省情况下不发,有需要的时候自己去申请。

这个良民证(Token)用处很多,它上面登记了当前用户的用户名和组的情况、此帐号的特权情况、以及我们这儿需要关注的:缺省的对象权限情况。Jeffrey Richter 和 Jason Clark 在 Programming Server-Side Applications for Microsoft Windows一书的第11章 User Context 中,提供了一个非常强大的工具,Token Master,可以查看当前系统任意进程、线程的良民证 (Token)。
    下面是一个典型的允许在系统管理员帐号下的进程的缺省对象权限设置:

********************************************************************************
Token Default DACL
********************************************************************************
ACCESS ALLOWED BUILTIN/Administrators
ACCESS_MASK:  00010000000000000000000000000000
 GENERIC_ALL

ACCESS ALLOWED NT AUTHORITY/SYSTEM
ACCESS_MASK:  00010000000000000000000000000000
 GENERIC_ALL

也就是说,在一个系统管理员帐号中,在建立一个新的内核对象时,如果 LPSECURITY_ATTRIBUTES lpAttributes 参数为 NULL,则此对象缺省是只有此帐号自己或管理员组和 SYSTEM 帐号具有完全访问权限的。如果一个运行在其他帐号下的
程序视图打开并访问此对象,就会被拒绝。这就是为什么hBifTs无法在 ASP.NET 中打开桌面程序建立的文件映射对象的原因,因为他的桌面程序八成是在系统管理员帐号下运行,建立的对象只有系统管理员组和系统帐号能够打开访问。而 ASP.NET 为安全起见,是在 ASPNET 帐号下运行 ASP.NET 页面的,其并不是系统管理员组成员,也就无法访问此对象。

hBifTs提出的第一种扮演的方法其实也是一种解决方法。通过设置 ASP.NET 的脚本,让 ASP.NET 在系统管理员帐号下运行,这样此页面运行所在线程的良民证就会暂时被换为系统管理员的,也就有权限进行处理了。但因为是通过配置文件设置,扮演的粒度太大,整个页面的生命期都会扮演管理员帐号,非常不安全。即使要用这种方法,也应该通过 WindowsIdentity.Impersonate 方法这种细粒度的控制,在需要访问对象的时候,动态扮演系统管理员角色,完成操作后恢复到原本帐号安全上下文中。不过就是这样也不安全,呵呵,还是存在安全隐患。

正规的思路是在创建对象的时候,就指定其访问权限。但 hBifTs 的第二种解决方法又太过了一些,它建立了一个 NULL DACL 的安全描述符,也就是允许任何人进行任何访问的对象。特别是对文件映射这种用于交互数据的对象,开启这种程度的权限,就给恶意代码留了巨大的空子,他们可以简单的打开这个对象,在里面填充一些垃圾数据或攻击数据,搞定使用此对象进行通讯的两方程序。以前我就用类似的方式成功搞定某种个人防火墙,通过操作它内部互斥量,欺骗引擎和界面的通讯,让你以外防火墙还在工作,但实际上已经被停止了,呵呵 :P

要缓解这种问题,就需要在建立对象的时候,为 ASP.NET 运行的帐号指定特殊的权限。可以使用配置文件提供系统 ASP.NET 帐号名,然后在建立对象是为此帐号增加访问权限。jiangsheng在其《跨进程访问共享内存的权限问题》一文中就是通过强制指定 everyone (SECURITY_WORLD_SID_AUTHORITY)都可以访问此对象来解决问题的。虽然这种权限管理也比较粗放,但比起前文中的 NULL DACL 稍好一些,NULL DACL 是真正的连 everyone 以外随便什么都能访问的无设防对象 :P

而较好的方式是细粒度地控制目标进程的访问,大概分为以下几个步骤:

1.获取当前线程或进程的令牌
    2.获取令牌使用者的 SID
    3.获取 ASP.NET 运行帐号的 SID
    4.设置安全描述符的 DACL
    5.使用安全描述符创建对象

实例代码如下(为演示简便,忽略了调试和错误处理代码,只保留最基本的检查):

第一步需要先尝试获取线程令牌,因为上面曾经提到,令牌是可以设置到线程一级的,而且线程本身还可能在扮演其他帐号。如果线程没有独立令牌则尝试获取进程令牌。至于获取令牌句柄的权限,能够查询足以,为后面进一步获取令牌信息做准备。

HANDLE hToken;
  if(!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken))
  {
    if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
    {
      std::cerr << "无法获得进程令牌 " << std::endl;
      return -1;
    }
  }
    第二步需要获取令牌使用者的 SID。GetTokenInformation 能够从令牌查询各种关联信息,如这儿我们需要的用户 SID。

typedef struct _TOKEN_USER {
  SID_AND_ATTRIBUTES User;
} TOKEN_USER;

typedef struct _SID_AND_ATTRIBUTES {
  PSID Sid;
  DWORD Attributes;
} SID_AND_ATTRIBUTES, *PSID_AND_ATTRIBUTES;

DWORD dwUserSize = 0;
  GetTokenInformation(hToken, TokenUser, NULL, 0, &dwUserSize);

std::string user(dwUserSize, '\0');
  BOOL ret = GetTokenInformation(hToken, TokenUser, (void *)user.c_str(), user.size(), &dwUserSize);

CloseHandle(hToken);

PSID sidUser = ((PTOKEN_USER)user.c_str())->User.Sid;

第三步则根据 ASP.NET 运行帐号获取其 SID。这里为简便,使用硬编码的帐号名 ASPNET,具体使用时,可以通过配置文件或直接读取系统设置和配置文件的方式获取灵活性。
  const char *ASPNET_ACCOUNT = "ASPNET";

DWORD dwSidSize = 0, dwDomainSize = 0;
  SID_NAME_USE use;

LookupAccountName(NULL, ASPNET_ACCOUNT, NULL, &dwSidSize, NULL, &dwDomainSize, &use);

std::string sid(dwSidSize-1, '\0'), domain(dwDomainSize-1, '\0');

ret = LookupAccountName(NULL, ASPNET_ACCOUNT, (PSID)sid.c_str(), &dwSidSize, (char *)domain.c_str(), &dwDomainSize, &use);

PSID sidAspNet = (PSID)sid.c_str();

第四步则根据前面获取两个 SID 来初始化一个安全描述符。InitializeAcl 初始化一个 ACL(任意访问控制列表),然后以 AddAccessAllowedAce 增加允许的 ACE(访问控制项)。这里将缺省用户和ASP.NET用户都赋给 GENERIC_ALL 访问权限,具体使用中可根据需求进一步细化。最后使用此 ACL 构造一个安全描述符,设置其 DACL(自由访问控制列表)。

DWORD dwACLSize = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) * 2 +
                    GetLengthSid(sidUser) + GetLengthSid(sidAspNet);

std::string acl(dwACLSize, '\0');

InitializeAcl((PACL)acl.c_str(), acl.size(), ACL_REVISION);
  AddAccessAllowedAce((PACL)acl.c_str(), ACL_REVISION,  GENERIC_ALL, sidUser);
  AddAccessAllowedAce((PACL)acl.c_str(), ACL_REVISION,  GENERIC_ALL, sidAspNet);

SECURITY_DESCRIPTOR sd;
  InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
  SetSecurityDescriptorDacl(&sd, TRUE, (PACL)acl.c_str(), TRUE);

此外也可以考虑使用 GetTokenInformation 配合 TokenDefaultDacl 选项直接获取令牌的缺省 DACL,再使用 SetEntriesInAcl 直接对其进行修改,生成新的安全描述符。不过这个就比较麻烦了,MSDN 里面提供了一个完整的代码示例,有兴趣的朋友可以看看。

第五步使用此安全描述符创建内核对象。可以使用 sysinternal 提供的 Process Explorer 工具查看此例子程序中创建的对象的权限,可以看到如我们所希望,当前用户和ASPNET用户被加入 GENERIC_ALL 权限。

SECURITY_ATTRIBUTES sa = { sizeof(sa), &sd, FALSE };
  HANDLE hMutex = CreateMutex(&sa, FALSE, "TestMutex");

char ch;
  std::cin >> ch;

CloseHandle(hMutex);

大致的使用思路如上,其中很多细节还可以深入展开谈,但因为时间精力有限,这儿就暂且略过,有兴趣的朋友可以进一步探讨。

参考:http://www.cnblogs.com/flier/archive/2004/07/15/24299.aspx

DACL, NULL or not NULL的更多相关文章

  1. IOS开发遇到(null)与<null>轻松处理

    在ios开发中不可避免的我们会遇到服务器返回的值有空值,但是如果是nil也就算了还可能得到(null)以及<null>的返回值,该如何处理呢?(当然有的字典转模型中已处理,可以通过遍历等) ...

  2. com.opensymphony.xwork2.ognl.OgnlValueStack] - target is null for setProperty(null, "emailTypeNo", [Ljava.lang.String;@6f205e]

    情况1,查询结果未转换为与前台交互的实体类DTO 实体类:EmailTypeDto package com.manage.email.dto; public class EmailTypeDto { ...

  3. ognl.OgnlException: target is null for setProperty(null, "emailTypeNo", [Ljava.lang.String;@1513fd0)

    [com.opensymphony.xwork2.ognl.OgnlValueStack] - Error setting expression 'emaiTypeDto.emailTypeNo' w ...

  4. SQL语句中=null和is null

    平时经常会遇到这两种写法:IS NOT NULL与!=NULL.也经常会遇到数据库有符合条件!=NULL的数据,但是返回为空集合.实际上,是由于对二者使用区别理解不透彻. 默认情况下,推荐使用 IS ...

  5. mysql探究之null与not null

    相信很多用了mysql很久的人,对这两个字段属性的概念还不是很清楚,一般会有以下疑问: 1.我字段类型是not null,为什么我可以插入空值 2.为毛not null的效率比null高 3.判断字段 ...

  6. 【MySQL】探究之null与not null

    相信很多用了mysql很久的人,对这两个字段属性的概念还不是很清楚,一般会有以下疑问: 我字段类型是not null,为什么我可以插入空值 为毛not null的效率比null高 判断字段不为空的时候 ...

  7. IOS开发中(null)与<null>的处理

    不小心在开发过程中,得到了(null)以及<null>的返回值,找了好长时间只找到了一个关于<null>的. 由于要根据返回值进行判断,做出必要反应,因此必须知道返回值所代表的 ...

  8. 数据库中is null(is not null)与=null(!=null)的区别

    在标准SQL语言(ANIS SQL)SQL-92规定的Null值的比较取值结果都为False,既Null=Null取值也是False.NULL在这里是一种未知值,千变万化的变量,不是“空”这一个定值! ...

  9. 原!! java直接打印一个对象时,并不是直接调用该类的toString方法 ,而是会先判断是否为null,非null才会调用toString方法

    网上看了好多java直接打印一个对象时,直接调用该类的toString方法 . 但是: Object obj=null; System.out.println(obj);//没有报错 System.o ...

  10. 关于ognl.OgnlException: target is null for setProperty(null的解决方案

    在跑struts2的时候有时候会出现上面的错,特别是新手, 这种情况是在struts2高级的POJO访问时候出现的s 警告: Error setting expression 'user.passwo ...

随机推荐

  1. 在windows下MySQLdb/MySQL-python的安装

    学习Python的时候总是遇到各种各样的问题,很多问题我也百度了很久,谷歌了很多,发现很多人也遇到这种问题:但是答案又各种不同,因人而异吧! 问题:windows系统下  安装了mysql数据库   ...

  2. NYOJ 214 最长上升子序列nlogn

    普通的思路是O(n2)的复杂度,这个题的数据量太大,超时,这时候就得用nlogn的复杂度的算法来做,这个算法的主要思想是只保存有效的序列,即最大递增子序列,然后最后得到数组的长度就是最大子序列.比如序 ...

  3. 小学生之Oracle分析函数

    分析函数是什么?分析函数是Oracle专门用于解决复杂报表统计需求的功能强大的函数,它可以在数据中进行分组然后计算基于组的某种统计值,并且每一组的每一行都可以返回一个统计值. 分析函数和聚合函数的不同 ...

  4. vmware-tools(vmware workstation 10.0.4)安装的时候遇到的bug

    有个GitHub,专门解决C++编译的时候出的问题 地址

  5. 自己动手写easyui的checkbox

    最近项目中用到了easyui这个框架,找了一圈也没有找到checkbox list控件,被迫只能自己实现了,为了便于复用,自己封装了下,有需要的,直接拿去用吧.有意见或建议的,欢迎指教啊. 调用示例 ...

  6. Navicat:cant create OCI environment.

    一直在使用 Navicat ,这是一个数据库客户端软件,能连接多种不同类型的数据库,给我们的日常的工作带来了不少的便捷. 最近,我在电脑上安装了oracle的客户端ODTwihtODAC121012, ...

  7. 在C#中internal关键字是什么意思?和protected internal区别

    我来补充一下,对于一些大型的项目,通常由很多个DLL文件组成,引用了这些DLL,就能访问DLL里面的类和类里面的方法.比如,你写了一个记录日志的DLL,任何项目只要引用此DLL就能实现记录日志的功能, ...

  8. mssql 2008 失败 需要重新启动计算机 的解决办法

    大致出错信息如下:RebootRequiredCheck 检查是否需要挂起计算机重新启动.挂起重新启动会导致安装程序失败. 失败 需要重新启动计算机.必须重新启动计算机才能安装 SQL Server. ...

  9. 使用三层交换机的ACL实现不同vlan间的隔离

    使用三层交换机的ACL实现不同vlan间的隔离   建立三个vlan vlan10 vlan20 vlan30    www.2cto.com   PC1 PC3属于vlan10 PC2 PC4属于v ...

  10. PHP 杂项 函数

    安装 杂项函数是 PHP 核心的组成部分.无需安装即可使用这些函数. Runtime 配置 杂项函数的行为受 php.ini 文件中的设置的影响. 杂项配置选项: 名称 默认 描述 可更改 ignor ...