Monday, 25 August 2014

A Per-Thread Java Security Manager

This post explains how you can customise the Java SecurityManager to so that it can be programatically enabled and disabled on a per-thread basis.

The security manager mechanism in Java is great (Java 1.7 bugs notwithstanding): it gives you a simple, reliable way to restrict what different parts of your code can do. Most of the time, the standard SecurityManager implementation along with a carefully-defined security policy will meet your security needs, which is lovely. However, there are times when the standard SecurityManager implementation doesn't cut it. This post address a couple of those cases.

One of the main problems with the SecurityManager is that it introduces a performance overhead. This is because every time a restricted method is called, the SecurityManager has to check the call stack to make sure that the necessary permissions have been granted to the code in every frame. A lot of the time this isn't an issue, but if your application is performance-sensitive and makes a lot of calls to restricted functions, these checks can add up.

It's also not uncommon to find applications that only need to place restrictions on code running in specific threads: most of the threads in the application will only ever run trusted code, but one or two threads will be used to call some un-trusted libraries. The standard SecurityManager, when installed, applies to all code running in the JVM, even if the majority of the threads in the VM won't run un-trusted code. Even if you define your security policy such that your trusted code is granted all the permissions it needs, a lot of unnecessary checks will be made by the SecurityManager.

A nice solution to this is to define a custom SecurityManager that can be enabled and disabled programatically on a per-thread basis. This can be achieved pretty easily using ThreadLocal variables to store the enabled/disabled state of the SecurityManager on each thread:

package demo;

import java.security.Permission;

public class SelectiveSecurityManager extends SecurityManager {

  private static final ToggleSecurityManagerPermission TOGGLE_PERMISSION = new ToggleSecurityManagerPermission();

  ThreadLocal enabledFlag = null;

  public SelectiveSecurityManager(final boolean enabledByDefault) {

    enabledFlag = new ThreadLocal() {

      @Override
      protected Boolean initialValue() {
        return enabledByDefault;
      }

      @Override
      public void set(Boolean value) {
        SecurityManager securityManager = System.getSecurityManager();
        if (securityManager != null) {
          securityManager.checkPermission(TOGGLE_PERMISSION);
        }
        super.set(value);
      }
    };
  }

  @Override
  public void checkPermission(Permission permission) {
    if (shouldCheck(permission)) {
      super.checkPermission(permission);
    }
  }

  @Override
  public void checkPermission(Permission permission, Object context) {
    if (shouldCheck(permission)) {
      super.checkPermission(permission, context);
    }
  }

  private boolean shouldCheck(Permission permission) {
    return isEnabled() || permission instanceof ToggleSecurityManagerPermission;
  }

  public void enable() {
    enabledFlag.set(true);
  }

  public void disable() {
    enabledFlag.set(false);
  }

  public boolean isEnabled() {
    return enabledFlag.get();
  }

}

The custom SecurityManager simply sub-classes the standard SecurityManager and overrides the checkPermisson methods so that a ThreadLocal boolean is tested to see if the check is necessary before it's carried out. The ThredLocal boolean can be set programatically via an enable/disable methods, and we make sure that only authorised code can enable/disable the security manager by checking a special ToggleSecurityManagerPermission in the set method of the ThredLocal. The ToggleSecurityManagerPermission class is very simple, and looks like this:

package demo;

import java.security.Permission;

public class ToggleSecurityManagerPermission extends Permission {

  private static final long serialVersionUID = 4812713037565136922L;
  private static final String NAME = "ToggleSecurityManagerPermission";

  public ToggleSecurityManagerPermission() {
    super(NAME);
  }

  @Override
  public boolean implies(Permission permission) {
    return this.equals(permission);
  }

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof ToggleSecurityManagerPermission) {
      return true;
    }
    return false;
  }

  @Override
  public int hashCode() {
    return NAME.hashCode();
  }

  @Override
  public String getActions() {
    return "";
  }

}

