This mod_evasive is a very simple piece of C code: only 699 lines in a single file, most of it for handling the necessary infrastructure for maintaing hash tables. The routine that actually decides what it does is very straightforward:
Code: Select all
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;
}
This opens the possibility of modifying it for our own purpose. E.g. the DDoS attack we are syffering disguises accesses to the same page by appending a query string with a different sid. This would thwart the recoginition of "too many requests for the same page", which takes the full query string as part of the page identity. But we could add code to eliminate the sid parameter of a query string, before calculating the hash key from it.