There are times you need to connect to ‘dirty’ networks such as public WiFi hotspots. Hopefully you’re ensuring that sensitive information is encapsulated in transport layer security enabled protocols such as SSL, because anyone on the same link (in the case of WiFi, that’s the air surrounding you. A vacuum will do, too, but that’s less common) can listen in on the traffic you’re sending. With SSL encapsulation such as HTTP over SSL (https://), your traffic can still be read — but for those who do it’s an extremely boring read because they don’t know the session key, only you and the other endpoint do. Hopefully.
One particularly nasty thing that can happen to you is when your machine is subverted into using the attacker’s machine as the router. That is known as ARP poison routing. The attacker can proceed to not only read the traffic coming from your machine (which, on a shared medium, could be done anyway), or read the traffic going into your machine (again: on a shared medium, that could be done anyway), but the attacker can now also modify the traffic between you and the rest of the non-local network, e.g., the internet, in both directions. And that’s when he can really go to town with your traffic. Injecting a javascript keylogger into all the webpages you visit. ‘Sidejacking‘ your sessions, so he does not even need to know your passwords, just your session cookies — which you happen to transmit with every page request.
All possible unless you use transport layer security, which is tamper-proof once properly set up. Once properly set up. But setting up can have problems of itself — there are ways of preventing you ever going from HTTP to HTTPS. If you know a thing or two about HTTP and SSL you’ll be delighted to learn about Moxie’s very evil but very clever ways of doing so.
Anyway, some level of security can be achieved if you tell your machine to ignore any messages sent to you from the other machines on the local network. That includes messages that will make your machine believe that the router has suddenly changed its physical address — which is quite unlikely to happen, but those messages are exactly the type of message an impersonator would send you. Of course we’d need to whitelist the routers of the network, otherwise we can’t get traffic out of it and onto other networks. DNS resolvers will need whitelisting too, unless you’re running one on your own machine (probably not).
Not openly announcing your presence may also be something you wish for. If you have ever been on a network with a Mac user you have probably seen them popping up in your Zeroconf service browser as “Firstname Lastname’s iSomething”. Let’s cut down on that kind of promiscuity, too. But you should understand now that you can not actually hide unless you turn off your WiFi. Shared medium, remember?
I prepared a simple script to accomplish the above. I’ve used ip from the iproute2 package instead of sticking to old-school route, ifconfig, arp & co. And I must say ip neigh flush nud stale has a poetic ring to it, wouldn’t you agree?
Take note: this will only protect you from some kind of attacks, and only partially. An attacker has a window of opportunity between your machine getting assigned a DHCP lease and you running this script, for instance. Or maybe the access point is rigged. Actually all protection other than end-to-end encryption combined with mutual authentication is pretty useless on shared networks ;-)
Here’s the script. Linux-only. If you want to use it, get the latest version from my public repository.
#!/bin/bash
# arpshield 0.2
# Protects against ARP poisoning and cloaks your machine for all
# local link devices but the router(s) and the DNS server(s).
# Whitelisting DHCP servers also works if you use the dhcpcd program
# to obtain DHCP leases.
# This program is of no help if your setup is already poisoned.
# Have a look at ArpON (http://arpon.sourceforge.net/manpage.html) if
# you need more extensive protection.
#
# Needs 'ip', 'awk', 'sed', 'arptables', and 'arping' and expects
# them on $PATH. Needs appropriate privileges (so use sudo).
# Takes a network interface as an argument. The network interface
# should be up and configured. If no argument is given, clear all
# rules. Obviously you should do that before connecting to a new
# network.
#
# Copyright 2010 Wicher Minnaard (wicher@gavagai.eu)
# License: Creative Commons Attribution-Share Alike 3.0
# Do you use dhcpcd for aquiring DHCP leases? And is it running?
dhcpcdLEASEFILE="/var/lib/dhcpcd-${1}.info"
dhcpcdPIDFILE="/var/run/dhcpcd-${1}.pid"
test -f ${dhcpcdLEASEFILE} && test -f ${dhcpcdPIDFILE} && source ${dhcpcdLEASEFILE}
# In case you lack the luxury of dhcpcd, where is your resolv.conf?
RESOLV="/etc/resolv.conf"
# No user-servicable parts below this line.
DEV="${1}"
# I know, I know. But if your routing table contains 0.333.456.789 you have bigger problems ;-)
IPREGEX="\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}"
# Register
MACreg=""
# If not run as root, bail
[ "$(id -u)" != "0" ] && echo "You need root privileges to modify networking parameters. Exiting." 1>&2 && exit 2
getmac(){
# sets MAC register by IP. Sets to nil, if the MAC is not on the local link.
getMAC=$(ip neigh show ${1} | awk '{print $5}')
if [ -z "${getMAC}" ]; then
arping -c1 -I ${DEV} ${1} > /dev/null 2>&1
getMAC=$(ip neigh show ${1} | awk '{print $5}')
fi
MACreg=${getMAC}
}
allow(){
# Whitelists traffic to and from particular IP+MAC pairings and
# adds them to static ARP.
IP=${1}
MAC=${2}
if [[ -n "${IP}" && -n "${MAC}" ]]; then
arptables -A INPUT -s ${IP} --source-mac ${MAC} -j ACCEPT
arptables -A OUTPUT -d ${IP} --destination-mac ${MAC} -j ACCEPT
ip neigh replace ${IP} lladdr ${MAC} nud permanent dev ${DEV}
fi
}
if [ -n "${DEV}" ]; then
# whitelist the routers
test -z ${GATEWAYS} && GATEWAYS=$(ip route show dev ${DEV}| sed -n "s:.* via \(${IPREGEX}\).*:\1:p")
for GWIP in ${GATEWAYS}; do
MACreg=""
getmac ${GWIP}
allow ${GWIP} ${MACreg}
done
# whitelist the DNS servers
test -z ${DNSSERVERS} && DNSSERVERS=$(sed -n "s:^nameserver \(${IPREGEX}\):\1:p" ${RESOLV})
for DNS in ${DNSSERVERS}; do
MACreg=""
getmac ${DNS}
allow ${DNS} ${MACreg}
done
# if using dhcpcd, we can whitelist the DHCP server too
test -n ${DHCPSID} && getmac ${DHCPSID} && allow ${DHCPSID} ${MACreg}
# set default policy to DROP
arptables -P INPUT DROP
arptables -P OUTPUT DROP
# clear out non-hardcoded ARP cache entries
ip neigh flush nud reachable
ip neigh flush nud stale
else
# No argument given, so clean up.
arptables -F
arptables -P INPUT ACCEPT
arptables -P OUTPUT ACCEPT
ip neigh flush nud permanent
fi
Tags: arp spoofing, en_GB, security, wifi —
Just finished up a 0.1 version of a LIRC (Linux Infrared Control) plugin for the Exaile media player. Now you can use your remote with Exaile efficiently. The plugin is in the public repository and is called Lircaile.
I haven’t touched Python much as of yet, but I’m pleased with it: it appears to be a consistent language. Well, here’s my 0.1 effort. I desperately wanted to have some fun with introspection, but I have the feeling the nested exception logic is a bit… unusual.
# A LIRC plugin for Exaile. Depends on pylirc from http://sourceforge.net/projects/pylirc/
# Copyright (C) 2009 Wicher Minnaard, http://smorgasbord.gavagai.nl / wicher@gavagai.eu
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
from xl import playlist, player, event
import pylirc, logging
LIRCAILE = None
def enable(exaile):
if (exaile.loading):
event.add_callback(_enable, 'exaile_loaded')
else:
_enable(None, exaile, None)
def _enable(eventname, exaile, nothing):
global LIRCAILE
LIRCAILE = Lircaile(exaile)
def disable(exaile):
pylirc.exit()
class Lircaile(object):
def polLirc(self):
"""Pops all queued signals off of the LIRC queue and hands them to
handleCode() for further processing."""
gopoll = True
while(gopoll):
code = (pylirc.nextcode())
if (code):
comval = code[0].split()
if (len(comval) == 1):
self.handleCode(comval[0])
else:
self.handleCode(comval[0], comval[1])
else:
# We're done, the queue is empty.
gopoll = False
return True
def __init__(self, exaile):
self.exaile = exaile
self.logger = logging.getLogger(__name__)
socket = pylirc.init('lircaile')
event.EventTimer(0.05, self.polLirc)
def handleCode(self, command, *arg):
"""Takes LIRC signals and uses introspection to try to find appropriate
exaile functions to call based on the name of the signal. """
if (command == 'chvol'):
self.exaile.player.set_volume(self.exaile.player.get_volume() + float(arg[0]))
else:
func = None
# Look for a matching playlist function
try:
func = getattr(self.exaile.queue, command)
except AttributeError:
# No? Then look for a matching player function
try:
func = getattr(self.exaile.player, command)
except AttributeError:
# No? Then we're out of options
self.logger.warning('No function to handle "'+ command +'" LIRC event')
if callable(func):
func()
Tags: en_GB, exaile, lirc, lircaile, remote —
The other day I compiled Firefox 3.5-beta4, and, apart from many improvements, I noticed that I am now affected by the infamous ‘hiccups’. Firefox will stall for seconds at a time on my poor netbook. Details on how this relates to the many fsync() calls made by the persistance layer (SQLite) can be found all over the net.
But I don’t want Firefox to stall and I don’t want to keep my harddisk awake with all these writes when I’m on battery power.
Luckily, both these problems go away if you put your Firefox profile on a ramdisk. There are numerous guides out there that save you from working out the details; I used this one from the Gentoo forums.
The guide uses cron to sync the (presumably) modified contents of the ramdisk – bookmarks, cookies, whatever – back to permanent storage (harddisk). You and I both know that while it’s often convenient to use cron, it’s not always the Right Way of Doing Things®. Why sync if nothing’s changed? I wrote a script that employs the inotify system to do the syncing only when necessary. You’ll find it in the forum thread I linked to earlier, but I post it here “ter lering ende vermaeck”. Depending on your browser there might be a vertical scrollbar at the bottom which lets you read up to the EOL’s ;-)
You can find the latest version at my public repo.
#!/bin/bash
# Packfox, a tool to facilitate running Firefox with its profile stored
# in RAM (tmpfs). Copyright 2008-2009 Wicher Minnaard, wicher@gavagai.eu .
# Distributed under the WTFPL, http://smormedia.gavagai.nl/dist/packfox/COPYING
# Latest version available at http://smormedia.gavagai.nl/dist/packfox/
# Change this to match your profile
PROFILE=$(hostname)
PFDIR="${HOME}/.mozilla/firefox"
# Tar every .. seconds (regardless of changes)
TMOUT="1800"
# But not more often than every .. seconds (regardless of changes)
TMMIN="60"
# Regex for which files not to act on when they're changed.
# Use inotifywait -m -e modify -e move -e create -e delete --exclude '(/Cache/)' -r your_profile_dir
# and watch the output while browsing to determine which regex will be right for YOU.
IEXCL="(.sqlite-journal$)|(\-log.txt$)|(cookies.sqlite$)|(sessionstore\-[0-9].js$)|(/weave/)|(/Cache/)"
# Have you read everything and have you made the necessary adjustments? Then remove the line below ;-)
echo "I should read the README and adjust the script variables before running this." && exit 2
# No user servicable parts below this line.
TGT="${PFDIR}/${PROFILE}"
# Global vars
INOTYPID=""
SLEEPPID=""
PACKLOCK=""
# Cleanup function
terminate(){
# If we are the daemon and we get SIGINTed/SIGTERMed, kill our children
# and if not already packing, do one last round of packing.
if [ "$(basename ${0})" == "packfox-daemon" ]
then
if [ -n "${INOTYPID}" ]; then kill ${INOTYPID}; fi
if [ -n "${SLEEPPID}" ]; then kill ${SLEEPPID}; fi
if [ -z "${PACKLOCK}" ];then packup; fi
exit
fi
}
# For cleaning up
trap terminate SIGINT SIGTERM
# Suicide with goodbye note. If gxmessage is installed, use that.
seppuku(){
echo "${1}" 1>&2
which gxmessage > /dev/null 2>&1 && gxmessage -nofocus -title "$(basename ${0})" "${1}" || xmessage "${1}"
exit 2
}
# Checks and setup
test -d "${PFDIR}" || seppuku "Profile dir doesn't exist"
if [ -z "$(mount -t tmpfs | grep -F "${TGT}" )" ]
then
mount "${PFDIR}/${PROFILE}" || seppuku "Mounting of profile's tmpfs failed. Check /etc/fstab and the output of 'dmesg'."
fi
test -f "${TGT}/.unpacked" || tar -xpf "${PFDIR}/${PROFILE}.packed.tar" -C "${PFDIR}" \
&& touch "${TGT}/.unpacked" || seppuku "Error unpacking the profile tarball. You might want to use the backup tarball located in ${PFDIR}."
# This tars up the profile
packup(){
PACKLOCK="locked"
cd "${PFDIR}"
tar --exclude '.unpacked' -cpf "${PFDIR}/${PROFILE}.packed.tmp.tar" "${PROFILE}"
mv "${PFDIR}/${PROFILE}.packed.tar" "${PFDIR}/${PROFILE}.packed.tar.old"
mv "${PFDIR}/${PROFILE}.packed.tmp.tar" "${PFDIR}/${PROFILE}.packed.tar"
PACKLOCK=""
}
# No daemon, just packing
if [ "$(basename ${0})" == "packfox" ]; then packup; fi
# The daemon loop
if [ "$(basename ${0})" == "packfox-daemon" ]
then
which inotifywait >/dev/null 2>&1 || seppuku " You'll need the 'inotify-tools' package for this script. Get it at http://inotify-tools.sourceforge.net or from your distro's repos".
while true
do inotifywait -q -q -t ${TMOUT} -e modify -e move -e create -e delete --exclude "${IEXCL}" \
-r "${PFDIR}/${PROFILE}" &
INOTYPID=${!}
wait ${INOTYPID}; INOTIFYPID=""
packup
sleep ${TMMIN} &
SLEEPPID=${!}
wait ${SLEEPPID}; SLEEPPID=""
done
exit
fi
Tags: code, en_GB, firefox, inotify, tmpfs —
[Update @20090607: De code moet regelmatig bijgewerkt worden om veranderingen op player.omroep.nl te reflecteren. De laatste versie kun je altijd downloaden van de officiële pagina, daar kun je ook pingen (via een issue) als het script aangepast moet worden aan een nieuwe versie van player.omroep.nl (m.a.w., als het niet meer werkt).]
Ik heb een Greasemonkey-scriptje geschreven dat op player.omroep.nl een dialoogje geeft waarvan je de inhoud direct in een shell (Bash-shell, wellicht ook de standaard Bourne-shell) kan pasten. Je krijgt iets dergelijks voorgeschoteld:
export TIEFDIR="${PWD}"; export TEMPDIR=$(mktemp -d); cd ${TEMPDIR} && mplayer -dumpstream -user-agent 'Windows-Media-Player/11.0.6001.7000' 'http://cgi.omroep.nl/legacy/player?/ceres/vara/rest/2009/VARA_101192917/bb.20090424.asf' && mv stream.dump ${TIEFDIR}/'De Wereld Draait Door - 25-04-2009.wmv' && cd - && rmdir ${TEMPDIR}
Dat maakt het downloaden van videomateriaal van uitzendinggemist.nl een stukje makkelijker. Natuurlijk moet je wel Firefox + Greasemonkey-extensie, een shell en MPlayer hebben, maar wie heeft dat nou niet ;-)
Hier kun je de source lezen en als je Greasemonkey hebt geïnstalleerd kun je hier op “Install” klikken.
Surf vervolgens naar uitzendinggemist.nl en fair-use er op los.
En hier natuurlijk het leukste stuk van de source. Ik schrijf haast nooit Javascript dus opmerkingen/aanvullingen zijn zeer welkom.
var playert = document.getElementById("player");
// Listener for node insertions (generated by omroep.nl's javascript)
if (playert) playert.addEventListener("DOMNodeInserted", genAlert, true);
function genAlert()
{
var playert = document.getElementById("player");
var embedelement = document.getElementById('MediaPlayer');
if (embedelement)
//The embed element got inserted - now we have all required parameters for dumping
{
var pastetext;
var source = embedelement.getAttribute("src").replace("'","\\'"); //These URL's shouldn't have "'" in them but we escape them just to be sure.
//Create a copy of the title & date info so we can use DOM functions to separate the two
var worktitle = document.importNode(playert.getElementsByTagName("h3")[0],true);
var worktitledatespan = worktitle.getElementsByTagName("span")[0];
var datum = worktitledatespan.innerHTML.replace("'","\\'"); //Escape ' to make shell-safe
if (!datum) datum = 'nodate';
worktitle.removeChild(worktitledatespan);
var titel = worktitle.innerHTML.replace("'","\\'"); //Escape ' to make shell-safe
if (titel && source)
{
playert.removeEventListener("DOMNodeInserted", genAlert, true);
pastetext = 'export TIEFDIR="${PWD}"; export TEMPDIR=$(mktemp -d); cd ${TEMPDIR} && '
pastetext+= "mplayer -dumpstream -user-agent 'Windows-Media-Player/11.0.6001.7000' '"+source+"' && ";
pastetext+= 'mv stream.dump ${TIEFDIR}/\''+titel+' - '+datum+'.wmv\' && cd - && rmdir ${TEMPDIR}';
alert(pastetext);
}
}
}