信息学竞赛在线评测系统的安全性解决方案探讨

Written by

in

,
  • 信息学竞赛在线评测系统的安全性解决方案探讨

一个高质量的 OJ 系统, Judger 要求有很高的安全性,因为它需要运行用户提交上来的代码,而用户会提交什么样的代码是无法预知的。 Judger 首先要把用户的代码编译成可执行文件,然后运行这个可执行文件,所以 Judger 的安全性主要是限制解题程序的行为,阻止它执行危险的操作。

根据 DCS 评级,要达到 ProCode 以上级别的 Judger 程序,需要至少达到以下的安全目标:

  1. 阻止选手程序读写文件;
  2. 阻止选手程序读写注册表;
  3. 阻止选手程序修改系统设置(例如修改系统时间等);
  4. 阻止选手程序关闭系统(包含注销等操作);
  5. 阻止选手程序调用 API 函数;
  6. 阻止选手程序恶意提交代码(例如通过一些语言提供的支持库重复提交自身代码)。

 
对于第四点,程序设计者可以将运行的进程放入一个作业对象中(可以参考《Windows核心编程》第五章)。对于第五点和第六点,可以将编译器中有关 API 的头文件和库文件删除,令其不能通过编译。
本文主要关注如何解决第一、第二和第三点,要实现这几个目标,仅仅靠判题程序是不能解决问题的,一定要对操作系统进行相应的配置。对于第一点,只需要在系统中新建一个用户,在各个驱动器中的安全设置中拒绝该用户的访问,然后以这个新用户的身份来运行解题程序即可。类似的,对于第二点,只要拒绝这个用户访问注册表的各个主键即可。第三点,由于修改系统设置需要管理员特权,因此去除这个用户的大部分特权,只保留必要的特权即可。为什么不去除所有特权呢?因为有些特权是必需的,这个会在下文讲解。总的来说,这个解决方案的主要思想就是建立一个几乎无权限的用户,然后以这个用户的身份去运行解题程序。下面主要说明一下一些需要注意的问题。
 
一、用户特权设置
刚刚建立的用户是没有显式赋予任何特权的,这是理想的设置,不过我们仍然要赋予它一个“允许本地登录”的特权,因为我们要获取该用户的访问令牌,这只有在用户登录后获得。
具体步骤:运行“gpedit.msc”,定位到“计算机配置-Windows设置-安全设置-本地策略-用户权限分配”,右侧列表中显示的就是用户可以拥有的特权列表。找到“允许本地登录”项,并双击,点击“添加用户或组”,在弹出的窗口中点击“对象类型”,选中“组”,确定返回,然后在下面的文本框中输入刚刚建立的用户组的名称,依次点击“确定”即可。
 
二、以新用户的身份启动选手程序
这个问题是本文将要说明的重点。无论是以指定用户身份启动进程还是以特定被降权了的 Token 启动进程都有如下几个Windows API函数可以使用(本文假定程序员使用 Unicode ):
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
);
 
以上这三个函数中第二个和第三个均没有提供 ANSI 版本。
因为需要启动的选手程序是需要分配给一个作业对象的,而 CreateProcessWithLogonW和 CreateProcessWithTokenW 函数启动的进程默认绑定到了一个匿名的对象,所以只能使用 CreateProcessAsUser 这个函数。
CreateProcessAsUser 函数与 CreateProcess 函数的参数几乎完全一样,唯一不同的是多了一个 hToken 参数。这个参数是某个用户的 Token ,标识了这个用户的身份,新的进程就使用这个令牌来运行。要获得用户的令牌,需要使用 LogonUser 函数(关于它的使用,请参考MSDN)。
调用该函数之后可以获得访问令牌,将该 Token 传入 CreateProcessAsUser 函数的第一个参数,就可以以指定用户的身份启动进程了。
三、关于 CreateProcessAsUser
关于 CreateProcessAsUser 函数,有很多需要注意的地方。首先,调用该函数的进程令牌中需要有两个特权: SE_ASSIGNPRIMARYTOKEN_NAME (替换一个进程级令牌)和 SE_INCREASE_QUOTA_NAME (为进程调整内存配额),否则函数调用会失败。这意味着 Judger 需要以较高权限的用户身份运行。例如,一个 Judger 普遍作为一个Windows服务运行,需要以 Local System 用户的身份来运行。很明显这样做存在安全隐患,因为 Local System 的权限实在是太大了。相比之下 CreateProcessWithLogonW 和 CreateProcessWithTokenW 则不需要这两个特权,安全性更佳。这也是使用 CreateProcessAsUser 的无奈之处。于是可以再建立一个用户专门用来运行判题程序,只赋予它这两个必要的特权。
另一个要注意的问题是,以 CreateProcessAsUser 启动的进程默认是不能交互的,换句话说,这个进程不能显示窗口界面,以及不能执行与窗口有关的操作,例如接收命令行输入,向命令行窗口输出信息等,甚至不能通过管道与该进程的标准输入输出通信——即使在调用 CreateProcessAsUser 的时候指定了 CREATE_NO_WINDOW 这个标记也是如此。所以必须使启动后的进程是可交互的。
这里要简单讲一下可交互的条件。在Windows中有两种对象, WindowStation 和 Desktop , WindowStation 是 Windows 中一种重要的安全对象, Desktop 是包含在 WindowStation 中的。这两种对象都可以存在多个,但是只有在某个特定的 Desktop 中启动的进程才是可交互的,这个 Desktop 名为“default”,位于名为“winsta0”的 WindowStation 中。要想在这个 Desktop 中启动进程,需要进程具有访问“winsta0”和“default”的权限。没有桌面权限连 Win32 子系统都启动不起来。
要达到这个目的,需要允许上面建立的运行解题程序的用户访问这两个对象,这是在调用 LogonUser 函数获得令牌后进行的,而不是手动配置的。MSDN上有一篇题为《Starting an Interactive Client Process in C++》的文章对此进行了详述,文章中的代码比较多,也比较复杂,这里简单讲一下文章中代码的原理:

  1. 获取 winsta0 WindowStation 的句柄。
  2. 获取 winsta0 的安全描述符。
  3. 复制原来的安全描述符。
  4. 获取安全描述符中的 DACL 。
  5. 复制原来的 DACL 。
  6. 向复制得到的 DACL 中添加允许用户访问的 ACE 。
  7. 将新的 DACL 设置到复制得到的安全描述符中。
  8. 将新的安全描述符设置到 winsta0 中。

对于default Desktop的操作也是一样的。
至此,我们可以正确地启动选手程序,并可以保证它的安全性了。
 
参考资料:
[Windows]ACM在线判题系统的安全性解决方案
Win7下超级管理员创建普通权限任务

Comments

4 responses to “信息学竞赛在线评测系统的安全性解决方案探讨”

  1. samhjn Avatar

    限制运行程序的用户权限这个方法在WindowsNT或者Linux下是可行的,但在Windows9X(有点过时。。。)却不好办。
    如果选手不用头文件,直接调用dll,这样删了编译器的东西也不一定管用。

    1. SXKDZ Avatar

      🙄 我想办法禁用所有有危害的API

  2. Xiaoxia Avatar

    还是Linux吧,给个普通用户它运行程序,用setrlimit函数限制下内存、CPU时间、文件数什么的。
    Windows纯属瞎折腾啊,弄出来了也是个复杂的系统。

Leave a Reply

Your email address will not be published. Required fields are marked *