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.
This commit is contained in:
Curt Hash 2010-01-22 16:57:45 -07:00
parent 9b14d50374
commit 3dd84a10f4
3 changed files with 79 additions and 20 deletions

View File

@ -46,6 +46,8 @@ else:
'poll_timeout': 0.5, 'poll_timeout': 0.5,
'heartbeat_dir': '/var/lib/pollster', 'heartbeat_dir': '/var/lib/pollster',
'results': '/var/lib/pollster/status.html', 'results': '/var/lib/pollster/status.html',
'poll_iface' : 'eth1',
'poll_mac_vendor' : '00:01:c0'
}, },
'puzzler': 'puzzler':
{ {

4
pollster/get_ip.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
ifconfig $1 | grep "inet addr" | awk '{print $2}' | awk -F: '{print $2}'

View File

@ -7,7 +7,9 @@ import sys
import time import time
import socket import socket
import traceback import traceback
import urllib.request import subprocess
import random
import http.client
from ctf import config from ctf import config
from ctf import pointscli from ctf import pointscli
@ -17,9 +19,45 @@ POLL_INTERVAL = config.get('pollster', 'poll_interval')
IP_DIR = config.get('pollster', 'heartbeat_dir') IP_DIR = config.get('pollster', 'heartbeat_dir')
REPORT_PATH = config.get('pollster', 'results') REPORT_PATH = config.get('pollster', 'results')
SOCK_TIMEOUT = config.get('pollster', 'poll_timeout') 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 socket_poll(ip, port, msg, prot, max_recv=1): 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))
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 <ip>:<port> using the ''' Connect via socket to the specified <ip>:<port> using the
specified <prot>, send the specified <msg> and return the specified <prot>, send the specified <msg> and return the
response or None if something went wrong. <max_recvs> specifies response or None if something went wrong. <max_recvs> specifies
@ -33,6 +71,7 @@ def socket_poll(ip, port, msg, prot, max_recv=1):
traceback.print_exc() traceback.print_exc()
return None return None
sock.bind((srcip, 0))
sock.settimeout(SOCK_TIMEOUT) sock.settimeout(SOCK_TIMEOUT)
# connect # 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 # 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. # 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. ''' ''' 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: if resp is None:
return None return None
return resp.strip(b'\r\n') 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. ''' ''' 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: if resp is None:
return None return None
return resp.strip(b'\r\n') 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. ''' ''' Poll the cat.cgi web service. Returns None or a team name. '''
try: 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: except:
return None return None
resp = url.read() resp = conn.getresponse()
return resp.strip(b'\r\n') if resp.status != 200:
conn.close()
return None
def poll_tftpd(ip): 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. ''' ''' 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: if resp is None:
return None return None
@ -117,7 +163,7 @@ def poll_tftpd(ip):
resp = resp.split(b'\n')[0] resp = resp.split(b'\n')[0]
# ack # 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') return resp[4:].strip(b'\r\n')
@ -134,6 +180,11 @@ poll_no = 0
# loop forever # loop forever
while True: while True:
random_mac()
dhcp_request()
srcip = get_ip()
t_start = time.time() t_start = time.time()
# gather the list of IPs to poll # gather the list of IPs to poll
@ -166,7 +217,9 @@ while True:
# perform polls # perform polls
for service,func in POLLS.items(): for service,func in POLLS.items():
try: try:
team = func(ip).decode('utf-8') team = func(srcip, ip).decode('utf-8')
if len(team) == 0:
team = 'dirtbags'
except: except:
team = 'dirtbags' team = 'dirtbags'