MIT 6.858 计算机系统安全讲义 2014 秋季(一)(2)https://developer.aliyun.com/article/1484145
这类似于一个胖指针,但具有以下优点……
- 标记指针与常规指针大小相同
- 对它们的写入是原子的
……以便不破坏程序员的期望,并且数据布局保持不变。
还要注意,使用标记指针,我们现在可以跟踪远离基本指针多得多的越界指针。这是因为现在我们可以使用偏移量标记指针,指示它们距离基本指针有多远。在 32 位世界中,如果没有额外的数据结构,我们无法跟踪越界偏移量!
在 baggy bounds 系统中仍然可以发动缓冲区溢出攻击吗?
是的,因为这个世界充满了悲伤。
- 可能会利用未经检测的库中的漏洞。
- 可能会利用时间漏洞(使用后释放)。
- 混合缓冲区和代码指针
例子:
struct { char buf[256]; void (*f) (void); } my_type;
请注意*f
不是分配的类型,因此在调用期间与其解引用相关联的边界检查不存在。因此,如果s.buf
溢出(例如,由未经检测的库中的错误引起),并且s.f
被损坏,那么对f
的调用不会导致边界错误!
重新排列 f 和 buf 会有帮助吗?
- 可能会破坏依赖结构布局的应用程序。
- 如果这是一个(
struct my_type
)数组,可能不会有帮助。
一般来说,边界检查的成本是什么?
- 边界信息的空间开销(胖指针或 baggy bounds 表)。
- Baggy bounds 还会为 buddy 分配器使用的额外填充内存增加空间开销(尽管所有流行的动态内存分配算法都会有一定程度的开销)。
- 指针算术和解引用的 CPU 开销。
- 虚警!
- 未使用的越界指针。
- 临时超出边界指针超过
slot_size/2
。 - 指针到整数的转换和反向转换。
- 将越界指针传递给未经检查的代码(高地址位被设置,因此如果未经检查的代码使用该指针进行算术运算,可能会导致混乱)。
- 需要大量编译器支持。
因此,baggy bounds 检查是一种减轻有缺陷代码中缓冲区溢出的方法。
实现边界检查的更多方法
方法 4:非可执行内存(AMD 的 NX 位,Windows DEP,W^X 等)
- 现代硬件允许为内存指定读取、写入和执行权限。(R、W 权限很久以前就有了;执行权限是最近才有的。)
- 可以将堆栈标记为不可执行,这样对手就无法运行他们的代码。
- 更一般地,一些系统执行“W^X”,意味着所有内存要么可写,要么可执行,但不能同时。 (当然,既不可写也不可执行也是可以的。)
- 优势: 可能无需进行任何应用程序更改即可运行。
- 优势: 硬件一直在监视你,不像操作系统。
- 缺点:动态生成代码更困难(尤其是使用 W^X)。
- 像 Java 运行时、Javascript 引擎这样的 JIT 会即时生成 x86 代码。
- 可以通过先写入,然后更改为可执行来解决问题。
方法 5:随机化内存地址(ASLR,堆栈随机化等)
- 观察:许多攻击在 shellcode 中使用硬编码地址![攻击者抓取一个二进制文件并使用 gdb 来找出东西的位置。]
- 因此,我们可以使攻击者难以猜测有效的代码指针。
- 堆栈随机化:将堆栈移动到随机位置,并/或在堆栈变量之间放置填充。这使得攻击者更难确定:
- 当前帧的返回地址位于何处
- 攻击者的 shellcode 缓冲区将位于何处
- 随机化整个地址空间(地址空间布局随机化):随机化堆栈、堆、DLL 的位置等。
- 依赖于很多代码是可重定位的这一事实。
- 动态加载器可以为每个库、程序选择随机地址。
- 对手不知道 system()等函数的地址。
- 这仍然可以被利用吗?
- 对手可能猜测随机性。特别是在 32 位机器上,没有太多的随机位(例如,1 位属于内核/用户模式划分,12 位不能被随机化,因为内存映射页面需要与页面边界对齐等)。
- 例如,攻击者可能进行缓冲区溢出并尝试用
usleep(16)
的地址覆盖返回地址,然后查看连接是否在 16 秒后挂起,或者是否崩溃(在这种情况下,服务器会使用相同的 ASLR 偏移量 fork 一个新的 ASLR 进程)。usleep()
可能在 2¹⁶ 或 2²⁸ 个地方之一。更多细节。 - ASLR 在 64 位机器上更实用(很容易有 32 位的随机性)。
- 对手可能提取随机性。
- 程序可能生成包含指针的堆栈跟踪或错误消息。
- 如果对手可以运行一些代码,他们可能能够提取真实地址(JIT 编译的代码?)。
- Flash 的字典(哈希表)中的可爱地址泄漏:
- 让受害者访问您的 Flash 启用页面(例如,购买广告)。
- 哈希表在内部计算键的哈希值。
- 整数的哈希值是整数本身。
- 对象的哈希值是其内存地址。
- 遍历哈希表是从最低哈希键到最高哈希键进行的。
- 因此,攻击者创建一个字典,插入一个包含 shellcode 的字符串对象,然后向字典中插入一堆数字。
- 通过遍历字典,攻击者可以确定字符串对象位于何处,看看对象引用落在哪些整数之间!
- 现在,用 shellcode 地址覆盖代码指针并绕过 ASLR!
- 对手可能不关心确切要跳转到哪里。
- 例如:“堆喷洒”:填充内存以便随机跳转是可以的!
- 对手可能利用一些未随机化的代码(如果存在这样的代码)。
- 随机化的一些其他有趣用途:
- 系统调用随机化(每个进程有自己的系统调用号码)。
- 指令集随机化,以便攻击者不能轻易确定特定程序实例的"shellcode"是什么样子。
- 例子: 想象一下,处理器有一个特殊的寄存器来保存“解码密钥”。每个特定应用程序的安装都与一个随机密钥相关联。应用程序中的每条机器指令都与该密钥进行异或运算。当操作系统启动进程时,它设置解码密钥寄存器,处理器使用此密钥解码指令后再执行它们。
实际上使用了哪些缓冲区溢出防御措施?
- gcc 和 MSVC 默认启用栈保护。
- Linux 和 Windows 默认包含 ASLR 和 NX。
- 由于:
- 性能开销
- 需要重新编译程序
- 误报:安全工具中的常见主题:误报阻止了工具的采用!通常,一些遗漏但没有误报比零遗漏但有误报更好。
返回导向编程(ROP)
ASLR 和 DEP 是非常强大的防御技术。
- DEP 防止攻击者执行自己选择的栈代码。
- ASLR 可以防止攻击者确定 shellcode 或返回地址的位置。
- 但是,如果攻击者能够找到位于已知位置的具有已知功能的预先存在的代码呢?那么,攻击者可以调用该代码来做坏事。
- 当然,预先存在的代码并不是故意恶意,因为它是应用程序的正常部分。
- 但是,攻击者可以传递意外的参数给该代码,或者跳转到代码的中间并仅执行该代码的所需部分。
这种攻击称为返回导向编程,或ROP。为了理解 ROP 的工作原理,让我们看一个具有安全漏洞的简单 C 程序。示例改编自此处。
void run_shell(){ system("/bin/bash"); } void process_msg(){ char buf[128]; gets(buf); }
假设系统不使用 ASLR 或栈保护,但使用了 DEP。process_msg()
存在明显的缓冲区溢出,但攻击者无法利用此溢出在buf
中执行 shellcode,因为 DEP 使栈不可执行。然而,run_shell()
函数看起来很诱人… 攻击者如何执行它?
- 攻击者反汇编程序并找出
run_shell()
的起始地址在哪里。 - 攻击者发起缓冲区溢出,并用
run_shell()
的地址覆盖process_msg()
的返回地址。砰!攻击者现在可以访问以应用程序权限运行的 shell。
例子:
+------------------+ entry %ebp ----> | .. prev frame .. | | | | | +------------------+ entry %esp ----> | return address | ^ <--Gets overwritten +------------------+ | with address of new %ebp ------> | saved %ebp | | run_shell() +------------------+ | | buf[127] | | | ... | | | buf[0] | | new %esp ------> +------------------+
这是我们已经看过的缓冲区溢出的直接扩展。但是我们如何向我们要跳转到的函数传递参数呢?
char *bash_path = "/bin/bash"; void run_cmd(){ system("/something/boring"); } void process_msg(){ char buf[128]; gets(buf); }
在这种情况下,我们要传递的参数已经位于程序代码中。程序中还存在一个对system()
的调用,但该调用并未传递我们想要的参数。
我们知道system()
必须与我们的程序链接。因此,使用我们可靠的朋友 gdb,我们可以找到system()
函数的位置以及 bash_path 的位置。
要使用bash_path
参数调用system()
,我们必须设置堆栈,以便在跳转到它时,system()
期望堆栈上有这些内容:
| ... | +------------------+ | argument | The system() argument. +------------------+ %esp ----> | return addr | Where system() should +------------------+ ret after it has finished.
因此,缓冲区溢出需要设置一个看起来像这样的堆栈:
+------------------+ entry %ebp ----> | .. prev frame .. | | | | | | * - - - - - - - | ^ | | | Address of bash_path + * - - - - - - - | | | | | Junk return addr for system() +------------------+ | entry %esp ----> | return address | | Address of system() +------------------+ | new %ebp ------> | saved %ebp | | Junk +------------------+ | | buf[127] | | | ... | | Junk | buf[0] | | new %esp ------> +------------------+ |
本质上,我们所做的是为system()
调用设置了一个虚假的调用帧!换句话说,我们模拟了编译器如果真的想要设置一个对system()
的调用会做什么。
如果字符串"/bin/bash"
不在程序中怎么办?
- 我们可以将该字符串包含在缓冲区溢出中,然后使
system()
的参数指向该字符串。
| h\0 | ^ | * - - - - - - - | | | /bas | | | * - - - - - - - | | | /bin | | <--------------------+ | * - - - - - - - | | | | | | Address of bash_path--+ + * - - - - - - - | | | | | Junk return addr from system() +------------------+ | entry %esp ----> | return address | | Address of system() +------------------+ | new %ebp ------> | saved %ebp | | Junk +------------------+ | | buf[127] | | | ... | | Junk | buf[0] | | new %esp ------> +------------------+ |
请注意,在这些示例中,我一直假设攻击者使用了来自system()
的无用返回地址。然而,攻击者也可以将其设置为有用的内容。
实际上,通过将其设置为有用的内容,攻击者可以链接调用在一起!
**目标:**我们想要多次调用system("/bin/bash")
。假设我们找到了三个地址:
system()
的地址- 字符串"/bin/bash"的地址
- 这些 x86 操作码的地址:
pop %eax //Pops the top-of-stack and puts it in %eax ret //Pops the top-of-stack and puts it in %eip • 1 • 2
这些操作码是“小工具”的一个示例。小工具是预先存在的指令序列,可以串联在一起创建一个利用。请注意,有一些用户友好的工具可以帮助您从现有二进制文件中提取小工具(例如,msfelfscan)。
| | ^ + * - - - - - - - + | | | | Address of bash_path -+ Fake calling + * - - - - - - - + | | frame for (4) | | | Address of pop/ret * + system() + * - - - - - - - + | (3) | | | Address of system() + * - - - - - - - + | (2) | | | Address of bash_path -+ Fake calling + * - - - - - - - + | | frame for (1) | | | Address of pop/ret * + system() +------------------+ | entry %esp ----> | return address | | Address of system() +------------------+ | new %ebp ------> | saved %ebp | | Junk +------------------+ | | buf[127] | | | ... | | Junk new %esp ------> | buf[0] | | +------------------+ |
那么,这是如何工作的呢?记住,返回指令弹出栈顶并将其放入%eip。
- 溢出的函数通过发出
ret
来终止。ret
弹出栈顶(system()
的地址)并将%eip
设置为它。system()
开始执行,%esp
现在在(1),并指向pop/ret
小工具。 system()
执行完毕并调用ret
。%esp
从(1)->(2),因为ret
指令弹出栈顶并将其分配给%eip
。%eip
现在是pop/ret
小工具的开始。pop/ret
小工具中的 pop 指令从栈中丢弃bash_path
变量。%esp
现在在(3)。我们仍然在pop/ret
小工具中!pop/ret
小工具中的ret
指令弹出栈顶并将其放入%eip
。现在我们再次在system()
中,并且%esp
在(4)。
等等。
基本上,我们创建了一种新类型的机器,它由堆栈指针驱动,而不是常规指令指针!随着堆栈指针沿着堆栈移动,它执行的小工具的代码来自预先存在的程序代码,数据来自缓冲区溢出创建的堆栈数据。
这种攻击规避了 DEP 保护–我们没有生成任何新代码,只是调用了现有的代码!
栈读取:打败金丝雀
假设:
- 远程服务器存在缓冲区溢出漏洞。
- 服务器崩溃并重新启动,如果金丝雀值设置为不正确的值。
- 当服务器重新启动时,canary 不会重新随机化,ASLR 也不会重新随机化,例如,因为服务器使用 Linux 的 PIE 机制,并且使用
fork()
来创建新的工作进程而不是execve()
。
因此,要确定一个 8 字节的 canary 值:
char canary[8]; for(int i = 1; i <= 8; i++){ //For each canary byte . . . for(char c = 0; c < 256; c++){ //. . . guess the value. canary[i-1] = c; server_crashed = try_i_byte_overflow(i, canary); if(!server_crashed){ //We've discovered i-th byte of the //the canary! break; } } }
此时我们已经有了 canary,但请记住,该攻击假设服务器在崩溃后使用相同的 canary。
猜测一个字节的正确值平均需要 128 次猜测,因此在 32 位系统上,我们只需要 4*128=512
次猜测来确定 canary(在 64 位系统上,我们需要 8*128=1024
次)。
- 比在 canary 上进行暴力破解攻击要快得多(在具有 16/28 位 ASLR 随机性的 32/64 位系统上,预期猜测次数为
2¹⁵
或2²⁷
)。 - 暴力破解攻击可以使用我们之前讨论过的
usleep(16)
探测。 - Canary 读取可以扩展到读取缓冲区溢出可以覆盖的任意值!
因此,我们已经讨论了如果服务器在重新生成时不更改 canaries,我们如何能够击败随机化的 canaries。我们还展示了如何使用 gdb 和 gadgets 来执行程序中预先存在的函数,使用攻击者控制的参数。但是如果服务器使用 ASLR 呢?这将阻止您使用离线分析来找到预先存在的函数的位置?
这就是今天讲座论文讨论的内容。该论文假设我们使用的是 64 位机器,所以从现在开始,在本讲座中我们也将假设如此。在这次讨论中,主要的变化是函数参数现在是通过寄存器传递而不是通过栈传递。
盲目返回导向编程
步骤 1:找到一个 stop gadget
- stop gadget 是指指向会挂起程序但不会崩溃的代码的返回地址。
- 一旦攻击者能够击败 canaries,他就可以覆盖溢出函数的返回地址并开始猜测 stop gadget 的位置。如果客户端网络连接突然关闭,猜测的地址不是 stop gadget。如果连接保持打开,那么该 gadget 就是 stop gadget。
步骤 2:找到弹出栈中条目的 gadgets
- 一旦你有了一个 stop gadget,你可以用它来找到其他将栈中条目弹出并存入寄存器的 gadgets。
- 定位栈弹出 gadgets 的三个构建块:
- probe: 潜在的栈弹出 gadget 的地址
- stop: stop gadget 的地址
- crash: 非可执行代码的地址(0x0)
示例: 找到一个弹出栈中一个元素的 gadget。
sleep(10) ^ ^ +--- pop rax / \ | ret / \ | \--->[stop] 0x5.... 0x5.... | [trap] 0x0 0x0 <-----------------+ +----------[probe] 0x4...8 0x4...c -->xor rax, rax | Crash! ret | \__________|
当你这样做很多次后,你将拥有一系列弹出栈中一个元素然后返回的 gadgets。然而,你不会知道这些 gadgets 将弹出的值存储在哪个 寄存器 中。
- 你需要知道哪些寄存器用于存储数据,以便您可以发出系统调用。每个系统调用都期望其参数在一组特定的寄存器中。
- 请注意,我们也不知道
syscall()
库函数的位置。
步骤 3:找到 syscall() 并确定 pop gadgets 使用哪些寄存器
pause()
是一个不带参数的系统调用(因此忽略寄存器中的所有内容)。- 要找到
pause()
,攻击者在栈上链接所有的"pop x; ret"
小工具,将pause()
的系统调用号作为每个小工具的"参数"推送进去。在链的底部,攻击者放置了syscall()
的猜测地址。
| | ^ + * - - - - - - - + | | | | Guessed addr of syscall() + * - - - - - - - + | | | | ... + * - - - - - - - + | | | | Sys call # for pause + * - - - - - - - + | | | | Address of pop rsi; ret //Gadget 2 + * - - - - - - - + | | | | Sys call # for pause +------------------+ | entry %esp ----> | return address | | Address of pop rdi; ret //Gadget 1 +------------------+ | new %ebp ------> | saved %ebp | | Junk +------------------+ | | buf[127] | | | ... | | Junk new %esp ------> | buf[0] | | +------------------+ |
因此,在这个链的末端,弹出小工具已经将pause()
的系统调用号放入了一堆寄存器中,希望包括rax
,这是syscall()
查找系统调用号的寄存器。
一旦这个超级小工具引发了暂停,我们就知道已确定了syscall()
的位置。现在我们需要确定哪个小工具将栈顶弹出到rax
中。攻击者可以通过逐步尝试一个小工具并查看是否可以调用pause()
来弄清楚这一点。
要识别任意的"pop x; ret"
小工具,可以使用与您试图找到的x
寄存器相关的其他系统调用的技巧。
因此,这个阶段的结果是知道"pop x; ret"
小工具,syscall()
的位置。
第 4 步:调用 write()
现在我们想要在服务器与攻击者客户端之间的网络套接字上调用写入调用。我们需要以下小工具:
pop rdi; ret (socket) pop rsi; ret (buffer) pop rdx; ret (length) pop rax; ret (write syscall number) syscall
我们必须猜测套接字的值,但这在实践中相当容易,因为 Linux 将进程限制为同时打开 1024 个文件描述符,并且新文件描述符必须是可用的最低文件描述符(因此猜测一个小文件描述符在实践中效果很好)。
要测试我们是否猜对了文件描述符,只需尝试写入并查看是否收到任何内容!
一旦我们有了套接字号码,我们发出一个写入请求,发送的数据是指向程序的.text
段的指针!这使得攻击者可以读取程序的代码(虽然已随机化,但现在完全为攻击者所知!)。现在攻击者可以直接找到更强大的小工具,并利用这些小工具打开一个 shell。
防御 BROP 攻击
- 每次崩溃后重新随机化 canaries 和地址空间!
- 使用
exec()
代替fork()
来创建进程,因为fork()
会将父进程的地址空间复制给子进程。 - 有趣的是,Windows 不容易受到 BROP 攻击的影响,因为 Windows 没有
fork()
的等效功能。
- 崩溃后休眠?
- 现在 BROP 攻击是一种拒绝服务攻击!
- 边界检查?
- 高达 2 倍的性能开销…
有关 ROP 和 x86 调用约定的更多信息
OKWS
注意: 这些讲座笔记略有修改,来自 2014 年 6.858 课程网站上发布的笔记。
今天的讲座:如何在 Unix 上构建一个安全的 Web 服务器。我们实验室 Web 服务器 zookws 的设计灵感来自于 OKWS。
特权分离
- 大型安全理念
- 将系统分割成各自具有特权的模块
- 想法: 如果一个模块被破坏,那么其他模块就不会被破坏
- 经常使用:
- 虚拟机(例如,在自己的虚拟机中运行网站)
- SSH(分离 sshd、agent)
- 挑战:
- 模块需要共享
- 需要操作系统支持
- 需要仔细使用操作系统正确设置事物
- 性能
OKWS
- 特权分离的有趣案例研究
- 服务之间有很多共享
- 严格的分区不起作用
- 大量的代码
- 在 OKcupid 之外并不广泛使用
- 许多网站都有他们的特权分离计划
- 但没有描述他们计划的论文
背景:Unix 中的安全和保护
- 典型的主体:用户 ID、组 ID(32 位整数)。
- 每个进程都有一个用户 ID(uid)和一组组 ID(gid + grouplist)。
- 出于大多是历史原因,一个进程有一个 gid +额外的组列表。
- 超级用户主体(root)由
uid=0
表示,绕过大多数检查。
- Unix 中的对象+操作是什么,操作系统如何进行访问控制?
- 文件、目录。
- 文件操作:读、写、执行、更改权限,…
- 目录操作:查找、创建、删除、重命名、更改权限,…
- 每个 inode 都有一个所有者用户和组。
- 每个 inode 对于用户、组、其他人都有读、写、执行权限。
- 通常表示为写入基数 8(八进制)的位向量;
- 八进制很好用,因为每个数字是 3 位(读、写、执行)。
- 谁可以更改文件的权限?
- 只有用户所有者(进程 UID)。
- 对文件进行硬链接:需要对文件有写权限。
- 可能的理由:配额。
- 可能的理由:防止将
/etc/passwd
硬链接到具有全局可写/var/mail
的/var/mail/root
。
- 目录的执行意味着能够查找名称(但不能 ls)。
- 检查进程打开文件
/etc/passwd
:
- 必须能够在
/
中查找'etc'
,在/etc
中查找'passwd'
。 - 必须能够打开
/etc/passwd
(读或读写)。
- 假设你想要文件对 group1 和 group2 的交集可读。
- 在 Unix 中是否可能实现这一点?
- 文件描述符。
- 文件打开时执行的文件访问控制检查。
- 一旦进程有一个打开的文件描述符,就可以继续访问。
- 进程可以传递文件描述符(通过 Unix 域套接字)。
- 进程。
- 你可以对一个进程做什么?
- 调试(ptrace),发送信号,等待退出并获取状态,
- 调试,发送信号:必须具有相同的 UID(几乎)。
- 各种例外,在实践中这变得棘手。
- 等待/获取退出状态:必须是该进程的父进程。
- 内存。
- 一个进程通常不能命名另一个进程的内存。
- 例外:调试机制。
- 例外:内存映射文件。
- 网络。
- 操作。
- 绑定到一个端口。
- 连接到某个地址。
- 读/写连接。
- 发送/接收原始数据包。
- 规则:
- 只有 root(UID 0)可以绑定到低于 1024 的端口;
- (例如,任意用户不能在端口 80 上运行 Web 服务器。)
- 只有 root 可以发送/接收原始数据包。
- 任何进程都可以连接到任何地址。
- 只能读取/写入进程具有文件描述符的连接上的数据。
- 此外,防火墙施加自己的检查,与进程无关。
- 进程的主体是如何设置的?
- 系统调用:
setuid()
、setgid()
、setgroups()
。 - 只有 root(UID 0)可以调用这些系统调用(粗略估计)。
- 用户 ID、组 ID 列表从哪里获取?
- 在典型的 Unix 系统上,登录程序以 root(UID 0)身份运行。
- 检查提供的用户密码是否与
/etc/shadow
中的匹配。 - 根据
/etc/passwd
找到用户的 UID。 - 根据
/etc/group
找到用户的组。 - 在运行用户的 shell 之前调用
setuid()
、setgid()
、setgroups()
。
- 在切换到非 root 用户后如何重新获得权限?
- 可以使用文件描述符传递(但必须编写专门的代码)
- 内核机制:setuid/setgid 二进制文件。
- 当执行二进制文件时,将进程 UID 或 GID 设置为二进制文件所有者。
- 通过文件权限中的特殊位指定。
- 例如,
su
/sudo
二进制文件通常是 setuid root。 - 即使您的 shell 不是 root,也可以运行
"su otheruser"
。 su
进程会检查密码,如果正确则以otheruser
身份运行 shell。- Unix 上有许多这样的程序,因为通常需要 root 权限。
- 为什么 setuid 二进制文件在安全方面可能是个坏主意?
- 对手(二进制调用者)操纵进程的许多方法。
- 在 Unix 中,执行的进程会继承环境变量、文件描述符等。
- setuid 程序可能使用的库不够谨慎
- 历史上存在许多漏洞(例如传递
$LD_PRELOAD
,…)
- 如何防止恶意程序利用 setuid-root 二进制文件?
- 内核机制:chroot
- 通过路径名打开文件时更改
/
的含义。 - 不能在 chroot 树之外命名文件(例如 setuid 二进制文件)。
- 例如,OKWS 使用 chroot 将程序限制在
/var/okws/run
中,… - 内核还确保
/../
不允许从 chroot 中逃脱。 - 为什么只允许 root 使用 chroot?
- setuid 二进制文件(如
su
)可能会混淆/etc/passwd
的内容。 - 许多内核实现(无意中?)允许递归调用
chroot()
以从 chroot 监狱中逃脱,因此 chroot 对于以 root 身份运行的进程来说不是一种有效的安全机制。
- 为什么 chroot 没有被修复以限制根进程在该目录中?
- Root 可以写入内核内存,加载内核模块,访问磁盘扇区,…
背景:传统的 Web 服务器架构(Apache)
- Apache 运行
N
个相同的进程,处理 HTTP 请求。 - 所有进程都以用户
'www'
身份运行。 - 应用程序代码(例如 PHP)通常在每个
N
个 Apache 进程中运行。 - 任何对操作系统状态(文件、进程等)的访问都由
www
的 UID 执行。 - 存储:SQL 数据库,通常一个连接具有对整个数据库的完全访问权限。
- 数据库主体是整个应用程序。
- 问题:如果任何组件被攻破,对手将获得所有数据。
- Web 应用可能会发生哪种攻击?
- 无意中的数据泄露(获取页面源代码,隐藏文件,…)
- 远程代码执行(例如,Apache 中的缓冲区溢出)
- 有 bug 的应用程序代码(难以编写安全的 PHP 代码),例如 SQL 注入
- 对 Web 浏览器的攻击(跨站脚本攻击)
MIT 6.858 计算机系统安全讲义 2014 秋季(一)(4)https://developer.aliyun.com/article/1484147