SEEDLab-ShellShock攻击
SEEDLab-ShellShock攻击
实验介绍
2014年9月24日,Bash中发现了一个严重漏洞。这个被昵称为“Shellshock”的漏洞可被用于攻击许多系统,既可以远程发起,也可以在本机上启动。在本实验中,学生需要研究这种攻击,以便理解Shellshock漏洞。该实验的学习目标是让学生亲身体验这一有趣的攻击,理解其工作原理,并思考我们能从此攻击中吸取的教训。
试验任务
任务一 攻击CGI程序
在本任务中,我们将对远程Web服务器发起Shellshock攻击。许多Web服务器都启用了CGI(通用网关接口),CGI是在网页和Web应用上生成动态内容的标准方法。许多CGI程序是用shell脚本编写的。因此,在执行CGI程序之前,首先会调用shell程序,而这种调用是由远程计算机上的用户触发的。
步骤 1:设置CGI程序。你可以像下面这样编写一个非常简单的CGI程序(命名为myprog.cgi)。它仅用shell脚本打印出“Hello World“。
1 | |
请将上述CGI程序放到/usr/lib/cgi-bin目录下,并将其权限设置为755(以便可执行)。由于该目录只能被root写入,你需要使用root权限来完成这些操作(例如使用sudo)。该目录是Apache Web服务器的默认CGI目录。若要更改此设置,可修改Apache的配置文件/etc/apache2/sites-available/default。
要从Web访问此CGI程序,可以在浏览器中输入以下URL:http://localhost/cgi-bin/myprog.cgi,也可以使用命令行工具curl执行相同的操作:
1 | |
在我们的实验环境中,Web服务器和攻击都运行在同一台计算机上,这就是我们使用localhost的原因。在真实攻击中,服务器运行在远程主机上,这时我们不会用localhost,而会使用服务器的主机名或IP地址。
步骤 2:发起攻击。完成上述CGI程序设置后,就可以发起Shellshock攻击。该攻击与CGI程序的具体内容无关,因为攻击目标是Bash程序——它在CGI脚本执行之前首先被调用。你的目标是通过URL http://localhost/cgi-bin/myprog.cgi发动攻击,从而实现作为远程用户无法做到的事情。例如,你可以在服务器上删除某个文件,或从服务器获取某些对攻击者不可访问的文件。
请描述你的攻击是如何工作的。请指出Bash源代码中variables.c文件中漏洞所在的位置。你只需要定位initialize_shell_variables()函数中(第308行到第369行之间)的具体行号。
如图1所示,Bash检查环境变量的值是否以“(){”开头。一旦发现这样的字符串,Bash将“=”替换成空格从而将环境变量变成一个函数定义。
然后,Bash调用parse_and_execute()函数来解析该函数定义。然而,parse_and_execute()函数的功能太过强大,它不仅仅解析函数定义,还可以解析和运行其他的shell指令。如果字符串只是一个函数定义,该函数只会解析它,而不是执行它。但如果该字符串包含用分号(“;”)隔开的多个shell命令,则parseand_execute()函数会解析和运行每一条命令。这就是问题所在。
在cgi-bin目录中存放一个secret.txt,文件中存放一段秘密信息,现在可以发动攻击读取这个文件。为User-Agent字段(或其他可用字段)构造一个字符串,以触发Bash中的错误解析逻辑,让CGI程序执行选择的命令。首先,使用curl –A可以将HTTP_USER_AGENT环境变量设置为指定的值。在输入这个命令之前,还要解决一个小问题。Apache服务器会收到CGI程序打印出的任何内容,并将这些内容返回给客户端。Apache服务器需要知道内容的类型,例如文本、多媒体或者其他类型。这个场景的输出是文本,因此可以通过“Content.type: text/plain”来告诉Apache服务器数据的具体类型(后面要跟一个空行)。
如图2,在(){}后加上要利用的指令,成功读取了secret.txt的内容。但这种攻击无法控制shell,不能给它提供命令,也无法看到它的输出,因此无法利用它来执行更多的命令。为此,需要构建反向shell。
首先,使用netcat命令来运行一个TCP服务器,该服务器能够打印客户端发来的所有信息,并向客户端发送用户在本地服务器程序中输入的信息。nc命令会阻塞并等待连接,等待Shellshock攻击成功后,通过重定向,可以触发一个连接到攻击者的TCP连接,并创建一个反向shell。如图3,利用Shellshock漏洞成功获取了shell,用户为www-data。
任务二 攻击Set-UID程序
在本任务中,我们利用Shellshock攻击Set-UID程序,目标是获取root权限。攻击之前,需先让/bin/sh指向/bin/bash(在我们的SEED Ubuntu 12.04虚拟机中,默认它指向/bin/dash)。你可以使用下面的命令来完成:
1 | |
任务2A 下面的程序是一个Set-UID程序,它只是运行“/bin/ls -l“命令。请编译这段代码,将其设置为Set-UID程序,并将其所有者改为root。我们知道,system()函数会调用“/bin/sh -c“来执行给定的命令,这意味着会调用/bin/bash。你能利用Shellshock漏洞来获取root权限吗?
1 | |
需要注意的是,使用setuid(geteuid())将real uid(真实用户ID)改为effective uid(生效用户 ID)并不是Set-UID程序中的常见做法,但确实存在这样的代码。
如图4,首先将/bin/sh链接到有漏洞的/bin/bash,之后编译并设置Set-UID位与环境变量,与任务一相同,在(){}之后加上/bin/sh以获得shell,最后运行程序,成功获得root
shell。 任务2B
现在,从上面的程序中移除setuid(geteuid())语句,然后重复你的攻击。你能获得root权限吗?请展示你的实验结果。
在我们的实验中,当移除该行时,攻击失败(有该行时攻击成功)。换句话说:如果real user id和effective user id相同,则会评估在环境变量中定义的函数,从而可以利用Shellshock漏洞;但如果real user id和effective user id不相同,则环境变量中定义的函数根本不会被评估。这一点可从Bash源代码(variables.c,第308行到369行之间)中得到验证。你可以从实验网页获取源码。请准确指出哪一行导致了这种差异,并解释Bash为什么要这样做。
如图5,注释掉setuid语句之后,无法攻击成功。如果真实用户ID和有效用户ID不同,则Bash不会处理从环境变量处获得的函数定义,因此不会受到Shellshock攻击。
如图5,函数定义使用的parse_and_execute()语句前有一个对privmode的检测,若privmode不为0,就不会运行该语句。Bash采取的这项安全措施是为了防止通过环境变量进行的权限升级攻击:当RUID和EUID不同时(即进程处于setuid模式并拥有更高权限,如root),Bash认为环境变量中的函数定义是不可信的,因此它会拒绝评估这些函数,从而避免低权限用户(RUID)利用Shellshock等漏洞以高权限(EUID)执行恶意代码。
任务2C 用C语言调用程序的另一种方式是使用execve(),而不是system()。下面的程序与任务2A中的程序做的完全相同。请编译该代码,并将其做成由root拥有的Set-UID程序。对这个新程序发起你的Shellshock攻击,并描述与解释你的观察结果。
1 | |
将system()改为execve()进行调用后,发现Shellshock攻击失败了,这是由于system()与execve()函数的实现机制造成的,system()函数直接调用/bin/sh运行指令,而execve()不是,这导致execve()没有使用有Shellshock漏洞的shell,因此自然无法攻击成功。
任务三 问题
这是一个书面任务,请在报告中回答以下问题:
- 除了上面描述的两种情形(CGI与Set-UID程序)之外,是否还有其他情形可能受到Shellshock攻击的影响?如果你能识别出一个明显不同的情形并通过自己的实验验证该攻击,我们将给你加分。
- Shellshock漏洞的根本问题是什么?我们可以从此漏洞中学到什么?
对于问题1,在使用SSH连接时也可能收到Shellshock攻击的影响。如图8,配置ssh,使用wsl
ssh连接虚拟机。如图9,在本机设置环境变量,使用ssh时,添加发送环境变量选项,成功运行了额外的指令。
对于问题2,Shellshock漏洞的根本问题在于Bash对环境变量中的数据进行了过度信任,它在解析并导入环境变量中以(){ :; }开头的函数定义时,错误地继续执行了函数定义后的内容,使得攻击者能够通过任何可以设置环境变量的通道注入和执行代码。从中我们汲取的教训是:核心系统组件必须对不可信输入进行严格验证,高权限程序应立即放弃不必要的权限,并且应避免不必要的Shell调用,以最小化攻击面。