From 3dd84a10f402dde57598d0509c167712e81d74e1 Mon Sep 17 00:00:00 2001 From: Curt Hash Date: Fri, 22 Jan 2010 16:57:45 -0700 Subject: [PATCH] In order to make it harder for clients to allow connections from pollster while blocking everyone else, pollster will now change IP addresses every minute. The pollster box will need a secondary interface (default: eth1) in order to do this. Before each poll, the mac address of the interface is changed to a random address with a particular vendor code (default: 00:01:c0 (FitPC)) and a new address is requested via DHCP. All connections made from pollster are bound to the new address, forcing them out through the secondary interface. This caused some problems, since the poll function for cat.cgi previously relied on urllib, which did not support binding. I ended up having to subclass http.client.HTTPConnection and override __init__() and connect(). These changes necessitated two new global configuration variables: (1) POLL_MAC_VENDOR : the vendor code to use in the random MAC (2) POLL_IFACE : the secondary interface to use I also fixed a bug where an empty string would be interpreted as a valid team name. --- ctf/config.py | 2 + pollster/get_ip.sh | 4 ++ pollster/pollster.py | 93 ++++++++++++++++++++++++++++++++++---------- 3 files changed, 79 insertions(+), 20 deletions(-) create mode 100755 pollster/get_ip.sh diff --git a/ctf/config.py b/ctf/config.py index 1bdbe96..bc52dd8 100755 --- a/ctf/config.py +++ b/ctf/config.py @@ -46,6 +46,8 @@ else: 'poll_timeout': 0.5, 'heartbeat_dir': '/var/lib/pollster', 'results': '/var/lib/pollster/status.html', + 'poll_iface' : 'eth1', + 'poll_mac_vendor' : '00:01:c0' }, 'puzzler': { diff --git a/pollster/get_ip.sh b/pollster/get_ip.sh new file mode 100755 index 0000000..1a9ac86 --- /dev/null +++ b/pollster/get_ip.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +ifconfig $1 | grep "inet addr" | awk '{print $2}' | awk -F: '{print $2}' + diff --git a/pollster/pollster.py b/pollster/pollster.py index c19fd6a..fd535e2 100755 --- a/pollster/pollster.py +++ b/pollster/pollster.py @@ -7,19 +7,57 @@ import sys import time import socket import traceback -import urllib.request +import subprocess +import random +import http.client from ctf import config from ctf import pointscli -DEBUG = False -POLL_INTERVAL = config.get('pollster', 'poll_interval') -IP_DIR = config.get('pollster', 'heartbeat_dir') -REPORT_PATH = config.get('pollster', 'results') -SOCK_TIMEOUT = config.get('pollster', 'poll_timeout') +DEBUG = False +POLL_INTERVAL = config.get('pollster', 'poll_interval') +IP_DIR = config.get('pollster', 'heartbeat_dir') +REPORT_PATH = config.get('pollster', 'results') +SOCK_TIMEOUT = config.get('pollster', 'poll_timeout') +POLL_IFACE = config.get('pollster', 'poll_iface') +POLL_MAC_VENDOR = config.get('pollster', 'poll_mac_vendor') +class BoundHTTPConnection(http.client.HTTPConnection): + ''' http.client.HTTPConnection doesn't support binding to a particular + address, which is something we need. ''' + + def __init__(self, bindip, host, port=None, strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT): + http.client.HTTPConnection.__init__(self, host, port, strict, timeout) + self.bindip = bindip + + def connect(self): + ''' Connect to the host and port specified in __init__, but + also bind first. ''' + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.bind((self.bindip, 0)) + self.sock.settimeout(self.timeout) + self.sock.connect((self.host, self.port)) -def socket_poll(ip, port, msg, prot, max_recv=1): + if self._tunnel_host: + self._tunnel() + +def random_mac(): + ''' Set a random mac on the poll interface. ''' + mac = ':'.join([POLL_MAC_VENDOR] + ['%02x' % random.randint(0,255) for i in range(3)]) + retcode = subprocess.call(('ifconfig', POLL_IFACE, 'hw', 'ether', mac)) + +def dhcp_request(): + ''' Request a new IP on the poll interface. ''' + retcode = subprocess.call(('dhclient', POLL_IFACE)) + +def get_ip(): + ''' Return the IP of the poll interface. ''' + path = os.path.join(os.getcwd(), 'get_ip.sh') + p = subprocess.Popen((path, POLL_IFACE), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (out, err) = p.communicate() + return out.strip(b'\r\n').decode('utf-8') + +def socket_poll(srcip, ip, port, msg, prot, max_recv=1): ''' Connect via socket to the specified : using the specified , send the specified and return the response or None if something went wrong. specifies @@ -33,6 +71,7 @@ def socket_poll(ip, port, msg, prot, max_recv=1): traceback.print_exc() return None + sock.bind((srcip, 0)) sock.settimeout(SOCK_TIMEOUT) # connect @@ -80,34 +119,41 @@ def socket_poll(ip, port, msg, prot, max_recv=1): # Each function should take an IP address and return a team name or None # if (a) the service is not up, (b) it doesn't return a valid team name. -def poll_fingerd(ip): +def poll_fingerd(srcip, ip): ''' Poll the fingerd service. Returns None or a team name. ''' - resp = socket_poll(ip, 79, b'flag\n', socket.SOCK_STREAM) + resp = socket_poll(srcip, ip, 79, b'flag\n', socket.SOCK_STREAM) if resp is None: return None return resp.strip(b'\r\n') -def poll_noted(ip): +def poll_noted(srcip, ip): ''' Poll the noted service. Returns None or a team name. ''' - resp = socket_poll(ip, 4000, b'rflag\n', socket.SOCK_STREAM) + resp = socket_poll(srcip, ip, 4000, b'rflag\n', socket.SOCK_STREAM) if resp is None: return None return resp.strip(b'\r\n') -def poll_catcgi(ip): +def poll_catcgi(srcip, ip): ''' Poll the cat.cgi web service. Returns None or a team name. ''' try: - url = urllib.request.urlopen('http://%s/cat.cgi/flag' % ip, timeout=SOCK_TIMEOUT) + conn = BoundHTTPConnection(srcip, ip, timeout=SOCK_TIMEOUT) + conn.request('GET', '/cat.cgi/flag') except: return None - - resp = url.read() - return resp.strip(b'\r\n') -def poll_tftpd(ip): + resp = conn.getresponse() + if resp.status != 200: + conn.close() + return None + + data = resp.read() + conn.close() + return data.strip(b'\r\n') + +def poll_tftpd(srcip, ip): ''' Poll the tftp service. Returns None or a team name. ''' - resp = socket_poll(ip, 69, b'\x00\x01' + b'flag' + b'\x00' + b'octet' + b'\x00', socket.SOCK_DGRAM) + resp = socket_poll(srcip, ip, 69, b'\x00\x01' + b'flag' + b'\x00' + b'octet' + b'\x00', socket.SOCK_DGRAM) if resp is None: return None @@ -117,7 +163,7 @@ def poll_tftpd(ip): resp = resp.split(b'\n')[0] # ack - _ = socket_poll(ip, 69, b'\x00\x04' + resp[2:4], socket.SOCK_DGRAM, 0) + _ = socket_poll(srcip, ip, 69, b'\x00\x04' + resp[2:4], socket.SOCK_DGRAM, 0) return resp[4:].strip(b'\r\n') @@ -134,6 +180,11 @@ poll_no = 0 # loop forever while True: + random_mac() + dhcp_request() + + srcip = get_ip() + t_start = time.time() # gather the list of IPs to poll @@ -166,7 +217,9 @@ while True: # perform polls for service,func in POLLS.items(): try: - team = func(ip).decode('utf-8') + team = func(srcip, ip).decode('utf-8') + if len(team) == 0: + team = 'dirtbags' except: team = 'dirtbags'