从攻击者的角度看Linux安全性

在这些笔记中,我将介绍Linux系统上安全性的一些方面。对于这个讨论,我们将发现采用潜在攻击者的视角是有用的。攻击者的主要目标是

以提升的权限执行任意代码

这些说明将涵盖攻击者可能用于实现此目标的一些方法,以及可以用来阻止这些攻击方法的一些适当的反措施。

危险:缓冲区溢出

这里是一个代码块从源代码的CGI web服务器,我们看到在课程早些时候。在前几行代码中,我们读取来自客户机的请求的第一行,并开始进行一些初步处理。

无效serverrequest (int fd) {char lineBuffer[256];//读取请求的第一行readLine(fd,lineBuffer,255);//获取方法和URL char方法[16];char url [128];sscanf (lineBuffer、“% s % s”方法,url);

这段代码依赖于readLine()函数将用户请求的第一行读入缓冲区。下面是该函数的代码:

void readLine(int fd,char* buffer,int maxBytes) {char* ptr = buffer;int bytes = 0;while(bytesRead < maxBytes) {read(fd,ptr,1);If (*ptr == '\n') break;ptr + +;} *(++ptr) = '\0';}

在这段代码中,我小心地确保当我们读取用户输入的第一行时,不会意外地读取超出lineBuffer处理能力的数据。一个不太小心的程序可能会写这样的代码:

void readLine(int fd,char* buffer) {char* ptr = buffer;Do {read(fd,ptr,1);ptr + +;} while(*(ptr-1) != '\n');*ptr = '\0';}无效serverrequest (int fd) {char lineBuffer[256];//读取请求的第一行readLine(fd,lineBuffer);//获取方法和URL char方法[16];char url [128];sscanf (lineBuffer、“% s % s”方法,url);

这个版本的readLine()函数每次从套接字中读取一个字符,直到遇到标志着第一行输入结束的‘\n’字符。注意,这里没有尝试确定读取是否会溢出缓冲区的容量:这是导致可利用错误的原因。

下面是另一个例子,它表明很容易引入缓冲区溢出缺陷。在这个版本的代码中,我们使用fdopen()将文件描述符转换为file指针,这使我们能够使用不安全的fscanf()函数:

无效serverrequest (int fd) {char方法[16];char url [128];//获取方法和URL FILE* fp = fdopen(fd,"r");fscanf (fp,“% s % s”方法,url);

使用Fscanf()是不安全的,因为它不会检查它复制到您提供的缓冲区中的字符串是否小于缓冲区。这使得该代码极易受到缓冲区溢出攻击。

的利用

我们要学习的第一类漏洞被称为“堆栈粉碎”,它是基于强制本地缓冲区溢出并随后影响堆栈的结构。

要理解缓冲区溢出如何导致可利用错误,我们必须了解Linux赢博体育程序中的堆栈结构。下面的图表说明了堆栈的结构以及与上面所示的serverrequest()函数相关的堆栈帧。

这里对我们来说重要的堆栈的关键特性是返回地址位于堆栈帧顶部上方的位置,以及在堆栈帧顶部存在局部变量。局部变量是按照从堆栈帧顶部到下的顺序排列的。由于buf数组首先出现在局部变量列表中,因此该数组将被放置在堆栈帧的顶部。

由于在Linux中堆栈向下增长,当buf数组溢出时,它将向上溢出超过堆栈帧的顶部,首先覆盖保存的帧指针,然后覆盖返回地址,最后覆盖参数fd及其他。我们的攻击者特别感兴趣的是覆盖返回地址,也可能是fd的值,所以他们会安排向我们有缺陷的服务器发送专门构造的第一行。然后,攻击者可以安排将第一行的前几个字符设置为“POST”,这样,serverrequest()函数中的逻辑将在读取第一行并溢出缓冲区后立即返回。

一旦serverrequest()函数返回,程序的执行将跳转到返回地址中指定的位置。由于攻击者已经小心地覆盖了返回地址,现在执行将继续到攻击者选择的点。

攻击者部署的下一个技巧是将执行路由回方法和url数组占用的内存空间,这些内存空间现在包含攻击者发送给服务器的数据。攻击者在这些数组中放置的数据中嵌入了攻击者想要强制服务器赢博体育程序运行的机器码。攻击者在这些数组中没有太多的空间来放置恶意代码,但是可用的空间对于攻击者接下来计划做的事情是足够的。

为了准备他们的恶意代码,攻击者编写了一个小函数并编译了它:

void doEvil(int fd) {char **empty = {NULL};if (fork() == 0){/*重定向stdout和stdin到我的套接字*/ dup2(fd, STDIN_FILENO);dup2 (fd, STDOUT_FILENO);Execve ("/bin/sh", empty, empty);/*启动shell */}}

攻击者从他们的小程序中提取编译后的代码,对汇编代码做一些简单的修改,以纠正fd在堆栈上的实际位置,将可执行代码放在要插入到数组中的字符串中,并安排返回地址指向这些数组中恶意代码的起始位置。一旦执行此代码,攻击者将拥有对受感染计算机的shell访问权限,并可以发起进一步的攻击。此外,如果编写有缺陷的服务器的程序员愚蠢地安排服务器以root权限运行(这并不罕见!),那么攻击者代码启动的进程将继承该级别的特权,并且攻击者的shell现在以完全root访问权限运行。

为防止这种攻击而部署的对策

为了防止这种攻击,我们可以部署的第一个也是最明显的对策是注意我们永远不允许在服务器赢博体育程序中进行无界读取。我上面展示的第一个版本的代码经过精心构造,不允许对缓冲区进行无界读取。

并非赢博体育程序员都这么小心,因此攻击者可能会转而寻找容易受到缓冲区溢出攻击的其他服务器赢博体育程序。

为了解决这个漏洞,Linux部署了一些进一步的对策。这些对策之一是地址空间布局随机化(ASLR),它在每次赢博体育程序启动时稍微随机化内存中堆栈基础的位置,使攻击者更难以准确地知道他们计划溢出的缓冲区在内存中的位置,这反过来又使他们更难以制作适当的返回地址以用于此漏洞利用。

进一步的对策是禁止执行在堆栈上找到的代码。通过设置适当的系统范围选项,系统管理员可以将包含堆栈的内存页标记为“不执行”,使攻击者无法执行他们放置在堆栈缓冲区中的恶意代码。

攻击者采取的对抗措施

计算机安全是攻击者和系统管理员之间不断升级的军备竞赛。随着攻击技术的广泛传播,系统开发人员开始采用对抗措施。随着这些对抗措施被广泛采用,攻击者被迫开发新的、更复杂的攻击策略。

例如,当系统开始采用防止从堆栈中执行任意代码的反措施时,攻击者转而使用其他方法来利用缓冲区溢出。一旦这样一组可选技术被称为“返回到libc”方法,它将用libc库中的地址覆盖返回地址。这种技术可能是有效的,因为如果攻击者可以猜测他们正在攻击的系统的系统细节,他们就可以指望libc中的例程位于固定的、可预测的位置。通过将程序执行路由到libc中的已知位置,并将此功能与堆栈的其他操作(包括在堆栈上放置虚假参数)相结合,攻击者已经能够成功地进行利用。最近,攻击者甚至发现了如何使用返回到libc策略,按顺序跳转到精心选择的libc函数片段,然后使用一种称为“面向返回编程”的技术实现任意复杂性的程序。

当这些技术开始出现时,系统程序员已经开始依次使用更复杂的反措施。其中一些新技术包括编译器选项,这些选项将“秘密”值放在堆栈上,然后如果攻击者试图利用缓冲区溢出覆盖这些值,则触发异常。例如,现代版本的gcc包含一个称为“堆栈粉碎保护(ssp)”的特性,它可以通过-fstack-protector编译器标志调用。

享有特权的程序

如果攻击者能够劫持正在运行的进程,那么他们将面临额外的障碍。在Unix系统上,操作系统将使用特权系统来确定允许进程执行的操作。例如,如果一个进程试图打开一个文件,但没有打开该文件的适当特权,系统将拒绝该进程访问。

一般来说,进程的特权级别由分配给启动该进程的用户的特权决定。每个进程都有一个与之关联的userid(和groupid),它将该进程与启动该进程的用户(以及该用户所属的组)关联起来。操作系统将使用userid和groupid的组合来确定允许进程执行哪些操作。

授予进程提升权限的一种方法是以root身份启动该进程。例如,这可以通过从命令行通过sudo命令调用程序来完成:

sudo ./miniweb

另一种用于授予程序高级特权的技术是“setuid”技术。在这种技术中,我们首先使用chown命令将程序的赢博体育者设置为root:

Sudo chown root miniweb

然后我们使用chmod命令来设置可执行文件的setuid位:

Sudo chmod u+s miniweb

一旦设置了这个位,程序将在启动时使用其赢博体育者的用户id,而不是使用实际启动它的用户的用户id。

减少特权程序中的危险

尽管使用根权限执行进程可能很有用,但这样做可能会产生安全问题。如果攻击者设法劫持了一个以root权限运行的进程,那么该攻击者就可以成为root并迅速造成大量破坏。

一种可能缓解此问题的方法是允许程序下降到降低的权限,只在需要时升级回根权限。为了实现这一点,Unix跟踪每个进程的有效用户id和真实用户id。对于设置了setuid位且赢博体育者为0的进程,该进程的有效用户id将为0,即root的用户id,而实际用户id将是实际启动该赢博体育程序的用户的用户id。

程序可以通过将其有效用户id设置回等于实际用户id来降低其有效权限。

Uid_t orig_euid = geteuid();seteuid (getuid ());//设置有效uid =真实uid

程序随后可以通过以下操作重新获得特权

seteuid (orig_euid);

您可能想知道程序如何获得将其有效用户id重置为具有更高权限的用户id的权限。为了管理这个过程,系统存储第三个用户id,即保存的用户id。当调用seteuid恢复权限时,系统会将请求的有效用户id与保存的用户id进行比较。如果匹配,系统将授予恢复更强大的有效用户id的权限。

要永久删除提升的权限,程序可以执行以下操作

getuid setreuid (getuid () ());

将真实用户id、有效用户id和保存的用户id都设置为真实用户id。

程序可以用来限制攻击者可能造成的损害的另一种策略是有意限制程序可以接触的文件系统部分。执行此操作的机制是chroot()系统调用。

#include <unist .h> int chroot(const char *path);

这个系统调用将文件系统的有效根更改为path参数中描述的位置。例如,如果提供的路径是/var/myapp,然后您尝试从位于/etc/myapp.conf的赢博体育程序打开一个文件,那么open命令使用的有效路径将是/var/myapp/etc/myapp.conf。这种技术使得攻击者实际上不可能看到/var/myapp目录之外的文件。这种技术通常被称为设置chroot jail。