The goal of this article is to isolate a small public web server on a simulated demilitarized zone (DMZ) network, and to restrict the local network access in case the server is breached. It is an extra security layer added to an existing home server setup.
The DMZ consists of an internal network 10.10.20.0/24 connected to br0 bridge device. On this network I place a Linux namespaces security sandbox at 10.10.20.10, running a web server. In case an intruder gets control of the web server, he will be running with low privileges as a generic www-data user. The host firewall configuration will not allow him to open connections anywhere outside DMZ network.
To build the sandbox I use Firejail on a Debian 7 computer. For any other Linux distribution the setup steps are similar. All the commands specified below are executed as root.
Step 1: Install Firejail
The download page provides source code (./configure && make && sudo make install), deb (dpkg -i firejail.deb) and rpm (rpm -i firejail.rpm) packages. The project page also lists an Arch Linux package. The software has virtually no dependencies and it will work with any 3.x Linux kernel.
Step 2: Install nginx web server
Install nginx or any other web server you are familiar with. Stop the server and make sure it is not started by default at power up:
# apt-get install nginx # /etc/init.d/nginx stop Stopping nginx: nginx. # insserv -r nginx
On Debian and Ubuntu, nginx serves pages from /usr/share/nginx/www directory. The logs are stored in /var/log/nginx/.
Step 3: Configure 10.10.20.0/24 network
Create and configure the internal 10.10.20.0/24 network. The host interface br0 has the address 10.10.20.1:
# apt-get install bridge-utils # brctl addbr br0 # ifconfig br0 10.10.20.1/24
You should have in this moment the bridge interface up an running:
# ifconfig br0 br0 Link encap:Ethernet HWaddr e6:55:ca:1c:29:4a inet addr:10.10.20.1 Bcast:10.10.20.255 Mask:255.255.255.0 inet6 addr: fe80::e455:caff:fe1c:294a/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:6 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:468 (468.0 B)
Step 4: Host firewall configuration
Forward TCP port 80 on the host to TCP port 80 in sandbox, and drop all traffic originated on DMZ network. Also, enable routing on the host. My iptables script is as follows:
#!/bin/bash # netfilter cleanup iptables --flush iptables -t nat -F iptables -X iptables -Z iptables -P INPUT ACCEPT iptables -P OUTPUT ACCEPT iptables -P FORWARD ACCEPT # enable ipv4 forwarding echo "1" > /proc/sys/net/ipv4/ip_forward # forward host tcp port 80 to our sandbox iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to 10.10.20.10:80 # drop any traffic originated on 10.10.20.0/24 network iptables -A FORWARD -i br0 -m state --state NEW,INVALID -j DROP iptables -A INPUT -i br0 -m state --state NEW,INVALID -j DROP
Step 5: Start the sandbox
Start the server using Firejail:
# firejail --private --seccomp --net=br0 --ip=10.10.20.10 \ "/etc/init.d/rsyslog start; \ /etc/init.d/nginx start; \ sleep inf" &
The command configures the bridge device and the IP address. It starts a syslog server and nginx inside the sandbox. Use sleep inf to keep the session open indefinitely.
Sandbox isolation:
- Filesystem: The sandbox has isolated the filesystem by making all directories read-only. Option –private removes /root, /home and /tmp directories. Among other things, this also isolates X11 socket and prevents any kind of snooping on X11 sessions.
- Process space: The only processes visible in the sandbox are the processes started in the sandbox (syslog and web server). This prevents attacks such as strace attack. You can get a list of the processes running in sandbox using firejail –list command:
# firejail --list 3867:root:firejail --private --seccomp --net=br0 --ip=10.10.20.10 /etc/init... 3868:root:bash -c /etc/init.d/rsyslog start; /etc/init.d/nginx st... 3885:root:/usr/sbin/rsyslogd -c5 3912:root:nginx: master process /usr/sbin/nginx 3913:www-data:nginx: worker process 3914:www-data:nginx: worker process 3916:www-data:nginx: worker process 3917:www-data:nginx: worker process 3915:root:sleep inf #
- Network stack: The sandbox uses a separate network stack, with different interfaces, its own routing table and firewall, and its own set of socket connections. The host firewall was set to forward TCP port 80 traffic to our sandbox, and to drop any connections originated on 10.10.20.0/24 network segment.
- Seccomp: Seccomp (alias for “secure computing”) is a filtering mechanism that allows processes to specify an arbitrary filter of system calls (expressed as a Berkeley Packet Filter program) that should be forbidden. Berkeley Packet Filter support for seccomp was introduced in Linux kernel 3.5. It greatly reduces kernel attack surface. The feature is enabled using –seccomp option.
Step 5: Monitoring the web server
Check the server using the log files in /var/log directory inside the sandbox. To reach them you would have to join the sandbox using firejail –join command:
# firejail --join=3868 [root@debian ~]$
Specify the PID number for one of the processes running in sandbox as –join argument. The option will only work on Linux kernels 3.8 or newer, and it is equivalent to a terminal login. If you are using an earlier kernel, add an ssh server to your sandbox:
# firejail --private --seccomp --net=br0 --ip=10.10.20.10 \ "/etc/init.d/rsyslog start; \ /etc/init.d/ssh start; \ /etc/init.d/nginx start; \ sleep inf" &
Conclusion
I start with a mainstream web server (nginx) running on one of the most popular web server platforms (Debian). This provides a secure baseline for my setup. I sandbox the server using Linux namespaces feature in Linux kernel, thus increasing the security of the setup.
This is a comparison of a regular server setup and a restricted server setup in a security sandbox:
Regular setup | Security sandbox setup | |
Filesystem | Read-write | Configurable, mostly read-only |
Process table | Access to all running processes | Access only to processes running in the sandbox |
Network | Full local network access | Controlled local network access |
The same solid server security practices are required for both the regular and the sandbox case. An attacker gaining unauthorized root access is bad in both cases.
Related Posts
Pingback: Securing a Web Server Using a Linux Namespaces Sandbox | Hallow Demon
Pingback: Links 18/6/2014: Red Hat to acquire eNovance | Techrights
I’m using firejail version 0.9.30
If I start firejail with a non root users and start a process listening on a port (like nc -l) inside the sandbox, i can’t connect to the port of the process from outside of the sandbox. If I start firejail as root it’s working as expected.
Is this an expected behaviour ?
Thanks
Seems to be working fine. As a regular user, you would need to listen (-l) on port numbers greater than 1024. As root you can below 1024. Example:
Thanks for the quick reply.
I’m sorry for my bad english, more information on my project : I want to expose on Internet a service running on my raspberry.
I have a firewall with two VLANs : 1 (internal LAN) and 9 (DMZ to which the sandbox is connected and port forwarded to internet by the firewall)
This is the network configuration of the raspberry (Ip forwarding is enabled and i have a bridge br0 on VLAN 9) :
In a raspberry shell i run firejail :
In another raspberry shell (outside sandbox)
In sandbox I see :
OK, it works
But if i run firejail from a not root user, the sandbox an nc starts with the same output, but in the other shell i have :
I have news.
If I start firejail with a not root user using the –noprofile option, then access from outside works like with root user.
What in generic.profile can cause this behaviour only for non root users ?
I think it has something to do with the way you start the server using nc. If you start it as “nc -l 172.25.1.110 10000” I can see it opening a server on port 47601. If I replace this command with “nc -p 10000” the server is opened on 100000 and immediately it starts working.
To verify what port nc is listening, I joined the sandbox from another terminal and run netstat:
It should say like this:
I can’t reproduce the strange behaviour you have when launch “nc -l 172.25.1.110 10000”.
In my sandbox nc listen always on the same port, the only difference is the address, 0.0.0.0 if i use -p parameter.
I have used nc as an example of a simple application listening on a port, but i have the same problem (a listening port can’t be contacted from outside of a sandbox launched with non root user) with other applications.
As i said previously, if I use the –noprofile parameter the listening port can be contacted from outside of the sandbox when i start a sandbox from a non root user, so I think we have to find out what in the generic.profile causes this behaviour.
The profile file is in /etc/firejail/generic.profile. You can go line by line and comment them out (add a # at the beginning of the line). My guess would be the line with “caps.drop all”.
It’s not caps related, but it depends from the “netfilter” line.
In man documentation –netfilter applies the following rules :
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m state –state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp –icmp-type destination-unreachable -j ACCEPT
-A INPUT -p icmp –icmp-type time-exceeded -j ACCEPT
-A INPUT -p icmp –icmp-type echo-request -j ACCEPT
COMMIT
The line “:INPUT DROP [0:0]” clearly blocks every incoming connection.
Launching firejail with a modified generic profile having the netfilter line changed in “netfilter=filename” and filename containing all the previous rules except the “:INPUT DROP [0:0]”, the connection to nc works as expected.
Now the open question is : Why the netfilter options has not effect when firejail is launched as root ?
Because servers are run as root. If you sandbox a server such as Apache or Nginx, you need to allow incoming connections.
But if i start firejail as root with –debug, it’s shown “Installing network filter:” with the same rules as when it’s started as a normal user. So it’s a bug or what else ?
I have done more tests :
If firejail starts with plain –netfilter without filename, if starts as root no filter rules are applied, if starts as normal user the default filter rules are applied.
If firejail starts with –netfilter=filename, filter rules are applied
independently which user started it.
I think it shoud be more explained in documentation the different behaviour when started as root or not.