Skip to content

Commit b9fdc4f

Browse files
author
Felipe Zimmerle
committed
Adds support to suspicious and whitelist to Read and Write limits
The operators @ipMatch, @ipMatchF and @ipMatchFromFile were added to the functions: SecReadStateLimit and SecReadStateLimit, by using them it is possible to declare a suspicious list. When a suspicious list is given, the {Read|Write}StateLimit will be applied just to the IPs that belongs to that restricted list. Note that the negative of those operators (e.g. !@ipMatch) can be used to place a whitelist. The {Read|Write}StateLimit restrictions will not be applied to those in the whitelist. This current version the Sec{Read|Write}StateLimit can be used varios times to add elements to both lists, however, the last informed limit will be applied for the entire group. This feature is experimental, and suggestions on how to improve it are very welcome. For further discussion use the issue: #353.
1 parent 8ff3de5 commit b9fdc4f

File tree

7 files changed

+518
-241
lines changed

7 files changed

+518
-241
lines changed

apache2/apache2_config.c

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1670,29 +1670,101 @@ static const char *cmd_rule_perf_time(cmd_parms *cmd, void *_dcfg,
16701670
return NULL;
16711671
}
16721672

1673+
char *parser_conn_limits_operator(apr_pool_t *mp, const char *p2,
1674+
TreeRoot **whitelist, msre_ipmatch **whitelist_param,
1675+
TreeRoot **suspicious_list, msre_ipmatch **suspicious_list_param,
1676+
const char *filename)
1677+
{
1678+
int res = 0;
1679+
char *config_orig_path;
1680+
char *param = strchr(p2, ' ');
1681+
char *file = NULL;
1682+
char *error_msg = NULL;
1683+
param++;
1684+
1685+
config_orig_path = apr_pstrndup(mp, filename,
1686+
strlen(filename) - strlen(apr_filepath_name_get(filename)));
1687+
1688+
apr_filepath_merge(&file, config_orig_path, param, APR_FILEPATH_TRUENAME,
1689+
mp);
1690+
1691+
if ((strncasecmp(p2, "!@ipMatchFromFile", strlen("!@ipMatchFromFile")) == 0) ||
1692+
(strncasecmp(p2, "!@ipMatchF", strlen("!@ipMatchF")) == 0)) {
1693+
1694+
res = ip_tree_from_file(whitelist,
1695+
file, mp, NULL);
1696+
}
1697+
else if (strncasecmp(p2, "!@ipMatch", strlen("!@ipMatch")) == 0) {
1698+
res = ip_list_from_param(mp, param,
1699+
whitelist_param, &error_msg);
1700+
}
1701+
else if ((strncasecmp(p2, "@ipMatchFromFile", strlen("@ipMatchFromFile")) == 0) ||
1702+
(strncasecmp(p2, "@ipMatchF", strlen("@ipMatchF")) == 0)) {
1703+
1704+
res = ip_tree_from_file(suspicious_list,
1705+
file, mp, NULL);
1706+
}
1707+
else if (strncasecmp(p2, "@ipMatch", strlen("@ipMatch")) == 0) {
1708+
res = ip_list_from_param(mp, param,
1709+
suspicious_list_param, &error_msg);
1710+
}
1711+
else {
1712+
return apr_psprintf(mp, "ModSecurity: Invalid operator for " \
1713+
"SecReadStateLimit: %s, expected operators: @ipMatch, @ipMatchF " \
1714+
"or @ipMatchFromFile with or without !", p2);
1715+
}
1716+
1717+
if (res) {
1718+
char *error;
1719+
error = apr_psprintf(mp, "ModSecurity: failed to load IPs " \
1720+
"from: %s", param);
1721+
1722+
if (*error_msg) {
1723+
error = apr_psprintf(mp, "%s %s", error, error_msg);
1724+
}
1725+
1726+
return error;
1727+
}
1728+
1729+
return NULL;
1730+
}
1731+
1732+
16731733
/**
16741734
* \brief Add SecReadStateLimit configuration option
16751735
*
16761736
* \param cmd Pointer to configuration data
16771737
* \param _dcfg Pointer to directory configuration
16781738
* \param p1 Pointer to configuration option
1739+
* \param p2 Pointer to configuration option
16791740
*
16801741
* \retval NULL On failure
16811742
* \retval apr_psprintf On Success
16821743
*/
16831744
static const char *cmd_conn_read_state_limit(cmd_parms *cmd, void *_dcfg,
1684-
const char *p1)
1745+
const char *p1, const char *p2)
16851746
{
16861747
directory_config *dcfg = (directory_config *)_dcfg;
16871748
long int limit;
16881749

16891750
if (dcfg == NULL) return NULL;
16901751

16911752
limit = strtol(p1, NULL, 10);
1692-
if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) {
1693-
return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecReadStateLimit: %s", p1);
1753+
if ((limit == LONG_MAX) || (limit == LONG_MIN) || (limit <= 0)) {
1754+
return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for " \
1755+
"SecReadStateLimit: %s", p1);
16941756
}
16951757

