Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix response body #84

Closed
wants to merge 15 commits into from
116 changes: 75 additions & 41 deletions src/ngx_http_modsecurity_body_filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ ngx_http_modsecurity_body_filter_init(void)

return NGX_OK;
}

ngx_int_t
ngx_http_modsecurity_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
int buffer_fully_loadead = 0;
ngx_chain_t *chain = in;

ngx_http_modsecurity_ctx_t *ctx = NULL;
ngx_chain_t *chain = in;
ngx_int_t ret;
ngx_pool_t *old_pool;
ngx_int_t is_request_processed = 0;
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ngx_http_modsecurity_conf_t *loc_cf = NULL;
ngx_list_part_t *part = &r->headers_out.headers.part;
Expand All @@ -49,14 +51,15 @@ ngx_http_modsecurity_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
return ngx_http_next_body_filter(r, in);
}

/* get context for request */
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);

dd("body filter, recovering ctx: %p", ctx);

if (ctx == NULL) {
if (ctx == NULL || r->filter_finalize) {
return ngx_http_next_body_filter(r, in);
}


#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
loc_cf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity_module);
if (loc_cf != NULL && loc_cf->sanity_checks_enabled != NGX_CONF_UNSET)
Expand Down Expand Up @@ -130,56 +133,87 @@ ngx_http_modsecurity_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
dd("%d header(s) were not inspected by ModSecurity, so exiting", worth_to_fail);
return ngx_http_filter_finalize_request(r,
&ngx_http_modsecurity_module, NGX_HTTP_INTERNAL_SERVER_ERROR);
&ngx_http_modsecurity_module, NGX_HTTP_INTERNAL_SERVER_ERROR);
}
}
#endif

for (; chain != NULL; chain = chain->next)
{
/* XXX: chain->buf->last_buf || chain->buf->last_in_chain */
if (chain->buf->last_buf) {
buffer_fully_loadead = 1;
for (chain = in; chain != NULL; chain = chain->next) {

ngx_buf_t *copy_buf;
ngx_chain_t* copy_chain;
is_request_processed = chain->buf->last_buf;
ngx_int_t data_size = chain->buf->end - chain->buf->start;
ngx_int_t data_offset = chain->buf->pos - chain->buf->start;
u_char *data = chain->buf->start;
msc_append_response_body(ctx->modsec_transaction, data,
chain->buf->end - data);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. Actual data must be referenced by .pos and .last. See #104

ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction,
r);
if (ret > 0) {
return ngx_http_filter_finalize_request(r,
&ngx_http_modsecurity_module, ret);
}
}

if (buffer_fully_loadead == 1)
{
int ret;
ngx_pool_t *old_pool;

for (chain = in; chain != NULL; chain = chain->next)
{
u_char *data = chain->buf->start;
if (data_size > 0){
copy_chain = ngx_alloc_chain_link(r->pool);
if (copy_chain == NULL) {
return NGX_ERROR;
}

msc_append_response_body(ctx->modsec_transaction, data, chain->buf->end - data);
ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r);
if (ret > 0) {
return ngx_http_filter_finalize_request(r,
&ngx_http_modsecurity_module, ret);
copy_buf = ngx_calloc_buf(r->pool);
if (copy_buf == NULL) {
return NGX_ERROR;
}
copy_buf->start = ngx_pcalloc(r->pool, data_size);
if (copy_buf->start == NULL) {
return NGX_ERROR;
}
ngx_memcpy(copy_buf->start, chain->buf->start, data_size);
copy_buf->pos = copy_buf->start + data_offset;
copy_buf->end = copy_buf->start + data_size;
copy_buf->last = copy_buf->pos + ngx_buf_size(chain->buf);
copy_buf->temporary = (chain->buf->temporary == 1) ? 1 : 0;
copy_buf->memory = (chain->buf->memory == 1) ? 1 : 0;
copy_chain->buf = copy_buf;
copy_chain->buf->last_buf = 1;
copy_chain->next = NULL;
chain->buf->pos = chain->buf->last;
}
else
copy_chain = chain;
if (ctx->temp_chain == NULL) {
ctx->temp_chain = copy_chain;
} else {
if (ctx->current_chain == NULL) {
ctx->temp_chain->next = copy_chain;
ctx->temp_chain->buf->last_buf = 0;
} else {
ctx->current_chain->next = copy_chain;
ctx->current_chain->buf->last_buf = 0;
}
ctx->current_chain = copy_chain;
}
}

if (is_request_processed) {
old_pool = ngx_http_modsecurity_pcre_malloc_init(r->pool);
msc_process_response_body(ctx->modsec_transaction);
ngx_http_modsecurity_pcre_malloc_done(old_pool);

/* XXX: I don't get how body from modsec being transferred to nginx's buffer. If so - after adjusting of nginx's
XXX: body we can proceed to adjust body size (content-length). see xslt_body_filter() for example */
ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r);
if (ret > 0) {
return ret;
}
else if (ret < 0) {
if (ret < NGX_HTTP_BAD_REQUEST && ctx->header_pt != NULL)
ctx->header_pt(r);
else return ngx_http_filter_finalize_request(r,
&ngx_http_modsecurity_module, ret);
} else if (ret < 0) {
return ngx_http_filter_finalize_request(r,
&ngx_http_modsecurity_module, NGX_HTTP_INTERNAL_SERVER_ERROR);
&ngx_http_modsecurity_module, NGX_HTTP_INTERNAL_SERVER_ERROR);
}
ctx->response_body_filtered = 1;
if (ctx->header_pt != NULL)
ctx->header_pt(r);
return ngx_http_next_body_filter(r, ctx->temp_chain);
} else {
return NGX_AGAIN;
}
else
{
dd("buffer was not fully loaded! ctx: %p", ctx);
}

