概述
在刷pwnable.kr的题目shellshock时,碰到了这个bash的老漏洞,这里我也对其分析一下,做个记录。
shellshock破壳漏洞,是一个高危的漏。漏洞原理简单来说就是子进程bash在获得父bash进程传递的shell环境变量时,对函数定义的变量解析除了问题,导致了任意代码被执行。
该漏洞可以分为本地利用和远程利用两种,其远程利用危害性极强,攻击者可以利用该漏洞精心伪造数据,通过网络请求到一台利用bash脚本来处理用户请求的网站上,来直接或间接的触发一个bash脚本,这样就可以远程执行而已代码了。
经测试,从bash1.14 到4.3都存在这样的漏洞。
环境
我是在ubuntu中玩耍的,先确定自己bash版本,新的ubuntu自带的bash版本都是4.3以上的,不存在shellshock漏洞,所以需要自己手动安装低版本的bash:
从gun.org/software/bash选择一个国内的镜像源,然后把bash-4.1下载并进行安装:
1 | wget https://mirrors.aliyun.com/gnu/bash//bash-4.1.tar.gz |
执行上述步骤后,在/usr/local/bash-4.1/下成功安装到新的bash,可以验证查看一下,成功安装到了指定版本的bash:
为了省事,这里就不再弄PATH变量等操作了,直接在bin/下使用新安装的bash进行漏洞测试即可。
针对这个shellshock漏洞,一个广为流传的本地测试poc命令如下,我们稍作修改,然后测试:
1 | env x='() { :;}; echo vulnerable' bash -c "echo this is a test" |
成功输出vulnerable,漏洞复成功:
漏洞分析
漏洞成因
首先需要了解一下shell中的变量类型和变量的作用域以及bash父子进程对于shell变量的使用,shell变量以及其对应的作用域可以分为如下三种:
- 局部变量(local var):只能在函数内部使用
- 全局变量(global var):在当前shell进程中可以使用
- 环境变量(env var):可以让当前shell进程的子进程使用
对于这三种变量依次分析一下,局部变量没什么好说的,主要是全局变量和环境变量这里简单说明一下。全局变量只在当前shell程序中有效,对于其它shell进程和子进程都无效,比如下面,定义一个变量a=910,在当前bash shell中可以输出,但是新开一个bash子进程就访问不到。
创建全局变量的shell进程称为父进程(这里用bash),父进程中新创建的进程就为子进程,要想子进程也访问到父bash进程的全局变量,那么就需要用export
将全局变量导出,被导出的变量就被称为环境变量。当shell产生子进程时,它会继承父进程的环境变量为自己所用(自动加载),也就是说父进程的环境变量传递给了子进程,当然环境变量还可以继续向下传递给孙进程。
bash中可以将shell函数也导出为环境变量,这有两种方式:1.直接定义函数并export -f(-f为参数,必须设置);2.通过环境变量值来定义函数
- 直接定义函数并导出比较好理解,用法如下:
而shellshock漏洞利用的正好是第二种方法,通过bash对环境变量值的转换来获得。bash中,当某个环境变量的值为字符串且以
"() {"
的格式开头(小括号和大括号间必有空格)书写时,那么该环境变量在被子bash进程加载时会被转换为一个shell函数,而不是当作一个shell变量,示例如下:注:上述通过环境变量来定义函数的方式,被称为bash的自动导入机制(自动导入函数到当前bash的子进程),应为出现了shellshock漏洞,所以现在发行版的linux都会默认关了bash的自动导入机制,想要测试该机制还得用刚才配置的bash低版本来测试。
Shellshock漏洞就是出现在bash的自动导入机制中,如果在() {}
完整函数变量尾部的花括号后加上一个“小尾巴”(shell 命令),由于bash中解析逻辑存在漏洞它会在转换过程中,把”小尾巴“的内容也进行执行:
源码分析
总结一下,shellshock漏洞主要出现在,子bash进程导入父bash进程的环境变量时,对函数型环境变量的值解析出了问题,导致额外的代码被执行。
该漏洞存在于bash源代码的variables.c文件中,拿上面bash4.1的对应文件进行分析,包含漏洞的代码在initialize_shell_variables()
函数中:
- 首先进入函数,第一步主要操作就是遍历整个env列表,对每一个有效环境变量进行操作,解析完成后,name-存储环境变量名,string-存储环境变量值,char_index-存储环境变量名长度:
- 判断有效的环境变量是否匹配
'() {'
也就是是否为导出函数(另外两个判关于特权模式的判断,需要保证real uid和effective id相同,一般情况下都是相同的),若是自动导出函数,就直接申请一块内存空间,然后把string(环境变量值)不做任何拷贝到空间中然后交由parse_and_execute()
函数执行
parse_and_execute()
函数原型如下,传递给函数的所有内容都会被当作普通bash命令执行,所以导致了漏洞的产生。
1 | /* Parse and execute the commands in STRING. Returns whatever |