That's pretty much all there is to it. You need to define a security policy that will ensure that any trusted code on the stack for threads where the SecurityManager will be enabled has the necessary permissions, and that trusted code has permission to enabled and disable the SecurityManager, but that's it. For a working example of the above with some simple tests, see the Github repository https://github.com/alphaloop/selective-security-manager.

Saturday, 2 August 2014

Sandboxing Python Scripts in a Java Application with Jython

The following post describes how (with a little hacking) you can use Jython and the standard Java security manager to create a sandboxed environment in which to securely run untrusted Python scripts.

Note: the following was tested with Jython 2.5.3 and Java 1.7u45. The command line examples assume Linux and Bash.

Let's say you've written a Java application, and you want your users to be able to customise it by providing Pyhton scripts that will be run by the application. Let's also say that you have a deep-seated distrust of your users: they're inherently suspect, and any code they supply to be run as part of your application should be treated as if it were written by the Devil himself.

This poses a problem. We would like to be able to use the Java implementation of the Pyhton interpreter, Jython. However, neither Jython not the Java JSR 223 scripting API (javax.script) offer much in the way of security sandboxing features. We know that Java offers excellent sandboxing through the JVM security manager and security policies, so that looks like a prime candidate, but we need to grant sufficient permissions to the Jython interpreter while at the same time placing restrictions on the Python scripts that the interpreter is running on our behalf. Tricky.

As you might expect, the Jython interpreter needs us to grant it some basic permissions in order to run; it needs to be able to load script files and access some Java system properties for instance. All in all, it turns out Jython needs the us to grant it the following permissions (assuming that the Jython jar is in our working directory):

grant codebase "file:${user.dir}/jython-standalone-2.5.3.jar" {
  permission java.lang.RuntimePermission "createClassLoader";
  permission java.lang.RuntimePermission "getProtectionDomain";
  permission java.lang.RuntimePermission "accessDeclaredMembers";
  permission java.io.FilePermission "${user.dir}/*", "read";
  permission java.util.PropertyPermission "java.vm.name", "read";
  permission java.util.PropertyPermission "java.vm.vendor", "read";
  permission java.util.PropertyPermission "os.name", "read";
  permission java.util.PropertyPermission "os.arch", "read";
  permission java.util.PropertyPermission "user.dir", "read";
  permission java.util.PropertyPermission "line.separator", "read";
};

Note: The accessDeclaredMembers permission is only necessary if you need to allow scripts to extend Java classes. Given our scenario, we'll assume that's a likely requirement of our Java application's API.

