ngx_int_t ngx_http_modsecurity_log_handler(ngx_http_request_t *r) { ngx_http_modsecurity_ctx_t *ctx = NULL; ngx_http_modsecurity_conf_t *cf; ngx_pool_t *old_pool; dd("catching a new _log_ phase handler"); cf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity_module); if (cf == NULL || cf->enable != 1) { dd("ModSecurity not enabled... returning"); return NGX_OK; } /* if (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_POST && r->method != NGX_HTTP_HEAD) { dd("ModSecurity is not ready to deal with anything different from " \ "POST, GET or HEAD"); return NGX_OK; } */ ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); dd("recovering ctx: %p", ctx); if (ctx == NULL) { dd("something really bad happened here. returning NGX_ERROR"); return NGX_ERROR; } dd("calling msc_process_logging for %p", ctx); old_pool = ngx_http_modsecurity_pcre_malloc_init(r->pool); msc_process_logging(ctx->modsec_transaction); ngx_http_modsecurity_pcre_malloc_done(old_pool); return NGX_OK; }
ngx_int_t ngx_http_modsecurity_rewrite_handler(ngx_http_request_t *r) { ngx_http_modsecurity_ctx_t *ctx = NULL; ngx_http_modsecurity_conf_t *cf; ngx_pool_t *old_pool; cf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity_module); if (cf == NULL || cf->enable != 1) { dd("ModSecurity not enabled... returning"); return NGX_DECLINED; } /* if (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_POST && r->method != NGX_HTTP_HEAD) { dd("ModSecurity is not ready to deal with anything different from " \ "POST, GET or HEAD"); return NGX_DECLINED; } */ dd("catching a new _rewrite_ phase handler"); ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); dd("recovering ctx: %p", ctx); if (ctx == NULL) { int ret = 0; ngx_connection_t *connection = r->connection; /** * FIXME: We may want to use struct sockaddr instead of addr_text. * */ ngx_str_t addr_text = connection->addr_text; ctx = ngx_http_modsecurity_create_ctx(r); dd("ctx was NULL, creating new context: %p", ctx); if (ctx == NULL) { dd("ctx still null; Nothing we can do, returning an error."); return NGX_HTTP_INTERNAL_SERVER_ERROR; } /** * FIXME: Check if it is possible to hook on nginx on a earlier phase. * * At this point we are doing an late connection process. Maybe * we have to hook into NGX_HTTP_FIND_CONFIG_PHASE, it seems to be the * erliest phase that nginx allow us to attach those kind of hooks. * */ int client_port = htons(((struct sockaddr_in *) connection->sockaddr)->sin_port); int server_port = htons(((struct sockaddr_in *) connection->listening->sockaddr)->sin_port); const char *client_addr = ngx_str_to_char(addr_text, r->pool); if (client_addr == (char*)-1) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } const char *server_addr = inet_ntoa(((struct sockaddr_in *) connection->sockaddr)->sin_addr); old_pool = ngx_http_modsecurity_pcre_malloc_init(r->pool); ret = msc_process_connection(ctx->modsec_transaction, client_addr, client_port, server_addr, server_port); ngx_http_modsecurity_pcre_malloc_done(old_pool); if (ret != 1){ dd("Was not able to extract connection information."); } /** * * FIXME: Check how we can finalize a request without crash nginx. * * I don't think nginx is expecting to finalize a request at that * point as it seems that it clean the ngx_http_request_t information * and try to use it later. * */ dd("Processing intervention with the connection information filled in"); ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r); if (ret > 0) { return ret; } const char *http_version; switch (r->http_version) { case NGX_HTTP_VERSION_9 : http_version = "0.9"; break; case NGX_HTTP_VERSION_10 : http_version = "1.0"; break; case NGX_HTTP_VERSION_11 : http_version = "1.1"; break; #if defined(nginx_version) && nginx_version >= 1009005 case NGX_HTTP_VERSION_20 : http_version = "2.0"; break; #endif default : http_version = "1.0"; break; } const char *n_uri = ngx_str_to_char(r->unparsed_uri, r->pool); const char *n_method = ngx_str_to_char(r->method_name, r->pool); if (n_uri == (char*)-1 || n_method == (char*)-1) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } old_pool = ngx_http_modsecurity_pcre_malloc_init(r->pool); msc_process_uri(ctx->modsec_transaction, n_uri, n_method, http_version); ngx_http_modsecurity_pcre_malloc_done(old_pool); dd("Processing intervention with the transaction information filled in (uri, method and version)"); ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r); if (ret > 0) { return ret; } /** * Since incoming request headers are already in place, lets send it to ModSecurity * */ ngx_list_part_t *part = &r->headers_in.headers.part; ngx_table_elt_t *data = part->elts; ngx_uint_t i = 0; for (i = 0 ; /* void */ ; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; data = part->elts; i = 0; } /** * By using u_char (utf8_t) I believe nginx is hoping to deal * with utf8 strings. * Casting those into to unsigned char * in order to pass * it to ModSecurity, it will handle with those later. * */ dd("Adding request header: %.*s with value %.*s", (int)data[i].key.len, data[i].key.data, (int) data[i].value.len, data[i].value.data); msc_add_n_request_header(ctx->modsec_transaction, (const unsigned char *) data[i].key.data, data[i].key.len, (const unsigned char *) data[i].value.data, data[i].value.len); } /** * Since ModSecurity already knew about all headers, i guess it is safe * to process this information. */ old_pool = ngx_http_modsecurity_pcre_malloc_init(r->pool); msc_process_request_headers(ctx->modsec_transaction); ngx_http_modsecurity_pcre_malloc_done(old_pool); dd("Processing intervention with the request headers information filled in"); ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r); if (ret > 0) { return ret; } } return NGX_DECLINED; }
/* NGX_HTTP_PREACCESS_PHASE阶段的处理句柄,处理请求报文体 */ ngx_int_t ngx_http_modsecurity_pre_access_handler(ngx_http_request_t *r) { #if 1 ngx_http_modsecurity_ctx_t *ctx = NULL; ngx_http_modsecurity_loc_conf_t *cf; dd("catching a new _preaccess_ phase handler"); cf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity_module); if (cf == NULL || cf->enable != 1) { dd("ModSecurity not enabled... returning"); return NGX_DECLINED; } /* * FIXME: * In order to perform some tests, let's accept everything. * if (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_POST && r->method != NGX_HTTP_HEAD) { dd("ModSecurity is not ready to deal with anything different from " \ "POST, GET or HEAD"); return NGX_DECLINED; } */ /* 获取安全模块儿的执行环境,在处理请求头时创建,此处不应该为空 */ ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); dd("recovering ctx: %p", ctx); if (ctx == NULL) { dd("ctx is null; Nothing we can do, returning an error."); return NGX_HTTP_INTERNAL_SERVER_ERROR; } /* 等待更多的报文内容,则继续等待;*/ if (ctx->waiting_more_body == 1) { dd("waiting for more data before proceed. / count: %d", r->main->count); return NGX_DONE; } /* 首次进入,读取报文体 */ if (ctx->body_requested == 0) { ngx_int_t rc = NGX_OK; ctx->body_requested = 1; /* 设置标识,开始处理请求报文体 */ dd("asking for the request body, if any. Count: %d", r->main->count); /** * TODO: Check if there is any benefit to use request_body_in_single_buf set to 1. * * saw some module using this request_body_in_single_buf * but not sure what exactly it does, same for the others options below. * * r->request_body_in_single_buf = 1; */ r->request_body_in_single_buf = 1; r->request_body_in_persistent_file = 1; r->request_body_in_clean_file = 1; rc = ngx_http_read_client_request_body(r, /* 设置等待报文的处理函数 */ ngx_http_modsecurity_request_read); if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { #if (nginx_version < 1002006) || \ (nginx_version >= 1003000 && nginx_version < 1003009) r->main->count--; #endif return rc; } if (rc == NGX_AGAIN) { dd("nginx is asking us to wait for more data."); ctx->waiting_more_body = 1; return NGX_DONE; } } /* 报文体接收完毕,处理 */ if (ctx->waiting_more_body == 0) { int ret = 0; int already_inspected = 0; dd("request body is ready to be processed"); ngx_chain_t *chain = r->request_body->bufs; /** * TODO: Speed up the analysis by sending chunk while they arrive. * * Notice that we are waiting for the full request body to * start to process it, it may not be necessary. We may send * the chunks to ModSecurity while nginx keep calling this * function. */ /* 上传文件形式的报文体 */ if (r->request_body->temp_file != NULL) { ngx_str_t file_path = r->request_body->temp_file->file.name; const char *file_name = ngx_str_to_char(file_path, r->pool); if (file_name == (char*)-1) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } /* * Request body was saved to a file, probably we don't have a * copy of it in memory. */ dd("request body inspection: file -- %s", file_name); msc_request_body_from_file(ctx->modsec_transaction, file_name); already_inspected = 1; } else { dd("inspection request body in memory."); } /* 上传缓存形式的报文体 */ while (chain && !already_inspected) { u_char *data = chain->buf->start; msc_append_request_body(ctx->modsec_transaction, data, chain->buf->last - chain->buf->pos); if (chain->buf->last_buf) { break; } chain = chain->next; /* XXX: chains are processed one-by-one, maybe worth to pass all chains and then call intervention() ? */ /** * ModSecurity may perform stream inspection on this buffer, * it may ask for a intervention in consequence of that. * */ ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r); if (ret > 0) { return ret; } } /** * At this point, all the request body was sent to ModSecurity * and we want to make sure that all the request body inspection * happened; consequently we have to check if ModSecurity have * returned any kind of intervention. */ /* XXX: once more -- is body can be modified ? content-length need to be adjusted ? */ /* 报文体完整上传到安全模块儿后,处理报文体 */ ngx_http_modsecurity_pcre_malloc_init(); msc_process_request_body(ctx->modsec_transaction); ngx_http_modsecurity_pcre_malloc_done(); ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r); if (ret > 0) { return ret; } } dd("Nothing to add on the body inspection, reclaiming a NGX_DECLINED"); #endif return NGX_DECLINED; }
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; #if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS) ngx_http_modsecurity_loc_conf_t *loc_cf = NULL; ngx_list_part_t *part = &r->headers_out.headers.part; ngx_table_elt_t *data = part->elts; ngx_uint_t i = 0; #endif if (in == NULL) { return ngx_http_next_body_filter(r, in); } ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module); dd("body filter, recovering ctx: %p", ctx); if (ctx == NULL) { 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) { #if 0 dd("dumping stored ctx headers"); for (i = 0; i < ctx->sanity_headers_out->nelts; i++) { ngx_http_modsecurity_header_t *vals = ctx->sanity_headers_out->elts; ngx_str_t *s2 = &vals[i].name, *s3 = &vals[i].value; dd(" dump[%d]: name = '%.*s', value = '%.*s'", (int)i, (int)s2->len, (char*)s2->data, (int)s3->len, (char*)s3->data); } #endif /* * Identify if there is a header that was not inspected by ModSecurity. */ int worth_to_fail = 0; for (i = 0; ; i++) { int found = 0; ngx_uint_t j = 0; ngx_table_elt_t *s1; ngx_http_modsecurity_header_t *vals; if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; data = part->elts; i = 0; } vals = ctx->sanity_headers_out->elts; s1 = &data[i]; /* * Headers that were inspected by ModSecurity. */ while (j < ctx->sanity_headers_out->nelts) { ngx_str_t *s2 = &vals[j].name; ngx_str_t *s3 = &vals[j].value; if (s1->key.len == s2->len && ngx_strncmp(s1->key.data, s2->data, s1->key.len) == 0) { if (s1->value.len == s3->len && ngx_strncmp(s1->value.data, s3->data, s1->value.len) == 0) { found = 1; break; } } j++; } if (!found) { dd("header: `%.*s' with value: `%.*s' was not inspected by ModSecurity", (int) s1->key.len, (const char *) s1->key.data, (int) s1->value.len, (const char *) s1->value.data); worth_to_fail++; } } if (worth_to_fail) { 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); } } #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; } } if (buffer_fully_loadead == 1) { int ret; for (chain = in; chain != NULL; chain = chain->next) { u_char *data = chain->buf->start; msc_append_response_body(ctx->modsec_transaction, data, chain->buf->end - data); ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r, 4); if (ret > 0) { return ngx_http_filter_finalize_request(r, &ngx_http_modsecurity_module, ret); } } ngx_http_modsecurity_pcre_malloc_init(); msc_process_response_body(ctx->modsec_transaction); ngx_http_modsecurity_pcre_malloc_done(); /* 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, 4); if (ret > 0) { return ret; } else if (ret < 0) { return ngx_http_filter_finalize_request(r, &ngx_http_modsecurity_module, NGX_HTTP_INTERNAL_SERVER_ERROR); } } 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); }