Viewing file: mod_evasive.c (18.94 KB) -rw-rw-rw- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
/* $Id: mod_evasive.c,v 1.3 2005/10/08 19:17:14 jonz Exp $ */
/* mod_evasive for Apache 1.3 Copyright (c) by Jonathan A. Zdziarski
LICENSE
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; version 2 of the License.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
#include <sys/types.h> #include <sys/socket.h> #include <sys/stat.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <time.h> #include <syslog.h> #include <errno.h>
#include "httpd.h" #include "http_core.h" #include "http_config.h" #include "http_log.h" #include "http_request.h"
module MODULE_VAR_EXPORT evasive_module;
/* BEGIN DoS Evasive Maneuvers Definitions */
#define MAILER "/bin/mail -t %s" #define LOG( A, ... ) { openlog("mod_evasive", LOG_PID, LOG_DAEMON); syslog( A, __VA_ARGS__ ); closelog(); }
#define DEFAULT_HASH_TBL_SIZE 3079ul // Default hash table size #define DEFAULT_PAGE_COUNT 2 // Default max page hit count/interval #define DEFAULT_SITE_COUNT 50 // Default max site hit count/interval #define DEFAULT_PAGE_INTERVAL 1 // Default 1 second page interval #define DEFAULT_SITE_INTERVAL 1 // Default 1 second site interval #define DEFAULT_BLOCKING_PERIOD 10 // Default block time (Seconds) #define DEFAULT_LOG_DIR "/tmp"
/* END DoS Evasive Maneuvers Definitions */
/* BEGIN NTT (Named Timestamp Tree) Headers */
enum { ntt_num_primes = 28 };
/* ntt root tree */ struct ntt { long size; long items; struct ntt_node **tbl; };
/* ntt node (entry in the ntt root tree) */ struct ntt_node { char *key; time_t timestamp; long count; struct ntt_node *next; };
/* ntt cursor */ struct ntt_c { long iter_index; struct ntt_node *iter_next; };
struct ntt *ntt_create(long size); struct ntt_node *ntt_find(struct ntt *ntt, const char *key); struct ntt_node *ntt_insert(struct ntt *ntt, const char *key, time_t timestamp); struct ntt_node *c_ntt_first(struct ntt *ntt, struct ntt_c *c); struct ntt_node *c_ntt_next(struct ntt *ntt, struct ntt_c *c); int ntt_destroy(struct ntt *ntt); int ntt_delete(struct ntt *ntt, const char *key); long ntt_hashcode(struct ntt *ntt, const char *key);
/* END NTT (Named Timestamp Tree) Headers */
/* BEGIN DoS Evasive Maneuvers Globals */
struct ntt *hit_list; // Our dynamic hash table struct ntt *white_list = NULL; // White list table
static unsigned long hash_table_size = DEFAULT_HASH_TBL_SIZE; static int page_count = DEFAULT_PAGE_COUNT; static int page_interval = DEFAULT_PAGE_INTERVAL; static int site_count = DEFAULT_SITE_COUNT; static int site_interval = DEFAULT_SITE_INTERVAL; static int blocking_period = DEFAULT_BLOCKING_PERIOD; static char *log_dir = NULL; static char *email_notify = NULL; static char *sys_command = NULL; int is_whitelisted(const char *ip); static const char *whitelist(cmd_parms *cmd, void *mconfig, char *ip);
/* END DoS Evasive Maneuvers Globals */
static void evasive_child_init(server_rec *s, pool *p) { hit_list = ntt_create(hash_table_size); }
static int check_access(request_rec *r) { int ret = OK;
/* BEGIN Evasive Maneuvers Code */
if (r->prev == NULL && r->main == NULL && hit_list != NULL) { unsigned long address = r->connection->remote_addr.sin_addr.s_addr; char *text_add = inet_ntoa(r->connection->remote_addr.sin_addr); char hash_key[2048]; struct ntt_node *n; time_t t = time(NULL);
/* Check whitelist */ if (is_whitelisted(text_add)) return OK;
/* First see if the IP itself is on "hold" */ snprintf(hash_key, 2048, "%ld", address); n = ntt_find(hit_list, hash_key);
if (n != NULL && t-n->timestamp<blocking_period) { /* If the IP is on "hold", make it wait longer in 403 land */ ret = FORBIDDEN; n->timestamp = time(NULL);
/* Not on hold, check hit stats */ } else {
/* Has URI been hit too much? */ snprintf(hash_key, 2048, "%ld_%s", address, r->uri); n = ntt_find(hit_list, hash_key); if (n != NULL) {
/* If URI is being hit too much, add to "hold" list and 403 */ if (t-n->timestamp<page_interval && n->count>=page_count) { ret = FORBIDDEN; snprintf(hash_key, 2048, "%ld", address); ntt_insert(hit_list, hash_key, time(NULL)); } else {
/* Reset our hit count list as necessary */ if (t-n->timestamp>=page_interval) { n->count=0; } } n->timestamp = t; n->count++; } else { ntt_insert(hit_list, hash_key, t); }
/* Has site been hit too much? */ snprintf(hash_key, 2048, "%ld_SITE", address); n = ntt_find(hit_list, hash_key); if (n != NULL) {
/* If site is being hit too much, add to "hold" list and 403 */ if (t-n->timestamp<site_interval && n->count>=site_count) { ret = FORBIDDEN; snprintf(hash_key, 2048, "%ld", address); ntt_insert(hit_list, hash_key, time(NULL)); } else {
/* Reset our hit count list as necessary */ if (t-n->timestamp>=site_interval) { n->count=0; } } n->timestamp = t; n->count++; } else { ntt_insert(hit_list, hash_key, t); } }
/* Perform email notification and system functions */ if (ret == FORBIDDEN) { char filename[1024]; struct stat s; FILE *file;
snprintf(filename, sizeof(filename), "%s/dos-%s", log_dir != NULL ? log_dir : DEFAULT_LOG_DIR, text_add); if (stat(filename, &s)) { file = fopen(filename, "w"); if (file != NULL) { fprintf(file, "%ld\n", getpid()); fclose(file);
LOG(LOG_ALERT, "Blacklisting address %s: possible attack.", text_add) if (email_notify != NULL) { snprintf(filename, sizeof(filename), MAILER, email_notify); file = popen(filename, "w"); if (file != NULL) { fprintf(file, "To: %s\n", email_notify); fprintf(file, "Subject: HTTP BLACKLIST %s\n\n", text_add); fprintf(file, "mod_evasive HTTP Blacklisted %s\n", text_add); pclose(file); } }
if (sys_command != NULL) { snprintf(filename, sizeof(filename), sys_command, text_add); system(filename); } } else { LOG(LOG_ALERT, "Couldn't open logfile %s: %s",filename, strerror(errno)); }
} /* if (temp file does not exist) */
} /* if (ret == FORBIDDEN) */
} /* if (r->prev == NULL && r->main == NULL && hit_list != NULL) */
/* END Evasive Maneuvers Code */
if (ret == FORBIDDEN && (ap_satisfies(r) != SATISFY_ANY || !ap_some_auth_required(r))) { ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r, "client denied by server configuration: %s", r->filename); }
return ret; }
static void evasive_child_exit(server_rec *s, pool *p) { ntt_destroy(hit_list); free(email_notify); free(sys_command); }
/* BEGIN NTT (Named Timestamp Tree) Functions */
static unsigned long ntt_prime_list[ntt_num_primes] = { 53ul, 97ul, 193ul, 389ul, 769ul, 1543ul, 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul };
/* Find the numeric position in the hash table based on key and modulus */
long ntt_hashcode(struct ntt *ntt, const char *key) { unsigned long val = 0; for (; *key; ++key) val = 5 * val + *key; return(val % ntt->size); }
/* Creates a single node in the tree */
struct ntt_node *ntt_node_create(const char *key) { char *node_key; struct ntt_node* node;
node = (struct ntt_node *) malloc(sizeof(struct ntt_node)); if (node == NULL) { return NULL; } if ((node_key = strdup(key)) == NULL) { free(node); return NULL; } node->key = node_key; node->timestamp = time(NULL); node->next = NULL; return(node); }
/* Tree initializer */
struct ntt *ntt_create(long size) { long i = 0; struct ntt *ntt = (struct ntt *) malloc(sizeof(struct ntt));
if (ntt == NULL) return NULL; while (ntt_prime_list[i] < size) { i++; } ntt->size = ntt_prime_list[i]; ntt->items = 0; ntt->tbl = (struct ntt_node **) calloc(ntt->size, sizeof(struct ntt_node *)); if (ntt->tbl == NULL) { free(ntt); return NULL; } return(ntt); }
/* Find an object in the tree */
struct ntt_node *ntt_find(struct ntt *ntt, const char *key) { long hash_code; struct ntt_node *node;
if (ntt == NULL) return NULL;
hash_code = ntt_hashcode(ntt, key); node = ntt->tbl[hash_code];
while (node) { if (!strcmp(key, node->key)) { return(node); } node = node->next; } return((struct ntt_node *)NULL); }
/* Insert a node into the tree */
struct ntt_node *ntt_insert(struct ntt *ntt, const char *key, time_t timestamp) { long hash_code; struct ntt_node *parent; struct ntt_node *node; struct ntt_node *new_node = NULL;
if (ntt == NULL) return NULL;
hash_code = ntt_hashcode(ntt, key); parent = NULL; node = ntt->tbl[hash_code];
while (node != NULL) { if (strcmp(key, node->key) == 0) { new_node = node; node = NULL; }
if (new_node == NULL) { parent = node; node = node->next; } }
if (new_node != NULL) { new_node->timestamp = timestamp; new_node->count = 0; return new_node; }
/* Create a new node */ new_node = ntt_node_create(key); new_node->timestamp = timestamp; new_node->timestamp = 0;
ntt->items++;
/* Insert */ if (parent) { /* Existing parent */ parent->next = new_node; return new_node; /* Return the locked node */ }
/* No existing parent; add directly to hash table */ ntt->tbl[hash_code] = new_node; return new_node; }
/* Tree destructor */
int ntt_destroy(struct ntt *ntt) { struct ntt_node *node, *next; struct ntt_c c;
if (ntt == NULL) return -1;
node = c_ntt_first(ntt, &c); while(node != NULL) { next = c_ntt_next(ntt, &c); ntt_delete(ntt, node->key); node = next; }
free(ntt->tbl); free(ntt); ntt = (struct ntt *) NULL;
return 0; }
/* Delete a single node in the tree */
int ntt_delete(struct ntt *ntt, const char *key) { long hash_code; struct ntt_node *parent = NULL; struct ntt_node *node; struct ntt_node *del_node = NULL;
if (ntt == NULL) return -1;
hash_code = ntt_hashcode(ntt, key); node = ntt->tbl[hash_code];
while (node != NULL) { if (strcmp(key, node->key) == 0) { del_node = node; node = NULL; }
if (del_node == NULL) { parent = node; node = node->next; } }
if (del_node != NULL) {
if (parent) { parent->next = del_node->next; } else { ntt->tbl[hash_code] = del_node->next; }
free(del_node->key); free(del_node); ntt->items--;
return 0; }
return -5; }
/* Point cursor to first item in tree */
struct ntt_node *c_ntt_first(struct ntt *ntt, struct ntt_c *c) {
c->iter_index = 0; c->iter_next = (struct ntt_node *)NULL; return(c_ntt_next(ntt, c)); }
/* Point cursor to next iteration in tree */
struct ntt_node *c_ntt_next(struct ntt *ntt, struct ntt_c *c) { long index; struct ntt_node *node = c->iter_next;
if (ntt == NULL) return NULL;
if (node) { if (node != NULL) { c->iter_next = node->next; return (node); } }
if (! node) { while (c->iter_index < ntt->size) { index = c->iter_index++;
if (ntt->tbl[index]) { c->iter_next = ntt->tbl[index]->next; return(ntt->tbl[index]); } } } return((struct ntt_node *)NULL); }
/* END NTT (Named Pointer Tree) Functions */
/* BEGIN Configuration Functions */
static const char * get_hash_tbl_size(cmd_parms *cmd, void *dconfig, char *value) { long n = strtol(value, NULL, 0);
if (n<=0) hash_table_size = DEFAULT_HASH_TBL_SIZE; else hash_table_size = n;
return NULL; }
static const char * get_page_count(cmd_parms *cmd, void *dconfig, char *value) { long n = strtol(value, NULL, 0); if (n<=0) page_count = DEFAULT_PAGE_COUNT; else page_count = n;
return NULL; }
static const char * get_site_count(cmd_parms *cmd, void *dconfig, char *value) { long n = strtol(value, NULL, 0); if (n<=0) site_count = DEFAULT_SITE_COUNT; else site_count = n;
return NULL; }
static const char * get_page_interval(cmd_parms *cmd, void *dconfig, char *value) { long n = strtol(value, NULL, 0); if (n<=0) page_interval = DEFAULT_PAGE_INTERVAL; else page_interval = n;
return NULL; }
static const char * get_site_interval(cmd_parms *cmd, void *dconfig, char *value) { long n = strtol(value, NULL, 0); if (n<=0) site_interval = DEFAULT_SITE_INTERVAL; else site_interval = n;
return NULL; }
static const char * get_blocking_period(cmd_parms *cmd, void *dconfig, char *value) { long n = strtol(value, NULL, 0); if (n<=0) blocking_period = DEFAULT_BLOCKING_PERIOD; else blocking_period = n;
return NULL; }
static const char * get_log_dir(cmd_parms *cmd, void *dconfig, char *value) { if (value != NULL && value[0] != 0) { if (log_dir != NULL) free(log_dir); log_dir = strdup(value); }
return NULL; }
static const char * get_email_notify(cmd_parms *cmd, void *dconfig, char *value) { if (value != NULL && value[0] != 0) { if (email_notify != NULL) free(email_notify); email_notify = strdup(value); }
return NULL; }
static const char * get_sys_command(cmd_parms *cmd, void *dconfig, char *value) { if (value != NULL && value[0] != 0) { if (sys_command != NULL) free(sys_command); sys_command = strdup(value); } return NULL; }
static const char *whitelist(cmd_parms *cmd, void *mconfig, char *ip) { char entry[128];
if (white_list == NULL) white_list = ntt_create(53ul); snprintf(entry, sizeof(entry), "%s", ip); ntt_insert(white_list, entry, time(NULL));
return NULL; }
/* END Configuration Functions */
int is_whitelisted(const char *ip) { char hashkey[128]; char octet[4][4]; char *dip; char *oct; int i = 0; memset(octet, 0, 16); dip = strdup(ip); if (dip == NULL) return 0; oct = strtok(dip, "."); while(oct != NULL && i<4) { if (strlen(oct)<=3) strcpy(octet[i], oct); i++; oct = strtok(NULL, "."); } free(dip); /* Exact Match */ snprintf(hashkey, sizeof(hashkey), "%s", ip); if (ntt_find(white_list, hashkey)!=NULL) return 1; /* IPv4 Wildcards */ snprintf(hashkey, sizeof(hashkey), "%s.*.*.*", octet[0]); if (ntt_find(white_list, hashkey)!=NULL) return 1; snprintf(hashkey, sizeof(hashkey), "%s.%s.*.*", octet[0], octet[1]); if (ntt_find(white_list, hashkey)!=NULL) return 1;
snprintf(hashkey, sizeof(hashkey), "%s.%s.%s.*", octet[0], octet[1], octet[2]); if (ntt_find(white_list, hashkey)!=NULL) return 1;
/* No match */ return 0; }
static command_rec command_table[] = {
{ "DOSWhitelist", whitelist, NULL, RSRC_CONF, ITERATE, "Whitelist an IP or Wildcard. "},
{ "DOSHashTableSize", get_hash_tbl_size, NULL, RSRC_CONF, TAKE1, "Set size of hash table. " },
{ "DOSPageCount", get_page_count, NULL, RSRC_CONF, TAKE1, "Set maximum page hit count per interval. " },
{ "DOSSiteCount", get_site_count, NULL, RSRC_CONF, TAKE1, "Set maximum site hit count per interval. " },
{ "DOSPageInterval", get_page_interval, NULL, RSRC_CONF, TAKE1, "Set page interval. " },
{ "DOSSiteInterval", get_site_interval, NULL, RSRC_CONF, TAKE1, "Set site interval. " },
{ "DOSLogDir", get_log_dir, NULL, RSRC_CONF, TAKE1, "Set log dir. "},
{ "DOSEmailNotify", get_email_notify, NULL, RSRC_CONF, TAKE1, "Set email notification. "},
{ "DOSSystemCommand", get_sys_command, NULL, RSRC_CONF, TAKE1, "Set system command. "},
{ "DOSBlockingPeriod", get_blocking_period, NULL, RSRC_CONF, TAKE1, "Set blocking period for detected DoS IPs. "},
{ NULL } };
module MODULE_VAR_EXPORT evasive_module = { STANDARD_MODULE_STUFF, NULL, /* initializer */ NULL, /* dir config creator */ NULL, /* dir config merger */ NULL, /* server config creator */ NULL, /* server config merger */ command_table, /* command table */ NULL, /* handlers */ NULL, /* filename translation */ NULL, /* check_user_id */ NULL, /* check auth */ check_access, /* check access */ NULL, /* type_checker */ NULL, /* fixups */ NULL, /* logger */ NULL, /* header parser */ evasive_child_init, /* child_init */ evasive_child_exit, /* child_exit */ NULL /* post read-request */ };
|