IPTables: Personal Firewall to protect my laptop

Firewall! What a high sounding word! Means high protection & a safe cocoon for all the newbies. Thats exactly what I did setup in my laptop: A firewall! A personalized firewall.

My use cases & reasons were very simple. Every once in a while, I expose my laptop to unsafe open Internet like the cafes, restaurants & hotel-accommodations. Apart from that, thanks to my work, I run many services like apache2, sshd, mongodb, mysqld, etc in my laptop, which are susceptible to malicious attacks. Club these two & I got a time-bomb ticking right on my lap!!

I did a bit of research and finally (I think & I hope this is final!) I have arrived at the min-ship requirement for my laptop to function correctly under any network without compromising itself!! These are things I felt like handling within my firewall:

  1. Enable (or Disable) a few kernel features
  2. Make the default rule to DROP instead of the ACCEPT
  3. Allow all packets from RELATED/ESTALBLISHED connectons
  4. Always allow loopback devices
  5. Drop all IANA reserved IPs
  6. Allow skype incoming
  7. Allow DHCP outgoing
  8. Allow DNS outgoing
  9. Allow HTTP outgoing
  10. Allow NTP outgoing
  11. Allow ping outgoing
  12. Allow SMTP outgoing
  13. Allow SSH outgoing

Simple, yeah?! 🙂

 

All of these steps are captured in my script – firewall.txt (Updated script: meetrp github). Just executing the script will enable everything as described above. But if you want to understand or wanna do them one-by-one yourselves then continue reading! 🙂

 

