kavin

当git遇上ssh——CVE-2017-1000117漏洞浅析

kavin 安全防护 2022-12-16 361浏览 0

Git是一个开源的分布式版本控制系统,主要用于项目管理。

而SSH是一种应用层的安全通信协议,最常用的就是为通信双方在在不安全网络上提供安全的远程登录。

当他们二者相遇会发生什么有趣的事呢?这里以CVE-2017-1000117漏洞为例,简要剖析该漏洞的成因及防护方法。

当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,如下图。

当git遇上ssh——CVE-2017-1000117漏洞浅析

本地的机器(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执行。

当git遇上ssh——CVE-2017-1000117漏洞浅析

在start_command函数中,最终调用exec系列函数执行ssh,由于错误的把 -oProxyCommand=gedit 作为远程待连接的host,最终引发了命令执行。

当git遇上ssh——CVE-2017-1000117漏洞浅析

但像上面ssh://-oProxyCommand=”gedit /tmp/xxx”的链接比较暴露,直接在链接中就出现命令。比较隐蔽的方法是,在正常仓库的目录下建立一个子模块submodule,而将恶意的ssh链接藏在.gitmodule文件中。

修复防护方法

看完上面漏洞发生的成因,其实可以发现这个过程就是git处理ssh这类智能协议的传输过程:ssh远程登录git服务器后,通过执行git-upload-pack处理下载的数据,这种处理方式较http哑协议传输更高效。

但是在这过程中,对一些恶意的ssh链接,没有正确识别,在解析时误将 -oProxyCommand 这类参数当做了远程主机名host,从而产生了漏洞。

当git遇上ssh——CVE-2017-1000117漏洞浅析

在新版本中,我们看到增加了对host和path的识别过滤。

对包含疑似命令的host和path及时进行了阻止,阻断了漏洞的发生。

当git遇上ssh——CVE-2017-1000117漏洞浅析

建议用户及时排查,更新系统存在漏洞的Git版本,在日常通过Git进行项目管理时,仔细检查项目中是否存在一些恶意ssh链接来预防安全问题。

继续浏览有关 安全 的文章
发表评论