Shellshock漏洞分析与攻击实践

Shellshock漏洞分析与攻击实践

前言:什么是Shellshock漏洞

Shellshock: CVE-2014-6271

? 该漏洞于2014年9月24日被发现。是Bash程序的漏洞,该漏洞允许攻击者执行任意执行代码

? 由于Bash程序被很多网络服务器用来处理CGI(Common Gateway Interface)请求,并且该漏洞非常容易利用,它利用了Bash将环境变量转换为函数定义时犯的一个错误:

? 在一个Bash进程中定义一个shell函数的环境变量再加上一个"小尾巴",当这个Bash进程执行一个子Bash进程时,父bash进程给子bash进程传递环境变量,由于Bash的shell函数定义特性,子bash进程会将shell函数形式的环境变量转化成子Bash进程的shell函数,这时由于Bash源码的漏洞会意外的额外执行"小尾巴"!

? 由于该漏洞的广泛性以及漏洞利用的简易程度,其危害等级甚至已经超过了ssh心脏出血漏洞!

受影响的Bash版本如下图:

Nrvf1I.png

一、Shell程序之Bash

? 在操作系统中,Shell程序是一个命令行解释器,它的主要工作是提供一系列用户与操作系统之间的操作接口,它通过在终端窗口或控制台中执行命令的方式调用这些操作接口来设置或读取操作系统中的数据,方便了用户与操作系统的通信。目前存在着许多不同类型的Shell,包括sh、bash、csh、zsh和Windows PowerShell等。

? Bash是Linux操作系统中最受欢迎的Shell程序之一。根据前言中对Bash的ShellShock漏洞的简述,我们可以知道本篇中介绍的Shellshock漏洞就跟Bash中的Shell函数定义有关。下面我们来讨论一下在Bash中获得Shell函数定义的两种方式。

二、Bash两种获得Shell函数定义的方式

? 在含有Shellshock漏洞的Bash中获得函数定义的方式有两种:1、直接定义函数; 2、通过环境变量的转换,Shellshock漏洞出现在第二种定义方式中。

1、直接定义函数

NspSDP.png

我们可以直接将函数定义好,并且直接通过输入函数名的方式来调用它。如果需要将这个函数传递给子Bash进程来让子Bash进程获得这个函数定义,我们需要使用export -f(指shell函数)命令将其导出。

NsixTx.png

此方法了解即可,ShellShock漏洞需要用到的有关Shell函数定义获得的方法是第二种。下面我们来看第二种方式。

2、定义Shell变量的方式

此方法仅限子Bash进程获得父Bash进程给的Shell函数定义

在父Bash进程中定义一个Shell变量,变量名是函数名,变量内容是函数体(需要用单引号括上)。例如:foo='() { echo "Hello World!"}',定义好该变量后,由于Bash的传递变量的机制,一旦父Bash进程运行了一个子Bash进程,就会将父Bash进程中 有关系统环境变量的Shell变量 和 用户自定义的并且标记export的Shell变量 复制一份到子Bash进程中。

环境变量和Shell变量是两个概念,相关参考推荐:《计算机安全导论:深度实践》杜文亮, P26

子Bash进程获得了父Bash进程传递来的变量以后,会检查变量中的值是否是以" () { "开始,如果是,就会将该变量转换成一个shell函数,而非一个shell变量。如下图所示。(bash_shellshock是带有shellshock漏洞版本的bash,前面实验中使用的bash也软链接了bash_shellshock)

NstJtf.png

ShellShock漏洞就出在这一Shell变量转换成函数的机制中,如果在变量中最后的花括号后加上“小尾巴”,由于Bash中解析逻辑的漏洞,Bash就会执行变量中“小尾巴的内容”。如下图所示。

NsdrMF.png

三、Bash源代码漏洞分析

由上面分析我们知道,Shellshock漏洞主要出现在,子Bash进程获得父Bash进程的Shell变量时,对函数定义的变量解析出现了问题,该问题导致额外的代码被执行。

