mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2024-12-13 23:14:46 +00:00
59bb97a192
This is a service that talks SPOE protocol and uses the Mod Defender (a NAXSI clone) functionality to detect HTTP attacks. It returns a HTTP status code to indicate whether the request is suspicious or not, based on NAXSI rules. The value of the returned code can be used in HAProxy rules to determine if the HTTP request should be blocked/rejected.
634 lines
15 KiB
C
634 lines
15 KiB
C
/*
|
|
* Mod Defender for HAProxy
|
|
*
|
|
* Copyright 2017 HAProxy Technologies, Dragan Dosen <ddosen@haproxy.com>
|
|
*
|
|
* Mod Defender
|
|
* Copyright (c) 2017 Annihil (https://github.com/VultureProject/mod_defender)
|
|
*
|
|
* Parts of code based on Apache HTTP Server source
|
|
* Copyright 2015 The Apache Software Foundation (http://www.apache.org/)
|
|
*
|
|
* 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
|
|
* 3 of the License, or (at your option) any later version.
|
|
*
|
|
*/
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
|
|
#include <common/defaults.h>
|
|
#include <common/standard.h>
|
|
#include <common/chunk.h>
|
|
#include <common/time.h>
|
|
|
|
#include <proto/spoe.h>
|
|
|
|
#include <http_core.h>
|
|
#include <http_main.h>
|
|
#include <http_log.h>
|
|
#include <http_request.h>
|
|
|
|
#include <apr_pools.h>
|
|
#include <apr_strings.h>
|
|
|
|
#include "spoa.h"
|
|
#include "standalone.h"
|
|
#include "defender.h"
|
|
|
|
#define DEFENDER_NAME "defender"
|
|
#define DEFENDER_INPUT_FILTER "DEFENDER_IN"
|
|
#define DEFENDER_DEFAULT_UNIQUE_ID "unique_id"
|
|
#define DEFENDER_BRIGADE_REQUEST "defender-brigade-request"
|
|
|
|
extern module AP_MODULE_DECLARE_DATA defender_module;
|
|
|
|
DECLARE_HOOK(int,post_config,(apr_pool_t *pconf,apr_pool_t *plog, apr_pool_t *ptemp,server_rec *s))
|
|
DECLARE_HOOK(int,fixups,(request_rec *r))
|
|
DECLARE_HOOK(int,header_parser,(request_rec *r))
|
|
|
|
char *defender_name = DEFENDER_NAME;
|
|
const char *defender_argv[] = { DEFENDER_NAME, NULL };
|
|
const char *defender_unknown_hostname = "";
|
|
|
|
void *defender_module_config = NULL;
|
|
static server_rec *server = NULL;
|
|
apr_pool_t *defender_pool = NULL;
|
|
|
|
char hostname[MAX_HOSTNAME_LEN];
|
|
char defender_cwd[MAXPATHLEN];
|
|
|
|
static apr_status_t defender_bucket_read(apr_bucket *b, const char **str,
|
|
apr_size_t *len, apr_read_type_e block);
|
|
static void defender_bucket_destroy(void *data);
|
|
|
|
static const apr_bucket_type_t apr_bucket_type_defender = {
|
|
"defender", 8, APR_BUCKET_DATA,
|
|
defender_bucket_destroy,
|
|
defender_bucket_read,
|
|
apr_bucket_setaside_noop,
|
|
apr_bucket_shared_split,
|
|
apr_bucket_shared_copy
|
|
};
|
|
|
|
struct apr_bucket_defender {
|
|
apr_bucket_refcount refcount;
|
|
struct chunk buf;
|
|
};
|
|
|
|
static apr_status_t defender_bucket_read(apr_bucket *b, const char **str,
|
|
apr_size_t *len, apr_read_type_e block)
|
|
{
|
|
struct apr_bucket_defender *d = b->data;
|
|
|
|
*str = d->buf.str;
|
|
*len = d->buf.len;
|
|
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
static void defender_bucket_destroy(void *data)
|
|
{
|
|
struct apr_bucket_defender *d = data;
|
|
|
|
if (apr_bucket_shared_destroy(d))
|
|
apr_bucket_free(d);
|
|
}
|
|
|
|
static apr_bucket *defender_bucket_make(apr_bucket *b, const struct chunk *buf)
|
|
{
|
|
struct apr_bucket_defender *d;
|
|
|
|
d = apr_bucket_alloc(sizeof(*d), b->list);
|
|
|
|
d->buf.str = buf->str;
|
|
d->buf.len = buf->len;
|
|
d->buf.size = 0;
|
|
|
|
b = apr_bucket_shared_make(b, d, 0, buf->len);
|
|
b->type = &apr_bucket_type_defender;
|
|
return b;
|
|
}
|
|
|
|
static apr_bucket *defender_bucket_create(const struct chunk *buf,
|
|
apr_bucket_alloc_t *list)
|
|
{
|
|
apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
|
|
|
|
APR_BUCKET_INIT(b);
|
|
b->free = apr_bucket_free;
|
|
b->list = list;
|
|
return defender_bucket_make(b, buf);
|
|
}
|
|
|
|
static void defender_logger(int level, char *str)
|
|
{
|
|
LOG(&null_worker, "%s", str);
|
|
}
|
|
|
|
static char *defender_strdup(apr_pool_t *pool, const char *src, uint64_t len)
|
|
{
|
|
char *dst;
|
|
|
|
if (!(dst = apr_pcalloc(pool, len + 1)))
|
|
return NULL;
|
|
|
|
memcpy(dst, src, len);
|
|
dst[len] = '\0';
|
|
|
|
return dst;
|
|
}
|
|
|
|
static char *defender_printf(apr_pool_t *pool, const char *fmt, ...)
|
|
{
|
|
va_list argp;
|
|
char *dst;
|
|
int len;
|
|
|
|
va_start(argp, fmt);
|
|
len = vsnprintf(NULL, 0, fmt, argp);
|
|
if (len < 0)
|
|
return NULL;
|
|
va_end(argp);
|
|
|
|
if (!(dst = apr_pcalloc(pool, len + 1)))
|
|
return NULL;
|
|
|
|
va_start(argp, fmt);
|
|
len = vsnprintf(dst, len + 1, fmt, argp);
|
|
va_end(argp);
|
|
|
|
return dst;
|
|
}
|
|
|
|
static char *defender_addr2str(apr_pool_t *pool, struct sample *addr)
|
|
{
|
|
sa_family_t family;
|
|
const void *src;
|
|
char *dst;
|
|
|
|
switch (addr->data.type) {
|
|
case SMP_T_IPV4:
|
|
src = &addr->data.u.ipv4;
|
|
family = AF_INET;
|
|
break;
|
|
case SMP_T_IPV6:
|
|
src = &addr->data.u.ipv6;
|
|
family = AF_INET6;
|
|
break;
|
|
default:
|
|
return NULL;
|
|
}
|
|
|
|
if (!(dst = apr_pcalloc(pool, INET6_ADDRSTRLEN + 1)))
|
|
return NULL;
|
|
|
|
if (inet_ntop(family, src, dst, INET6_ADDRSTRLEN))
|
|
return dst;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void defender_pre_config()
|
|
{
|
|
apr_pool_t *ptemp = NULL;
|
|
|
|
defender_module.module_index = 0;
|
|
defender_module.register_hooks(defender_pool);
|
|
|
|
apr_pool_create(&ptemp, defender_pool);
|
|
run_ap_hook_post_config(defender_pool, defender_pool, ptemp, server);
|
|
apr_pool_destroy(ptemp);
|
|
}
|
|
|
|
static const char *defender_read_config(const char *file)
|
|
{
|
|
apr_pool_t *ptemp = NULL;
|
|
const char *err;
|
|
const char *fullname;
|
|
|
|
defender_module_config = defender_module.create_dir_config(defender_pool, "/");
|
|
if (defender_module_config == NULL) {
|
|
return "cannot allocate space for the configuration structure";
|
|
}
|
|
|
|
apr_pool_create(&ptemp, defender_pool);
|
|
|
|
fullname = ap_server_root_relative(ptemp, file);
|
|
|
|
err = read_module_config(server, defender_module_config,
|
|
defender_module.cmds,
|
|
defender_pool, ptemp, fullname);
|
|
|
|
apr_pool_destroy(ptemp);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void defender_post_config()
|
|
{
|
|
apr_pool_t *ptemp = NULL;
|
|
|
|
apr_pool_create(&ptemp, defender_pool);
|
|
run_ap_hook_post_config(defender_pool, defender_pool, ptemp, server);
|
|
apr_pool_destroy(ptemp);
|
|
}
|
|
|
|
static const char *defender_set_logger(const char *file)
|
|
{
|
|
char *logname;
|
|
|
|
logger = defender_logger;
|
|
|
|
if (file == NULL)
|
|
return NULL;
|
|
|
|
logname = ap_server_root_relative(defender_pool, file);
|
|
|
|
if (apr_file_open(&server->error_log, logname,
|
|
APR_APPEND | APR_WRITE | APR_CREATE | APR_LARGEFILE,
|
|
APR_OS_DEFAULT, defender_pool) != APR_SUCCESS) {
|
|
return apr_pstrcat(defender_pool, "Cannot open log file, ",
|
|
logname, NULL);
|
|
}
|
|
server->error_fname = logname;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static apr_status_t defender_input_filter(ap_filter_t *f,
|
|
apr_bucket_brigade *new_bb,
|
|
ap_input_mode_t mode,
|
|
apr_read_type_e block,
|
|
apr_off_t readbytes)
|
|
{
|
|
apr_bucket_brigade *bb = NULL;
|
|
apr_bucket *b = NULL, *a = NULL;
|
|
apr_status_t rv;
|
|
|
|
bb = (apr_bucket_brigade *)apr_table_get(f->r->notes, DEFENDER_BRIGADE_REQUEST);
|
|
|
|
if (bb == NULL || (bb && !APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb)))) {
|
|
b = apr_bucket_eos_create(f->c->bucket_alloc);
|
|
APR_BRIGADE_INSERT_TAIL(new_bb, b);
|
|
if (bb == NULL)
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
rv = apr_brigade_partition(bb, readbytes, &a);
|
|
if (rv != APR_SUCCESS && rv != APR_INCOMPLETE)
|
|
return rv;
|
|
|
|
b = APR_BRIGADE_FIRST(bb);
|
|
|
|
while (b != a) {
|
|
if (APR_BUCKET_IS_EOS(b))
|
|
ap_remove_input_filter(f);
|
|
|
|
APR_BUCKET_REMOVE(b);
|
|
APR_BRIGADE_INSERT_TAIL(new_bb, b);
|
|
b = APR_BRIGADE_FIRST(bb);
|
|
}
|
|
|
|
return APR_SUCCESS;
|
|
}
|
|
|
|
static conn_rec *defender_create_conn()
|
|
{
|
|
conn_rec *c = NULL;
|
|
apr_pool_t *ptrans = NULL;
|
|
|
|
apr_pool_create(&ptrans, defender_pool);
|
|
|
|
c = apr_pcalloc(ptrans, sizeof(conn_rec));
|
|
|
|
c->pool = ptrans;
|
|
c->local_ip = "127.0.0.1";
|
|
c->local_addr = server->addrs->host_addr;
|
|
c->local_host = defender_name;
|
|
c->client_addr = server->addrs->host_addr;
|
|
c->remote_host = defender_name;
|
|
|
|
c->id = 1;
|
|
c->base_server = server;
|
|
c->bucket_alloc = apr_bucket_alloc_create(ptrans);
|
|
|
|
return c;
|
|
}
|
|
|
|
static request_rec *defender_create_request(conn_rec *conn)
|
|
{
|
|
request_rec *r = NULL;
|
|
apr_pool_t *p = NULL;
|
|
struct ap_logconf *l;
|
|
|
|
apr_pool_create(&p, conn->pool);
|
|
|
|
r = apr_pcalloc(p, sizeof(request_rec));
|
|
|
|
r->pool = p;
|
|
r->connection = conn;
|
|
r->server = conn->base_server;
|
|
|
|
r->headers_in = apr_table_make(p, 25);
|
|
r->headers_out = apr_table_make(p, 12);
|
|
r->subprocess_env = apr_table_make(p, 25);
|
|
r->err_headers_out = apr_table_make(p, 5);
|
|
r->notes = apr_table_make(p, 5);
|
|
|
|
r->request_config = apr_palloc(p, sizeof(void *));
|
|
r->per_dir_config = apr_palloc(p, sizeof(void *));
|
|
((void **)r->per_dir_config)[0] = defender_module_config;
|
|
|
|
r->handler = defender_name;
|
|
|
|
r->parsed_uri.scheme = "http";
|
|
r->parsed_uri.is_initialized = 1;
|
|
r->parsed_uri.port = 80;
|
|
r->parsed_uri.port_str = "80";
|
|
r->parsed_uri.fragment = "";
|
|
|
|
r->input_filters = NULL;
|
|
r->output_filters = NULL;
|
|
|
|
l = apr_pcalloc(p, sizeof(struct ap_logconf));
|
|
l->level = APLOG_DEBUG;
|
|
r->log = l;
|
|
|
|
return r;
|
|
}
|
|
|
|
static int defender_process_headers(request_rec *r)
|
|
{
|
|
return run_ap_hook_header_parser(r);
|
|
}
|
|
|
|
static int defender_process_body(request_rec *r)
|
|
{
|
|
ap_add_input_filter(DEFENDER_INPUT_FILTER, NULL, r, r->connection);
|
|
return run_ap_hook_fixups(r);
|
|
}
|
|
|
|
int defender_init(const char *config_file, const char *log_file)
|
|
{
|
|
apr_status_t rv;
|
|
const char *msg;
|
|
|
|
if (!config_file) {
|
|
LOG(&null_worker, "Mod Defender configuration file not specified.\n");
|
|
return 0;
|
|
}
|
|
|
|
apr_initialize();
|
|
apr_pool_create(&defender_pool, NULL);
|
|
apr_hook_global_pool = defender_pool;
|
|
|
|
ap_server_root = getcwd(defender_cwd, APR_PATH_MAX);
|
|
|
|
server = (server_rec *) apr_palloc(defender_pool, sizeof(server_rec));
|
|
server->process = apr_palloc(defender_pool, sizeof(process_rec));
|
|
server->process->argc = 1;
|
|
server->process->argv = defender_argv;
|
|
server->process->short_name = defender_name;
|
|
server->process->pconf = defender_pool;
|
|
server->process->pool = defender_pool;
|
|
|
|
server->addrs = apr_palloc(defender_pool, sizeof(server_addr_rec));
|
|
rv = apr_sockaddr_info_get(&server->addrs->host_addr,
|
|
"127.0.0.1", APR_UNSPEC, 0, 0,
|
|
defender_pool);
|
|
if (rv != APR_SUCCESS) {
|
|
LOG(&null_worker, "Mod Defender getaddrinfo failed.\n");
|
|
return 0;
|
|
}
|
|
|
|
server->path = "/";
|
|
server->pathlen = strlen(server->path);
|
|
server->port = 0;
|
|
server->server_admin = defender_name;
|
|
server->server_scheme = "";
|
|
server->error_fname = NULL;
|
|
server->error_log = NULL;
|
|
server->limit_req_line = DEFAULT_LIMIT_REQUEST_LINE;
|
|
server->limit_req_fieldsize = DEFAULT_LIMIT_REQUEST_FIELDSIZE;
|
|
server->limit_req_fields = DEFAULT_LIMIT_REQUEST_FIELDS;
|
|
server->timeout = apr_time_from_sec(DEFAULT_TIMEOUT);
|
|
|
|
memset(hostname, 0, sizeof(hostname));
|
|
gethostname(hostname, sizeof(hostname) - 1);
|
|
server->server_hostname = hostname;
|
|
|
|
server->addrs->host_port = 0;
|
|
server->names = server->wild_names = NULL;
|
|
server->is_virtual = 0;
|
|
|
|
server->lookup_defaults = NULL;
|
|
server->module_config = NULL;
|
|
|
|
msg = defender_set_logger(log_file);
|
|
if (msg != NULL) {
|
|
LOG(&null_worker, "Mod Defender init failed: %s\n", msg);
|
|
return 0;
|
|
}
|
|
|
|
ap_register_input_filter(DEFENDER_INPUT_FILTER, defender_input_filter,
|
|
NULL, AP_FTYPE_RESOURCE);
|
|
|
|
defender_pre_config();
|
|
|
|
msg = defender_read_config(config_file);
|
|
if (msg != NULL) {
|
|
LOG(&null_worker, "Mod Defender configuration failed: %s\n", msg);
|
|
return 0;
|
|
}
|
|
|
|
defender_post_config();
|
|
|
|
return 1;
|
|
}
|
|
|
|
int defender_process_request(struct worker *worker, struct defender_request *request)
|
|
{
|
|
struct conn_rec *c = NULL;
|
|
struct request_rec *r = NULL;
|
|
|
|
struct apr_bucket_brigade *bb = NULL;
|
|
struct apr_bucket *d = NULL, *e = NULL;
|
|
|
|
struct chunk *method;
|
|
struct chunk *path;
|
|
struct chunk *query;
|
|
struct chunk *version;
|
|
struct chunk *body;
|
|
|
|
struct defender_header hdr;
|
|
char *hdr_ptr, *hdr_end;
|
|
|
|
const char *ptr;
|
|
|
|
int status = DECLINED;
|
|
|
|
if (!(c = defender_create_conn()))
|
|
goto out;
|
|
|
|
if (!(r = defender_create_request(c)))
|
|
goto out;
|
|
|
|
/* request */
|
|
r->request_time = apr_time_now();
|
|
|
|
if (request->clientip.data.type != SMP_T_IPV4 &&
|
|
request->clientip.data.type != SMP_T_IPV6)
|
|
goto out;
|
|
|
|
if (!(r->useragent_ip = defender_addr2str(r->pool, &request->clientip)))
|
|
goto out;
|
|
|
|
if (request->id.data.u.str.str && request->id.data.u.str.len > 0) {
|
|
apr_table_setn(r->subprocess_env, "UNIQUE_ID",
|
|
defender_strdup(r->pool, request->id.data.u.str.str,
|
|
request->id.data.u.str.len));
|
|
}
|
|
else {
|
|
apr_table_setn(r->subprocess_env, "UNIQUE_ID",
|
|
DEFENDER_DEFAULT_UNIQUE_ID);
|
|
}
|
|
|
|
method = &request->method.data.u.str;
|
|
path = &request->path.data.u.str;
|
|
query = &request->query.data.u.str;
|
|
version = &request->version.data.u.str;
|
|
|
|
r->method_number = lookup_builtin_method(method->str, method->len);
|
|
if (!(r->method = defender_strdup(r->pool, method->str, method->len)))
|
|
goto out;
|
|
|
|
r->unparsed_uri = defender_printf(r->pool, "%.*s%s%.*s",
|
|
path->len, path->str,
|
|
query->len > 0 ? "?" : "",
|
|
query->len, query->str);
|
|
if (!r->unparsed_uri)
|
|
goto out;
|
|
|
|
if (!(r->uri = defender_strdup(r->pool, path->str, path->len)))
|
|
goto out;
|
|
|
|
r->parsed_uri.path = r->filename = r->uri;
|
|
|
|
if (!(r->args = defender_strdup(r->pool, query->str, query->len)))
|
|
goto out;
|
|
|
|
r->parsed_uri.query = r->args;
|
|
|
|
r->protocol = defender_printf(r->pool, "%s%.*s",
|
|
version->len > 0 ? "HTTP/" : "",
|
|
version->len, version->str);
|
|
if (!r->protocol)
|
|
goto out;
|
|
|
|
r->the_request = defender_printf(r->pool, "%.*s %s%s%s",
|
|
method->len, method->str,
|
|
r->unparsed_uri,
|
|
version->len > 0 ? " " : "",
|
|
r->protocol);
|
|
if (!r->the_request)
|
|
goto out;
|
|
|
|
/* headers */
|
|
if (request->headers.data.type != SMP_T_BIN)
|
|
goto misc;
|
|
|
|
hdr_ptr = request->headers.data.u.str.str;
|
|
hdr_end = hdr_ptr + request->headers.data.u.str.len;
|
|
|
|
while (1) {
|
|
memset(&hdr, 0, sizeof(hdr));
|
|
|
|
if (decode_varint(&hdr_ptr, hdr_end, &hdr.name.len) == -1)
|
|
goto out;
|
|
if (!(hdr.name.str = defender_strdup(r->pool, hdr_ptr, hdr.name.len)))
|
|
goto out;
|
|
|
|
hdr_ptr += hdr.name.len;
|
|
if (hdr_ptr > hdr_end)
|
|
goto out;
|
|
|
|
if (decode_varint(&hdr_ptr, hdr_end, &hdr.value.len) == -1)
|
|
goto out;
|
|
if (!(hdr.value.str = defender_strdup(r->pool, hdr_ptr, hdr.value.len)))
|
|
goto out;
|
|
|
|
hdr_ptr += hdr.value.len;
|
|
if (hdr_ptr > hdr_end)
|
|
goto out;
|
|
|
|
if (!hdr.name.len && !hdr.value.len)
|
|
break;
|
|
|
|
apr_table_setn(r->headers_in, hdr.name.str, hdr.value.str);
|
|
}
|
|
|
|
misc:
|
|
|
|
r->hostname = apr_table_get(r->headers_in, "Host");
|
|
if (!r->hostname)
|
|
r->hostname = defender_unknown_hostname;
|
|
r->parsed_uri.hostname = (char *)r->hostname;
|
|
|
|
r->content_type = apr_table_get(r->headers_in, "Content-Type");
|
|
r->content_encoding = apr_table_get(r->headers_in, "Content-Encoding");
|
|
ptr = apr_table_get(r->headers_in, "Content-Length");
|
|
if (ptr)
|
|
r->clength = strtol(ptr, NULL, 10);
|
|
|
|
/* body */
|
|
body = &request->body.data.u.str;
|
|
|
|
bb = apr_brigade_create(r->pool, c->bucket_alloc);
|
|
if (bb == NULL)
|
|
goto out;
|
|
|
|
d = defender_bucket_create(body, c->bucket_alloc);
|
|
if (d == NULL)
|
|
goto out;
|
|
|
|
APR_BRIGADE_INSERT_TAIL(bb, d);
|
|
|
|
e = apr_bucket_eos_create(c->bucket_alloc);
|
|
APR_BRIGADE_INSERT_TAIL(bb, e);
|
|
|
|
apr_table_setn(r->notes, DEFENDER_BRIGADE_REQUEST, (char *)bb);
|
|
|
|
/* process */
|
|
status = defender_process_headers(r);
|
|
|
|
if (status == DECLINED)
|
|
status = defender_process_body(r);
|
|
|
|
apr_brigade_cleanup(bb);
|
|
|
|
/* success */
|
|
if (status == DECLINED)
|
|
status = OK;
|
|
|
|
out:
|
|
|
|
if (r && r->pool) {
|
|
apr_table_clear(r->headers_in);
|
|
apr_table_clear(r->headers_out);
|
|
apr_table_clear(r->subprocess_env);
|
|
apr_table_clear(r->err_headers_out);
|
|
apr_table_clear(r->notes);
|
|
apr_pool_destroy(r->pool);
|
|
}
|
|
|
|
if (c && c->pool) {
|
|
apr_bucket_alloc_destroy(c->bucket_alloc);
|
|
apr_pool_destroy(c->pool);
|
|
}
|
|
|
|
return status;
|
|
}
|