首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第23期->技术专题
期刊号: 类型: 关键词:
Solaris ypbind 远程溢出漏洞分析

作者:warning3 < mailto: warning3@nsfocus.com >
主页:http://www.nsfocus.com
日期:2001-07-17

前言:

  ypbind用来为NIS(网络信息服务)客户端与服务器端通信提供接口。它在NIS客户端和服
  务器上都被运行。有人发现它存在一个缓冲区溢出漏洞,Sun还为此出了一个安全公告
  #00203。

  由于ypbind是以root身份运行,本地或者远程攻击者就可以利用这个漏洞获取root权限。
  因此这个漏洞还是比较危险的。所幸的是,如果你没有启动NIS服务,ypbind缺省不会
  启动。


起因:
  
  让我们来看这个漏洞是怎么发生的。
  
  问题出在函数 <ypbindproc_setdom_3+784>()中,它执行了类似这样的一条语句(可能不准
  确)来进行串拷贝:
  
  sprintf(buf, "%s/%s/cache_binding", "/var/yp/binding", user_string);
  
  上面buf是一个300字节的缓冲区,user_string是一个客户端传送过来的字符串。
  ypbind在传送这个字符串的时候没有检查串的长度,这使得远程溢出成为可能。

  函数调用顺序为:ypbindprog_3()-->ypbindproc_domain_3()--><ypbindproc_setdom_3+784>()
  
  相关的定义如下:
  #define YPBINDPROG ((u_long)100007)
  #define YPBINDVERS ((u_long)3)
  #define YPBINDPROC_DOMAIN ((u_long)1)
  
  如果远程用户调用YPBINDPROC_DOMAIN函数时,就会触发ypbindprog_3()以及后续的函数
  执行。
  
  重要的参数结构如下:
  
  struct ypbind_domain {
        char           *ypbind_domainname;
        long            ypbind_vers;
  };

  其中ypbind_domainname是一个字符串指针,如果它指向一个较长的字符串(超过500字
  节),就可能引发溢出。