Shellshock漏洞存在于Bash源代码中的variables.c文件中。当子Bash进程在它的foo环境变量中找到“foo=() {”开头的字符串时将触发解析逻辑。不幸的是,在解析逻辑中有一个错误,与该错误有关的代码片段如下所示。

ftp://ftp.gnu.org/gnu/bash/bash-4.2.tar.gz

1
2
3
4
5
6
7
8
9
10
11
12
13
void
initialize_shell_variables (env, privmode)
     char **env;
     int privmode;
{
  ......
      /* 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))
    {
    ......
       //Shellshock漏洞在于这句话
      parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);

上述代码段是variables.c中的一部分。这段代码首先会先判断Shell变量是否是以"() {"开头,如果是的话,会使用parse_and_execute函数将这个变量解析成Shell函数。而由于parse_and_execute函数的功能过于强大,除了解析函数,它还有执行命令的功能。如果解析的字符串只是一个函数定义,该函数只会解析它,不会执行它。但如果该字符串中包括使用分号“;”隔开的多条Shell命令,该函数则会解析和运行每一条命令,这就是问题所在。

四、利用Shellshock漏洞进行攻击实验

1、利用Shellshock攻击Set-UID程序

Set-UID程序介绍
什么是Set-UID程序

Set-UID程序简单来说,它是一个穿着超人铠甲却行动受限的战士。它的能力超强,但是人性不容试探,它的超能力只能规定用于有限的地方。一般在linux中,passwd、su等命令都是Set-UID程序,因为passwd、su等命令可以实现密码的修改(修改/etc/shadow)、切换用户等功能,这些功能的操作权限要求是root但是普通用户却也可以执行,就是因为它们拥有Set-UID这个亦正亦邪的特性。

Set-UID程序的原理

? 在Linux中一共存在着三种用户ID:有效用户ID、真实用户ID和保留用户ID。真实用户ID表明运行该进程的用户有效用户ID表明该进程拥有什么权限

? 对于Set-UID程序,在该进程被运行时,该进程有效用户ID(该进程的权限)取决于该进程的属主的权限。因此,普通用户执行passwd、su等命令时,进程的权限提升为文件属主的权限,文件属主是root,所以就可以以普通权限的身份去提升进程的权限,执行高权限的操作了。而在非Set-UID程序中一般不加入有效用户ID的定义,也就是谁运行的该进程,该进程就是谁的身份权限。

Set-UID的特点

一般来说,Set-UID程序的共同特点是文件属主是root(sudo chown root XXX), 并且在它的权限位中Set-UID比特位被设置(sudo chmod u+s XXX)

NsRjW4.png

利用Shellshock漏洞对Set-UID程序进行攻击

存在如下可攻击的程序vul.c

1
2
3
4
5
6
7
8
#include<stdlib.h>
int main()
{
    setuid(geteuid());
    //因为Bash的保护机制,如果真实Id和有效Id不同,则Bash不会处理从环境变量处获得的函数定义,因此这里将真实Id设置为和有效Id相同
    system("ls");
    //system函数实际上是执行了fork和execl("/bin/sh", "sh", "-c", command...),实际上是通过/bin/sh来执行命令。在本实验中需要将/bin/sh软链接指向/bin/bash_shock
}

将程序设定为Set-UID程序。

NsWRn1.png

设置环境变量执行攻击:

Nshpa6.png

这里首先利用漏洞定义一个带有“小尾巴”的Shell函数,小尾巴的功能是开启一个shell。执行这个程序之后,发现成功拿到了shell,并且权限是root。

前面解释过,vul.c中使用的system函数其实是通过/bin/sh(已软链接/bin/bash_shock)来执行shell命令,而这正好提供了我们触发shellshock漏洞的机会。我们可以提前构造好带有攻击目的的函数环境变量,运行该vul程序后,system函数会执行/bin/sh,此时shell进程会解析Shell变量中的函数定义,并触发漏洞执行后面的"小尾巴/bin/sh"。又因为此程序为Set-UID程序,进程权限或者说真实用户Id是root,所以执行/bin/sh也是以root的身份执行的,因此最后成功拿到最高权限shell。

2、利用Shellshock攻击CGI程序

什么是CGI程序

CGI(common gateway interface)是通用网关接口的缩写,是被Web服务器用来生成动态网页的可执行程序。许多CGI程序是shell脚本。如果程序使用了带有漏洞的Bash,那么它便成为了Shellshock的攻击目标。

实验环境解释

实验环境正常来说最好使用两台虚拟机,一台是作为被攻击方的Web服务器,另一台是攻击方。但是本次实验使用了一台虚拟机同时作为攻击方和被攻击方,在实验过程中思路清楚即可。

反向Shell攻击

Apache服务器中,一般CGI程序的默认目录在"/usr/lib/cgi-bin/"下,首先,我们准备一个简单的CGI脚本test.cgi。

1
2
3
4
5
6
7
#!/bin/bash_shellshock

echo "Content-type: text/plain"
echo
echo
echo "Hello World"
strings /proc/$$/environ #查看当前进程的环境变量,下面分析用到

根据前面对漏洞的分析,我们已经很清楚的知道,要想完成Shellshock攻击,必要的两个条件就是:1、拥有bash_shock的执行机会; 2、可以设置环境变量作为输入传入

目前来说,我们已经知道在Web服务器中存在一个test.cgi脚本,并且脚本运行的环境是有Shellshock的bash环境,下面需要考虑的就是如何进行服务器中环境变量的构造。

利用Apache服务器的访问机制,我们可以使用curl工具完成这一需求,curl是一个可以与服务器进行请求与响应的工具。众所周知,我们在向Web服务器发送请求时,在http请求头中包含了发送请求的代理工具的信息(一般是浏览器等)存放在User-Agent字段中,而Apach服务器在收到一些用户信息的请求头字段时会将这些信息转换成Shell变量传递给CGI程序,一遍CGI程序可以根据这些信息对程序进行判断与优化。因此,我们可以通过这一特点,使用curl工具对请求头中User-Agent字段进行设置,进而对环境变量进行设置。

首先先尝试使用curl工具向服务器的CGI发起请求,通过环境变量的打印,查看我们前面的分析是否正确。

Ns7fYt.png

可以看到,Apache将http请求中的相关信息转化成了环境变量传递给了CGI程序。下面,我们将使用curl -A命令对请求头中的User-Agent字段进行改变环境变量HTTP_USER_AGENT进行设置。(可改变其他字段的选项还有-H、-e等)

NsHQ7d.png
可以看到,利用curl -A命令,我们成功的控制了环境变量的输入,至此,Shellshock攻击所需的两个条件全部满足,下面进行反向Shell攻击。

NsODqU.png

从上图可知,成功拿到Web服务器的Apache进程Shell权限。

参考资料

《计算机安全导论:深度实践》 杜文亮