1758+
if (p2 != NULL) {
1759+
char *param = parser_conn_limits_operator(cmd->pool, p2,
1760+
&conn_read_state_whitelist, &conn_read_state_whitelist_param,
1761+
&conn_read_state_suspicious_list,
1762+
&conn_read_state_suspicious_list_param, cmd->directive->filename);
1763+
1764+
if (param)
1765+
return param;
1766+
}
1767+
16961768
conn_read_state_limit = limit;
16971769

16981770
return NULL;
@@ -1704,21 +1776,33 @@ static const char *cmd_conn_read_state_limit(cmd_parms *cmd, void *_dcfg,
17041776
* \param cmd Pointer to configuration data
17051777
* \param _dcfg Pointer to directory configuration
17061778
* \param p1 Pointer to configuration option
1779+
* \param p2 Pointer to configuration option
17071780
*
17081781
* \retval NULL On failure
17091782
* \retval apr_psprintf On Success
17101783
*/
17111784
static const char *cmd_conn_write_state_limit(cmd_parms *cmd, void *_dcfg,
1712-
const char *p1)
1785+
const char *p1, const char *p2)
17131786
{
17141787
directory_config *dcfg = (directory_config *)_dcfg;
17151788
long int limit;
17161789

17171790
if (dcfg == NULL) return NULL;
17181791

17191792
limit = strtol(p1, NULL, 10);
1720-
if ((limit == LONG_MAX)||(limit == LONG_MIN)||(limit <= 0)) {
1721-
return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for SecWriteStateLimit: %s", p1);
1793+
if ((limit == LONG_MAX) || (limit == LONG_MIN) || (limit <= 0)) {
1794+
return apr_psprintf(cmd->pool, "ModSecurity: Invalid value for " \
1795+
"SecWriteStateLimit: %s", p1);
1796+
}
1797+
1798+
if (p2 != NULL) {
1799+
char *param = parser_conn_limits_operator(cmd->pool, p2,
1800+
&conn_write_state_whitelist, &conn_write_state_whitelist_param,
1801+
&conn_write_state_suspicious_list,
1802+
&conn_write_state_suspicious_list_param, cmd->directive->filename);
1803+
1804+
if (param)
1805+
return param;
17221806
}
17231807

17241808
conn_write_state_limit = limit;
@@ -3192,15 +3276,15 @@ const command_rec module_directives[] = {
31923276
"Threshold to log slow rules in usecs."
31933277
),
31943278

3195-
AP_INIT_TAKE1 (
3279+
AP_INIT_TAKE12 (
31963280
"SecReadStateLimit",
31973281
cmd_conn_read_state_limit,
31983282
NULL,
31993283
CMD_SCOPE_ANY,
32003284
"maximum number of threads in READ_BUSY state per ip address"
32013285
),
32023286

3203-
AP_INIT_TAKE1 (
3287+
AP_INIT_TAKE12 (
32043288
"SecWriteStateLimit",
32053289
cmd_conn_write_state_limit,
32063290
NULL,

apache2/mod_security2.c

Lines changed: 95 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ unsigned long int DSOLOCAL msc_pcre_match_limit_recursion = 0;
6464
int DSOLOCAL status_engine_state = STATUS_ENGINE_DISABLED;
6565

6666
unsigned long int DSOLOCAL conn_read_state_limit = 0;
67+
TreeRoot DSOLOCAL *conn_read_state_whitelist = 0;
68+
TreeRoot DSOLOCAL *conn_read_state_suspicious_list = 0;
69+
msre_ipmatch DSOLOCAL *conn_read_state_whitelist_param = 0;
70+
msre_ipmatch DSOLOCAL *conn_read_state_suspicious_list_param = 0;
71+
72+
TreeRoot DSOLOCAL *conn_write_state_whitelist = 0;
73+
TreeRoot DSOLOCAL *conn_write_state_suspicious_list = 0;
74+
msre_ipmatch DSOLOCAL *conn_write_state_whitelist_param = 0;
75+
msre_ipmatch DSOLOCAL *conn_write_state_suspicious_list_param = 0;
6776

6877
unsigned long int DSOLOCAL conn_write_state_limit = 0;
6978

@@ -1363,29 +1372,30 @@ static int hook_connection_early(conn_rec *conn)
13631372
{
13641373
sb_handle *sb = conn->sbh;
13651374
int i, j;
1366-
unsigned long int ip_count = 0, ip_count_w = 0;
1375+
unsigned long int ip_count_r = 0, ip_count_w = 0;
1376+
char *error_msg;
13671377
worker_score *ws_record = NULL;
13681378
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
13691379
ap_sb_handle_t *sbh = NULL;
1380+
char *client_ip = conn->client_ip;
1381+
#else
1382+
char *client_ip = conn->remote_ip;
13701383
#endif
13711384

1372-
if(sb != NULL && (conn_read_state_limit > 0 || conn_write_state_limit > 0)) {
1385+
if (sb != NULL && (conn_read_state_limit > 0 || conn_write_state_limit > 0)) {
13731386

13741387
ws_record = &ap_scoreboard_image->servers[sb->child_num][sb->thread_num];
1375-
if(ws_record == NULL)
1388+
if (ws_record == NULL)
13761389
return DECLINED;
13771390

1378-
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
1379-
apr_cpystrn(ws_record->client, conn->client_ip, sizeof(ws_record->client));
1380-
#else
1381-
apr_cpystrn(ws_record->client, conn->remote_ip, sizeof(ws_record->client));
1382-
#endif
1391+
apr_cpystrn(ws_record->client, client_ip, sizeof(ws_record->client));
1392+
13831393
for (i = 0; i < server_limit; ++i) {
13841394
for (j = 0; j < thread_limit; ++j) {
13851395

13861396
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
13871397
sbh = conn->sbh;
1388-
if (sbh == NULL) {
1398+
if (sbh == NULL) {
13891399
return DECLINED;
13901400
}
13911401

@@ -1394,50 +1404,96 @@ static int hook_connection_early(conn_rec *conn)
13941404
ws_record = ap_get_scoreboard_worker(i, j);
13951405
#endif
13961406

1397-
if(ws_record == NULL)
1407+
if (ws_record == NULL)
13981408
return DECLINED;
13991409

14001410
switch (ws_record->status) {
14011411
case SERVER_BUSY_READ:
1402-
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
1403-
if (strcmp(conn->client_ip, ws_record->client) == 0)
1404-
ip_count++;
1405-
#else
1406-
if (strcmp(conn->remote_ip, ws_record->client) == 0)
1407-
ip_count++;
1408-
#endif
1412+
if (strcmp(client_ip, ws_record->client) == 0)
1413+
ip_count_r++;
14091414
break;
1415+
14101416
case SERVER_BUSY_WRITE:
1411-
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
1412-
if (strcmp(conn->client_ip, ws_record->client) == 0)
1413-
ip_count_w++;
1414-
#else
1415-
if (strcmp(conn->remote_ip, ws_record->client) == 0)
1417+
if (strcmp(client_ip, ws_record->client) == 0)
14161418
ip_count_w++;
1417-
#endif
1419+
14181420
break;
14191421
default:
14201422
break;
14211423
}
14221424
}
14231425
}
14241426

1425-
if ((conn_read_state_limit > 0) && (ip_count > conn_read_state_limit)) {
1426-
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
1427-
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, "ModSecurity: Access denied with code 400. Too many threads [%ld] of %ld allowed in READ state from %s - Possible DoS Consumption Attack [Rejected]", ip_count,conn_read_state_limit,conn->client_ip);
1428-
#else
1429-
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, "ModSecurity: Access denied with code 400. Too many threads [%ld] of %ld allowed in READ state from %s - Possible DoS Consumption Attack [Rejected]", ip_count,conn_read_state_limit,conn->remote_ip);
1430-
#endif
1431-
return OK;
1432-
} else if ((conn_write_state_limit > 0) && (ip_count_w > conn_write_state_limit)) {
1433-
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
1434-
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, "ModSecurity: Access denied with code 400. Too many threads [%ld] of %ld allowed in WRITE state from %s - Possible DoS Consumption Attack [Rejected]", ip_count_w,conn_write_state_limit,conn->client_ip);
1435-
#else
1436-
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, "ModSecurity: Access denied with code 400. Too many threads [%ld] of %ld allowed in WRITE state from %s - Possible DoS Consumption Attack [Rejected]", ip_count_w,conn_write_state_limit,conn->remote_ip);
1437-
#endif
1438-
return OK;
1439-
} else {
1440-
return DECLINED;
1427+
1428+
if (conn_read_state_limit > 0 && ip_count_r > conn_read_state_limit)
1429+
{
1430+
if (conn_read_state_suspicious_list &&
1431+
(!((tree_contains_ip(conn->pool,
1432+
conn_read_state_suspicious_list, client_ip, NULL, &error_msg) <= 0) ||
1433+
(list_contains_ip(conn->pool,
1434+
conn_read_state_suspicious_list_param, client_ip, &error_msg) <= 0))))
1435+
{
1436+
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL,
1437+
"ModSecurity: Too many threads [%ld] of %ld allowed in " \
1438+
"READ state from %s - There is a suspission list but " \
1439+
"that IP is not part of it, access granted", ip_count_r,
1440+
conn_read_state_limit, client_ip);
1441+
}
1442+
1443+
else if ((tree_contains_ip(conn->pool,
1444+
conn_read_state_whitelist, client_ip, NULL, &error_msg) > 0) ||
1445+
(list_contains_ip(conn->pool,
1446+
conn_read_state_whitelist_param, client_ip, &error_msg) > 0))
1447+
{
1448+
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL,
1449+
"ModSecurity: Too many threads [%ld] of %ld allowed in " \
1450+
"READ state from %s - Ip is on whitelist, access granted",
1451+
ip_count_r, conn_read_state_limit, client_ip);
1452+
}
1453+
else
1454+
{
1455+
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL,
1456+
"ModSecurity: Access denied with code 400. Too many " \
1457+
"threads [%ld] of %ld allowed in READ state from %s - " \
1458+
"Possible DoS Consumption Attack [Rejected]", ip_count_r,
1459+
conn_read_state_limit, client_ip);
1460+
return OK;
1461+
}
1462+
}
1463+
1464+
if (conn_write_state_limit > 0 && ip_count_w > conn_write_state_limit)
1465+
{
1466+
if (conn_write_state_suspicious_list &&
1467+
(!((tree_contains_ip(conn->pool,
1468+
conn_write_state_suspicious_list, client_ip, NULL, &error_msg) <= 0) ||
1469+
(list_contains_ip(conn->pool,
1470+
conn_write_state_suspicious_list_param, client_ip, &error_msg) <= 0))))
1471+
{
1472+
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL,
1473+
"ModSecurity: Too many threads [%ld] of %ld allowed in " \
1474+
"WRITE state from %s - There is a suspission list but " \
1475+
"that IP is not part of it, access granted", ip_count_w,
1476+
conn_read_state_limit, client_ip);
1477+
}
1478+
else if ((tree_contains_ip(conn->pool,
1479+
conn_write_state_whitelist, client_ip, NULL, &error_msg) > 0) ||
1480+
(list_contains_ip(conn->pool,
1481+
conn_write_state_whitelist_param, client_ip, &error_msg) > 0))
1482+
{
1483+
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL,
1484+
"ModSecurity: Too many threads [%ld] of %ld allowed in " \
1485+
"WRITE state from %s - Ip is on whitelist, access granted",
1486+
ip_count_w, conn_read_state_limit, client_ip);
1487+
}
1488+
else
1489+
{
1490+
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL,
1491+
"ModSecurity: Access denied with code 400. Too many " \
1492+
"threads [%ld] of %ld allowed in WRITE state from %s - " \
1493+
"Possible DoS Consumption Attack [Rejected]", ip_count_w,
1494+
conn_write_state_limit, client_ip);
1495+
return OK;
1496+
}
14411497
}
14421498
}
14431499

