blob: 370682bcf190d53eefea48c3f19ac52b999834e2 [file] [log] [blame]
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +02001/*
2 * Modsecurity wrapper for haproxy
3 *
4 * This file contains the wrapper which sends data in ModSecurity
5 * and returns the verdict.
6 *
7 * Copyright 2016 OZON, Thierry Fournier <thierry.fournier@ozon.io>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version
12 * 2 of the License, or (at your option) any later version.
13 *
14 */
15#include <limits.h>
16#include <stdio.h>
17#include <stdarg.h>
18
19#include <common/time.h>
20
21#include <types/global.h>
22#include <types/stream.h>
23
24#include <proto/arg.h>
25#include <proto/hdr_idx.h>
26#include <proto/hlua.h>
27#include <proto/log.h>
28#include <proto/proto_http.h>
29#include <proto/spoe.h>
30
31#include <api.h>
32
33#include "modsec_wrapper.h"
34#include "spoa.h"
35
36static char host_name[60];
37
38/* Note: The document and the code of "apr_table_make" considers
39 * that this function doesn't fails. The Apache APR code says
40 * other thing. If the system doesn't have any more memory, a
41 * a segfault occurs :(. Be carrefull with this module.
42 */
43
44struct directory_config *modsec_config = NULL;
45static server_rec *modsec_server = NULL;
46
47struct apr_bucket_haproxy {
48 apr_bucket_refcount refcount;
49 char *buffer;
50 size_t length;
51};
52
53static void haproxy_bucket_destroy(void *data)
54{
55 struct apr_bucket_haproxy *bucket = data;
56
57 if (apr_bucket_shared_destroy(bucket))
58 apr_bucket_free(bucket);
59}
60
61static apr_status_t haproxy_bucket_read(apr_bucket *bucket, const char **str,
62 apr_size_t *len, apr_read_type_e block)
63{
64 struct apr_bucket_haproxy *data = bucket->data;
65
66 if (bucket->start) {
67 *str = NULL;
68 *len = 0;
69 return APR_SUCCESS;
70 }
71
72 *str = data->buffer;
73 *len = data->length;
74 bucket->start = 1; /* Just a flag to say that the read is started */
75
76 return APR_SUCCESS;
77}
78
79static const apr_bucket_type_t apr_bucket_type_haproxy = {
80 "HAProxy", 7, APR_BUCKET_DATA,
81 haproxy_bucket_destroy,
82 haproxy_bucket_read,
83 apr_bucket_setaside_noop,
84 apr_bucket_shared_split,
85 apr_bucket_shared_copy
86};
87
88static char *chunk_strdup(struct request_rec *req, const char *str, size_t len)
89{
90 char *out;
91
92 out = apr_pcalloc(req->pool, len + 1);
93 if (!out)
94 return NULL;
95 memcpy(out, str, len);
96 out[len] = '\0';
97 return out;
98}
99
100static char *printf_dup(struct request_rec *req, char *fmt, ...)
101{
102 char *out;
103 va_list ap;
104 int len;
105
106 va_start(ap, fmt);
107 len = vsnprintf(NULL, 0, fmt, ap);
Dragan Dosen2f1cacb2017-09-18 09:20:43 +0200108 va_end(ap);
109
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +0200110 if (len == -1)
111 return NULL;
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +0200112
113 out = apr_pcalloc(req->pool, len + 1);
114 if (!out)
115 return NULL;
116
117 va_start(ap, fmt);
118 len = vsnprintf(out, len + 1, fmt, ap);
Dragan Dosen2f1cacb2017-09-18 09:20:43 +0200119 va_end(ap);
120
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +0200121 if (len == -1)
122 return NULL;
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +0200123
124 return out;
125}
126
127/* This function send logs. For now, it do nothing. */
128static void modsec_log(void *obj, int level, char *str)
129{
130 LOG(&null_worker, "%s", str);
131}
132
Joseph Herlant9fe83fa2018-11-09 18:25:59 -0800133/* This function load the ModSecurity file. It returns -1 if the
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +0200134 * initialisation fails.
135 */
136int modsecurity_load(const char *file)
137{
138 const char *msg;
139 char cwd[128];
140
141 /* Initialises modsecurity. */
142
143 modsec_server = modsecInit();
144 if (modsec_server == NULL) {
145 LOG(&null_worker, "ModSecurity initilisation failed.\n");
146 return -1;
147 }
148
149 modsecSetLogHook(NULL, modsec_log);
150
151 gethostname(host_name, 60);
152 modsec_server->server_hostname = host_name;
153
154 modsecStartConfig();
155
156 modsec_config = modsecGetDefaultConfig();
157 if (modsec_config == NULL) {
158 LOG(&null_worker, "ModSecurity default configuration initilisation failed.\n");
159 return -1;
160 }
161
162 msg = modsecProcessConfig(modsec_config, file, getcwd(cwd, 128));
163 if (msg != NULL) {
164 LOG(&null_worker, "ModSecurity load configuration failed.\n");
165 return -1;
166 }
167
168 modsecFinalizeConfig();
169
170 modsecInitProcess();
171
172 return 1;
173}
174
175struct modsec_hdr {
176 const char *name;
177 uint64_t name_len;
178 const char *value;
179 uint64_t value_len;
180};
181
182int modsecurity_process(struct worker *worker, struct modsecurity_parameters *params)
183{
184 struct conn_rec *cr;
185 struct request_rec *req;
186 struct apr_bucket_brigade *brigade;
187 struct apr_bucket *link_bucket;
188 struct apr_bucket_haproxy *data_bucket;
189 struct apr_bucket *last_bucket;
190 int i;
191 long clength;
192 char *err;
193 int fail;
194 const char *lang;
195 char *name, *value;
196 // int body_partial;
197 struct timeval now;
198 int ret;
199 char *buf;
200 char *end;
201 const char *uniqueid;
202 uint64_t uniqueid_len;
203 const char *meth;
204 uint64_t meth_len;
205 const char *path;
206 uint64_t path_len;
207 const char *qs;
208 uint64_t qs_len;
209 const char *vers;
210 uint64_t vers_len;
211 const char *body;
212 uint64_t body_len;
213 uint64_t body_exposed_len;
214 uint64_t hdr_nb;
215 struct modsec_hdr hdrs[255];
216 struct modsec_hdr hdr;
217 int status;
218 int return_code = -1;
219
220 /* Decode uniqueid. */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200221 uniqueid = params->uniqueid.data.u.str.area;
222 uniqueid_len = params->uniqueid.data.u.str.data;
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +0200223
224 /* Decode method. */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200225 meth = params->method.data.u.str.area;
226 meth_len = params->method.data.u.str.data;
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +0200227
228 /* Decode path. */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200229 path = params->path.data.u.str.area;
230 path_len = params->path.data.u.str.data;
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +0200231
232 /* Decode query string. */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200233 qs = params->query.data.u.str.area;
234 qs_len = params->query.data.u.str.data;
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +0200235
236 /* Decode version. */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200237 vers = params->vers.data.u.str.area;
238 vers_len = params->vers.data.u.str.data;
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +0200239
240 /* Decode header binary block. */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200241 buf = params->hdrs_bin.data.u.str.area;
242 end = buf + params->hdrs_bin.data.u.str.data;
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +0200243
244 /* Decode each header. */
245 hdr_nb = 0;
246 while (1) {
247
248 /* Initialise the storage struct. It is useless
249 * because the process fail if the struct is not
250 * fully filled. This init is just does in order
251 * to prevent bug after some improvements.
252 */
253 memset(&hdr, 0, sizeof(hdr));
254
255 /* Decode header name. */
256 ret = decode_varint(&buf, end, &hdr.name_len);
257 if (ret == -1)
258 return -1;
259 hdr.name = buf;
260 buf += hdr.name_len;
261 if (buf > end)
262 return -1;
263
264 /* Decode header value. */
265 ret = decode_varint(&buf, end, &hdr.value_len);
266 if (ret == -1)
267 return -1;
268 hdr.value = buf;
269 buf += hdr.value_len;
270 if (buf > end)
271 return -1;
272
273 /* Detect the end of the headers. */
274 if (hdr.name_len == 0 && hdr.value_len == 0)
275 break;
276
277 /* Store the header. */
278 if (hdr_nb < 255) {
279 memcpy(&hdrs[hdr_nb], &hdr, sizeof(hdr));
280 hdr_nb++;
281 }
282 }
283
284 /* Decode body length. Note that the following control
285 * is just set for avoifing a gcc warning.
286 */
287 body_exposed_len = (uint64_t)params->body_length.data.u.sint;
288 if (body_exposed_len < 0)
289 return -1;
290
291 /* Decode body. */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200292 body = params->body.data.u.str.area;
293 body_len = params->body.data.u.str.data;
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +0200294
295 fail = 1;
296
297 /* Init processing */
298
299 cr = modsecNewConnection();
300 req = modsecNewRequest(cr, modsec_config);
301
302 /* Load request. */
303
304 req->proxyreq = PROXYREQ_NONE;
305 req->header_only = 0; /* May modified later */
306
307 /* Copy header list. */
308
309 for (i = 0; i < hdr_nb; i++) {
310 name = chunk_strdup(req, hdrs[i].name, hdrs[i].name_len);
311 if (!name) {
312 errno = ENOMEM;
313 goto fail;
314 }
315 value = chunk_strdup(req, hdrs[i].value, hdrs[i].value_len);
316 if (!value) {
317 errno = ENOMEM;
318 goto fail;
319 }
320 apr_table_setn(req->headers_in, name, value);
321 }
322
323 /* Process special headers. */
324 req->range = apr_table_get(req->headers_in, "Range");
325 req->content_type = apr_table_get(req->headers_in, "Content-Type");
326 req->content_encoding = apr_table_get(req->headers_in, "Content-Encoding");
327 req->hostname = apr_table_get(req->headers_in, "Host");
Yann Cézardbf60f6b2019-04-25 14:30:23 +0200328 if (req->hostname != NULL) {
329 req->parsed_uri.hostname = chunk_strdup(req, req->hostname, strlen(req->hostname));
330 } else {
331 req->parsed_uri.hostname = NULL;
332 }
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +0200333
334 lang = apr_table_get(req->headers_in, "Content-Languages");
335 if (lang != NULL) {
336 req->content_languages = apr_array_make(req->pool, 1, sizeof(const char *));
337 *(const char **)apr_array_push(req->content_languages) = lang;
338 }
339
340 lang = apr_table_get(req->headers_in, "Content-Length");
341 if (lang) {
342 errno = 0;
343 clength = strtol(lang, &err, 10);
344 if (*err != '\0' || errno != 0 || clength < 0 || clength > INT_MAX) {
345 errno = ERANGE;
346 goto fail;
347 }
348 req->clength = clength;
349 }
350
351 /* Copy the first line of the request. */
352 req->the_request = printf_dup(req, "%.*s %.*s%s%.*s %.*s",
353 meth_len, meth,
354 path_len, path,
355 qs_len > 0 ? "?" : "",
356 qs_len, qs,
357 vers_len, vers);
358 if (!req->the_request) {
359 errno = ENOMEM;
360 goto fail;
361 }
362
363 /* Copy the method. */
364 req->method = chunk_strdup(req, meth, meth_len);
365 if (!req->method) {
366 errno = ENOMEM;
367 goto fail;
368 }
369
370 /* Set the method number. */
371 if (meth_len < 3) {
372 errno = EINVAL;
373 goto fail;
374 }
375
376 /* Detect the method */
377 switch (meth_len) {
378 case 3:
379 if (strncmp(req->method, "GET", 3) == 0)
380 req->method_number = M_GET;
381 else if (strncmp(req->method, "PUT", 3) == 0)
382 req->method_number = M_PUT;
383 else {
384 errno = EINVAL;
385 goto fail;
386 }
387 break;
388 case 4:
389 if (strncmp(req->method, "POST", 4) == 0)
390 req->method_number = M_POST;
391 else if (strncmp(req->method, "HEAD", 4) == 0) {
392 req->method_number = M_GET;
393 req->header_only = 1;
394 }
395 else if (strncmp(req->method, "COPY", 4) == 0)
396 req->method_number = M_COPY;
397 else if (strncmp(req->method, "MOVE", 4) == 0)
398 req->method_number = M_MOVE;
399 else if (strncmp(req->method, "LOCK", 4) == 0)
400 req->method_number = M_LOCK;
401 else {
402 errno = EINVAL;
403 goto fail;
404 }
405 break;
406 case 5:
407 if (strncmp(req->method, "TRACE", 5) == 0)
408 req->method_number = M_TRACE;
409 else if (strncmp(req->method, "PATCH", 5) == 0)
410 req->method_number = M_PATCH;
411 else if (strncmp(req->method, "MKCOL", 5) == 0)
412 req->method_number = M_MKCOL;
413 else if (strncmp(req->method, "MERGE", 5) == 0)
414 req->method_number = M_MERGE;
415 else if (strncmp(req->method, "LABEL", 5) == 0)
416 req->method_number = M_LABEL;
417 else {
418 errno = EINVAL;
419 goto fail;
420 }
421 break;
422 case 6:
423 if (strncmp(req->method, "DELETE", 6) == 0)
424 req->method_number = M_DELETE;
425 else if (strncmp(req->method, "REPORT", 6) == 0)
426 req->method_number = M_REPORT;
427 else if (strncmp(req->method, "UPDATE", 6) == 0)
428 req->method_number = M_UPDATE;
429 else if (strncmp(req->method, "UNLOCK", 6) == 0)
430 req->method_number = M_UNLOCK;
431 else {
432 errno = EINVAL;
433 goto fail;
434 }
435 break;
436 case 7:
437 if (strncmp(req->method, "CHECKIN", 7) == 0)
438 req->method_number = M_CHECKIN;
439 else if (strncmp(req->method, "INVALID", 7) == 0)
440 req->method_number = M_INVALID;
441 else if (strncmp(req->method, "CONNECT", 7) == 0)
442 req->method_number = M_CONNECT;
443 else if (strncmp(req->method, "OPTIONS", 7) == 0)
444 req->method_number = M_OPTIONS;
445 else {
446 errno = EINVAL;
447 goto fail;
448 }
449 break;
450 case 8:
451 if (strncmp(req->method, "PROPFIND", 8) == 0)
452 req->method_number = M_PROPFIND;
453 else if (strncmp(req->method, "CHECKOUT", 8) == 0)
454 req->method_number = M_CHECKOUT;
455 else {
456 errno = EINVAL;
457 goto fail;
458 }
459 break;
460 case 9:
461 if (strncmp(req->method, "PROPPATCH", 9) == 0)
462 req->method_number = M_PROPPATCH;
463 else {
464 errno = EINVAL;
465 goto fail;
466 }
467 break;
468 case 10:
469 if (strncmp(req->method, "MKACTIVITY", 10) == 0)
470 req->method_number = M_MKACTIVITY;
471 else if (strncmp(req->method, "UNCHECKOUT", 10) == 0)
472 req->method_number = M_UNCHECKOUT;
473 else {
474 errno = EINVAL;
475 goto fail;
476 }
477 break;
478 case 11:
479 if (strncmp(req->method, "MKWORKSPACE", 11) == 0)
480 req->method_number = M_MKWORKSPACE;
481 else {
482 errno = EINVAL;
483 goto fail;
484 }
485 break;
486 case 15:
487 if (strncmp(req->method, "VERSION_CONTROL", 15) == 0)
488 req->method_number = M_VERSION_CONTROL;
489 else {
490 errno = EINVAL;
491 goto fail;
492 }
493 break;
494 case 16:
495 if (strncmp(req->method, "BASELINE_CONTROL", 16) == 0)
496 req->method_number = M_BASELINE_CONTROL;
497 else {
498 errno = EINVAL;
499 goto fail;
500 }
501 break;
502 default:
503 errno = EINVAL;
504 goto fail;
505 }
506
507 /* Copy the protocol. */
508 req->protocol = chunk_strdup(req, vers, vers_len);
509 if (!req->protocol) {
510 errno = ENOMEM;
511 goto fail;
512 }
513
514 /* Compute the protocol number. */
515 if (vers_len >= 8)
516 req->proto_num = 1000 + !!(vers[7] == '1');
517
518 /* The request time. */
519 gettimeofday(&now, NULL);
520 req->request_time = apr_time_make(now.tv_sec, now.tv_usec / 1000);
521
522 /* No status line. */
523 req->status_line = NULL;
524 req->status = 0;
525
526 /* Copy path. */
527 req->parsed_uri.path = chunk_strdup(req, path, path_len);
528 if (!req->parsed_uri.path) {
529 errno = ENOMEM;
530 goto fail;
531 }
532
533 /* Copy args (query string). */
534 req->args = chunk_strdup(req, qs, qs_len);
535 if (!req->args) {
536 errno = ENOMEM;
537 goto fail;
538 }
539
540 /* Set parsed_uri */
541
542 req->parsed_uri.scheme = "http";
543
544 if (req->hostname && req->parsed_uri.scheme && req->parsed_uri.path) {
545 i = snprintf(NULL, 0, "%s://%s%s",
546 req->parsed_uri.scheme, req->hostname, req->parsed_uri.path);
547 req->uri = apr_pcalloc(req->pool, i + 1);
548 if (!req->uri) {
549 errno = ENOMEM;
550 goto fail;
551 }
552 i = snprintf(req->uri, i + 1, "%s://%s%s",
553 req->parsed_uri.scheme, req->hostname, req->parsed_uri.path);
554 }
555
556 req->filename = req->parsed_uri.path;
557
558 /* Set unique id */
559
560 apr_table_setn(req->subprocess_env, "UNIQUE_ID", chunk_strdup(req, uniqueid, uniqueid_len));
561
562 /*
563 *
564 * Load body.
565 *
566 */
567
568 /* Create an empty bucket brigade */
569 brigade = apr_brigade_create(req->pool, req->connection->bucket_alloc);
570 if (!brigade) {
571 errno = ENOMEM;
572 goto fail;
573 }
574
Joseph Herlant9fe83fa2018-11-09 18:25:59 -0800575 /* Stores HTTP body available data in a bucket */
Thierry FOURNIERa5ec06d2017-04-10 23:47:23 +0200576 data_bucket = apr_bucket_alloc(sizeof(*data_bucket), req->connection->bucket_alloc);
577 if (!data_bucket) {
578 errno = ENOMEM;
579 goto fail;
580 }
581 data_bucket->buffer = (char *)body;
582 data_bucket->length = body_len;
583
584 /* Create linked bucket */
585 link_bucket = apr_bucket_alloc(sizeof(*link_bucket), req->connection->bucket_alloc);
586 if (!link_bucket) {
587 errno = ENOMEM;
588 goto fail;
589 }
590 APR_BUCKET_INIT(link_bucket); /* link */
591 link_bucket->free = apr_bucket_free;
592 link_bucket->list = req->connection->bucket_alloc;
593 link_bucket = apr_bucket_shared_make(link_bucket, data_bucket, 0, body_len);
594 link_bucket->type = &apr_bucket_type_haproxy;
595
596 /* Insert the bucket at the end of the brigade. */
597 APR_BRIGADE_INSERT_TAIL(brigade, link_bucket);
598
599 /* Insert the last bucket. */
600 last_bucket = apr_bucket_eos_create(req->connection->bucket_alloc);
601 APR_BRIGADE_INSERT_TAIL(brigade, last_bucket);
602
603 /* Declares the bucket brigade in modsecurity */
604 modsecSetBodyBrigade(req, brigade);
605
606 /*
607 *
608 * Process analysis.
609 *
610 */
611
612 /* Process request headers analysis. */
613 status = modsecProcessRequestHeaders(req);
614 if (status != DECLINED && status != DONE)
615 return_code = status;
616
617 /* Process request body analysis. */
618 status = modsecProcessRequestBody(req);
619 if (status != DECLINED && status != DONE)
620 return_code = status;
621
622 /* End processing. */
623
624 fail = 0;
625 if (return_code == -1)
626 return_code = 0;
627
628fail:
629
630 modsecFinishRequest(req);
631 modsecFinishConnection(cr);
632
633 if (fail) {
634
635 /* errno == ERANGE / ENOMEM / EINVAL */
636 switch (errno) {
637 case ERANGE: LOG(worker, "Invalid range");
638 case ENOMEM: LOG(worker, "Out of memory error");
639 case EINVAL: LOG(worker, "Invalid value");
640 default: LOG(worker, "Unknown error");
641 }
642 }
643
644 return return_code;
645}