zoukankan      html  css  js  c++  java
  • [Windows]ACM在线判题系统的安全性解决方案

    ACM在线判题系统所使用的判题程序要求有很高的安全性,因为判题程序需要运行用户提交上来的代码,而用户会提交什么样的代码是无法预知的。判题程序首先要把用户的代码编译成可执行文件,然后运行这个可执行文件(本文姑且称之为解题程序),所以判题系统的安全性主要是限制解题程序的行为,阻止它执行危险的操作。

     

    我们可以列举出以下的安全目标(各项之间可能有重复):

    1.       阻止解题程序访问文件系统。

    2.       阻止解题程序访问注册表。

    3.       阻止解题程序修改系统设置,例如修改系统时间等。

    4.       阻止解题程序关闭系统。

    5.       阻止解题程序无限制使用内存。

    6.       阻止解题程序调用系统API函数。

     

    对于第四和第五点,可以通过将解题程序进程分配给一个含有相应限制的作业对象中来实现,具体方法可以参考《Windows核心编程》。对于第六点,可以将编译器中有关系统API的头文件和库文件删除,令其不能通过编译。本文主要关注如何解决第一、第二和第三点,这个解决方案是我个人想出来的,缺陷在所难免,还请各位见谅。另外,本文涉及较多的关于Windows访问控制的知识,恕不再一一详细讲解。

     

    要实现这几个目标,仅仅靠判题程序是不能解决问题的,一定要对操作系统进行相应的配置。对于第一点,比较简单,只需要在系统中新建一个用户,在各个驱动器中的安全设置中拒绝该用户的访问,然后以这个新用户的身份来运行解题程序即可。

     

    类似的,对于第二点,只要拒绝这个用户访问注册表的各个主键即可。

     

    第三点,由于修改系统设置需要管理员特权,因此去除这个用户的大部分特权,只保留必要的特权即可。为什么不去除所有特权呢?因为有些特权是必需的,这个会在下文讲解。

     

    总的来说,这个解决方案的主要思想就是建立一个几乎无权限的用户,然后以这个用户的身份去运行解题程序。下面详细讲解一下各个步骤。

     

    一、建立用户组以及用户。

    第一步是建立这个低权限的用户。要注意的是不要在控制面板中添加用户,这样添加的用户不是属于Users组就是Administrators组,达不到安全要求,而且这个用户会在出现在登录界面中。正确的做法是在计算机管理程序中进行操作。当然,如果你熟悉命令行操作那更好,部署判题系统的时候使用命令行脚本会更高效。

     

    首先,右击我的电脑,选择“管理”,然后定位到“系统工具-本地用户和组-组”,在右侧的窗口中右击,选择“新建组”,接着输入组名和描述,再点“确定”即可。

     

    接下来定位到“用户”,同样在右侧窗口中右击,选择“新建用户”,输入用户名和密码等信息,去掉对“用户下次登录时须更改密码”的选择,选中“用户不能更改密码”和“密码永不过期”,点击“创建”。创建用户后右击该用户,选择“属性”,在“隶属于”选项卡中点击“添加”,然后在“输入对象名称来选择”文本框中输入刚才建立的用户组名,再点击“确定”即可。

     

    至此用户组和用户的创建就完成了。其实只要创建用户就可以了,为什么还要创建用户组呢?这是为了使用户的管理更灵活,接下来权限的设置都是针对的用户组而不是针对用户的,这样即使删除了用户再重新建立,也不需要重新设置一遍权限。

     

    二、用户特权设置

    刚刚建立的用户是没有显式赋予任何特权的,这是理想的设置,不过我们仍然要赋予它一个“允许本地登录”的特权,因为我们要获取该用户的访问令牌,这只有在用户登录后获得。

     

    具体步骤:运行“gpedit.msc”,定位到“计算机配置-Windows设置-安全设置-本地策略-用户权限分配”,右侧列表中显示的就是用户可以拥有的特权列表。找到“允许本地登录”项,并双击,点击“添加用户或组”,在弹出的窗口中点击“对象类型”,选中“组”,确定返回,然后在下面的文本框中输入刚刚建立的用户组的名称,依次点击“确定”即可。

     

    三、驱动器权限设置

    接下来对系统中每个驱动器都执行以下的操作:打开我的电脑,右击驱动器图标,选择“属性”,定位到“安全”选项卡,点击“编辑”,在弹出的对话框中点击“添加”,在文本框中输入刚才建立的用户组的名称,确定返回,然后在下方的权限列表中,对每一项都选择“拒绝”,依次确定返回即可。

     

    驱动器的权限设置稍显麻烦,可以编写批处理脚本,借助cacls.exe命令行工具进行设置。具体语法这里不再展开了。

     

    四、注册表权限设置

    运行regedit,对每个主键执行以下操作:右击主键,选择“权限”,在弹出的对话框中仿照驱动器权限的设置即可。

     

    对于注册表的权限设置,我目前还不知道有什么命令行工具可以使用,只能手动设置了。

     

    五、以新用户的身份启动解题程序

    好了,对系统的设置告一段落,接下来的重点是如何以新用户的身份启动解题程序。以指定用户身份启动进程有几个Windows API函数可以使用:

     

    BOOL WINAPI CreateProcessAsUser(
      __in          HANDLE hToken,
      __in          LPCTSTR lpApplicationName,
      __in          LPTSTR lpCommandLine,
      __in          LPSECURITY_ATTRIBUTES lpProcessAttributes,
      __in          LPSECURITY_ATTRIBUTES lpThreadAttributes,
      __in          BOOL bInheritHandles,
      __in          DWORD dwCreationFlags,
      __in          LPVOID lpEnvironment,
      __in          LPCTSTR lpCurrentDirectory,
      __in          LPSTARTUPINFO lpStartupInfo,
      __out         LPPROCESS_INFORMATION lpProcessInformation
    );

    BOOL WINAPI CreateProcessWithLogonW(
      __in          LPCWSTR lpUsername,
      __in          LPCWSTR lpDomain,
      __in          LPCWSTR lpPassword,
      __in          DWORD dwLogonFlags,
      __in          LPCWSTR lpApplicationName,
      __in          LPWSTR lpCommandLine,
      __in          DWORD dwCreationFlags,
      __in          LPVOID lpEnvironment,
      __in          LPCWSTR lpCurrentDirectory,
      __in          LPSTARTUPINFOW lpStartupInfo,
      __out         LPPROCESS_INFORMATION lpProcessInfo
    );

     
    BOOL WINAPI CreateProcessWithTokenW(
      __in          HANDLE hToken,
      __in          DWORD dwLogonFlags,
      __in          LPCWSTR lpApplicationName,
      __in          LPWSTR lpCommandLine,
      __in          DWORD dwCreationFlags,
      __in          LPVOID lpEnvironment,
      __in          LPCWSTR lpCurrentDirectory,
      __in          LPSTARTUPINFOW lpStartupInfo,
      __out         LPPROCESS_INFORMATION lpProcessInfo
    );
     

    理论上这三个函数都可以使用,不过我们启动的解题程序是需要分配给一个作业对象的,我发现以CreateProcessWithLogonWCreateProcessWithTokenW函数启动的进程是无法分配给作业对象的(至少我不知道有什么方法可以这么做),所以只能使用CreateProcessAsUser函数了。

     

    CreateProcessAsUser函数与CreateProcess函数的参数几乎完全一样,唯一不同的是多了一个hToken参数。这个参数是某个用户的令牌,标识了这个用户的身份,新的进程就使用这个令牌来运行。要获得用户的令牌,需要使用下面的函数:

     

    BOOL LogonUser(
      __in          LPTSTR lpszUsername,
      __in          LPTSTR lpszDomain,
      __in          LPTSTR lpszPassword,
      __in          DWORD dwLogonType,
      __in          DWORD dwLogonProvider,
      __out         PHANDLE phToken
    );
     

    该函数用来登录一个用户,并获取该用户的访问令牌。

     

    lpszUsername 指定用户名。

    lpszDomain 指定用户所在的域,对于本地登录来说,将该参数设置为NULL

    lpszPassword 指定用户的密码。

    dwLogonType 指定登录的类型,将该参数设置为LOGON32_LOGON_INTERACTIVE即可,意思是本地登录,这也是为什么要赋予新用户“允许本地登录”特权的原因。

    dwLogonProvider 目前还不清楚该参数的意思,设置为LOGON32_PROVIDER_DEFAULT即可。

    phToken 用于返回已登录用户令牌的句柄,需要传入一个HANDLE的指针。

     

    调用该函数之后可以获得访问令牌,将该令牌传入CreateProcessAsUser函数的第一个参数,就可以以指定用户的身份启动进程了。

     

    六、关于CreateProcessAsUser

    关于CreateProcessAsUser函数,有一些需要注意的地方。首先,调用该函数的进程令牌中需要有两个特权:SE_ASSIGNPRIMARYTOKEN_NAME(替换一个进程级令牌)和SE_INCREASE_QUOTA_NAME(为进程调整内存配额),否则函数调用会失败。这意味着判题程序需要以较高权限的用户身份运行。例如,我开发的判题程序作为一个Windows服务运行,需要以Local System用户的身份来运行。很明显这样做存在安全隐患,Local System的权限实在是太大了。相比之下CreateProcessWithLogonWCreateProcessWithTokenW则不需要这两个特权,安全性更佳。这也是使用CreateProcessAsUser的无奈之处。或许我们可以再建立一个用户专门用来运行判题程序,只赋予它这两个必要的特权。这个方法我没有试过,应该是可行的。相对来说,解决解题程序的安全问题比解决判题程序的更重要,所以这里我偷懒了一下。

     

    另一个要注意的问题是,以CreateProcessAsUser启动的进程默认是不能交互的,换句话说,这个进程不能显示窗口界面,以及不能执行与窗口有关的操作,例如接收命令行输入,向命令行窗口输出信息等,甚至不能通过管道与该进程的标准输入输出通信——即使在调用CreateProcessAsUser的时候指定了CREATE_NO_WINDOW也是如此。所以必须使启动后的进程是可交互的。

     

    这里要简单讲一下可交互的条件。在Windows中有两种对象,WindowStationDesktopWindowStationWindows中一种重要的安全对象,Desktop是包含在WindowStation中的。这两种对象都可以存在多个,但是只有在某个特定的Desktop中启动的进程才是可交互的,这个Desktop名为“default”,位于名为“winsta0”的WindowStation中。要想在这个Desktop中启动进程,需要进程具有访问“winsta0”和“default”的权限。要达到这个目的,需要允许上面建立的运行解题程序的用户访问这两个对象,这是在调用LogonUser函数获得令牌后进行的,而不是手动配置的。MSDN上有一篇题为《Starting an Interactive Client Process in C++》的文章对此进行了详述,地址是ms-help://MS.MSDNQTR.v90.chs/secauthz/security/starting_an_interactive_client_process_in_c__.htm。文章中的代码比较多,也比较复杂,这里简单讲一下文章中代码的原理:

     

    1.获取winsta0 WindowStation的句柄。

    2.获取winsta0的安全描述符。

    3.复制原来的安全描述符。

    4.获取安全描述符中的DACL

    6.复制原来的DACL

    7.向复制得到的DACL中添加允许用户访问的ACE

    8.将新的DACL设置到复制得到的安全描述符中。

    9.将新的安全描述符设置到winsta0中。

     

    对于default Desktop的操作也是一样的。

     

    至此,我们可以正确地启动解题程序,并可以保证它的安全性了。

  • 相关阅读:
    scrapy 断点续爬
    Tornado
    python 列表去重的几种方法
    安装Mysql-python报错EnvironmentError: mysql_config not found
    安装setuptools 报错缺少zlib
    微信小程序-if条件渲染
    微信小程序-遍历列表
    微信小程序-数据绑定
    超强过滤器
    如何在tomcat安装部署php项目
  • 原文地址:https://www.cnblogs.com/zplutor/p/1698290.html
Copyright © 2011-2022 走看看