/* XXX: xflt_filter() -- return NGX_OK here */
return ngx_http_next_body_filter(r, in);
}
5 changes: 4 additions & 1 deletion src/ngx_http_modsecurity_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ typedef struct {
ngx_str_t value;
} ngx_http_modsecurity_header_t;


typedef struct {
ngx_http_request_t *r;
Transaction *modsec_transaction;
Expand All @@ -52,6 +51,10 @@ typedef struct {
unsigned waiting_more_body:1;
unsigned body_requested:1;
unsigned processed:1;
ngx_http_output_header_filter_pt header_pt;
ngx_chain_t* temp_chain;
ngx_chain_t* current_chain;
unsigned response_body_filtered:1;
} ngx_http_modsecurity_ctx_t;


Expand Down
18 changes: 12 additions & 6 deletions src/ngx_http_modsecurity_header_filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -542,10 +542,16 @@ ngx_http_modsecurity_header_filter(ngx_http_request_t *r)
*
*/

/*
* The line below is commented to make the spdy test to work
*/
//r->headers_out.content_length_n = -1;

return ngx_http_next_header_filter(r);
/*
* The line below is commented to make the spdy test to work
*/
//r->headers_out.content_length_n = -1;

if (ctx->response_body_filtered){
return ngx_http_next_header_filter(r);
}
else {
ctx->header_pt = ngx_http_next_header_filter;
return NGX_AGAIN;
}
}
3 changes: 2 additions & 1 deletion src/ngx_http_modsecurity_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,8 @@ ngx_http_modsecurity_create_ctx(ngx_http_request_t *r)
ctx->modsec_transaction = msc_new_transaction(cf->modsec, loc_cf->rules_set, r->connection->log);

dd("transaction created");


ctx->response_body_filtered = 0;
ngx_http_set_ctx(r, ctx, ngx_http_modsecurity_module);

cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_http_modsecurity_ctx_t));
Expand Down
8 changes: 4 additions & 4 deletions tests/modsecurity-proxy.t
Original file line number Diff line number Diff line change
Expand Up @@ -116,25 +116,25 @@ unlike(http_head('/'), qr/SEE-THIS/, 'proxy head request');
like(http_get('/phase1?what=redirect302'), qr/302 Moved Temporarily/, 'redirect 302 - phase 1');
like(http_get('/phase2?what=redirect302'), qr/302 Moved Temporarily/, 'redirect 302 - phase 2');
like(http_get('/phase3?what=redirect302'), qr/302 Moved Temporarily/, 'redirect 302 - phase 3');
is(http_get('/phase4?what=redirect302'), '', 'redirect 302 - phase 4');
like(http_get('/phase4?what=redirect302'), qr/404/, 'redirect 302 - phase 4');