Enable (or Disable) a few kernel features
The common rule in protecting oneself is: “Deactivate everything you do not need.” Keeping in line with this principle, I have disabled (or enabled) a few kernel parameters to protect my laptop from malicious (or unwanted) intrusion.

  1. Ignore the broadcast pings: ICMP echo messages are the messages used by the “ping” command-line tool. By ignoring broadcast ICMP echo requests, your machine won’t respond when someone tries to ping a broadcast address (such as 255.255.255.255, or, say, 192.168.1.255 on a 192.168.1.0/24 subnet) to find all the hosts on the network or subnet at the same time.
  2. $> echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
    
  3. Deactivate source routed packets: Attackers could be using source-routed packets to generate traffic that seems to be intra-net, but actually was created outside and has been redirected.
  4. $> for iter in /proc/sys/net/ipv4/conf/*/accept_source_route; do; echo 0 > $iter; done
    
  5. Disable ICMP redirects: ICMP redirects are used by routers to specify better routing paths out of one network, based on the host choice, so basically it affects the way packets are routed and destinations. The atacker can then on basically alter your host’s routing tables and diver traffic towards external hosts on a path of his/her choice; the new path is kept active by the router for 10 minutes.
  6. $> for iter in /proc/sys/net/ipv4/conf/*/accept_redirects; do; echo 0 > $iter; done
    
  7. Disable IP forwarding: If there are mulitple network interfaces (like eth0, eth1, wlan0) active at the same time, then traffic coming in from one interface can be forwarded to another interface. This feature is not required in a traditional laptop
  8. $> echo 0 > /proc/sys/net/ipv4/ip_forward
    
  9. Turn on source address verfication: By default, routers route everything, even packets which ‘obviously’ don’t belong on your network. A common example is private IP space escaping onto the Internet. If you have an interface with a route of 195.96.96.0/24 to it, you do not expect packets from 212.64.94.1 to arrive there. Enabling this verification implies if the reply to a packet wouldn’t go out the interface this packet came in, then this is a bogus packet and should be ignored.
  10. $> for iter in /proc/sys/net/ipv4/conf/*/rp_filter; do; echo 1 > $iter; done
    
  11. Turn on syn cookies protection: The TCP Syn is DoS (Denial of Service) attack. It consumes resources on your Linux server. The attacker begin with the TCP connection handshake sending the SYN packet, and then never completing the process to open the connection. This results into massive half-open connections.
  12. $> echo 1 > /proc/sys/net/ipv4/tcp_syncookies
    

 
Where is the rules set?
Check the attached: firewall.txt (Updated script: meetrp github)! Rename this file with ‘.sh’ extension & execute it.

$> ls -l ./firewall.txt 
-rw-rw-r-- 1 rp rp 17288 Aug 31 00:15 ./firewall.txt

$> mv firewall.txt myfirewall.sh
$> ls -l *firewall*
-rwxrwxr-x 1 rp rp 17288 Aug 31 00:15 myfirewall.sh

$> chmod +x ./myfirewall.sh 

$> sudo ./myfirewall.sh 
[Sunday 31 August 2014 00:16:20] Not a root!
[Sunday 31 August 2014 00:16:20] ignore ICMP echo broadcasts
[Sunday 31 August 2014 00:16:20] log all packets
[Sunday 31 August 2014 00:16:21] enable reverse path filtering
[Sunday 31 August 2014 00:16:21] enable syn cookies protetion
[Sunday 31 August 2014 00:16:21] disable ICMP redirects
[Sunday 31 August 2014 00:16:21] disable ip forwarding
[Sunday 31 August 2014 00:16:21] disable source route
[Sunday 31 August 2014 00:16:21] -------------- IPv4 ---------------
[Sunday 31 August 2014 00:16:21] clear all rules
[Sunday 31 August 2014 00:16:21] default drop
[Sunday 31 August 2014 00:16:21] allow all related & established
[Sunday 31 August 2014 00:16:21] allow loop back
[Sunday 31 August 2014 00:16:21] drop all IANA reserved IPs
[Sunday 31 August 2014 00:16:21] --> eth0
[Sunday 31 August 2014 00:16:21] allow skype in
[Sunday 31 August 2014 00:16:21] allow DHCP out
[Sunday 31 August 2014 00:16:21] allow DNS out
[Sunday 31 August 2014 00:16:21] allow HTTP out
[Sunday 31 August 2014 00:16:21] allow NTP out
[Sunday 31 August 2014 00:16:21] allow ping out
[Sunday 31 August 2014 00:16:21] allow SMTP out
[Sunday 31 August 2014 00:16:21] allow SSH out
[Sunday 31 August 2014 00:16:21] --> wlan0
[Sunday 31 August 2014 00:16:21] allow skype in
[Sunday 31 August 2014 00:16:21] allow DHCP out
[Sunday 31 August 2014 00:16:21] allow DNS out
[Sunday 31 August 2014 00:16:21] allow HTTP out
[Sunday 31 August 2014 00:16:21] allow NTP out
[Sunday 31 August 2014 00:16:21] allow ping out
[Sunday 31 August 2014 00:16:22] allow SMTP out
[Sunday 31 August 2014 00:16:22] allow SSH out
[Sunday 31 August 2014 00:16:22] -------------- IPv6 ---------------
[Sunday 31 August 2014 00:16:22] clear all rules
[Sunday 31 August 2014 00:16:22] default drop

This is my firewall setup script. Whenever I want, I execute this script and voila, my firewall is setup.

 
Dump the IPTables for verfication

 $> sudo iptables -S
-P INPUT DROP
-P FORWARD DROP
-P OUTPUT DROP
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -s 0.0.0.0/7 -j DROP
-A INPUT -s 2.0.0.0/8 -j DROP
-A INPUT -s 5.0.0.0/8 -j DROP
-A INPUT -s 7.0.0.0/8 -j DROP
-A INPUT -s 10.0.0.0/8 -j DROP
-A INPUT -s 23.0.0.0/8 -j DROP
-A INPUT -s 27.0.0.0/8 -j DROP
-A INPUT -s 31.0.0.0/8 -j DROP
-A INPUT -s 36.0.0.0/7 -j DROP
-A INPUT -s 39.0.0.0/8 -j DROP
-A INPUT -s 42.0.0.0/8 -j DROP
-A INPUT -s 49.0.0.0/8 -j DROP
-A INPUT -s 50.0.0.0/8 -j DROP
-A INPUT -s 77.0.0.0/8 -j DROP
-A INPUT -s 78.0.0.0/7 -j DROP
-A INPUT -s 92.0.0.0/6 -j DROP
-A INPUT -s 96.0.0.0/4 -j DROP
-A INPUT -s 112.0.0.0/5 -j DROP
-A INPUT -s 120.0.0.0/8 -j DROP
-A INPUT -s 169.254.0.0/16 -j DROP
-A INPUT -s 172.16.0.0/12 -j DROP
-A INPUT -s 173.0.0.0/8 -j DROP
-A INPUT -s 174.0.0.0/7 -j DROP
-A INPUT -s 176.0.0.0/5 -j DROP
-A INPUT -s 184.0.0.0/6 -j DROP
-A INPUT -s 192.0.2.0/24 -j DROP
-A INPUT -s 197.0.0.0/8 -j DROP
-A INPUT -s 198.18.0.0/15 -j DROP
-A INPUT -s 223.0.0.0/8 -j DROP
-A INPUT -s 224.0.0.0/3 -j DROP
-A INPUT -i eth0 -p udp -m udp --dport 16514 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 16514 -j ACCEPT
-A INPUT -i wlan0 -p udp -m udp --dport 16514 -j ACCEPT
-A INPUT -i wlan0 -p tcp -m tcp --dport 16514 -j ACCEPT
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A OUTPUT -o lo -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --sport 67:68 --dport 67:68 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --dport 53 -j ACCEPT
-A OUTPUT -o eth0 -p tcp -m tcp --dport 80 -m state --state NEW -j ACCEPT
-A OUTPUT -o eth0 -p tcp -m tcp --dport 443 -m state --state NEW -j ACCEPT
-A OUTPUT -o eth0 -p udp -m udp --sport 123 --dport 123 -j ACCEPT
-A OUTPUT -o eth0 -p icmp -m icmp --icmp-type 8 -j ACCEPT
-A OUTPUT -o eth0 -p tcp -m tcp --dport 25 -m state --state NEW -j ACCEPT
-A OUTPUT -o eth0 -p tcp -m tcp --dport 22 -m state --state NEW -j ACCEPT
-A OUTPUT -o wlan0 -p udp -m udp --sport 67:68 --dport 67:68 -j ACCEPT
-A OUTPUT -o wlan0 -p udp -m udp --dport 53 -j ACCEPT
-A OUTPUT -o wlan0 -p tcp -m tcp --dport 80 -m state --state NEW -j ACCEPT
-A OUTPUT -o wlan0 -p tcp -m tcp --dport 443 -m state --state NEW -j ACCEPT
-A OUTPUT -o wlan0 -p udp -m udp --sport 123 --dport 123 -j ACCEPT
-A OUTPUT -o wlan0 -p icmp -m icmp --icmp-type 8 -j ACCEPT
-A OUTPUT -o wlan0 -p tcp -m tcp --dport 25 -m state --state NEW -j ACCEPT
-A OUTPUT -o wlan0 -p tcp -m tcp --dport 22 -m state --state NEW -j ACCEPT

$> sudo ip6tables -S
-P INPUT DROP
-P FORWARD DROP
-P OUTPUT DROP

If you notice I have plenty of rules for IPv4 but dropped the IPv6 entirely!! Who wants IPv6 right away?! I still get only IPv4 address.;)

If you wanna make these rules permanent and persistent then follow these steps:

Save the rules set while networking is going down

$> cat /etc/network/if-down.d/saveiptables
#!/bin/bash

/sbin/iptables-save > /etc/ipv4tables.rules
/sbin/ip6tables-save > /etc/ipv6tables.rules

exit 0

Restore the rules while networking is coming up

$> cat /etc/network/if-up.d/loadiptables
#!/bin/bash

/sbin/iptables-restore < /etc/ipv4tables.rules
/sbin/ip6tables-restore < /etc/ipv6tables.rules

exit 0

Btw, don't forget to change it to executable!

$> sudo chmod +x /etc/network/if-down.d/saveiptables /etc/network/if-up.d/loadiptable

After this, hopefully, my laptop is secure as compared to before.

FYI, these were my experiments limited to my understanding. If I can be of any help & esp vice-versa, please feel free to contact me!

Updated script: meetrp github

Courtesy

  1. Security - Linux StepByStep
  2. The Kernel - Linux inside
  3. IPTables Tips and Tricks: More Than Just ACCEPT or DROP
  4. Saving iptables rules to be persistent
  5. http://hermann-uwe.de/files/fw_laptop
  6. Laptop Iptables configuration
  7. iptables: Small manual and tutorial with some examples and tips

Simple Character Device Driver

It has been nearly 5 years since I did any work on device drivers. Also since then I have been off C or its sibling languages (on Python now-a-days), I thought first lemme get comfortable with linux kernel (& C, of course) before I can even dream of getting back to my core. In that line, I wrote a very simple character device driver which upon read returns a character buffer with every bit turned to one.

In other words, the idea is simple, our (virtual) device is almost like an inverse of (or a not of, if you prefer) of /dev/zero. Reading from this device would result in every bit turning one. For instance, if we read 1 Byte of data from /dev/ones & store it in file ‘out’:

$> dd if=/dev/ones of=out bs=1 count=1
1+0 records in
1+0 records out
1 byte (1 B) copied, 0.000142743 s, 7.0 kB/s

Then dump the content of ‘out’ file in hex mode, we should get:

$> od -t x1 out
0000000 ff
0000001

Download the entire code.

What & Why a character device driver?

Lemme answer the why? Because it is the easiest! 🙂 No complication and a virtual character device can be as limited in its capabilities as you can think of. So, now what is a character device? It is where:

  • Read/write arbitrary bytes at a time
  • Generally seek is _not_ supported. (Generally!)
  • Very importantly, works on streams of bytes (or data, if you like). In other words, it does _NOT_ support buffered I/O. So every R/W is directly sent to (or from) the device.

Examples of character devices are printers, mice, keyboards & likewise.
Examples of pseudo(or virtual) character devices in Linux are /dev/null, /dev/zero, /dev/random & similar devices.

Basics

I am revisiting so lemme just wanna throw some light to the forgotten corners of my knowledge. Writing a device driver (at least, in Linux) is like writing any C – program. The only difference is that the Linux kernel kinda provides a framework and set of libraries that the driver code should adhere to. Very similar to, say, .NET framework or MFC. Linux kernel subsystem expects the driver to:

  1. define the entry & exit points.
  2. implement a set of operations (typically, file operations)
  3. register with the subsystem.
  4. create the device file

Entry & Exit points

These are like constructor & destructor available in many OOP languages like C++ or Java. Unlike C++, these entry points are mandatory. These are called when a kernel module is loaded. (Note: I said kernel module & not a driver!)

A device driver is a kernel module which talks (or at least, can talk to) a hardware device.
A kernel module is just any pluggable piece of code that can be inserted into the Linux subsystem (or framework, if that helps you).

But for this blog let us not worry about the nitty-gritty and the words kernel module and driver are interchangeably used.
So, a simple kernel module, which does nothing, is as follow:

#include <linux/module.h>

static int __init
one_init(void)
{
    return 0;
}

static void __exit
one_exit(void)
{
    return;
}

module_init(one_init);
module_exit(one_exit);

Suppose you name this as ones.c. You can compile the above using a Makefile as defined below:

TARGET_MODULE := ones
BUILDSYSTEM_DIR := '/lib/modules/${shell uname -r}/build'
PWD := $(shell pwd)

obj-m := $(TARGET_MODULE).o

default: test
    ${MAKE} -C ${BUILDSYSTEM_DIR} SUBDIRS=${PWD} modules

clean:
    ${MAKE} -C ${BUILDSYSTEM_DIR} SUBDIRS=${PWD} clean

Don’t worry about the details of the Makefile. Just know & note that the kernel module is compile with Kernel header files (at least). So you gotta download the kernel header files in accordance to your OS flavor. Once you compile, you should get a few additional file of which ‘ones.ko’ is of importance to us & this is the kernel module (or Kernel Object as the extension suggests). The module related Linux commands are:

#insert the kernel module into the kernel subsystem
$> insmod ones.ko

# list to verify
$> lsmod | grep ones
ones 19598 0

# details on the module
$> modinfo ./ones.ko
filename: ./ones.ko
author: Rp <rp@meetrp.com>
description: This device driver turns on every bit to '1'
license: GPL
srcversion: C5FDECEF298D041105E333F
depends:
vermagic: 2.6.32-358.18.1.el6.x86_64 SMP mod_unload modversions

# remove the module from kernel subsystem
$> rmmod ones

Implement of file operations

Let us add more meat to the simple module. Let us develop this simple kernel module into a character device driver. Now, what are our requirements: “Read should result in every bit turned to one. ” This means the use should be able to open, read and close our virtual device. As mentioned above, Linux kernel subsystem provides a framework to define & implement these operations. The are defined under ‘struct file_operations,’ which holds pointers to functions defined by the driver to perform various operations on the device. In other words, Linux kernel subsystem uses the file_operation structure to access driver’s functions. So, continuing with our driver, the structure is populated thus:

static struct file_operations fops = {
    .owner = THIS_MODULE, // pointer to the module struct
    .open = one_open,     // address of one_open
    .release = one_close, // address of one_close
    .read = one_read,     // address of one_read
};

Don’t worry about the “.owner,” part of it. You can safely ignore it as well. Now these function declarations (or prototypes) are available within linux/fs.h. Also the prototypes for the functions of interest are:

int (*open) (struct inode *, struct file *);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
int (*release) (struct inode *, struct file *);

So now add these to our driver code:

#include <linux/fs.h>

// called when 'open' system call is invoked
static int
one_open(struct inode *in, struct file *fp)
{
    open_count++;
    debug("Count of open dev files #%d", open_count);

    return 0;
}

// called when 'close' is invoked
static int
one_close(struct inode *in, struct file *fp)
{
    if (! open_count) {
        error("Device close attempted while count is #%d", open_count);
        return -EFAULT;
    }

    debug("Device closed. count is #%d", open_count);
    open_count--;
    return 0;
}

Since we are working on virtual device I just maintain the count of open requests and matching those with the number of close. This is _not_ required but then my zodiac sign is Virgo! 😉 Let us define the read functionality, which is where our core logic resides. Note the declaration of ‘read’ function pointer:

ssize_t (*read) (struct file *, char *, size_t, loff_t *);
  • The first parameter is a pointer to file structure, which can be ignored in our case as we are _not_ creating (during open) or destroying (during close) any of the file pointers.
  • The second parameter is the buffer that is allocated in the user space for the read data.
  • The third is the number of bytes to be read and finally,
  • the fourth is the offset within the file, which, again, is not required in our case.
  • Also note that the function should return the number of bytes successfully read.

So, what we are interested in is how to copy number of bytes from kernel space to user space pointed by . Simple, yeah?! 🙂

Now the question is how to copy from Kernel space to user space. Without going into details, we have a special set of functions to do such transfers and the one of interest to us is ‘copy_to_user‘ function, which:

  1. as name suggests, copies data from a buffer in kernel to a buffer in user space.
  2. checks the pointer validity
  3. checks the sufficiency of the size of the buffer allocated in user space.

The self-explanatory prototype looks like this:

long copy_to_user( void __user *to, const void * from, unsigned long n );

Now, the final piece of the puzzle. The ‘ones’. I used a global buffer array (one_arr) of size of a page (PAGE_SIZE) which has every of its bit of every Byte turned on, i.e., 0xFF. So, our read function is nothing more than just copy data from this global buffer array to the user space, one page at a time. Thus our code is:

// called when the 'read' system call is done on the device file
static ssize_t
one_read(struct file *fp, char *buf, size_t buf_size, loff_t *off)
{
    size_t count = 0;

    if (! buf_size) {
        debug("buf_size is ZERO!!");
        return 0;
    }

    debug("requested size #%ld", buf_size);
    while (buf_size) {
        size_t chunk = buf_size;

        if (chunk > PAGE_SIZE)
            chunk = PAGE_SIZE;

        debug("about to copy #%ld size of data", chunk);
        debug("data: 0x%x", (unsigned char)one_arr[0]);
        if (copy_to_user(buf, one_arr, chunk))
            return -EFAULT;

        buf += chunk;
        count += chunk;
        buf_size -= chunk;
    }

    debug("return #%ld", count);
    return count;
}

Register of character device with the subsystem

Finally, we are now ready to register this module as a character device driver. The registration happens in the constructor (i.e., init function). The registration API has the following prototype:

int register_chrdev (unsigned int major, const char *name, const struct file_operations *fops);

I am not gonna talk about the major number, the first parameter, in this blog. The second parameter is the name we would like to give to our device driver. The final parameter is the pointer to the file_operations structure defined earlier. We can add the following into the init function:

major = register_chrdev(0, "one", &amp;fops);
if (major &lt; 0) {
    error("Device registration failed - %d!!", major);
    return -EFAULT;
}

Thus ends our coding for the character device driver.
The rest of the entire code can be downloaded from here.

Create the device file

When you load our driver we should be able to see these entries in the log (in CentOS/RHEL, it is /var/log/messages):

Sep 13 20:22:50 rp kernel: [ ONE : one_init : 038 ] [DBG] registering the character device
Sep 13 20:22:50 rp kernel: [ ONE : one_init : 040 ] [DBG] return value: 247
Sep 13 20:22:50 rp kernel: [ ONE : one_init : 046 ] [INF] Device registration successful. Major #247
Sep 13 20:22:50 rp kernel: [ ONE : one_init : 047 ] [DBG] about to prepare the array

Note the major number printed. Using this, we can create a device node on the CLI as shown below.

$> mknod /dev/ones c 247 1
$> chmod a+w+r /dev/ones

Remember to be run as a root or in sudo mode.

Tada! The first simple driver ready! 🙂

You can download the entire code including a test program from here.

References

The blog & articles I referenced while rebuilding my knowledge. Thought these might be useful to you guys as well: