Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

473 lines (348 sloc) 13.145 kB
/*
* Copyright (C) agentzh
*/
#ifndef DDEBUG
#define DDEBUG 0
#endif
#include "ddebug.h"
#include "ngx_http_xss_filter_module.h"
#include "ngx_http_xss_util.h"
#include <nginx.h>
#include <ngx_config.h>
#define ngx_http_xss_default_output_type "application/x-javascript"
static ngx_str_t ngx_http_xss_default_types[] = {
ngx_string("application/json"),
ngx_null_string
};
static void *ngx_http_xss_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_xss_merge_loc_conf(ngx_conf_t *cf, void *parent,
void *child);
static ngx_int_t ngx_http_xss_filter_init(ngx_conf_t *cf);
static void *ngx_http_xss_create_main_conf(ngx_conf_t *cf);
static char *ngx_http_xss_get(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static volatile ngx_cycle_t *ngx_http_xss_prev_cycle = NULL;
static ngx_command_t ngx_http_xss_commands[] = {
{ ngx_string("xss_get"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
|NGX_HTTP_LIF_CONF|NGX_CONF_FLAG,
ngx_http_xss_get,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_xss_loc_conf_t, get_enabled),
NULL },
{ ngx_string("xss_callback_arg"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_xss_loc_conf_t, callback_arg),
NULL },
{ ngx_string("xss_input_types"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
|NGX_CONF_1MORE|NGX_HTTP_LIF_CONF,
ngx_http_types_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_xss_loc_conf_t, input_types_keys),
&ngx_http_xss_default_types[0] },
{ ngx_string("xss_check_status"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
|NGX_CONF_FLAG|NGX_HTTP_LIF_CONF,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_xss_loc_conf_t, check_status),
NULL },
{ ngx_string("xss_override_status"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
|NGX_CONF_FLAG|NGX_HTTP_LIF_CONF,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_xss_loc_conf_t, override_status),
NULL },
{ ngx_string("xss_output_type"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF
|NGX_CONF_1MORE|NGX_HTTP_LIF_CONF,
ngx_conf_set_str_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_xss_loc_conf_t, output_type),
NULL },
ngx_null_command
};
static ngx_http_module_t ngx_http_xss_filter_module_ctx = {
NULL, /* preconfiguration */
ngx_http_xss_filter_init, /* postconfiguration */
ngx_http_xss_create_main_conf, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_xss_create_loc_conf, /* create location configuration */
ngx_http_xss_merge_loc_conf /* merge location configuration */
};
ngx_module_t ngx_http_xss_filter_module = {
NGX_MODULE_V1,
&ngx_http_xss_filter_module_ctx, /* module context */
ngx_http_xss_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
static ngx_int_t
ngx_http_xss_header_filter(ngx_http_request_t *r)
{
ngx_http_xss_ctx_t *ctx;
ngx_http_xss_loc_conf_t *xlcf;
ngx_str_t callback;
u_char *p, *src, *dst;
if (r != r->main) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"xss skipped in subrequests");
return ngx_http_next_header_filter(r);
}
xlcf = ngx_http_get_module_loc_conf(r, ngx_http_xss_filter_module);
if (!xlcf->get_enabled) {
return ngx_http_next_header_filter(r);
}
if (r->method != NGX_HTTP_GET) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"xss skipped due to the unmatched request method: %V",
&r->method_name);
return ngx_http_next_header_filter(r);
}
if (xlcf->check_status) {
if (r->headers_out.status != NGX_HTTP_OK
&& r->headers_out.status != NGX_HTTP_CREATED)
{
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"xss skipped due to unmatched response status "
"\"%ui\"", r->headers_out.status);
return ngx_http_next_header_filter(r);
}
}
if (xlcf->callback_arg.len == 0) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"xss: xss_get is enabled but no xss_callback_arg "
"specified");
return ngx_http_next_header_filter(r);
}
if (ngx_http_test_content_type(r, &xlcf->input_types) == NULL) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"xss skipped due to unmatched Content-Type response "
"header");
return ngx_http_next_header_filter(r);
}
if (ngx_http_arg(r, xlcf->callback_arg.data, xlcf->callback_arg.len,
&callback)
!= NGX_OK)
{
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"xss skipped: no GET argument \"%V\" specified in "
"the request", &xlcf->callback_arg);
return ngx_http_next_header_filter(r);
}
p = ngx_palloc(r->pool, callback.len);
if (p == NULL) {
return NGX_ERROR;
}
src = callback.data; dst = p;
ngx_unescape_uri(&dst, &src, callback.len, NGX_UNESCAPE_URI_COMPONENT);
if (src != callback.data + callback.len) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"xss: unescape uri: input data not consumed completely");
return NGX_ERROR;
}
callback.data = p;
callback.len = dst - p;
if (ngx_http_xss_test_callback(callback.data, callback.len) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"xss: bad callback argument: \"%V\"", &callback);
return ngx_http_next_header_filter(r);
}
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_xss_ctx_t));
if (ctx == NULL) {
return NGX_ERROR;
}
/*
* set by ngx_pcalloc():
*
* ctx->callback = { 0, NULL };
* ctx->before_body_sent = 0;
*/
ctx->callback = callback;
ngx_http_set_ctx(r, ctx, ngx_http_xss_filter_module);
r->headers_out.content_type = xlcf->output_type;
r->headers_out.content_type_len = xlcf->output_type.len;
r->headers_out.content_type_lowcase = NULL;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"xss output Content-Type header \"%V\"",
&xlcf->output_type);
ngx_http_clear_content_length(r);
ngx_http_clear_accept_ranges(r);
if (xlcf->override_status
&& r->headers_out.status >= NGX_HTTP_SPECIAL_RESPONSE)
{
r->headers_out.status = NGX_HTTP_OK;
}
return ngx_http_next_header_filter(r);
}
static ngx_int_t
ngx_http_xss_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_uint_t last;
ngx_chain_t *cl, *orig_in;
ngx_chain_t **ll = NULL;
ngx_http_xss_ctx_t *ctx;
size_t len;
ngx_buf_t *b;
if (in == NULL || r->header_only) {
return ngx_http_next_body_filter(r, in);
}
ctx = ngx_http_get_module_ctx(r, ngx_http_xss_filter_module);
if (ctx == NULL) {
return ngx_http_next_body_filter(r, in);
}
orig_in = in;
if (!ctx->before_body_sent) {
ctx->before_body_sent = 1;
dd("callback: %.*s", (int) ctx->callback.len,
ctx->callback.data);
len = ctx->callback.len + sizeof("(") - 1;
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NGX_ERROR;
}
b->last = ngx_copy(b->last, ctx->callback.data, ctx->callback.len);
*b->last++ = '(';
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NGX_ERROR;
}
cl->buf = b;
cl->next = in;
in = cl;
}
last = 0;
for (cl = orig_in; cl; cl = cl->next) {
if (cl->buf->last_buf) {
cl->buf->last_buf = 0;
cl->buf->sync = 1;
ll = &cl->next;
last = 1;
}
}
if (last) {
len = sizeof(");") - 1;
b = ngx_create_temp_buf(r->pool, len);
if (b == NULL) {
return NGX_ERROR;
}
*b->last++ = ')';
*b->last++ = ';';
b->last_buf = 1;
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NGX_ERROR;
}
cl->buf = b;
cl->next = NULL;
*ll = cl;
ngx_http_set_ctx(r, NULL, ngx_http_xss_filter_module);
}
return ngx_http_next_body_filter(r, in);
}
static ngx_int_t
ngx_http_xss_filter_init(ngx_conf_t *cf)
{
int multi_http_blocks;
ngx_http_xss_main_conf_t *xmcf;
xmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_xss_filter_module);
if (ngx_http_xss_prev_cycle != ngx_cycle) {
ngx_http_xss_prev_cycle = ngx_cycle;
multi_http_blocks = 0;
} else {
multi_http_blocks = 1;
}
if (multi_http_blocks || xmcf->requires_filter) {
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_xss_header_filter;
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_xss_body_filter;
}
return NGX_OK;
}
static void *
ngx_http_xss_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_xss_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_xss_loc_conf_t));
if (conf == NULL) {
return NULL;
}
/*
* set by ngx_pcalloc():
*
* conf->callback_arg = { 0, NULL };
* conf->input_types = { NULL };
* conf->input_types_keys = NULL;
* conf->output_type = { 0, NULL };
*/
conf->get_enabled = NGX_CONF_UNSET;
conf->check_status = NGX_CONF_UNSET;
conf->override_status = NGX_CONF_UNSET;
return conf;
}
static char *
ngx_http_xss_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_xss_loc_conf_t *prev = parent;
ngx_http_xss_loc_conf_t *conf = child;
ngx_conf_merge_str_value(conf->callback_arg, prev->callback_arg, "");
ngx_conf_merge_value(conf->get_enabled, prev->get_enabled, 0);
ngx_conf_merge_value(conf->check_status, prev->check_status, 1);
ngx_conf_merge_value(conf->override_status, prev->override_status, 1);
#if defined(nginx_version) && nginx_version >= 8029
if (ngx_http_merge_types(cf, &conf->input_types_keys, &conf->input_types,
&prev->input_types_keys, &prev->input_types,
ngx_http_xss_default_types)
!= NGX_OK)
#else /* 0.7.x or 0.8.x < 0.8.29 */
if (ngx_http_merge_types(cf, conf->input_types_keys, &conf->input_types,
prev->input_types_keys, &prev->input_types,
ngx_http_xss_default_types)
!= NGX_OK)
#endif
{
return NGX_CONF_ERROR;
}
ngx_conf_merge_str_value(conf->output_type, prev->output_type,
ngx_http_xss_default_output_type);
return NGX_CONF_OK;
}
static void *
ngx_http_xss_create_main_conf(ngx_conf_t *cf)
{
ngx_http_xss_main_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_xss_main_conf_t));
if (conf == NULL) {
return NULL;
}
/* set by ngx_pcalloc:
* conf->requires_filter = 0;
*/
return conf;
}
static char *
ngx_http_xss_get(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_xss_main_conf_t *xmcf;
xmcf = ngx_http_conf_get_module_main_conf(cf,
ngx_http_xss_filter_module);
xmcf->requires_filter = 1;
return ngx_conf_set_flag_slot(cf, cmd, conf);
}
Jump to Line
Something went wrong with that request. Please try again.