Git是一个开源的分布式版本控制系统,主要用于项目管理。
而SSH是一种应用层的安全通信协议,最常用的就是为通信双方在在不安全网络上提供安全的远程登录。
当他们二者相遇会发生什么有趣的事呢?这里以CVE-2017-1000117漏洞为例,简要剖析该漏洞的成因及防护方法。
漏洞相关信息:
版本控制软件爆出远程命令执行漏洞 涉及Git、SVN、Mercurial、CVS版本控制
简述:几个流行的版本控制系统受到可能严重的远程命令执行漏洞的影响。受影响产品的开发人员本周发布了更新补丁来修补安全漏洞。该缺陷影响版本控制软件, 如 Git (CVE-2017-1000117)、Apache Subversion (CVE-2017-9800)、Mercurial (CVE-2017-1000116) 和 CVS。由于CVS 系统上次更新已经是9年前的事情了, 因此没有为它分配 CVE 标识符。
背景知识
ssh客户端登录时,有一个ProxyCommand选项,该选项的指定链接服务器时执行的命令。
ProxyCommand Specifiesthecommandtousetoconnecttotheserver.The commandstringextendstotheendoftheline,andisexecuted withtheuser’sshell.Inthecommandstring,anyoccurrence of‘%h’willbesubstitutedbythehostnametoconnect,‘%p’ bytheport,and‘%r’bytheremoteusername.
该选项常用的场景是通过代理服务器与目标机器相连,因此被称作ProxyCommand,如下图。
本地的机器(Local)无法直接与目标机器(Target)相连,必须通过一个代理机器(Proxy)才能和目标机器建立连接。此场景多见于企业或有较强访问控制的需求的地方。
因此在这种情况下,ssh客户端可以采用ProxyCommand选项,通过下面命令最终和目标机器建立连接。
ssh-oProxyCommand=’sshuser@proxync%h22′user@Target
加上ProxyCommand选项后。ssh客户端会先用当前用户的shell执行ProxyCommand中的内容。
例如下面的命令,在Linux桌面环境中执行,就会弹出gedit文本编辑器。
ssh-oProxyCommand=gedituser@Target
即便最后的user@hostname不合法,也不会影响ProxyCommand中先执行的命令,照样可以弹出gedit。
好了介绍完了ProxyCommand,可以理解为这个选项如处理不当,是可以进行命令注入的!
CVE-2017-1000117漏洞
CVE-2017-1000117这个漏洞就是没有正确处理ssh链接的请求,导致受害人通过Git版本控制系统,访问恶意链接时,存在安全隐患,一旦黑客攻击成功,可在受害人机器上执行任意命令。
git clone是Git版本控制系统中常用的将远程仓库克隆到本地的命令。当使用git clone访问下面的恶意ssh链接时,会在本地执行命令,弹出gedit。
gitclonessh://-oProxyCommand=”gedit/tmp/xxx”
下面我们来详细看一看其中的过程,当git遇上ssh后,最终是如何触发这个漏洞执行的。
git客户端在执行上面的命令后,通过一系列的参数解析后,进入git_connect函数,向git的服务端建立连接。
structchild_process*git_connect(intfd[2],constchar*url, constchar*prog,intflags) { char*hostandport,*path; structchild_process*conn=&no_fork; enumprotocolprotocol; structstrbufcmd=STRBUF_INIT; /*Withoutthiswecannotrelyonwaitpid()totell *whathappenedtoourchildren. */ signal(SIGCHLD,SIG_DFL); protocol=parse_connect_url(url,&hostandport,&path); if((flags&CONNECT_DIAG_URL)&&(protocol!=PROTO_SSH)){ printf(“Diag:url=%s\n”,url?url:“NULL”); printf(“Diag:protocol=%s\n”,prot_name(protocol)); printf(“Diag:hostandport=%s\n”,hostandport?hostandport:“NULL”); printf(“Diag:path=%s\n”,path?path:“NULL”); conn=NULL; }elseif(protocol==PROTO_GIT){ ….. }else{ conn=xmalloc(sizeof(*conn)); child_process_init(conn); strbuf_addstr(&cmd,prog); strbuf_addch(&cmd,‘‘); sq_quote_buf(&cmd,path); /*removerepo-localvariablesfromtheenvironment*/ conn->env=local_repo_env; conn->use_shell=1; conn->in=conn->out=-1; if(protocol==PROTO_SSH){ constchar*ssh; intputty=0,tortoiseplink=0; char*ssh_host=hostandport; constchar*port=NULL; transport_check_allowed(“ssh”); get_host_and_port(&ssh_host,&port); if(!port) port=get_port(ssh_host); ssh=getenv(“GIT_SSH_COMMAND”); if(!ssh){ constchar*base; char*ssh_dup; /* *GIT_SSHistheno-shellversionof *GIT_SSH_COMMAND(andmustremainsofor *historicalcompatibility). */ conn->use_shell=0; ssh=getenv(“GIT_SSH”); if(!ssh) ssh=“ssh”; ssh_dup=xstrdup(ssh); base=basename(ssh_dup); free(ssh_dup); } argv_array_push(&conn->args,ssh); if(port){ /*PisforPuTTY,pisforOpenSSH*/ argv_array_push(&conn->args,putty?“-P”:“-p”); argv_array_push(&conn->args,port); } argv_array_push(&conn->args,ssh_host); }else{ transport_check_allowed(“file”); } argv_array_push(&conn->args,cmd.buf); if(start_command(conn)) die(“unabletofork”); ….. } }
git_connect函数的第二个参数url,即为传入的ssh链接,在此例中为 “ssh://-oProxyCommand=gedit /tmp/xxx”。
在git_connect函数中通过parse_connect_url函数将待连接的url解析出来,返回url的主机名、相对路径及url采用的协议。
https://github.com/git/git/blob/master/connect.c#L620
/* *ExtractprotocolandrelevantpartsfromthespecifiedconnectionURL. *Thecallermustfree()thereturnedstrings. */ staticenumprotocolparse_connect_url(constchar*url_orig,char**ret_host,char**ret_path)
对于正常的ssh链接,如 ssh://user@host.xzy/path/to/repo.git/,经parse_connect_url解析后,其返回的ret_host和ret_path的值应该为 user@host.xzy 和 /path/to/repo.git/ 。
但由于没有对ssh做正确过滤及识别,对于恶意的ssh链接,返回的ret_host和ret_path的值则是 -oProxyCommand=gedit 和 /tmp/xxx ,误将 -oProxyCommand=gedit 作为了主机名ret_host。
在后续处理中,git_connect得到本地ssh的路径,将上面获取的ssh host和path填充到struct child_process *conn中,再通过start_command调用本地ssh执行。
在start_command函数中,最终调用exec系列函数执行ssh,由于错误的把 -oProxyCommand=gedit 作为远程待连接的host,最终引发了命令执行。
但像上面ssh://-oProxyCommand=”gedit /tmp/xxx”的链接比较暴露,直接在链接中就出现命令。比较隐蔽的方法是,在正常仓库的目录下建立一个子模块submodule,而将恶意的ssh链接藏在.gitmodule文件中。
修复防护方法
看完上面漏洞发生的成因,其实可以发现这个过程就是git处理ssh这类智能协议的传输过程:ssh远程登录git服务器后,通过执行git-upload-pack处理下载的数据,这种处理方式较http哑协议传输更高效。
但是在这过程中,对一些恶意的ssh链接,没有正确识别,在解析时误将 -oProxyCommand 这类参数当做了远程主机名host,从而产生了漏洞。
在新版本中,我们看到增加了对host和path的识别过滤。
对包含疑似命令的host和path及时进行了阻止,阻断了漏洞的发生。
建议用户及时排查,更新系统存在漏洞的Git版本,在日常通过Git进行项目管理时,仔细检查项目中是否存在一些恶意ssh链接来预防安全问题。
转载请注明:IT运维空间 » 安全防护 » 当git遇上ssh——CVE-2017-1000117漏洞浅析
发表评论