首页 > 安全 > 网络安全 >

GNU tar解压路径绕过漏洞分析(CVE-2016-6321)

2016-11-08

GNU tar文档管理命令是linux系统下常用的一个打包、压缩的命令,CSS(FSC1V Cyber Security Services)团队的研究员Harry Sintonen发现,tar命令在解压时存在一个路径名绕过漏洞,在一些特定的场景下,利用此漏洞甚至可导致远程代码执行。该漏洞获得CVE编号CVE-2016-6321。

0x00 摘要

GNU tar文档管理命令是linux系统下常用的一个打包、压缩的命令,CSS(FSC1V Cyber Security Services)团队的研究员Harry Sintonen发现,tar命令在解压时存在一个路径名绕过漏洞,在一些特定的场景下,利用此漏洞甚至可导致远程代码执行。该漏洞获得CVE编号CVE-2016-6321。

0x01 细节

如果攻击者利用该漏洞,精心构造一个特殊的打包文档,受害者在使用tar命令对其进行解压操作时,不论他在命令行指定的目标路径是什么,都可被攻击者绕过,并将提交准备好的文件与目录解压到攻击者指定的地方。

通过这种方式,可以对特定文件进行覆盖与重写,如果解压操作是以高权限身份进行的,例如root账户,那么可以覆写的文件内容就更多也更重要,在最坏的场景下,攻击者可以借助此漏洞对系统的完全控制(root身份下的远程代码执行)。

这个漏洞的影响范围从GNU tar 1.14 to 1.29 (包含1.29) ,影响包括Red Hat,Alphine Linux,Red Star OS以及其他所有使用GNU tar的Linux系统。接下来我们从tar的官网上下载1.29的版本以进行源代码审计,[下载传送门]。

在lib/paxnames.c文件中,有一个safer_name_suffix()函数,如果absolute_names变量为0,则将文件名中文件系统的前缀给去掉,并且会对 file_name 进行了一些安全检查 。

if ( absolute_names )

p = file_name;

else{

/* Skip file system prefixes, leading file name components that contain

* "..", and leading slashes. */

size_t prefix_len = FILE_SYSTEM_PREFIX_LEN( file_name );

for ( p = file_name + prefix_len; *p; )

{

if ( p[0] == '.' && p[1] == '.' && (ISSLASH( p[2] ) || !p[2]) )

prefix_len = p + 2 - file_name;

do

{

char c = *p++;

if ( ISSLASH( c ) )

break;

}

while ( *p );

}

for ( p = file_name + prefix_len; ISSLASH( *p ); p++ )

continue;

prefix_len = p - file_name;

if ( prefix_len )

{

const char *prefix;

if ( hash_string_insert_prefix( &prefix_table[link_target], file_name,

prefix_len, &prefix ) )

{

static char const *const diagnostic[] =

{

N_( "Removing leading `%s' from member names" ),

N_( "Removing leading `%s' from hard link targets" )

};

WARN( (0, 0, _( diagnostic[link_target] ), prefix) );

}

}

}

//...

return p ;

从上述代码中可以读出,经过safer_name_suffix 操作之后,不论在命令行中指定的目标路径是什么,只要文件名中包含"../",那么“../”序列之后部分的文件名与你的当前工作目录就变成了相对关系。

这个漏洞的历史情况有些复杂,

1. 在13.12.1999 commit 之前,压缩文件条目可以通过“../”字符串直接绕过解压路径问题,并将文件写到任意位置。

2. 在13.12.1999 commit之后,代码进行了一些修复,会警告压缩文件文件名中存在..字符串,并且会跳过不去处理这些文件。

3. 然而,在05.07.2003 commit 时,tar引入safer_name_suffix函数,不再使用自己的检查机制。

在05.07.2003 提交的变动中,safer_name_suffix 不再跳过文件名中包含“../”字符串的恶意成员文件,而是想办法去让纠正恶意的文件名,为了让文件名变得安全,该函数做了一些措施,删除所有存在攻击性的成分如"../"及其之前的部分,强行将它们与解压目标路径变成相对关系,看起来这的确更加安全了,但不幸的是,这些操作会带来一些副作用。

例如,test.tar包中有一个文件项,其路径为 etc/motd/../etc/shadow,你在根目录/下,你想使用tar xvf test.tar etc/motd/ 将tar包中打包的motd解压恢复到本机的motd目录中,然而在safer_name_suffix函数中经过一系列的安全操作后,已经将文件项“../”前面的内容都去掉了,路径文件名只剩下etc/shadow,这时你的/etc/shadow就被攻击者覆写了。

0x02 具体攻击场景

1. 攻击者可以用这种手段诱使用户替换一些重要的文件,例如 .ssh/authorized_keys , .bashrc , .bash_logout , .profile , .subversion 或 .anyconnect。示例代码如下:

user@host:~$ dpkg --fsys-tarfile evil.deb | tar -xf - \

--wildcards 'blurf*'

tar: Removing leading `blurf/../' from member names

user@host:~$ cat .ssh/authorized_keys

ssh-rsa AAAAB3...nU= mrrobot@fsociety

user@host:~$

2. 有一些从web应用或者其它类似来源自动解压文件的脚本,这些脚本一般会以setuid root权限执行,通常这类脚本的解压命令如下:

#tar -C / -zxf /tmp/tmp.tgz etc/application var/chroot/application/etc

在这种情况下,攻击者可以重写 /var/spoon/cron/crontabs/root 以获取root身份的代码执行能力;也可以将可能被root身份执行的二进制文件替换成一个有后门的版本;或者投放一个setuid root的二进制文件,等待被管理员执行,使攻击者有机会获取root权限。

在现实生活中,这种场景下的漏洞利用曾有过好几例成功的案例。

3. 以root身份执行解压命令也可能被攻击 。例如上文中提到覆写/etc/shadow的例子

# tar -C / -xvf archive.tar etc/motd

tar: Removing leading `etc/motd/../' from member names

etc/motd/../etc/shadow

#

如果--exclude规则与--anchored选项同时使用,那么即使手动加了--exclude规则也没有用,例如:

#tar -C / -xvf archive.tar --anchored --exclude etc/shadow

tar: Removing leading `etc/motd/../' from member names

etc/motd/../etc/shadow

在两种情况下,攻击者都成功地把/etc/shadow替换成了任意内容。

不过,在实际利用这个漏洞时,攻击者需要首先知道一些特定的前导信息,例如解压命令执行时实际在命令行下指定的路径名,毕竟在构造攻击tar包时“../”序列之前的路径前缀需要符合tar命令中所输入的路径,攻击才能奏效。

0x03 漏洞修复补丁

漏洞作者提出了一些解决方法,并给出了补丁文件,他的想法是直接在safer_name_suffix函数中添加逻辑,如果检测到../字符串,直接报FATAL_ERROR,然后退出程序。补丁内容如下:

--- lib/paxnames.c.orig 2016-04-06 00:04:47.314860045 +0300

+++ lib/paxnames.c 2016-04-06 02:08:44.962297881 +0300

@@ -18,6 +18,7 @@

#include

#include

#include

+#include

/* Hash tables of strings. */

@@ -114,7 +115,15 @@

for (p = file_name + prefix_len; *p; )

{

if (p[0] == '.' && p[1] == '.' && (ISSLASH (p[2]) || !p[2]))

- prefix_len = p + 2 - file_name;

+ {

+ static char const *const diagnostic[] =

+ {

+ N_("%s: Member name contains '..'"),

+ N_("%s: Hard link target contains '..'")

+ };

+ FATAL_ERROR ((0, 0, _(diagnostic[link_target]),

+ quotearg_colon (file_name)));

+ }

do

{

虽然这种做法简单粗暴,的确把漏洞给补了,但是并不是完美的补丁,毕竟在恢复网站或者配置的时候,还原到一半就停下来了,这体验肯定是tar开发者自身都不能容忍的。后来,开发者在scr/extract.c代码中做了修改,当检测到文件名中存在../时,直接跳过该文件,时间仿佛回到了十七年前那个冬天的13.12.1999 commit。

diff --git a/src/extract.c b/src/extract.c

index f982433..7904148 100644

--- a/src/extract.c

+++ b/src/extract.c

@@ -1629,12 +1629,20 @@ extract_archive (void)

{

char typeflag;

tar_extractor_t fun;

{C} + bool skip_dotdot_name;

fatal_exit_hook = extract_finish;

set_next_block_after (current_header);

+ skip_dotdot_name = (!absolute_names_option

+ && contains_dot_dot (current_stat_info.orig_file_name));

+ if (skip_dotdot_name)

+ ERROR ((0, 0, _("%s: Member name contains '..'"),

+ quotearg_colon (current_stat_info.orig_file_name)));

+

if (!current_stat_info.file_name[0]

+ || skip_dotdot_name

|| (interactive_option

&& !confirm ("extract", current_stat_info.file_name)))

{

0x04 最后

虽然有些攻击看起来条件很难满足,但是在实际的攻击场景中,这些看起来很苛刻的洞可能正有奇效。

以下是漏洞作者给出的PoC,用户可用其自检,目前正是漏洞生存周期0day与1day交替阶段,基本都可成功。

$ curl https://sintonen.fi/advisories/tar-poc.tar | tar xv etc/motd

$ cat etc/shadow

相关文章
最新文章
热点推荐