Viewing file: mod_evasive20.c (17.81 KB) -rw-rw-rw- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
/* mod_evasive for Apache 2 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; either version 2 of the License, or (at your option) any later version. 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 AP_MODULE_DECLARE_DATA evasive20_module;
/* BEGIN DoS Evasive Maneuvers Definitions */
#define MAILER "/bin/mail %s" #define LOG( A, ... ) { openlog("mod_evasive", LOG_PID, LOG_DAEMON); syslog( A, __VA_ARGS__ ); closelog(); }
#define DEFAULT_HASH_TBL_SIZE 3097ul // Default hash table size #define DEFAULT_PAGE_COUNT 2 // Default maximum page hit count per interval #define DEFAULT_SITE_COUNT 50 // Default maximum site hit count per 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 for Detected IPs; blocked for 10 seconds #define DEFAULT_LOG_DIR "/tmp" // Default temp directory
/* 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); int ntt_destroy(struct ntt *ntt); 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); int ntt_delete(struct ntt *ntt, const char *key); long ntt_hashcode(struct ntt *ntt, const char *key); 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);
/* END NTT (Named Timestamp Tree) Headers */
/* BEGIN DoS Evasive Maneuvers Globals */
struct ntt *hit_list; // Our dynamic hash 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 *email_notify = NULL; static char *log_dir = NULL; static char *system_command = NULL; static const char *whitelist(cmd_parms *cmd, void *dconfig, const char *ip); int is_whitelisted(const char *ip);
/* END DoS Evasive Maneuvers Globals */
static void * create_hit_list(apr_pool_t *p, server_rec *s) { /* Create a new hit list for this listener */
hit_list = ntt_create(hash_table_size); }
static const char *whitelist(cmd_parms *cmd, void *dconfig, const char *ip) { char entry[128]; snprintf(entry, sizeof(entry), "WHITELIST_%s", ip); ntt_insert(hit_list, entry, time(NULL)); return NULL; }
static int access_checker(request_rec *r) { int ret = OK;
/* BEGIN DoS Evasive Maneuvers Code */
if (r->prev == NULL && r->main == NULL && hit_list != NULL) { char hash_key[2048]; struct ntt_node *n; time_t t = time(NULL);
/* Check whitelist */ if (is_whitelisted(r->connection->remote_ip)) return OK;
/* First see if the IP itself is on "hold" */ n = ntt_find(hit_list, r->connection->remote_ip);
if (n != NULL && t-n->timestamp<blocking_period) { /* If the IP is on "hold", make it wait longer in 403 land */ ret = HTTP_FORBIDDEN; n->timestamp = time(NULL);
/* Not on hold, check hit stats */ } else {
/* Has URI been hit too much? */ snprintf(hash_key, 2048, "%s_%s", r->connection->remote_ip, 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 = HTTP_FORBIDDEN; ntt_insert(hit_list, r->connection->remote_ip, 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, "%s_SITE", r->connection->remote_ip); 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 = HTTP_FORBIDDEN; ntt_insert(hit_list, r->connection->remote_ip, 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 == HTTP_FORBIDDEN) { char filename[1024]; struct stat s; FILE *file;
snprintf(filename, sizeof(filename), "%s/dos-%s", log_dir != NULL ? log_dir : DEFAULT_LOG_DIR, r->connection->remote_ip); 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 DoS attack.", r->connection->remote_ip); 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", r->connection->remote_ip); fprintf(file, "mod_evasive HTTP Blacklisted %s\n", r->connection->remote_ip); pclose(file); } }
if (system_command != NULL) { snprintf(filename, sizeof(filename), system_command, r->connection->remote_ip); system(filename); } } else { LOG(LOG_ALERT, "Couldn't open logfile %s: %s",filename, strerror(errno)); }
} /* if (temp file does not exist) */
} /* if (ret == HTTP_FORBIDDEN) */
} /* if (r->prev == NULL && r->main == NULL && hit_list != NULL) */
/* END DoS Evasive Maneuvers Code */
if (ret == HTTP_FORBIDDEN && (ap_satisfies(r) != SATISFY_ANY || !ap_some_auth_required(r))) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "client denied by server configuration: %s", r->filename); }
return ret; }
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), "WHITELIST_%s", ip); if (ntt_find(hit_list, hashkey)!=NULL) return 1;
/* IPv4 Wildcards */ snprintf(hashkey, sizeof(hashkey), "WHITELIST_%s.*.*.*", octet[0]); if (ntt_find(hit_list, hashkey)!=NULL) return 1;
snprintf(hashkey, sizeof(hashkey), "WHITELIST_%s.%s.*.*", octet[0], octet[1]); if (ntt_find(hit_list, hashkey)!=NULL) return 1;
snprintf(hashkey, sizeof(hashkey), "WHITELIST_%s.%s.%s.*", octet[0], octet[1], octet[2]); if (ntt_find(hit_list, hashkey)!=NULL) return 1;
/* No match */ return 0; }
static apr_status_t destroy_hit_list(void *not_used) { ntt_destroy(hit_list); free(email_notify); free(system_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, const 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, const 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, const 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, const 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, const 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, const 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, const 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, const 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_system_command(cmd_parms *cmd, void *dconfig, const char *value) { if (value != NULL && value[0] != 0) { if (system_command != NULL) free(system_command); system_command = strdup(value); } return NULL; }
/* END Configuration Functions */
static const command_rec access_cmds[] = { AP_INIT_TAKE1("DOSHashTableSize", get_hash_tbl_size, NULL, RSRC_CONF, "Set size of hash table"),
AP_INIT_TAKE1("DOSPageCount", get_page_count, NULL, RSRC_CONF, "Set maximum page hit count per interval"),
AP_INIT_TAKE1("DOSSiteCount", get_site_count, NULL, RSRC_CONF, "Set maximum site hit count per interval"),
AP_INIT_TAKE1("DOSPageInterval", get_page_interval, NULL, RSRC_CONF, "Set page interval"),
AP_INIT_TAKE1("DOSSiteInterval", get_site_interval, NULL, RSRC_CONF, "Set site interval"),
AP_INIT_TAKE1("DOSBlockingPeriod", get_blocking_period, NULL, RSRC_CONF, "Set blocking period for detected DoS IPs"),
AP_INIT_TAKE1("DOSEmailNotify", get_email_notify, NULL, RSRC_CONF, "Set email notification"),
AP_INIT_TAKE1("DOSLogDir", get_log_dir, NULL, RSRC_CONF, "Set log dir"),
AP_INIT_TAKE1("DOSSystemCommand", get_system_command, NULL, RSRC_CONF, "Set system command on DoS"),
AP_INIT_ITERATE("DOSWhitelist", whitelist, NULL, RSRC_CONF, "IP-addresses wildcards to whitelist"),
{ NULL } };
static void register_hooks(apr_pool_t *p) { ap_hook_access_checker(access_checker, NULL, NULL, APR_HOOK_MIDDLE); apr_pool_cleanup_register(p, NULL, apr_pool_cleanup_null, destroy_hit_list); };
module AP_MODULE_DECLARE_DATA evasive20_module = { STANDARD20_MODULE_STUFF, NULL, NULL, create_hit_list, NULL, access_cmds, register_hooks };
|