译者:飞龙 协议:CC BY-NC-SA 4.0
2014 年由Nickolai Zeldovich 教授和James Mickens 教授教授授课的 6.858 讲座笔记。这些讲座笔记略有修改,与 6.858 课程网站上发布的内容略有不同。
我们阅读的论文列表(papers/):
**注意:**这些讲座笔记略有修改,来源于 2014 年 6.858 课程网站上发布的内容课程网站。
F
。
F
。更难的是检查没有可能的方式让艾丽丝读取文件F
。
F
的访问权限。@me.com
账户。
@N
账户劫持。
http://firewall/?action=disable
就足够了。
>> M/day.
SecureRandom
弱点导致比特币被盗。
SecureRandom
API。
webserver.c:
int read_req(void) {
char buf[128];
int i;
gets(buf);
i = atoi(buf);
return i;
}
%esp
指向栈上最后(最底部)有效的内容。
%ebp
指向调用者的%esp
值。
read_req() stack layout:
+------------------+
entry %ebp ----> | .. prev frame .. |
| |
| |
+------------------+
entry %esp ----> | return address |
+------------------+
new %ebp ------> | saved %ebp |
+------------------+
| buf[127] |
| ... |
| buf[0] |
+------------------+
| i |
new %esp ------> +------------------+
| ... |
+------------------+
main()
):
read_req()
read_req()
的汇编代码:
push %ebp
mov %esp -> %ebp
sub 168, %esp # stack vars, etc
...
mov %ebp -> %esp
pop %ebp
ret
ret
使用。
gets()
的栈帧。/bin/sh
(因此称为“shell 代码”)。root
或 Administrator
运行,可以做任何事情。
strcpy
,gets
,sprintf
)。
strncpy
不会在末尾加上空字符)。
malloc()
之后再次返回相同的内存。int isLoggedIn
,int isRoot
)。
gets()
,而是使用 fgets()
可以限制缓冲区长度。
注意: 这些讲座笔记略有修改,来自 2014 年 6.858 课程网站。
在上一讲中,我们看了执行缓冲区溢出攻击的基础知识。该攻击利用了几个观察结果:
read_req.c:
void read_req() {
char buf[128];
int i;
gets(buf);
//. . . do stuff w/buf . . .
}
编译器在内存布局方面生成了什么?x86 栈看起来像这样:
%esp points to the last (bottom-most) valid thing on
the stack.
%ebp points to the caller's %esp value.
+------------------+
entry %ebp ----> | .. prev frame .. |
| | |
| | | stack grows down
+------------------+ |
entry %esp ----> | return address | v
+------------------+
new %ebp ------> | saved %ebp |
+------------------+
| buf[127] |
| ... |
| buf[0] |
+------------------+
new %esp ------> | i |
+------------------+
对手如何利用这段代码?
攻击者在执行代码后可以做什么?
方法 #1: 避免 C 代码中的错误。
strncpy()
而不是 strcpy()
,fgets()
而不是 gets()
等)。
gcc
和 Visual Studio 在程序使用不安全函数(如 gets())时会发出警告。一般来说,你不应该忽略编译器警告。 将警告视为错误!
fgets()
或strcpy()
作为基本操作的缓冲区操作函数。
方法 2: 构建工具来帮助程序员找到错误。
foo.c:
void foo(int *p) {
int offset;
int *z = p + offset;
if(offset > 7){
bar(offset);
}
}
if
语句还限制了我们可能传播到 bar 的偏移量。
方法 3: 使用内存安全语言(JavaScript,C#,Python)。
上述 3 种方法都是有效且广泛使用的,但在实践中缓冲区溢出仍然是一个问题。
尽管存在有缺陷的代码,我们如何减轻缓冲区溢出?
堆栈布局:
| |
+------------------+
entry %esp ----> | return address | ^
+------------------+ |
new %ebp ------> | saved %ebp | |
+------------------+ |
| CANARY | | Overflow goes
+------------------+ | this way.
| buf[127] | |
| ... | |
| buf[0] | |
+------------------+
| |
'a'
字符。这有什么问题吗?
gets()
,sprintf()
)。因此,如果金丝雀与这些终结符之一匹配,那么进一步的写入将不会发生。
堆栈金丝雀不会捕捉到哪些类型的漏洞?
数据指针示例:
int *ptr = ...;
char buf[128];
gets(buf); // Buffer is overflowed, and overwrites ptr.
*ptr = 5; // Writes to an attacker-controlled address!
// Canaries can't stop this kind of thing.
malloc
/free
溢出
malloc 示例:
int main(int argc, char **argv) {
char *p, *q;
p = malloc(1024);
q = malloc(1024);
if(argc >= 2)
strcpy(p, argv[1]);
free(q);
free(p);
return 0;
}
p
和q
的两个内存块在内存中是相邻/附近的。
malloc
和free
表示内存块如下:
malloc 内存块:
+----------------+
| |
| App data |
| | Allocated memory block
+----------------+
| size |
+----------------+
+----------------+
| size |
+----------------+
| ...empty... |
+----------------+
| bkwd ptr |
+----------------+
| fwd ptr | Free memory block
+----------------+
| size |
+----------------+
p
的缓冲区溢出将覆盖q
内存块中的大小值!为什么这是一个问题?
free()
合并两个相邻的空闲块时,需要操作bkwd
和fwd
指针…
free()
内部:
p = get_free_block_struct(size);
bck = p->bk;
fwd = p->fd;
fwd->bk = bck; // Writes memory!
bck->fd = fwd; // Writes memory!
struct
;通过破坏大小值,攻击者可以强制free()
在位于攻击者控制的内存中的伪造struct
上操作,并具有攻击者控制的值用于前向和后向指针。
free()
如何更新指针,他可以使用该更新代码将任意值写入任意位置。例如,攻击者可以覆盖返回地址。
char x[1024];
char *y = &x[107];
y
以访问后续元素是否可以?
x
代表一个字符串缓冲区,也许是。
x
代表一个网络消息,也许不。
联合体示例:
union u{
int i;
struct s{
int j;
int k;
};
};
int *ptr = &(u.s.k); // Does this point to valid data?
p
派生出的指针p'
,p'
只能被解引用以访问属于p
的有效内存区域。边界检查方法 #1: 电子围栏
电子围栏:
+---------+
| Guard |
| | ^
+---------+ | Overflows cause a page exception
| Heap | |
| obj | |
+---------+
malloc
。**边界检查方法#2:**胖指针
示例:
Regular 32-bit pointer
+-----------------+
| 4-byte address |
+-----------------+
Fat pointer (96 bits)
+-----------------+----------------+---------------------+
| 4-byte obj_base | 4-byte obj_end | 4-byte curr_address |
+-----------------+----------------+---------------------+
base ... end
范围的指针时中止程序。示例:
int *ptr = malloc(sizeof(int) * 2);
while(1){
*ptr = 42; <----------|
ptr++; |
} |
______________________________|
|
This line checks the current address of the pointer and
ensures that it's in-bounds. Thus, this line will fail
during the third iteration of the loop.
sizeof(that_struct)
将会改变!
**想法:**使用影子数据结构来跟踪边界信息(Jones and Kelly,Baggy bounds)。
char *p = malloc(mem_size);
char p[256];
char *q = p + 256;
char ch = *q;
log_2(alloc_size)
。
2¹ = 2 字节,2² = 4 字节...,2³¹ 字节或 2³² 字节
,并且我们存储分配大小的以 2 为底的对数,这是一个介于 1 和 32 之间的数字,因此我们只需要 5 位来表示它。示例:
slot_size = 16
p = malloc(16); --> table[p/slot_size] = 4;
p = malloc(32); --> table[p/slot_size] = 5;
\-> table[(p/slot_size) + 1] = 5;
p
,和一个派生指针p'
,我们可以通过检查这两个指针的地址位中是否有相同的前缀,并且它们只在它们的e
个最低有效位上有所不同,其中e
等于分配大小的对数,来测试p'
是否有效。示例:
C code
------
p' = p + i;
Bounds check
------------
size = 1 << table[p >> log_of_slot_size];
base = p & ~(size - 1);
(p' >= base) && ((p' - base) < size)
Optimized bounds check
----------------------
(p^p') >> table[p >> log_of_slot_size] == 0
示例代码(假设slot_size=16
)
char *p = malloc(44); //Note that the nearest power of 2 (i.e.,
//64 bytes) are allocated. So, there are
//64/(slot_size) = 4 bounds table entries
//that are set to log_2(64) = 6.
char *q = p + 60; //This access is ok: It's past p's object
//size of 44, but still within the baggy
//bounds of 64.
char *r = q + 16; //r is now at an offset of 60+16=76 from
//p. This means that r is (76-64)=12 bytes
//beyond the end of p. This is more than
//half a slot away, so baggy bounds will
//raise an error.
char *s = q + 8; //s is now at an offset of 60+8=68 from p.
//So, s is only 4 bytes beyond the baggy
//bounds, which is les than half a slot
//away. No error is raised, but the OOB
//high-order bit is set in s, so that s
//cannot be dereferenced.
char *t = s - 32; //t is now back inside the bounds, so
//the OOB bit is cleared.
对于 OOB 指针,高位被设置(如果 OOB 在半个插槽内)。- 通常,操作系统内核位于上半部分,通过分页硬件保护自身。- 问: 为什么越界是半个插槽?
那么作业问题的答案是什么?
char *p = malloc(256);
char *q = p + 256;
char ch = *q; // Does this raise an exception?
// Hint: How big is the baggy bound for p?
Baggy bounds 论文中的一些错误:
size = 1 << table[p >> log_of_slot_size]
(p^p') >> table[p >> log_of_slot_size] == 0
char *p = &buf[i];
char *p = buf + i;
注意: 这些讲座笔记是从 2014 年 6.858 课程网站上发布的笔记中稍作修改而来。
示例代码:(假设slot_size = 16
)
char *p = malloc(44); // Note that the nearest power of 2 (i.e.,
// 64 bytes) are allocated. So, there are
// 64/(slot_size) = 4 bounds table entries
// that are set to log_2(64) = 6.
char *q = p + 60; // This access is ok: It's past p's object
// size of 44, but still within the baggy
// bounds of 64.
char *r = q + 16; // ERROR: r is now at an offset of 60+16=76
// from p. This means that r is (76-64)=12
// beyond the end of p. This is more than
// half a slot away, so baggy bounds will
// raise an error.
char *s = q + 8; // s is now at an offset of 60+8=68 from p.
// So, s is only 4 bytes beyond the baggy
// bounds, which is less than half a slot
// away. No error is raised, but the OOB
// high-order bit is set in s, so that s
// cannot be derefernced.
char *t = s - 32; // t is now back inside the bounds, so
// the OOB bit is cleared.
对于越界指针,高位被设置(如果在半个插槽内越界)。
那么作业问题的答案是什么?
char *p = malloc(255);
char *q = p + 256;
char ch = *q; // Does this raise an exception?
// Hint: How big is the baggy bound for p?
宽松边界检查是否必须检测每个内存地址计算和访问?
处理函数调用参数有点棘手,因为 x86 调用约定是固定的,即硬件期望栈上的某些内容放在特定位置。
特别是,宽松边界代码如何与由未经检测的代码分配的内存指针交互?
例子:
Contiguous range of
memory used for the
heap
+-------------------+
| |
| |
| Heap allocated by |
| uninstrumented |---+
| code | \ Bounds table
| | \
+-------------------+ \ +-----------+
| | +->| |
| | | Always 31 |
| Heap allocated by | | |
| instrumented code | +-----------+
| | | Set using |
| |--------->| baggy bnds|
+-------------------+ +-----------+
strcpy()
和memcpy()
?
strcpy()
不能确保目标有足够的空间来存储源!
可以摆脱存储边界信息的表,并将其放入指针中。
Regular pointer
+---------------+-------+------------------------+
| zero | size | supported addr space |
+---------------+-------+------------------------+
21 5 38
OOB pointer
+--------+------+-------+------------------------+
| offset | size | zero | supported addr space |
+--------+------+-------+------------------------+
13 5 8 38
这类似于一个胖指针,但具有以下优点……
……以便不破坏程序员的期望,并且数据布局保持不变。
还要注意,使用标记指针,我们现在可以跟踪远离基本指针多得多的越界指针。这是因为现在我们可以使用偏移量标记指针,指示它们距离基本指针有多远。在 32 位世界中,如果没有额外的数据结构,我们无法跟踪越界偏移量!
是的,因为这个世界充满了悲伤。
例子:
struct {
char buf[256];
void (*f) (void);
} my_type;
请注意*f
不是分配的类型,因此在调用期间与其解引用相关联的边界检查不存在。因此,如果s.buf
溢出(例如,由未经检测的库中的错误引起),并且s.f
被损坏,那么对f
的调用不会导致边界错误!
重新排列 f 和 buf 会有帮助吗?
struct my_type
)数组,可能不会有帮助。
slot_size/2
。
因此,baggy bounds 检查是一种减轻有缺陷代码中缓冲区溢出的方法。
usleep(16)
的地址覆盖返回地址,然后查看连接是否在 16 秒后挂起,或者是否崩溃(在这种情况下,服务器会使用相同的 ASLR 偏移量 fork 一个新的 ASLR 进程)。 usleep()
可能在 2¹⁶ 或 2²⁸ 个地方之一。更多细节。
ASLR 和 DEP 是非常强大的防御技术。
这种攻击称为返回导向编程,或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
这些操作码是“小工具”的一个示例。小工具是预先存在的指令序列,可以串联在一起创建一个利用。请注意,有一些用户友好的工具可以帮助您从现有二进制文件中提取小工具(例如,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 保护–我们没有生成任何新代码,只是调用了现有的代码!
假设:
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
次)。
2¹⁵
或 2²⁷
)。
usleep(16)
探测。
因此,我们已经讨论了如果服务器在重新生成时不更改 canaries,我们如何能够击败随机化的 canaries。我们还展示了如何使用 gdb 和 gadgets 来执行程序中预先存在的函数,使用攻击者控制的参数。但是如果服务器使用 ASLR 呢?这将阻止您使用离线分析来找到预先存在的函数的位置?
这就是今天讲座论文讨论的内容。该论文假设我们使用的是 64 位机器,所以从现在开始,在本讲座中我们也将假设如此。在这次讨论中,主要的变化是函数参数现在是通过寄存器传递而不是通过栈传递。
示例: 找到一个弹出栈中一个元素的 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()
库函数的位置。
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()
的位置。
现在我们想要在服务器与攻击者客户端之间的网络套接字上调用写入调用。我们需要以下小工具:
pop rdi; ret (socket)
pop rsi; ret (buffer)
pop rdx; ret (length)
pop rax; ret (write syscall number)
syscall
我们必须猜测套接字的值,但这在实践中相当容易,因为 Linux 将进程限制为同时打开 1024 个文件描述符,并且新文件描述符必须是可用的最低文件描述符(因此猜测一个小文件描述符在实践中效果很好)。
要测试我们是否猜对了文件描述符,只需尝试写入并查看是否收到任何内容!
一旦我们有了套接字号码,我们发出一个写入请求,发送的数据是指向程序的.text
段的指针!这使得攻击者可以读取程序的代码(虽然已随机化,但现在完全为攻击者所知!)。现在攻击者可以直接找到更强大的小工具,并利用这些小工具打开一个 shell。
exec()
代替fork()
来创建进程,因为fork()
会将父进程的地址空间复制给子进程。
fork()
的等效功能。
注意: 这些讲座笔记略有修改,来自 2014 年 6.858 课程网站上发布的笔记。
今天的讲座:如何在 Unix 上构建一个安全的 Web 服务器。我们实验室 Web 服务器 zookws 的设计灵感来自于 OKWS。
uid=0
表示,绕过大多数检查。
/etc/passwd
硬链接到具有全局可写/var/mail
的/var/mail/root
。
/etc/passwd
:
/
中查找'etc'
,在/etc
中查找'passwd'
。
/etc/passwd
(读或读写)。
setuid()
、setgid()
、setgroups()
。
/etc/shadow
中的匹配。
/etc/passwd
找到用户的 UID。
/etc/group
找到用户的组。
setuid()
、setgid()
、setgroups()
。
su
/ sudo
二进制文件通常是 setuid root。
"su otheruser"
。
su
进程会检查密码,如果正确则以otheruser
身份运行 shell。
$LD_PRELOAD
,…)
/
的含义。
/var/okws/run
中,…
/../
不允许从 chroot 中逃脱。
su
)可能会混淆/etc/passwd
的内容。
chroot()
以从 chroot 监狱中逃脱,因此 chroot 对于以 root 身份运行的进程来说不是一种有效的安全机制。
N
个相同的进程,处理 HTTP 请求。
'www'
身份运行。
N
个 Apache 进程中运行。
www
的 UID 执行。
okd -> oklogd
-> pubd
-> svc -> dbproxy
-> oklogd
okld
,okd
,pubd
,oklogd
,svc
)
dbproxy
,DB)
okld
为每个服务设置socketpair
s(双向管道)。
okld
首先通过 RPC 从oklogd
获取它)。
okd
:HTTP 服务的套接字对的服务器端 FD(HTTP+RPC)。
okd
监听一个单独的套接字以接收控制请求(repub,relaunch)。
okd
与pubd
通信以生成新模板,
okld
的目的是什么?
okld
不同于okd
?
okld
需要以 root 身份运行?(端口 80,chroot/setuid。)
okld
启动服务需要什么?
oklogd
套接字
fork
,setuid/setgid
,exec
服务
okd
oklogd
的目的是什么?
pubd
的目的是什么?
okld
都在同一个 chroot 中?
/cores/<uid>
。
/etc/okws_config
,由okld
保存在内存中。
oklogd
和pubd
有单独的 chroots,因为它们具有重要状态:
oklogd
的 chroot 包含日志文件,希望确保它没有被修改。
pubd
的 chroot 包含模板,希望避免泄露它们(?)。
okld
变得复杂。
okld
:对 Web 服务器机器的根访问权限,但也许没有对数据库的访问权限。
okd
:拦截/修改所有用户 HTTP 请求/响应,窃取密码。
pubd
:损坏模板,利用可能利用某些服务中的错误?
oklogd
:损坏/忽略/删除/伪造日志条目。
service
:向用户发送垃圾,访问 svc 的数据(模块化 dbproxy)。
dbproxy
:访问/更改其所连接的数据库中的所有用户数据。
okld
以 root 身份运行,与 Apache 中没有任何东西相比,但可能不重要。
dbproxy
中找到 SQL 注入攻击。
zookws
受 OKWS 启发,运行 Python 代码。
scripts.mit.edu
有类似的设计,以不同的 UID 运行脚本。
注意: 这些讲座笔记是从 2014 年 6.858 课程网站 上发布的笔记稍作修改而来。
/sysx/fort
(Unix 文件名语法)
/sysx/stat
。
/sysx/fort
“家庭文件许可证”(类似于关于/sysx 的 setuid)
/sysx/stat
。
/sysx/fort
/my/code.f -o /sysx/stat
/sysx/stat
文件。
/sysx/fort
只是编译器中的一个错误?
/etc/gcc.stats
中保留统计信息
/etc/gcc.stats
),也许不需要检查。
/etc/gcc.stats
,或/etc/passwd
,或…
/etc/gcc.stats
文件,将一个打开的文件描述符传递回我们的编译器进程。
G
中,并且项目dir
允许该组访问。
chmod
)。
“环境特权”:每次访问都隐式使用的权限。
Name Process privileges
| |
V V
Object -> Permissions -> Allow?
chroot
(同样,必须是 root)。
chroot
目录中?
chroot
对文件进行某种程度的解决,但很麻烦。*示例:*确保绝密程序无法泄露机密信息。
Name Operation + caller process
| |
V V
Object --------> Allow?
^
|
Policy -----------+
seccomp_filter
。
shm
,根据 Capsicum 论文。seccomp_filter
与常规/旧版 seccomp
有很大不同,而 Capsicum 论文讨论的是常规/旧版 seccomp
。
seccomp
)提供了一种折衷方案。
能力 --> 对象
chroot
。
openat
)。
/foo
拥有能力 C1
。
竞争条件示例:
T1: mkdir(C1, "a/b/c")
T1: C2 = openat(C1, "a")
T1: C3 = openat(C2, "b/c/../..") # should return a cap for /foo/a
Let openat() run until it's about to look up the first ".."
T2: renameat(C1, "a/b/c", C1, "d")
T1: Look up the first "..", which goes to "/foo"
Look up the second "..", which goes to "/"
namei
函数。
cap_enter()
vs lch_start()
exec
而不是 cap_enter
进行沙盒化的优势是什么?
tcpdump
在 stdin
、stdout
、stderr
上具有特权。
dhclient
具有原始套接字,syslogd
管道,租约文件。
CAP_KILL, CAP_SETUID
, CAP_SYS_CHROOT
, …
openat()
。
tcpdump
cap_enter()
。
procstat
查看生成的能力。
stdin
/stdout
/stderr
。
stderr
日志,更改终端设置,…
dhclient
gzip
Chromium
OKWS
dhclient
,Chromium)。
gzip
压缩级别错误。fork
/exec
设置沙盒需要花费O(1msec)
,非平凡的。