Great, with the above in our Java security policy file (which we'll cunningly call security.policy), we can run Jython with the Java security manager installed, and the interpreter runs our untrusted script, the imaginatively-titled untrusted-script.py:

java -cp jython-standalone-2.5.3.jar -Djava.security.manager -Djava.security.policy==security.policy org.python.util.jython untrusted-script.py

This has already significantly restricted what our fiendishly evil script can do. For instance, if we try to run the following code:

f = open('/etc/passwd')
userdetails = f.read()
print(userdetails)

We get this error message:

java.security.AccessControlException: java.security.AccessControlException: access denied ("java.io.FilePermission" "/etc/passwd" "read")

Busted! This is great: we know that because the Python script is running in the Jython interpreter, and the Jython interpreter is running in the JVM, our untrusted Python script won't be able to do anything that we haven't granted the Jython jar permission to do.

However, there's still a problem. One of the permissions we had to grant the Jython jar in order for it to run at all was the java.lang.RuntimePermission "createClassLoader", and as it turns out, granting code this permission effectively invalidates your security policy, because code that can create its own classloader can create a classloader that grants full permissions to any classes it loads. From the Java API for RuntimePermission:

"This is an extremely dangerous permission to grant. Malicious applications that can instantiate their own class loaders could then load their own rogue classes into the system. These newly loaded classes could be placed into any protection domain by the class loader, thereby automatically granting the classes the permissions for that domain."

But wait, we're only running Python scripts: what harm can they do with the createClassLoader permission? Well, this is Jython, where Python code can call Java APIs, and do this:

from java.security import AccessControlException, Permissions, AllPermission, SecureClassLoader, CodeSource
from java.net import URL

import base64

class DodgyClassLoader(SecureClassLoader):

  def __init__(self):
    SecureClassLoader.__init__(self)
    self.datamap = {}
    self.codeSource = CodeSource(URL('file:/dummy'), None)

  def addClass(self, name, data):
    self.datamap[name] = data

  def findClass(self, name):
    data = self.datamap[name]
    return self.super__defineClass(name, data, 0, len(data), self.codeSource)

  def getPermissions(self, codesource):
    permissions = Permissions()
    permissions.add(AllPermission())
    return permissions

fileReaderClassDef = base64.b64decode('<<base64 ecoding of a class file defining a class that reads files>>')

fileReaderInnerClassDef = base64.b64decode('<<base64 encoding of the inner class containing the code to read files within a doPrivileged block>>')

classloader = DodgyClassLoader()
classloader.addClass('dodgy.FileReader', fileReaderClassDef)
classloader.addClass('dodgy.FileReader$1', fileReaderInnerClassDef)
fileReaderClass = classloader.findClass('dodgy.FileReader')
fileReader = fileReaderClass.newInstance()
userDetails = fileReader.readFile('/etc/passwd')
print(userDetails)

Confound it all, we were so close. If only there were some way to allow the Jython interpreter permission to create classloaders, while at the same time preventing Python scripts from exploiting this. We could maybe do some filtering of the untrusted scripts to check they're not creating classloaders, but we're now relying on mechanisms outside of our nice safe Java sandbox, and we'd have to account for all manner of Python trickery.

Hold on though: in order to grant code permission to do some sensitive operation, the Java security manager needs every frame on the stack to have the necessary permission. At the moment, we've given all the code in the Jython jar the same set of permissions. What if there were a few classes that aren't on the stack when Jython needs to do its classloading, but are on the stack whenever a script tries to call Java APIs? We could place these classes in a separate jar file and not grant them any permissions that we don't want to grant to our Python scripts, limiting the what scripts can do through the Java APIs.

As luck would have it, there are such classes:
  • org.python.core.PyReflectedConstructor
  • org.python.core.PyReflectedField
  • org.python.core.PyReflectedFunction
Let's pull these out of the Jython jar and put them in their own jar:
unzip jython-standalone-2.5.3.jar org/python/core/PyReflected*
jar cvf jython-standalone-2.5.3-nonsecure.jar org/python/core/*
rm -rf org
zip -d jython-standalone-2.5.3.jar org/python/core/PyReflected*

This removes the classes from the main Jython jar and puts them into a new jar: jython-standalone-2.5.3-nonsecure.jar. As this jar isn't listed in our security policy file, these classes don't get any security permissions. Now we can run Jython by adding this extra jar to the classpath as follows:

java -cp jython-standalone-2.5.3-nonsecure.jar:jython-standalone-2.5.3.jar -Djava.security.manager -Djava.security.policy==security.policy org.python.util.jython untrusted-script.py

This way, if our untrusted script tries to create a classloader, the Jython interpreter runs correctly, but the script throws the following exception:

java.security.AccessControlException: java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "createClassLoader")

This is exactly the behavior we want, and it means that we've closed off the only permission that we had to grant the Jython interpreter that was a major concern. Huzzah!

Now, getting back to out original problem, if we wanted to take advantage of this setup in a Java application, all we'd need to do is set the Java security manager when we start up our application, grant java.security.AllPermissions to our application code, and invoke Jython from Java, most likely with the JSR 223 API.

Notes:
  • The hacking around with jar files is only necessary into order to deal with the createClassLoader permission. This is something that we'd like to avoid doing because of the power it potentially gives to code running in the Jython interpreter. However, it's worth noting that the example exploit above, which creates a dodgy subclass of classloader, is only possible because we also granted the accessDeclaredMembers permission. If there's no requirement to allow scripts to extend Java classes, this permission isn't necessary, and it becomes much harder to exploit the createClassLoader permission.
Shortcomings:
  • We had to grant Jython permission to read files in the working directory in order to read the script file, and the tweaks we made to the jar files don't block access to scripts. We therefore shouldn't put anything important in the working directory, or anywhere we intend to put script files.
  • For some reason I've not got to the bottom of yet, these changes prevent Jython running up a command line, which isn't too much of a problem as we want to supply a script, rather than interact with Python via the command line.
  • This is something of a hack and very susceptible to changes in the Jython source code.

Thursday, 16 January 2014

Raspberry Pi as a VPN Wireless Access Point

The following post explains how you can turn a Raspberry Pi (RPI) into a wireless router that connects to the Internet over a VPN. By plugging this into your existing wireless router, you give yourself a second wireless network, and any devices connected to this network will access the Internet over the VPN.

This is especially useful for:
  • Sharing a single VPN connection between several devices.
  • Using the VPN connection with devices that don't support VPN or proxy settings.

As an added bonus, the use of NAT and a couple of firewall rules provides a good level of security for any connected device.

I've found this setup works very nicely, and is fine even for streaming media.

Sources

These instructions draw heavily from the following extremely useful articles:


You'll Need

  • A Rasberry Pi with Raspbian installed.
  • A wireless USB adapter with a chipset that supports Access Point or Master mode. I used a Panda PAU03, and it worked perfectly and has a good signal. See the RPI Wireless Hotspot article for other options.
  • A wired Ethernet connection between the RPI and your router.
  • A VPN service you can connect to that supports tunneled connections and OpenVPN. It's entirely possible you could get this to work with a tap VPN connection, but I can only vouch for the tunneled variety. OpenVPN support is a must: watch out as not all providers support it.

Instructions

The following instructions assume a basic knowledge of Linux, the command prompt, and the ability to edit files with an editor such as Vi or Nano.

I've reconstructed these from my command-line history and the above articles, but haven't done a clean run-through, but I think they should work. Please let me know if you find any mistakes.

Basic Security

Your going to be connecting your RPI to the rest of the Internet via a VPN, which means it won't enjoy the protection of your router's firewall: the VPN tunnel will punch right through and expose your RPI to any machine on the Internet. We'll lock down the VPN connection later on, but before you start, make sure you've changed the default user password using the passwd command.

Initial Setup

Before you start, your RPI will need to be connected to your router via the Ethernet port and able to access the Internet, and your wirless USB adapter will need to be plugged-in.

Install Software

Install the access point server (hostapd), DHCP server (udhcpd), OpenVPN and DNS proxy server (bind9):
sudo apt-get install hostapd udhcpd bind9 openvpn

Configure and Secure the VPN

Your VPN service provider should be provide an OpenVPN configuration file you can use to connect to their VPN server. Copy this file into /etc/openvpn, and rename it openvpn.conf:
cp <your config file> /etc/openvpn/openvpn.conf
Start the OpenVPN service:
sudo service openvpn start
You can check the connection is open with:
ifconfig
You should see a network interface listed called tun0, assuming your VPN provider uses a tunnel (rather than a tap) interface.

You can test the VPN tunnel using the following command:
curl --interface tun0 freegeoip.net/json/
This uses an IP geolocation service to look up the geographic details of the IP address your tunnel connection is using (you might need to give the connection a few seconds to come up). The IP address and other details should be different if you stop the VPN service:
sudo service openvpn stop
curl freegeoip.net/json/
You now need to lock down that VPN tunnel using iptables. The following changes will prevent any unsolicited connections from other machines on the Internet, and make your Pi much less visible on the network:
sudo iptables -A INPUT -i tun0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A INPUT -i tun0 -j DROP
Now save the iptables rules:
sudo sh -c "iptables-save > /etc/iptables.nat.vpn.secure"
To ensure the rules are re-applied at reboot, edit the file /etc/network/interfaces and add the following line at the end of the file:
up iptables-restore < /etc/iptables.nat.vpn.secure
Before we go any further, restart the VPN connection:
sudo service openvpn restart

Configure Wireless Network and Access Point

Now we have a VPN, we can set up our wireless network and access point.
First, check your wireless adapter is working:
ifconfig
You should see an interface listed called wlan0.

Edit the file /etc/udhcpd.conf as follows:
start 192.168.0.2
end 192.168.0.254
interface wlan0
remaining yes
opt dns 192.168.0.1
option subnet 255.255.255.0
opt router 192.168.0.1
option lease 864000 # 10 days
This will give your new wireless network the IP range 192.168.0.1 - 192.168.0.254, and assign the address 192.168.0.1 to the wireless connection of your RPI. You might need to change the IP addresses if they clash with your existing network (check using ifconfig and looking for the IP address of the eth0 interface). The above configuration also tells any connected devices to use the RPI for their DNS server: we'll get to that later.

Edit the file /etc/default/udhcpd and un-comment the following line by removing the # from the front:
#DHCPD_ENABLED="yes"
becomes:
DHCPD_ENABLED="yes"

Set your Pi's IP address:
sudo ifconfig wlan0 192.168.0.1
and to keep the change at reboot, edit the file /etc/network/interfaces and replace the line:
iface wlan0 inet dhcp
with:
iface wlan0 inet static
  address 192.168.0.1
  netmask 255.255.255.0

Note those are tabs in front of the indented lines.

In the same file, comment out the following lines by adding a hash at the start:
allow-hotplug wlan0
wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
iface default inet manual
becomes:
#allow-hotplug wlan0
#wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
#iface default inet manual

Now configure your wireless connection by editing the file /etc/hostapd/hostapd.conf as follows (you'll need to create it if it doesn't exist already):
interface=wlan0
driver=nl80211
ssid=YOUR_SSID
hw_mode=g
channel=6
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=YOUR_PASSWORD
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
Change YOUR_SSID and YOUR_PASSWORD to be network name and password respectively for your new wireless network. It's also worth checking the channel your existing router is using and making sure this one doesn't clash.

Now edit the file /etc/default/hostapd and change the line:
#DAEMON_CONF=""
to:
DAEMON_CONF="/etc/hostapd/hostapd.conf"

Now start up the wireless network:
sudo service hostapd start
sudo service udhcpd start
and make sure the services start at reboot:
sudo update-rc.d hostapd enable
sudo update-rc.d udhcpd enable

Configure DNS

Now we'll set up a local caching DNS server on the RPI which will be used by the connected devices.

Edit the file /etc/bind/named.conf.options and add a forwarders section as follows:
forwarders {
8.8.8.8;
8.8.4.4;
};
The above IP addresses will use Googles public DNS server, but obviously you can choose an alternative if you prefer. Just don't try to use the DNS of your existing router, which won't be accessible over the VPN.

Now restart the DNS server:
sudo service bind9 restart
and make sure it starts again at reboot:
sudo update-rc.d bind9 enable

Set Up NAT for the VPN Connection

Finally, we just need to set up NAT for the VPN connection, which will allow us to share the connection with any connected devices.

Enable NAT:
sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
To set this at reboot, edit the file /etc/sysctl.conf and add the following line at the end:
net.ipv4.ip_forward=1
Now set up NAT for the VPN connection:
sudo iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
and to save this change so it's re-applied at reboot:
sudo sh -c "iptables-save > /etc/iptables.nat.vpn.secure"

Interestingly the RPI Wireless Hotspot article I based a lot of this on suggested adding a couple of other iptables rules to link the wireless and wired network adapters, but I found they weren't necessary. If you find the above alone isn't working, try the following:
sudo iptables -A FORWARD -i tun0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i wlan0 -o tun0 -j ACCEPT

Testing

OK, you're done. To test the setup, connect a device to the new wireless network using the password you configured. Now open a browser and navigate to the IP geolocation service we used to test the VPN connection earlier:


With any luck you should see the same details you saw when you accessed the service over the VPN on the command line. To make absolutely sure things have gone well, try changing back to your other wireless network and refreshing the page: you should see the details change.

Now if you're feeling really confident you can reboot your RPI and check everything comes back up OK. :)