I have needed to delve more deeply into web application security methods in the last year. As part of this, I have come to recognize a lot of short comings in this area. There has been a continuous stream of Java updates from Oracle and a number of security fixes for various Java EE/servlet containers and libraries commonly used in web applications. It is good to keep the technology stack updated with the most current security fixes - no denying that. The problem is that this leaves the potential for too much time between unethical people finding the info needed to exercise an exploit and actually having a security fix in production. And this assumes that a security fix doesn't break your web application which is a real possibility. So this isn't what I would call a first line of security.
So what does one do? I have been doing lots of research and the funny thing is that most of it is what I would call "additive security". What I mean by that is, it is security provided by adding some new mechanism into the technology stack. Things SELinux, AppArmor, Java Security Manager policies, TripWire, fancy application security firewalls, etc. I won't deny that those have the potential to prevent various security flaws from being exercised or at least identifying when it occurs so they can have a benefit but they also add complexity and workload to what is often an already overworked IT staff.
I also looked into some various research in the area of "hardened" run times and such. What I found there was still more on the "additive" side and was more targeted at the typical older exploits - stack smashing, etc. Useful stuff but this didn't really fit the types of problems I was trying solve and there were implications of high overheads in some of the research implementations.
I finally came to the conclusion that a better first step is the disabling or removal of functionality which is most often exploited. This got me thinking a bit out of the box. With Java, it isn't really practical to remove "insecure" features from Java and it's libraries. The use of Java Security Manager can disable the functionality but that turns out to be a nightmare when you start working with lots of Open Source dependencies in large/complex web applications. I really like the idea of disabling items which are commonly exploited though.
As I was thinking about this, it occurred to me that maybe there is a way to disable some items with a minimal overhead. Java is generally implemented in C/C++ and uses things such as the standard C/C++ libraries. The nice thing about this is that features that I "care about" tend to funnel down to a fixed set of functions in the C/C++ libraries. I decided it was not very practical to physically create new versions of glibc, etc though. The good news is that I didn't have to. There is functionality within the Linux loader which makes things fairly easy.
[I had to cut a lot of detail out of this for now; somewhat for time - may be able to include more details later].
A simple use case goes like this; you want to prevent a web server from being able to launch an external process. This is an attack vector which has plagued a number of open source technology on/off. First write a simple Java program which only calls "Runtime.getRuntime.exec()" and have it do something you can verify like adding/appending some data to a file. Next run the test program while prefixing the command with "LD_DEBUG=symbols" - so the command line would look similar to "LD_DEBUG=symbols java p1.p2.C1" . If you start a 'script' before this, you can capture the somewhat substantial output. In the output, I found that the exec() call was using vfork() from the threading library. I determined that my web server really should not need to call vfork() so I chose it as my target for "disablement". So next, write a small C file which implements a dummy vfork() - use the various Unix/Linux man pages to determine what return value should represent an error/failure to the caller and return that in the implementation. In this case, -1 was the appropriate value. Next compile and link that into a shared library. Now, all that remains is to get the Java process to use your vfork() instead of that from the standard threading library. This can be accomplished by starting Java with a "LD_PRELOAD=libyourlib.so" which for testing purposes will include "LD_DEBUG" as well and would look something like: "LD_DEBUG=symbols LD_PRELOAD=libyourlib.so java p1.p2.C1". Again, if you run 'script' before this you can easily review the output and note that the vfork() func was picked from your library or some error in your lib prevented it from loading. Once you get any loading errors resolved (maybe you needed to compile/link with -fPIC, etc) and you see the vfork getting loaded from your library - go back and verify that the Java test program is no longer producing the effect that the Runtime.exec() call was producing.
So at this point you have written a C func for a "standard library" function which hard-codes an error type return value and effectively hides the real implementation of the function.
You can take this general idea as far as you want. Make sure that various IO calls only have access to a predefined list of resources, etc. You are mainly limited by imagination. It is fairly low in overhead but is not a total solution in itself. You could incrementally secure an application over time though which is a nice aspect.
This isn't a silver bullet and won't help everyone all the time. I am sure there are applications which would require a lot more effort to make similar changes to because of an actual need for some of the functionality which does get exploited in web apps.
I plan on keeping this idea in the toolbox for use when it is a good fit. It could also be a good reactive method in a crisis.
[Update 2014/02/25] It seems quite awkward to implement this in a minimally invasive manner with software such as Apache Tomcat because of how the startup scripts work. It may be possible but it will take a lot more scripting research if so. I think the general recommendation I found for a similar need is to simply create a new script with the end result of what the startup script uses to start the Java process. Not a very operational/upgrade friendly method but if you must do it..
[Update 2014/03/17] I am wondering whether JEP178 may provide a better/easier supported way to remove certain limited functionality from the runtime at the process level through some static linking. I think this will need from further review at a later date. This would prevent the need to customize start up scripts and such.
No comments:
Post a Comment