# Redirect (301)
like(http_get('/phase1?what=redirect301'), qr/301 Moved Permanently/, 'redirect 301 - phase 1');
like(http_get('/phase2?what=redirect301'), qr/301 Moved Permanently/, 'redirect 301 - phase 2');
like(http_get('/phase3?what=redirect301'), qr/301 Moved Permanently/, 'redirect 301 - phase 3');
is(http_get('/phase4?what=redirect301'), '', 'redirect 301 - phase 4');
like(http_get('/phase4?what=redirect301'), qr/404/, 'redirect 301 - phase 4');

# Block (401)
like(http_get('/phase1?what=block401'), qr/401 Unauthorized/, 'block 401 - phase 1');
like(http_get('/phase2?what=block401'), qr/401 Unauthorized/, 'block 401 - phase 2');
like(http_get('/phase3?what=block401'), qr/401 Unauthorized/, 'block 401 - phase 3');
is(http_get('/phase4?what=block401'), '', 'block 401 - phase 4');
like(http_get('/phase4?what=block401'), qr/401 Unauthorized/, 'block 401 - phase 4');

# Block (403)
like(http_get('/phase1?what=block403'), qr/403 Forbidden/, 'block 403 - phase 1');
like(http_get('/phase2?what=block403'), qr/403 Forbidden/, 'block 403 - phase 2');
like(http_get('/phase3?what=block403'), qr/403 Forbidden/, 'block 403 - phase 3');
is(http_get('/phase4?what=block403'), '', 'block 403 - phase 4');
like(http_get('/phase4?what=block403'), qr/403 Forbidden/, 'block 403 - phase 4');

# Nothing to detect
like(http_get('/phase1?what=nothing'), qr/phase1\?what=nothing\' not found/, 'nothing phase 1');
Expand Down
8 changes: 4 additions & 4 deletions tests/modsecurity.t
Original file line number Diff line number Diff line change
Expand Up @@ -123,25 +123,25 @@ $t->plan(20);
like(http_get('/phase1?what=redirect302'), qr/302 Moved Temporarily/, 'redirect 302 - phase 1');
like(http_get('/phase2?what=redirect302'), qr/302 Moved Temporarily/, 'redirect 302 - phase 2');
like(http_get('/phase3?what=redirect302'), qr/302 Moved Temporarily/, 'redirect 302 - phase 3');
is(http_get('/phase4?what=redirect302'), '', 'redirect 302 - phase 4');
like(http_get('/phase4?what=redirect302'), qr/200/, 'redirect 302 - phase 4');

# Redirect (301)
like(http_get('/phase1?what=redirect301'), qr/301 Moved Permanently/, 'redirect 301 - phase 1');
like(http_get('/phase2?what=redirect301'), qr/301 Moved Permanently/, 'redirect 301 - phase 2');
like(http_get('/phase3?what=redirect301'), qr/301 Moved Permanently/, 'redirect 301 - phase 3');
is(http_get('/phase4?what=redirect301'), '', 'redirect 301 - phase 4');
like(http_get('/phase4?what=redirect301'), qr/200/, 'redirect 301 - phase 4');

# Block (401)
like(http_get('/phase1?what=block401'), qr/401 Unauthorized/, 'block 401 - phase 1');
like(http_get('/phase2?what=block401'), qr/401 Unauthorized/, 'block 401 - phase 2');
like(http_get('/phase3?what=block401'), qr/401 Unauthorized/, 'block 401 - phase 3');
is(http_get('/phase4?what=block401'), '', 'block 401 - phase 4');
like(http_get('/phase4?what=block401'), qr/401 Unauthorized/, 'block 401 - phase 4');

# Block (403)
like(http_get('/phase1?what=block403'), qr/403 Forbidden/, 'block 403 - phase 1');
like(http_get('/phase2?what=block403'), qr/403 Forbidden/, 'block 403 - phase 2');
like(http_get('/phase3?what=block403'), qr/403 Forbidden/, 'block 403 - phase 3');
is(http_get('/phase4?what=block403'), '', 'block 403 - phase 4');
like(http_get('/phase4?what=block403'), qr/403 Forbidden/, 'block 403 - phase 4');

# Nothing to detect
like(http_get('/phase1?what=nothing'), qr/should be moved\/blocked before this./, 'nothing phase 1');
Expand Down