6.2. Filter¶
6.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.
6.2.2. Basic TCP/IP filtering¶
Basic IP filtering¶
The directory <haka_install_path>/share/haka/sample/filter/ contains all the example used in this tutorial.
A basic filter script on IP fields is provided as ipfilter.lua:
------------------------------------
-- Loading dissectors
------------------------------------
local ipv4 = require('protocol/ipv4')
-- drop all packets originating from
-- blacklisted address 192.168.10.10
haka.rule{
-- This rule is applied on each ip incoming packet
hook = ipv4.events.receive_packet,
eval = function (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
-- Raise 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 ran with a pcap file provided in the sample directory.
$ cd <haka_install_path>/share/haka/sample/filter/
$ hakapcap ipfilter.lua ipfilter.pcap
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 ipfilter.lua trace.pcap -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 Haka script file. A script containing a lua error is provided as tcpfilter.lua.
This script will authorize only packets from/to port 80.
------------------------------------
-- Loading dissectors
------------------------------------
require('protocol/ipv4')
local tcp = require('protocol/tcp')
-- Allow only packets to/from port 80
haka.rule{
hook = tcp.events.receive_packet,
eval = function (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("Authorizing trafic on port 80")
else
haka.log("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 tcfilter.lua tcpfilter.pcap
When running this script, 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 --debug-lua at the end of the command line:
$ cd <haka_install_path>/share/haka/sample/filter/
$ hakapcap tcpfilter.lua tcpfilter.pcap --debug-lua
When a Lua error code occurs, the debugger breaks and outputs the error and a backtrace.
entering debugger: unknown field 'destport' thread: 0 Backtrace =>0 [C]: in function '(null)' #1 [C]: in function '(null)' #2 [C]: in function '__index' #3 tcpfilter.lua:18: in function 'signal' #4 /usr/share/haka/core/events.bc:0: in the main chunk #5 /usr/share/haka/core/events.bc:0: in the main chunk #6 /usr/share/haka/core/context.bc:0: in the main chunk #7 [string "tcp"]:14: in function 'receive' #8 /usr/share/haka/core/dissector.bc:0: in the main chunk #9 [C]: in function 'xpcall' ...
The general syntax of the debugger is close to the syntax of gdb.
Here we are interested in the third frame 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 14: hook = haka.event('tcp', 'receive_packet'), 15: eval = function (pkt) 16: -- The next line will generate a lua error: 17: -- there is no 'destport' field. replace 'destport' by 'dstport' 18=> if pkt.destport == 80 or pkt.srcport == 80 then 19: haka.log("Filter", "Authorizing trafic on port 80") 20: else 21: haka.log("Filter", "Trafic not authorized on port %d", pkt.dstport) 22: pkt:drop() 23: end
We now see that Lua is complaining about an unknown 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 { ack_seq : 0 checksum : 417 dstport : 80 flags : userdata tcp_flags { ack : false all : 2 cwr : false ecn : false fin : false psh : false rst : false syn : true urg : false } hdr_len : 40 ip : userdata ipv4 { ... } ... srcport : 37542 urgent_pointer : 0 window_size : 14600 }
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.
A rule group has 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 final function is called after all rules have been ran. 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 and port 22.
------------------------------------
-- Loading dissectors
------------------------------------
require('protocol/ipv4')
require('protocol/tcp')
local tcp_connection = require('protocol/tcp_connection')
------------------------------------
-- Security rule group
------------------------------------
-- 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",
hook = tcp_connection.events.new_connection,
init = function (flow, pkt)
haka.log.debug("Entering packet filtering rules : %d --> %d",
pkt.srcport, pkt.dstport)
end,
-- rules will return a boolean
-- if the boolean is false, we continue
-- if the boolean is true, we leave the group immediately
continue = function (ret, flow, pkt)
return not ret
end,
-- if we reach the final function, all rules have returned false
-- Nobody has accepted the packet => we drop it.
final = function (flow, pkt)
haka.alert{
description = "Packet dropped : drop by default",
targets = { haka.alert.service("tcp", pkt.dstport) }
}
pkt:drop()
end
}
------------------------------------
-- Security rules for my_group
------------------------------------
my_group:rule{
eval = function (flow, pkt)
if pkt.dstport == 80 then
haka.log("Authorizing traffic on port 80")
return true
end
end
}
my_group:rule{
eval = function (flow, pkt)
if pkt.dstport == 22 then
haka.log("Authorizing traffic on port 22")
return true
end
end
}
6.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"
[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 Haka script file used here is the one from the first tutorial. This filter will discard 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 tcp_connection = require('protocol/tcp_connection')
local http = require('protocol/http')
-- Allow only connections on port 80, close all other connections
-- Forward all accepted connections to the HTTP dissector
haka.rule{
hook = tcp_connection.events.new_connection,
eval = function (flow, pkt)
if pkt.dstport == 80 then
http.dissect(flow)
else
haka.log("Dropping TCP connection: tcp dstpport=%d",
pkt.dstport)
pkt:reset() -- Send a TCP RST packet to both sides: client and server
end
end
}
-- Allow only connections from the 'Mozilla' user agent.
haka.rule{
hook = http.events.request,
eval = function (http, request)
if string.match(request.headers['User-Agent'], 'Mozilla') then
haka.log("User-Agent Mozilla detected")
else
haka.log("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 alter packet content 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
http.install_tcp_rule(80)
-------------------------------------
-- Rule group definition
-------------------------------------
safe_update = haka.rule_group{
hook = http.events.response,
-- Initialization
init = function (http, response)
local host = http.request.headers['Host']
if host then
haka.log("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 (ret)
return not ret
end
}
-- Traffic to all websites in the whitelist is unconditionally allowed
safe_update:rule{
eval = function (http, response)
local host = http.request.headers['Host'] or ''
for _, dom in ipairs(update_domains) do
if string.find(host, dom) then
haka.log("Update domain: go for it")
return true
end
end
end
}
-- If the User-Agent contains firefox and the version is outdated,
-- then redirect the traffic to firefox_web_site
safe_update:rule{
eval = function (http, response)
-- Uncomment the following line to see the the content of the request
-- debug.pprint(request, nil, nil, { debug.hide_underscore, debug.hide_function })
local UA = http.request.headers["User-Agent"] or "No User-Agent header"
haka.log("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
response.status = "307"
response.reason = "Moved Temporarily"
response.headers["Content-Length"] = "0"
response.headers["Location"] = firefox_web_site
response.headers["Server"] = "A patchy server"
response.headers["Connection"] = "Close"
response.headers["Proxy-Connection"] = "Close"
debug.pprint(response, nil, nil, { debug.hide_underscore, debug.hide_function })
end
else
haka.log("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