apache2/modsecurity.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ typedef struct msc_parm msc_parm;
4040
#include "msc_util.h"
4141
#include "msc_json.h"
4242
#include "msc_xml.h"
43+
#include "msc_tree.h"
4344
#include "msc_geo.h"
4445
#include "msc_gsb.h"
4546
#include "msc_unicode.h"
@@ -145,8 +146,16 @@ extern DSOLOCAL unsigned long int msc_pcre_match_limit_recursion;
145146
extern DSOLOCAL int status_engine_state;
146147

147148
extern DSOLOCAL unsigned long int conn_read_state_limit;
149+
extern DSOLOCAL TreeRoot *conn_read_state_whitelist;
150+
extern DSOLOCAL TreeRoot *conn_read_state_suspicious_list;
151+
extern DSOLOCAL msre_ipmatch *conn_read_state_whitelist_param;
152+
extern DSOLOCAL msre_ipmatch *conn_read_state_suspicious_list_param;
148153

149154
extern DSOLOCAL unsigned long int conn_write_state_limit;
155+
extern DSOLOCAL TreeRoot *conn_write_state_whitelist;
156+
extern DSOLOCAL TreeRoot *conn_write_state_suspicious_list;
157+
extern DSOLOCAL msre_ipmatch *conn_write_state_whitelist_param;
158+
extern DSOLOCAL msre_ipmatch *conn_write_state_suspicious_list_param;
150159

151160
extern DSOLOCAL unsigned long int unicode_codepage;
152161

0 commit comments

Comments
 (0)