5.2. Filter¶
5.2.1. Introduction¶
This chapter will document how to use haka to filter packets and streams according to their different fields.
This tutorial is divided into two parts. The first part will use hakapcap and a pcap file to do some basic, offline, filtering. The second part will use nfqueue to alter tcp streams as they pass through haka.
5.2.2. Basic TCP/IP filtering¶
Basic IP filtering¶
The directory <haka_install_path>/share/haka/sample/filter/ contains all the example files for this section.
A basic filter script on IP fields is provided as ipfilter.lua:
------------------------------------
-- Loading dissectors
------------------------------------
-- Load the ipv4 dissector to filter on IPV4 fields
local ipv4 = require('protocol/ipv4')
------------------------------------
-- Detects packets originating from 192.168.10.10
-- * Log an alert
-- * Drop the packet
------------------------------------
haka.rule{
-- This rule is applied on each IP incoming packet
hooks = { 'ipv4-up' },
eval = function (self, pkt)
-- Parse the IP address and assign it to a variable
local bad_ip = ipv4.addr('192.168.10.10')
if pkt.src == bad_ip then
-- Log an alert
haka.alert{
description = string.format("Filtering IP %s", bad_ip),
severity = 'low'
}
-- and drop the packet
pkt:drop()
end
-- All other packets will be accepted
end
}
This script can be run with a pcap file provided in the sample directory.
$ cd <haka_install_path>/share/haka/sample/filter/
$ hakapcap ipfilter.pcap ipfilter.lua
To create a pcap file containing all packets that were not dropped, run the following command:
$ cd <haka_install_path>/share/haka/sample/filter/
$ hakapcap trace.pcap ipfilter.lua -o output.pcap
The resulting pcap file can be opened with wireshark to check that haka correctly filtered the packets according to their IP source address.
Interactive rule debugging¶
Haka allows to interactively debug lua rules. A script containing a lua error is provided as tcpfilter.lua.
This script will filter TCP packets, only allowing packets to/from port 80 to pass through.
------------------------------------
-- Loading dissectors
------------------------------------
require('protocol/ipv4')
require('protocol/tcp')
------------------------------------
-- Only allow packets to/from port 80
------------------------------------
haka.rule{
-- The hooks tells where this rule is applied. Only TCP packets will be
-- intecepted by this rule. Other protocol will flow.
hooks = { 'tcp-up' },
eval = function (self, pkt)
-- The next line will generate a lua error:
-- there is no 'destport' field. replace 'destport' by 'dstport'
if pkt.destport == 80 or pkt.srcport == 80 then
haka.log("Filter", "Authorizing trafic on port 80")
else
haka.log("Filter", "Trafic not authorized on port %d", pkt.dstport)
pkt:drop()
end
end
}
To run this example, use the following commands:
$ cd <haka_install_path>/share/haka/sample/filter/
$ hakapcap tcpfilter.pcap tcfilter.lua
When this script is run, haka will output a high number of errors, complaining that the field destport doesn’t exist. We will use haka’s debug facilities to find out precisely where the error occurs.
See also
Debugging contains documentation on haka’s debugging facilities.
To start haka in debugging mode, add --luadebug at the end of the command line:
$ cd <haka_install_path>/share/haka/sample/filter/
$ hakapcap tcpfilter.pcap tcpfilter.lua --luadebug
When a lua error code occurs, the debugger breaks and outputs the error and a backtrace.
entering debugger: unknown field 'destport' Backtrace =>0 [C]: in function '(null)' #1 [C]: in function '(null)' #2 [C]: in function '__index' #3 tcpfilter.lua:21: in function 'eval' #4 /opt/haka/share/haka/core/rule.bc:0: in the main chunk #5 /opt/haka/share/haka/core/rule.bc:0: in the main chunk #6 /opt/haka/share/haka/core/rule.bc:0: in the main chunk #7 [C]: in function 'xpcall' #8 /opt/haka/share/haka/core/rule.bc:0: in the main chunk ...
The general syntax of the debugger is close to the syntax of gdb.
Here we are interested in the third frames which is the one in the lua script itself.
To set the debugger to focus on that particular frame, type frame 3. We can now use the list command to display the faulty source code:
debug> list 16: hooks = { 'tcp-up' }, 17: eval = function (self, pkt) 18: -- The next line will generate a lua error: 19: -- there is no 'destport' field. replace 'destport' by 'dstport' 20=> if pkt.destport == 80 or pkt.srcport == 80 then 21: haka.log("Filter", "Authorizing trafic on port 80") 22: else 23: haka.log("Filter", "Trafic not authorized on port %d", pkt.dstport) 24: pkt:drop() 25: end
We now see that lua is complaining about an unknonw field destport on the line testing the destination port of the packet.
Packets, like all structures provided by haka, can be printed easily using the debugger.
To see the content of the packet, type print pkt:
debug> print pkt #1 userdata tcp { ... checksum : 417 res : 0 next_dissector : "tcp-connection" srcport : 37542 payload : userdata tcp_payload ip : userdata ipv4 { ... } flags : userdata tcp_flags { ... } ack_seq : 0 seq : 3827050607 dstport : 80 hdr_len : 40 }
You can notice that there is no field called destport. The correct name for the field is dstport. Once this typo is corrected, the script will run properly
Press CTRL-C to quit or type help to get the list of available commands.
Using rule groups¶
Haka can handle multiple rules as a group.
Rule groups have three functions:
- The init function is called before any rule from the group is applied
- The continue function is called between each rule of the group and can decide to stop processing the group at any point.
- The fini function is called after all rules have been run. It is not called if continue has forced a cancelation mid-group.
The following example uses the concept of group to implement a simple filter that only accepts connections on port 80 or port 22.
------------------------------------
-- Loading dissectors
------------------------------------
require('protocol/ipv4')
require('protocol/tcp')
------------------------------------
-- Security group
------------------------------------
-- This group implements an logical 'or' between rules
-- Each rule returns a boolean,
--
-- if any rule return 'true' we leave the rule group
-- if all rules return 'false' the packet is droped
-- create a new group with no rules in it
-- The 'my_group' lua variable will be used to add rules to the group
local my_group = haka.rule_group{
name = "my_group",
init = function (self, pkt)
haka.log.debug("filter", "Entering packet filtering rules : %d --> %d",
pkt.tcp.srcport, pkt.tcp.dstport)
end,
-- rules will return a boolean
-- if the boolean is false, we continue
-- if the boolean is true, we finish the group immediately
continue = function (self, pkt, ret)
return not ret
end,
-- if we reach the fini function, all rules have returned false
-- Nobody has accepted the packet => we drop it.
fini = function (self, pkt)
haka.alert{
description = "Packet dropped : drop by default",
targets = { haka.alert.service("tcp", pkt.tcp.dstport) }
}
pkt:drop()
end
}
------------------------------------
-- Security rules for my_group
------------------------------------
-- return true if connection is on port 80
my_group:rule{
hooks = { 'tcp-connection-new' },
eval = function (self, pkt)
if pkt.tcp.dstport == 80 then
haka.log("Filter", "Authorizing traffic on port 80")
return true
end
end
}
-- return true if connection is on port 22
my_group:rule{
hooks = { 'tcp-connection-new' },
eval = function (self, pkt)
if pkt.tcp.dstport == 22 then
haka.log("Filter", "Authorizing traffic on port 22")
return true
end
end
}
5.2.3. Advanced TCP/IP Filtering¶
Filtering with NFQueue¶
All the examples so far have used hakapcap to test some recorded packets.
Haka can also use nfqueue to capture packets from a live interface. The following examples will illustrate how to do that.
When configured to use nfqueue, haka will hook itself up to the raw nfqueue table in order to inspect, modify, create and delete packets in real time.
The rest of this tutorial assumes that the haka package is installed on a host which has a network interface named eth0.
The configuration file for the daemon is given below:
[general]
# Select the haka configuration file to use
configuration = "ipfilter.lua"
# Optionally select the number of thread to use.
#thread = 4
# Pass-through mode
# If yes, haka will only inspect packet
# If no, it means that haka can also modify and create packet
pass-through = no
[packet]
# Select the capture model, nfqueue or pcap
module = "packet/nfqueue"
# Select the interfaces to listen to
interfaces = "eth0"
# Select packet dumping for nfqueue
#dump = yes
#dump_input = "/tmp/input.pcap"
#dump_output = "/tmp/output.pcap"
#dump_drop = "/tmp/drop.pcap"
[log]
# Select the log module
module = "log/syslog"
[alert]
# Select the alert module
module = "alert/syslog"
In order to be able to capture packets, the haka daemon needs to be run as root. The –no-daemon` option will prevent haka from detaching from the command line and will force haka to send its outputs to stdout instead of syslog.
# cd <haka_install_path>/share/haka/sample/filter/
# haka -c daemon.conf --no-daemon
The lua file used here is the one from the first tutorial. This filter will refuse all packets coming from 192.168.10.10
HTTP filtering¶
Haka comes with an HTTP parser. Using that module it is easy to filter packets using specific fields from HTTP headers.
------------------------------------
-- Loading dissectors
------------------------------------
require('protocol/ipv4')
require('protocol/tcp')
local http = require('protocol/http')
------------------------------------
-- Only allow connections on port 80, close all other connections
-- Forward all accepted connections to the HTTP dissector
------------------------------------
haka.rule{
hooks = { 'tcp-connection-new' },
eval = function (self, pkt)
if pkt.tcp.dstport == 80 then
pkt.next_dissector = 'http'
else
haka.log("Filter", "Dropping TCP connection: tcp dstpport=%d",
pkt.tcp.dstport)
pkt:reset() -- Send a TCP RST packet to both sides: client and server
end
end
}
-- Only allow connections from from the 'Mozilla' user agent.
haka.rule{
hooks = { 'http-request' },
eval = function (self, http)
if string.match(http.request.headers['User-Agent'], 'Mozilla') then
haka.log("Filter", "User-Agent Mozilla detected")
else
haka.log("Filter", "Only Mozilla User-Agent authorized")
http:drop()
end
end
}
To test this filter you will need to modify dameon.conf to tell haka to use httpfilter.lua:
[general]
# Select the haka configuration file to use.
configuration = "httpfilter.lua"
It is now possible to start the daemon using this new configuration.
# cd <haka_install_path>/share/haka/sample/filter/
# haka -c dameon.conf --no-daemon
Modifying HTTP responses¶
Haka can also be used to modify packets as they pass through the system. The following example will redirect traffic based on HTTP headers.
More specifically, it will detect outdated firefox versions and will redirect all traffic from these browsers to the firefox update site.
------------------------------------
-- Loading dissectors
------------------------------------
require('protocol/ipv4')
require('protocol/tcp')
local http = require('protocol/http')
local last_firefox_version = 24.0
local firefox_web_site = 'http://www.mozilla.org'
-------------------------------------
-- Domain whitelist,
-- all traffic to these domains will be unmodified
-------------------------------------
local update_domains = {
'mozilla.org',
'mozilla.net',
-- You can extend this list with other domains
}
-------------------------------------
-- Forward all traffic on port 80 to the HTTP dissector
-------------------------------------
haka.rule{
hooks = { 'tcp-connection-new' },
eval = function (self, pkt)
if pkt.tcp.dstport == 80 then
pkt.next_dissector = 'http'
end
end
}
-------------------------------------
-- Rule group implementing a logical 'or'
-------------------------------------
safe_update = haka.rule_group{
name = 'safe_update',
-- Initialization
init = function (self, http)
local host = http.request.headers['Host']
if host then
haka.log("Filter", "Domain requested: %s", host)
end
end,
-- Continue is called after evaluation of each security rule the ret
-- parameter decide whether to read next rule or skip the evaluation of
-- the other rules in the group
continue = function (self, http, ret)
return not ret
end
}
-- Traffic to all websites in the whitelist is unconditionnally allowed
safe_update:rule{
hooks = { 'http-response' },
eval = function (self, http)
local host = http.request.headers['Host'] or ''
for _, dom in ipairs(update_domains) do
if string.find(host, dom) then
haka.log("Filter", "Update domain: go for it")
return true
end
end
end
}
-- If the User-Agent contains firefox and the version is outdated
-- the redirect the traffic to firefox_web_site
safe_update:rule{
hooks = { 'http-response' },
eval = function (self, http)
-- Uncomment the following line to see the the content of the request
-- http.request:dump()
local UA = http.request.headers["User-Agent"] or "No User-Agent header"
haka.log("Filter", "UA detected: %s", UA)
local FF_UA = (string.find(UA, "Firefox/"))
if FF_UA then -- Firefox was detected
local version = tonumber(string.sub(UA, FF_UA+8))
if not version or version < last_firefox_version then
haka.alert{
description= "Firefox is outdated, please upgrade",
severity= 'medium'
}
-- redirect browser to a safe place where updates will be made
http.response.status = "307"
http.response.reason = "Moved Temporarily"
http.response.headers["Content-Length"] = "0"
http.response.headers["Location"] = firefox_web_site
http.response.headers["Server"] = "A patchy server"
http.response.headers["Connection"] = "Close"
http.response.headers["Proxy-Connection"] = "Close"
-- dump the response for illustrative purpose
http.response:dump()
end
else
haka.log("Filter", "Unknown or missing User-Agent")
end
end
}
Modify the dameon.conf in order to load the httpmodif.lua configuration file:
[general]
# Select the haka configuration file to use
configuration = "httpmodif.lua"
And start it.
# cd <haka_install_path>/share/haka/sample/filter/
# haka -c dameon.conf --no-daemon