In light of recent “OS X needs Antivirus” controversy, I’ve been trying to get across the point that there’s more effective measures than AV software for reducing attack vectors to your system. After a bit of arguing back and forth on various discussion boards about this, it seemed apparent that a lot of people who care about security aren’t aware of such technologies, so I am going to write a 4-part tutorial series on various ways you can secure Linux against attacks.
This first part is an introduction to the big-picture idea of what we are trying to do. I’ll explain in simple terms the problem with traditional privilege models used in most OS’es today, along with a solution. I will then close off with a simple real-world example to show how simple this can be in practice.
The Problem
Exactly what threat are we up against? Well, allow me to describe a recent real-world example that has happened too many times in the past:
VLC media player contains an unchecked buffer which allowed an attacker to craft a malicious video file. An attacker sends the victim a video file. The user opens it up and the video file exploits this hole in VLC to read the user’s SSH private key and send it to the attacker. With this in hand, the attacker is able to log into many of the victim’s SSH machines.
What is wrong with this picture?
The astute reader should be asking in outrage: What the HELL is VLC media player doing reading private SSH keys? How could the operating system allow this to happen? Well, let’s use UNIX 101 to see why. This is what VLC looks like running on my system:
jdong 7603 3.0 0.5 378660 23040 pts/1 Sl+ 09:24 0:00 vlc
The first column says that I, jdong, own the VLC process. What do the permissions on my SSH key say?
-rw——- 1 jdong jdong 1264 2007-10-01 12:50 /home/jdong/.ssh/id_dsa
Again, I own my SSH key. Note the tight -rw——- permissions that are required and default on a SSH key: only the owner is allowed to have any access to it. Let’s read that again: only the owner is allowed to have any access to it. But, VLC’s process is owned by me, jdong, the same person as the SSH key. The OS puts 2 and 2 together and says that VLC has access to my SSH key.
What this example shows is that the UNIX permissions scheme is not fine-grained enough to allow me to specify the minimum amount of access VLC has to my system. Ideally, I’d like to tell VLC it can read my Videos directory, but NOT my Documents/Tax Forms directory
The Solution: AntiVirus edition
What is the AV solution to this problem? Write an AV signature to recognize the malicious video file that caused this exploit. While that sounds good in theory, it should be clear to see why this idea is ridiculous. Do we stop terrorists by compiling a big list of pictures of known terrorists, distribute the list to the TSA and if a person at the security checkpoint doesn’t look like anyone in the list, he’s not a terrorist? Ok fine, we do, but how well is THAT working out?
While this approach can be useful for blacklisting individual types of exploits, what if tomorrow a different hole is found in VLC? You’d have no protection against that exploit until your AV vendor figures out.
The Solution: AppArmor Edition:
The solution I’d like to propose is to be able to define finer grained permissions that I hinted to earlier. I want to tell Linux: VLC Media Player can access ~/Videos and its own data. It can write to its preferences directory. It CANNOT touch anything else, launch any other processes, su to root, etc. A rule like this would universally stop a wide class of exploits against VLC.
Quick Intro to AppArmor:
I’ve used the name of the product but I haven’t really said what it is. AppArmor is known as a Mandatory Access Control mechanism for Linux. What that means is basically it allows you to specify finer grained permissions on stuff running on your system than the traditional ownership-permissions model. It is Open Source software, and installed by default on Ubuntu and OpenSUSE. It’s known for being relatively simple to configure for restricting a select set of at-risk services and applications.
AppArmor works inside the kernel based on configuration files known as AppArmor Profiles stored in /etc/apparmor.d. The tricky part of configuring AppArmor is writing a profile that gives the right level of access that allows your program to do its work, but not pose a security threat to the rest of your system.
What about SELinux?
RedHat/Fedora users are probably thinking at this point: Wait a minute, isn’t that what SELinux is? Yes, you’re right! SELinux is another framework that’s in the vanilla kernel and default in RedHat Enterprise Linux and Fedora which accomplishes similar goals to AppArmor. It’s much more powerful and allows even more fine-grained permissions control, and even offers other neat features like Role-Based Access Control which allows you to have a different set of permissions depending on what you’re doing at the moment. I plan to cover SELinux in detail at a later time, too.
Our Example: myscript
Ok, now that we know what AppArmor is, and why we’d want to use it, I figure it’s better to explain the rest in practice instead of all this hypothetical talk. Let’s try a simple example to illustrate how AppArmor can work to save our rear. Suppose I have a simple Python script called myscript
#!/usr/bin/python
import os
print "Welcome to MyScript!"
foo=raw_input("Please give me the name of a file to md5sum:")
os.system("md5sum "+foo)
Ok, so it’s supposed to do something useful (calculate md5sums) but is poorly written. Perfect recipe for disaster! Let’s see what it does when it works:
[jdong@droptop:code/myscript]$ ./myscript (12-05 10:12)
Welcome to MyScript!
Please give me the name of a file to md5sum:myscript
9149480416db480c99f4f95b79fda227 myscript
Cool! It works! Or does it work TOO well:
[jdong@droptop:code/myscript]$ ./myscript (12-05 10:16)
Welcome to MyScript!
Please give me the name of a file to md5sum: /dev/null;bash
d41d8cd98f00b204e9800998ecf8427e /dev/null
jdong@droptop:~/code/myscript$ whoami
jdong
Ahh, when foo; bar gets passed to os.system, bar is executed as if it were a separate command. Yikes, we spawned an arbitrary shell!
The Solution: AV Style
Luckily for me, I am the author of award-winning jdong Antivirus ™, so I incorporate my advanced AV engine in myscript:
#!/usr/bin/python
import os
print “Welcome to MyScript!”
foo=raw_input(”Please give me the name of a file to md5sum:”)
if “;” in foo:
print “BZZ! JDONG ANTIVIRUS FOUND MyScript.ShellEscapeExploit!”
else:
os.system(”md5sum “+foo)
Does that work? Let’s see:
[jdong@droptop:code/myscript]$ ./myscript (12-05 10:22)
Welcome to MyScript!
Please give me the name of a file to md5sum:/dev/null; bash
BZZ! JDONG ANTIVIRUS FOUND MyScript.ShellEscapeExploit!
[jdong@droptop:code/myscript]$ ./myscript (12-05 10:27)
Welcome to MyScript!
Please give me the name of a file to md5sum:/dev/null && bash
d41d8cd98f00b204e9800998ecf8427e /dev/null
fjdong@droptop:~/code/myscript$ id
uid=1000(jdong) gid=1000(jdong) groups=4(adm),20(dialout),24(cdrom),44(video),46(plugdev),108(lpadmin),116(pulse-access),117(pulse-rt),123(admin),124(sambashare),127(libvirtd),1000(jdong)
Well, the typical problem with AV signature based approaches — I slightly changed my attack and the signature no longer matches. Note that myscript is doing the same thing wrong, executing bash when it shouldn’t have the ability to, but a different way. Again, an AV doesn’t care what is being done, just the exact way how it is doing it. Even worse,it gets REAL hair when I start talking about files with ; or & in the filename — at some point the AV will have to trade off between false-positives and completeness of detection.
The solution, AppArmor style
Since this is a motivational introduction to Apparmor, I’ll just present the solution without gory details on how I did it. I’ll link to some resources for those who want to know NOW but next week’s installment will go through step-by-step a more useful example.
So, instead of blocking bad input, I will use an AppArmor profile to rigorously define what myscript should do. We make the following observations:
- MyScript uses Python. So, it probably needs access to the Python interpreter and the Python libraries.
- MyScript executes md5sum. So, it probably needs access to /usr/bin/md5sum and perhaps /bin/sh because of the way os.system works (not intuitively obvious). It shouldn’t have to execute anything else.
- MyScript NEVER has to write anything but to the screen.
- Suppose MyScript is for checksumming my media collection and itself. Hence, it should only be able to read ~/Videos and ~/code/myscript.
With these rules, we can concoct an AppArmor profile, like the one I prepared. This is /etc/apparmor.d/home.jdong.code.myscript.myscript after about 10 minutes of work:
# Last Modified: Fri Dec 5 10:41:50 2008
#include <tunables/global>
/home/jdong/code/myscript/myscript {
#include <abstractions/base>
/usr/bin/python2.5 ixmr,
/home/jdong/code/myscript/* mr,
/home/jdong/Videos/*/ mr,
/home/jdong/Videos/** mr,
/usr/share/pyshared/** mr,
/usr/local/lib/python2.5/** mr,
/usr/local/lib/python2.5/*/ mr,
/usr/lib/python2.5/** mr,
/usr/lib/python2.5/*/ mr,
/var/lib/python-support/*/ mr,
/var/lib/python-support/** mr,
/bin/dash ixmr,
/lib/* mr,
/usr/bin/md5sum ixmr,
/etc/python2.5/** mr,
}
Now, what does it do? Let’s try our normal workflow:
[jdong@droptop:code/myscript]$ ./myscript (12-05 10:50)
Welcome to MyScript!
Please give me the name of a file to md5sum:myscript
9149480416db480c99f4f95b79fda227 myscript
Well, that still works. Shall we try an exploit?
[jdong@droptop:code/myscript]$ ./myscript (12-05 10:53)
Welcome to MyScript!
Please give me the name of a file to md5sum:myscript; bash
9149480416db480c99f4f95b79fda227 myscript
sh: bash: Permission denied
What the heck? Permission denied? /bin/bash is chmodded 755, everyone can access it, right? Not according to AppArmor and dmesg:
[ 7487.802336] type=1503 audit(1228492429.848:201): operation="inode_permission" requested_mask="::x" denied_mask="::x" fsuid=1000 name="/bin/bash" pid=9671 profile="/home/jdong/code/myscript/myscript"
So, AppArmor is telling us that myscript has no access to /bin/bash. Attack averted. The observant reader will ask: But you allowed access to /bin/dash, can’t we still exploit that? Good question, let’s try!
[jdong@droptop:code/myscript]$ ./myscript (12-05 10:54)
Welcome to MyScript!
Please give me the name of a file to md5sum:myscript;/bin/dash
9149480416db480c99f4f95b79fda227 myscript
ls
^C
^C^C^C^C^C^C^C^Z^Z^Z^Z^C^C
Well… that did something different. What exactly happened? Let’s ask dmesg again:
[ 7651.571036] type=1503 audit(1228492593.617:202): operation="inode_permission" requested_mask="::rw" denied_mask="::rw" fsuid=1000 name="/dev/tty" pid=9712 profile="/home/jdong/code/myscript/myscript"
[ 7655.969245] type=1503 audit(1228492598.017:203): operation="inode_permission" requested_mask="::x" denied_mask="::x" fsuid=1000 name="/bin/ls" pid=9714 profile="/home/jdong/code/myscript/myscript"
In English:
- dash was started, no error generated of course.
- However, apparmor denied its right to open the terminal at /dev/tty to output to the screen, and accept special terminal input such as CTRL-C.
- It did hear me say ls, but AppArmor said it has no access to ls.
Well that’s effective, but no fun! For amusement’s sake, I’ve allowed access to /dev/tty so it’s easier to see what an attacker of myscript has the access to do:
[jdong@droptop:code/myscript]$ ./myscript (12-05 11:19)
Welcome to MyScript!
Please give me the name of a file to md5sum:/dev/null;/bin/dash
d41d8cd98f00b204e9800998ecf8427e /dev/null
[%{%}%n%{%}@%{%}%U%m%u%{%}:%{%}%2c%{%}]%(!.#.$) export PS1="#"
#ls
/bin/dash: ls: Permission denied
#echo foo
foo
#echo "foo" > myscript
/bin/dash: cannot create myscript: Permission denied
Well, now that we can see the shell’s output, we can see that this is really an anticlimactic end to an exploit. From this shell, we can only run dash again or execute shell built-ins like echo or redirections. Since we don’t have write access anywhere, we can’t even overwrite myscript with redirection. Now we’re having fun, aren’t we? Let’s ramp it up: I’m going to add rmix permissions /bin/* and /usr/bin/*, which will allow us to execute the standard UNIX commands , as an ultimate demonstration of AppArmor’s power:
[jdong@droptop:code/myscript]$ ./myscript (12-05 11:24)
Welcome to MyScript!
Please give me the name of a file to md5sum:/dev/null;/bin/bash
d41d8cd98f00b204e9800998ecf8427e /dev/null
bash: /etc/bash.bashrc: Permission denied
bash: /home/jdong/.bashrc: Permission denied
<2m%}%U%m%u%{%}:%{%}%2c%{%}]%(!.#.$) export PS1="$"
$ $ls -al
ls: cannot open directory .: Permission denied
$ls -al myscript
-rwxr-xr-x 1 1000 1000 235 2008-12-05 10:50 myscript
$rm myscript
rm: remove write-protected regular file `myscript'? y
rm: cannot remove `myscript': Permission denied
$echo "foo" > myscript
bash: myscript: Permission denied
$ls /
ls: cannot open directory /: Permission denied
$find /
/
find: `/': Permission denied
$ping google.com
ping: unknown host google.com
$wget http://localhost/
wget: Cannot read /etc/wgetrc (Permission denied).
--2008-12-05 11:26:27-- http://localhost/
Resolving localhost... failed: Name or service not known.
wget: unable to resolve host address `localhost'
$wget http://127.0.0.1/
wget: Cannot read /etc/wgetrc (Permission denied).
--2008-12-05 11:26:35-- http://127.0.0.1/
Connecting to 127.0.0.1:80... failed: Permission denied.
Retrying.
...
$sudo
sudo: uid 1000 does not exist in the passwd file!
$su root
$id
uid=1000 gid=1000 groups=4,20,24,44,46,108,116,117,123,124,127,1000
As you can see, even with the ability to execute every command on the system, you STILL can’t do anything useful. Note that wget and ping don’t work and su seems to fail silently. dmesg is interesting for those cases:
[ 9438.556736] type=1503 audit(1228494380.604:256): operation="socket_create" family="inet" sock_type="dgram" protocol=0 pid=10298 profile="/home/jdong/code/myscript/myscript"
[ 9453.934059] type=1503 audit(1228494395.980:263): operation="socket_create" family="inet" sock_type="stream" protocol=0 pid=10304 profile="/home/jdong/code/myscript/myscript"
[ 9512.316953] type=1503 audit(1228494454.364:291): operation="capable" name="setuid" pid=10317 profile="/home/jdong/code/myscript/myscript"
[ 9512.316958] type=1503 audit(1228494454.364:292): operation="capable" name="setgid" pid=10317 profile="/home/jdong/code/myscript/myscript"
In English these 4 errors are showing:
- socket_create is a POSIX capability to create network sockets. I never granted this privilege in AppArmor to myscript, so MyScript is actually incapable of any network access period.
- setuid and setgid are also POSIX capabilties that MyScript doesn’t have. Therefore, it can’t even use a freebie su-to-root to elevate its access.
Ready to see something even cooler?
[jdong@droptop:code/myscript]$ sudo ./myscript (12-05 11:28)
Welcome to MyScript!
Please give me the name of a file to md5sum:/dev/null;/bin/bash
d41d8cd98f00b204e9800998ecf8427e /dev/null
bash: /etc/bash.bashrc: Permission denied
bash: /home/jdong/.bashrc: Permission denied
<2m%}%U%m%u%{%}:%{%}%2c%{%}]%(!.#.$) export PS1="# "
# ls
ls: cannot open directory .: Permission denied
# whoami
whoami: cannot find name for user ID 0
# id
uid=0 gid=0 groups=0
# ls -al myscript
-rwxr-xr-x 1 1000 1000 235 2008-12-05 10:50 myscript
# rm myscript
rm: cannot remove `myscript': Permission denied
# su
# su jdong
# id
uid=0 gid=0 groups=0
# ping google.com
ping: unknown host google.com
Yes, you are reading that correctly: even with free root access, myscript still can’t do anything. Not even root is immune to AppArmor. And without write access to /sys, root can’t even disable AppArmor.
In conclusion, and coming soon…
I hope this introduction has shown why technologies such as AppArmor and SELinux can provide a robust level of protection against a wide range of threats, better than an AntiVirus program can. I hope it also shows that the general concept and process of doing so is simple. Next time, I will provide a step-by-step walkthrough of a lengthier, more useful example: Bodhi-Zazen recently posted a HOWTO on using rbash to restrict shell users to a safe set of commands. I will show how I broke free of the jail in 10 seconds and was snooping around the rest of the system, and how AppArmor can be used to robustly provide users restricted SSH access.
In the meantime, if you really want to get started writing some AppArmor profiles, OpenSuse and Ubuntu both have great step-by-step guides.
What do I protect with AppArmor? I have rigorous profiles defined for:
- Skype
- Firefox
- my fetchmail script
- my script-rich irssi client
- Apache
- dnsmasq
These are the services I’ve identified to be at the greatest risk on my system for this class of exploits.
your ads here (468x60) - after 1st post.