天天看点

漏洞分析——shellshock实验Shellshock Attack Lab

Shellshock Attack Lab

前导知识

SHELL:命令行界面的解释器

Linux下常见Shell:

  • Bourne Again Shell(/bin/bash)
  • C Shell(/usr/bin/csh)
  • K Shell(/usr/bin/ksh)
  • Bourne Again Shell(

    /bin/bash

查看目前使用的shell是否是/bin/bash:

[email protected]:/usr/lib/cgi-bin$ echo $SHELL
/bin/bash
           

vim编辑shell脚本:

#!/bin/bash
echo "Hello World !"
           

可以使用 vi/vim 命令来创建文件,扩展名为 sh(sh代表shell),#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。

shellshock原理

[04/14/2021 00:28] [email protected]:~$ foo='() { echo "wdnmd"; } ;echo "8848";'
[04/14/2021 00:29] [email protected]:~$ echo $foo
() { echo "wdnmd"; } ;echo "8848";
[04/14/2021 00:29] [email protected]:~$ export foo
[04/14/2021 00:29] [email protected]:~$ bash
8848
[04/14/2021 00:29] [email protected]:~$ declare -f foo
foo () 
{ 
    echo "wdnmd"
}
           

foo本来是一个shell变量,但是在export foo使其成为环境变量后,在子shell进程中,foo成为了一个函数,并且命令行自动执行了echo "8848"的命令

分析:

foo='() { echo "wdnmd"; } ;echo "8848";'
foo  () { echo "wdnmd"; } ;echo "8848";
           

foo变量的字符串被解释成了两个命令,分别是声明了一个函数foo 和 输出字符串 8848

查看variables.c的源码:

可以看到,只要子进程在传递父进程的环境变量的时候,匹配到

() {

这四个字符,就会将其解释为函数

此后,倘若该环境变量字符串包含多个用分号

隔开的shell命令,

parse_and_execute

函数会执行每一条命令

漏洞分析——shellshock实验Shellshock Attack Lab

因此,对于满足shellshock的情景,我们可以构造出一个特殊的字符串,使得字符串前面部分是空函数定义,后面是用分号

;

隔开的恶意指令

  • 一个攻击者能够控制的环境变量,该变量以 () { 开始。
  • 必须调用bash
  • 系统存在bash漏洞

Task 1: Attack CGI programs

  • CGI(通用网关接口,Common Gateway Interface)

    一个在Web服务器中使用的技术,是最早的可以创建动态网页内容的技术之一。它会把一个HTTP请求转化为一次shell调用。

  • Apache HTTP Server

    世界上最为广泛使用的Web服务器软件,使用了UNIX shell 环境变量来保存从Web服务器传递出去的参数

    (注:Apache不是指阿帕奇武装直升机)

原理

漏洞分析——shellshock实验Shellshock Attack Lab

当用户将CGI URL发送到Apache Web服务器时,Apache将检查该请求

如果是CGI请求,Apache将使用fork()启动新进程,然后使用exec())函数执行CGI程序

Shellshock的原理是利用了Bash在导入环境变量函数时候的漏洞,启动Bash的时候,它不但会导入这个函数,而且也会把函数定义后面的命令执行。在有些CGI脚本的设计中,数据是通过环境变量来传递的,这样就给了数据提供者利用Shellshock漏洞的机会。

实验步骤

/usr/lib/cgi-bin

目录下使用vim编辑文件

myprog.cgi

,并设置755权限

漏洞分析——shellshock实验Shellshock Attack Lab
漏洞分析——shellshock实验Shellshock Attack Lab
#!/bin/bash
echo "Content-type: text/plain"
echo
echo
echo "wdnmd"
           

#!

告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序

echo "Content-type: text/plain"

是告诉shell解析的时候保持为纯文本,不用其他的解释型语言来解析

使用

ifconfig

命令得到ip地址为

192.168.32.134

漏洞分析——shellshock实验Shellshock Attack Lab

为了访问该cgi程序,在命令行中输入:(curl 是发起http请求的命令行工具)

curl http://localhost/cgi-bin/myprog.cgi
           
漏洞分析——shellshock实验Shellshock Attack Lab

用浏览器打开也可以访问该cgi程序:

漏洞分析——shellshock实验Shellshock Attack Lab

阿帕奇服务器从http请求中获得user_agent的信息并将它赋值给http_user_agent环境变量,而curl -A可以更改user_agent字段。因此,我们可以通过curl -A指令修改该字段从而发起攻击

漏洞分析——shellshock实验Shellshock Attack Lab
漏洞分析——shellshock实验Shellshock Attack Lab

使用curl指令构造攻击,查看/

etc/passwd

目录下的内容

漏洞分析——shellshock实验Shellshock Attack Lab

task2 Attack Set-UID programs

task2A

#include <stdio.h>
void main()
{
setuid(geteuid()); // make real uid = effective uid.
system("/bin/ls -l");
}
           

/bin/sh

指向

/bin/bash.

sudo ln -sf /bin/bash /bin/sh
           
漏洞分析——shellshock实验Shellshock Attack Lab

编译文件,设为root所有,SUID权限

gcc task2.c -o task2 
sudo chown root task2
sudo chmod 4755 task2
           
漏洞分析——shellshock实验Shellshock Attack Lab

创建环境变量foo

漏洞分析——shellshock实验Shellshock Attack Lab

输入

./t

,system函数通过

fork()

函数创建子进程,并得到父进程的环境变量,bash被调用并执行放在末尾的

/bin/sh

指令

通过攻击,得到一个所有者为root的

Set-UID

程序

漏洞分析——shellshock实验Shellshock Attack Lab

task2B

移除

setuid(geteuid())

将无法获取root权限

setuid(geteuid())

语句将真实的UID转换为有效的UID,当UID和EUID相同的时候(详见教材第一章,p20),privmod==0,才会执行环境变量中构造的函数,详见源代码中第342行的if语句

教材p20
漏洞分析——shellshock实验Shellshock Attack Lab

当UID和EUID不相等时,privmode不为0,导致不能执行环境变量中构造的函数

Task 2C

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
char ** environ;
int main()
{
char * argv[3];
argv[0] = "/bin/ls";
argv[1] = "-l";
argv[2] = NULL;
setuid(geteuid()); // make real uid = effective uid.
execve(argv[0], argv, environ);
return 0 ;
}
           

编译代码,并文件成为root拥有的set-uid程序

漏洞分析——shellshock实验Shellshock Attack Lab

输出了ls -l的结果,但是没有获得root权限

这是因为,不同于

system()

函数调用子bash程序作为单独的进程执行命令,

execve()

函数接受三个参数:1 运行的指令,2 指令用到的参数,3 传入新程序的环境变量,该函数会直接请求操作系统(而不是shell程序执行指定的命令),因此不会执行foo后面的

/bin/sh

task3

受shellshock攻击满足的条件:

  • 一个攻击者能够控制的环境变量,该变量以 () { 开始。
  • 必须调用bash
  • 系统存在bash漏洞

其他可能受到ShellShock攻击的情景

  • DHCP客户端

    一些DHCP客户端也可以将命令传递给Bash;连接到开放的Wi-Fi网络时,易受攻击的系统可能受到攻击。DHCP客户端通常从DHCP服务器请求并获取IP地址,但也可以提供一系列附加选项。恶意DHCP服务器可以在这些选项之一中提供一个字符串,用于在易受攻击的工作站或笔记本电脑上执行代码

  • Qmail服务器

    当使用Bash处理电子邮件时,邮件服务器通过一种可以利用脆弱版本的Bash的方式传递外部输入。

ShellShock漏洞的根本问题是什么?我们可以从这个漏洞中学到什么?

  • 根本问题在于在一定条件下,子进程会把父进程的环境变量解释为一个函数,并且通过

    parse_and_execute()

    函数执行用分号分割的指令,因此会受到被精心构造的指令的攻击

附录

shellshock 308~369行

initialize_shell_variables (env, privmode)
     char **env;
     int privmode;
{
  char *name, *string, *temp_string;
  int c, char_index, string_index, string_length;
  SHELL_VAR *temp_var;

  create_variable_tables ();

  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) */

      temp_var = (SHELL_VAR *)NULL;

      /* 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);

	  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] = '(';		/* ) */
	}
           

继续阅读