前言
在编程中,文件操作是一个非常常见的任务,但同时也是一个容易忽视的安全隐患。如果在使用open()函数打开文件后忘记使用close()函数关闭文件,就可能引发严重的信息泄露问题。在Unix和Linux系统中,文件描述符是一个关键的概念,几乎所有的操作都涉及文件描述符的使用。当一个文件被打开后,系统会为其分配一个文件描述符,程序可以通过这个描述符对文件进行读写操作。然而,如果在程序执行完相关操作后未能及时关闭文件,这些文件描述符可能会在系统中保持打开状态,从而导致潜在的安全风险。
什么是文件描述符
文件描述符(File Descriptor)是计算机操作系统中的一个抽象概念,用于表示一个指向文件或其他输入/输出资源(如管道、网络连接等)的引用。在Unix和Linux系统中,文件描述符是一个非负整数,当程序打开一个文件时,操作系统会返回一个文件描述符,程序可以通过这个文件描述符来执行读写操作。
例如,标准输入、标准输出和标准错误这三个特殊的文件描述符分别对应0、1和2。程序可以通过这三个描述符与用户进行交互,而不必关心实际的输入输出设备。
在Python中,使用open()函数打开文件时,会返回一个文件对象,其包含一个文件描述符。代码如下:
f = open("/etc/passwd", 'r') # 打开/etc/passwd文件
此时,文件描述符指向该文件,可以进行读取操作。相应地,可以通过调用close()方法关闭文件:
f.close() # 关闭文件
什么是proc文件系统
proc 是 Linux 系统中的一个伪文件系统,用于提供系统运行时的信息和配置接口。与普通文件系统不同,proc 文件系统中的文件和目录并不存储在磁盘上,而是驻留在内存中,实时反映内核和进程的状态信息。通过 proc,用户和应用程序可以方便地获取系统内核、硬件和进程的信息,甚至可以通过它来配置某些内核参数。
这里我运行python3后,使用pidof获取python3的进程id
pidof是Linux系统中用来查找正在运行进程的进程号(PID)的工具
当前,python3进程的id号为1058930,进入该进程的 proc 目录:
cd /proc/1058930/fd
ls -al
这里有三个不同的文件,其实是三个不同的文件描述符
0:标准输入
1:标准输出
2:标准错误输出
回到python3,然后随意打开一个文件,来实际了解一下是如何运行的
f = open("/etc/passwd",'r') #读取passwd文件里的内容,然后存储在f变量里
然后再查看/proc/1058930/fd目录下的文件
可以看见这个文件夹下多了一个python刚刚导入的文件,使用close()函数关闭文件
现在文件已经成功关闭了,但即使文件已经成功关闭,在编程时如果打开了一个具有 root 权限的文件并且未能关闭其文件描述符,可能导致严重的安全隐患。未关闭的文件描述符会在系统中保持打开状态,潜在地暴露敏感信息。例如,恶意用户或进程可以通过访问这些残留的文件描述符,读取或修改具有高权限的文件内容,甚至可能获取系统的控制权限
实例演示
简单写一个实验的脚本
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main () {
int file_descriptor;
file_descriptor = open("/etc/shadow", 0); //打开/etc/shadow这个文件
setuid(getuid()); //当前权限从root变为user权限
char *args[] = {"sh", 0};
execvp("/bin/sh", args); //直接进入shell,方便演示
return 0;
}
编译文件
gcc filename -o test
然后给编译后的文件权限
chmod u+s test
./test
切换到普通用户下运行程序
我现在只是个普通用户的权限,然后进入到我们程序正在运行的proc目录下
cd /proc/self/fd #self表示为当前自身程序
使用重定向字符串读取第三个文件描述符,因为文件正在被使用
cat <&3
现在普通用户也可以通过文件描述符读取/etc/shadow文件,但是我们用当前shell直接读/etc/shadow文件是不行的
因为普通用户权限不能直接读取/etc/shadow文件
防范文件描述符导致的安全危害
在每次完成文件操作后,立即调用close()方法关闭文件描述符即可,最后附上一个因为文件描述符而导致权限提升的漏洞案例
https://www.sektioneins.de/en/blog/15-07-07-dyld_print_to_file_lpe.html