攻击:
  
  理论上说,我们可以传递一个很长的字符串以增大成功几率,但是由于buf缓冲区所在
  的位置比较靠近堆栈的底端(高地址处),所以,如果字符串较长,比如超过2000字节,
  往往导致覆盖到了栈底,发生段访问错误.
  
  本来这是一个非常简单的堆栈溢出,但是我在测试TCP连接时,发现一个非常奇怪的现象。
  例如,溢出是发生在<ypbindproc_setdom_3+784>()中的,它会覆盖它的调用函数
  (ypbindproc_domain_3())的保存栈幀,按说在ypbindproc_domain_3()返回的时候就应该
  跳到我们的shellcode去执行了。问题是如果我用gdb跟踪,将断点设在了
  <ypbindproc_setdom_3+784>()  函数处,然后程序会在这里停一下,然后我在gdb中用"c"
  命令继续执行,这就会发生正常的堆栈溢出过程,shellcode可以被执行。但是,如果我
  不在<ypbindproc_setdom_3+784>()那里设置断点,溢出仍然发生了,但是奇怪的是,唯独
  ypbindproc_domain_3()的保存栈幀(一块64字节长的内存)又恢复成正常的值了,它前后
  的内存都被我的数据覆盖了,这导致程序正常返回到了ypbindprog_3()!
  看起来好像存在一种保护机制,例如信号句柄,使得被覆盖的保存栈幀又恢复了。
  比较奇怪。
    
  幸运地是,在ypbindprog_3()中调用ypbindproc_domain_3()后会调用另一个函数
  svc_sendreply(),它会调用一个函数指针,而这个指针可以为我们所控制:
  
  (gdb)...
  Program received signal SIGSEGV, Segmentation fault.
  0xef73f714 in svc_sendreply ()
  (gdb) bt
  #0  0xef73f714 in svc_sendreply ()
  #1  0x12b64 in ypbindprog_3 ()
  #2  0xef73fcb0 in _svc_prog_dispatch ()
  #3  0xef73fab0 in svc_getreq_common ()
  #4  0xef73f9e8 in svc_getreq_poll ()
  #5  0xef7435fc in _svc_run ()
  #6  0xef743350 in svc_run ()
  #7  0x12a24 in main ()
  
  (gdb) x/20i $pc
  [...]
  0xef73f738 <svc_sendreply+56>:  ld  [ %i0 + 8 ], %o0
  0xef73f73c <svc_sendreply+60>:  ld  [ %o0 + 0xc ], %o2
  0xef73f740 <svc_sendreply+64>:  call  %o2
  
  上面的$i0是我们可以覆盖的,所以我们要设法使%i0的值指向这样的一个模板:
  +--+--+---+---+
  |xx|xx|adr|ret|
  +--+--+---+---+
  ^
  |_adr
  
  假设这个模板的地址是adr,那么
  adr + 8(字节) = adr (%o0)
  adr + 12(字节) = ret (shellcode所在地址,%o2)
  
  ret地址可以通过使用NOP指令来增大命中几率。
  
  现在问题是adr是一个未知的地址,如果保证这个模板的起始地址刚好在%i0所指的地址
  上呢,如果是在本地的话,可以通过构造环境变量的方法来将我们的模板放在一个准确
  的地址上。
  
  但是远程就不行了,只能猜测这个adr地址。如果要增大命中几率,就要有很多16字节的
  模板,如下图所示:
  
  +--+--+---+---+--+--+---+---+--+--+---+---+--+--+---+---+
  |xx|xx|adr|ret|xx|xx|adr|ret|xx|xx|adr|ret|xx|xx|adr|ret|
  +--+--+---+---+--+--+---+---+--+--+---+---+--+--+---+---+
  
  这要求我们猜测的地址要刚好在16字节的边界上,这个几率也不算太大,我发现标了"xx"
  的字节我们并没有被用到,而且有用的两个字节又刚好是挨在一起的,那么我们就可以
  将"xx"也利用一下,变成这样的模板:
                                                              ret   
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+   +---------------+
  |adr|ret|adr|ret|adr|ret|adr|ret|adr|ret|adr|ret|adr|ret|...|nop...shellcode|
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+   +---------------+
          ^
          |_adr
            
  这样我们只要保证我们猜测的地址刚好落在任意一个"adr"的地址上就行了,这样的命中
  几率就比较高了。
  
  有意思的是,如果使用UDP进行连接,那么我们不会碰到上面svc_sendreply()的问题,
  这里好像也有一个保护机制,反而是ypbindprog_3()的栈幀可以被覆盖,因此,从
  ypbindprog_3  返回时,就可以跳到shellcode中去执行。
  
  原理已经说完了,有兴趣的朋友不妨自己试着写一写测试程序。:)

解决:

  Sun已经提供了补丁。根据我的分析,补丁很简单,只是在调用ypbindproc_setdom前检
  查了一下字符串长度,如果长度小于或等于0x100(256)字节,就继续操作,否则就返回。
  
  <...>
  0x137d4 <ypbindproc_domain_3+28>:       call  0x265cc <strlen>
  0x137d8 <ypbindproc_domain_3+32>:       ld  [ %i0 ], %o0
  0x137dc <ypbindproc_domain_3+36>:       cmp  %o0, 0x100 <-- 比较strlen的结果是否大于0x100
  0x137e0 <ypbindproc_domain_3+40>:       bleu  0x137f8 <ypbindproc_domain_3+64>
  0x137e4 <ypbindproc_domain_3+44>:       mov  2, %o0      <--- 大于0x100,返回
  0x137e8 <ypbindproc_domain_3+48>:       st  %o0, [ %i1 ]
  0x137ec <ypbindproc_domain_3+52>:       st  %o0, [ %i1 + 4 ]
  0x137f0 <ypbindproc_domain_3+56>:       ret
  0x137f4 <ypbindproc_domain_3+60>:       restore  %g0, %i1, %o0
  0x137f8 <ypbindproc_domain_3+64>:       call  0x144e4 <ypbindproc_setdom_3+832>
                                          ^
                                          |-- 否则,继续进行。
  
  具体解决方法可以看参考资料中的内容,这里就不罗嗦了。
  
<参考资料>:

  [1.] Sun Microsystems, Inc. Security Bulletin #00203
       http://sunsolve.sun.com/pub-cgi/retrieve.pl?doctype=coll&doc=secbull/203&type=0&nav=sec.sba
  [2.] Solaris ypbind 远程缓冲区溢出漏洞
       http://security.nsfocus.com/showQuery.asp?bugID=1602
版权所有,未经许可,不得转载