├── .gitattributes ├── .gitignore ├── .travis.yml ├── README.md ├── config ├── src ├── ddebug.h ├── ngx_http_xss_filter_module.c ├── ngx_http_xss_filter_module.h ├── ngx_http_xss_util.c ├── ngx_http_xss_util.h └── ngx_http_xss_util.rl ├── t ├── gzip.t ├── sanity.t └── unused.t ├── util ├── build.sh ├── fix-clang-warnings ├── update-readme.sh └── wiki2pod.pl └── valgrind.suppress /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.mobi 2 | genmobi.sh 3 | .libs 4 | *.swp 5 | *.slo 6 | *.la 7 | *.swo 8 | *.lo 9 | *~ 10 | *.o 11 | print.txt 12 | .rsync 13 | *.tar.gz 14 | dist 15 | build[789] 16 | build 17 | build1[0-9] 18 | tags 19 | update-readme 20 | *.tmp 21 | test/Makefile 22 | test/blib 23 | test.sh 24 | go 25 | t/t.sh 26 | test/t/servroot/ 27 | releng 28 | reset 29 | *.t_ 30 | src/handler.h 31 | src/util.c 32 | src/module.h 33 | src/module.c 34 | src/drizzle.c 35 | src/processor.h 36 | src/handler.c 37 | src/util.h 38 | src/drizzle.h 39 | src/processor.c 40 | src/output.c 41 | src/output.h 42 | libdrizzle 43 | ctags 44 | src/stream.h 45 | nginx 46 | keepalive 47 | reindex 48 | src/module.h 49 | src/module.c 50 | src/util.h 51 | src/util.c 52 | src/util.rl 53 | all 54 | t/servroot/ 55 | buildroot/ 56 | build12 57 | *.plist 58 | Makefile 59 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: focal 3 | 4 | branches: 5 | only: 6 | - "master" 7 | 8 | os: linux 9 | 10 | language: c 11 | compiler: gcc 12 | 13 | addons: 14 | apt: 15 | packages: [ axel, ragel, cpanminus, libtest-base-perl, libtext-diff-perl, liburi-perl, libwww-perl, libtest-longstring-perl, liblist-moreutils-perl ] 16 | 17 | cache: 18 | apt: true 19 | 20 | env: 21 | global: 22 | - LUAJIT_PREFIX=/opt/luajit21 23 | - LUAJIT_LIB=$LUAJIT_PREFIX/lib 24 | - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH 25 | - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 26 | - LUA_INCLUDE_DIR=$LUAJIT_INC 27 | - LUA_CMODULE_DIR=/lib 28 | - JOBS=3 29 | - PCRE_VER=8.44 30 | - PCRE_PREFIX=/opt/pcre 31 | - PCRE_LIB=$PCRE_PREFIX/lib 32 | - PCRE_INC=$PCRE_PREFIX/include 33 | - OPENSSL_PREFIX=/opt/ssl 34 | - OPENSSL_LIB=$OPENSSL_PREFIX/lib 35 | - OPENSSL_INC=$OPENSSL_PREFIX/include 36 | - NGX_BUILD_JOBS=$JOBS 37 | - TEST_NGINX_SLEEP=0.006 38 | matrix: 39 | - NGINX_VERSION=1.27.1 OPENSSL_VER=1.1.1u 40 | 41 | install: 42 | - if [ ! -d download-cache ]; then mkdir download-cache; fi 43 | - if [ ! -f download-cache/pcre-$PCRE_VER.tar.gz ]; then wget -P download-cache https://downloads.sourceforge.net/project/pcre/pcre/${PCRE_VER}/pcre-${PCRE_VER}.tar.gz; fi 44 | - if [ ! -f download-cache/openssl-$OPENSSL_VER.tar.gz ]; then wget -O download-cache/openssl-$OPENSSL_VER.tar.gz https://www.openssl.org/source/openssl-$OPENSSL_VER.tar.gz; fi 45 | - git clone https://github.com/openresty/nginx-devel-utils.git 46 | - git clone https://github.com/openresty/openresty.git ../openresty 47 | - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core 48 | - git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache 49 | - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx 50 | - git clone https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module 51 | - git clone https://github.com/openresty/test-nginx.git 52 | - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git 53 | - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module 54 | - git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module 55 | 56 | script: 57 | - cd luajit2 58 | - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT' > build.log 2>&1 || (cat build.log && exit 1) 59 | - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1) 60 | - cd .. 61 | - tar zxf download-cache/pcre-$PCRE_VER.tar.gz 62 | - cd pcre-$PCRE_VER/ 63 | - ./configure --prefix=$PCRE_PREFIX --enable-jit --enable-utf --enable-unicode-properties > build.log 2>&1 || (cat build.log && exit 1) 64 | - make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1) 65 | - sudo PATH=$PATH make install > build.log 2>&1 || (cat build.log && exit 1) 66 | - cd .. 67 | - tar zxf download-cache/openssl-$OPENSSL_VER.tar.gz 68 | - cd openssl-$OPENSSL_VER/ 69 | - ./config shared --prefix=$OPENSSL_PREFIX -DPURIFY > build.log 2>&1 || (cat build.log && exit 1) 70 | - make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1) 71 | - sudo make PATH=$PATH install_sw > build.log 2>&1 || (cat build.log && exit 1) 72 | - cd ../test-nginx && sudo cpanm . && cd .. 73 | - export PATH=$PWD/work/nginx/sbin:$PWD/nginx-devel-utils:$PATH 74 | - export NGX_BUILD_CC=$CC 75 | - sh util/build.sh $NGINX_VERSION > build.log 2>&1 || (cat build.log && exit 1) 76 | - nginx -V 77 | - ldd `which nginx`|grep -E 'luajit|ssl' 78 | - prove -I. -r t 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | xss-nginx-module - Native cross-site scripting support in nginx 5 | 6 | Table of Contents 7 | ================= 8 | 9 | * [Name](#name) 10 | * [Synopsis](#synopsis) 11 | * [Description](#description) 12 | * [Directives](#directives) 13 | * [xss_get](#xss_get) 14 | * [xss_callback_arg](#xss_callback_arg) 15 | * [xss_override_status](#xss_override_status) 16 | * [xss_check_status](#xss_check_status) 17 | * [xss_input_types](#xss_input_types) 18 | * [Limitations](#limitations) 19 | * [Trouble Shooting](#trouble-shooting) 20 | * [Installation](#installation) 21 | * [Compatibility](#compatibility) 22 | * [TODO](#todo) 23 | * [Author](#author) 24 | * [Copyright & License](#copyright--license) 25 | * [See Also](#see-also) 26 | 27 | Synopsis 28 | ======== 29 | 30 | ```nginx 31 | # accessing /foo?callback=process gives the response 32 | # body "process(...);" (without quotes) where "..." 33 | # is the original response body of the /foo location. 34 | server { 35 | location /foo { 36 | # your content handler goes here... 37 | 38 | xss_get on; 39 | xss_callback_arg 'callback'; 40 | xss_input_types 'application/json'; # default 41 | xss_output_type 'application/x-javascript'; # default 42 | } 43 | ... 44 | } 45 | ``` 46 | 47 | Description 48 | =========== 49 | 50 | This module adds cross-site AJAX support to nginx. Currently only 51 | cross-site GET is supported. But cross-site POST will be added 52 | in the future. 53 | 54 | The cross-site GET is currently implemented as JSONP 55 | (or "JSON with padding"). See http://en.wikipedia.org/wiki/JSON#JSONP 56 | for more details. 57 | 58 | Directives 59 | ========== 60 | 61 | [Back to TOC](#table-of-contents) 62 | 63 | xss_get 64 | ------- 65 | **syntax:** *xss_get on | off* 66 | 67 | **default:** *xss_get off* 68 | 69 | **context:** *http, server, location, if location* 70 | 71 | Enables JSONP support for GET requests. 72 | 73 | [Back to TOC](#table-of-contents) 74 | 75 | xss_callback_arg 76 | ---------------- 77 | **syntax:** *xss_callback_arg <name>* 78 | 79 | **default:** *none* 80 | 81 | **context:** *http, http, location, if location* 82 | 83 | Specifies the JavaScript callback function name 84 | used in the responses. 85 | 86 | For example, 87 | 88 | ```nginx 89 | location /foo { 90 | xss_get on; 91 | xss_callback_arg c; 92 | ... 93 | } 94 | ``` 95 | 96 | then 97 | 98 | ``` 99 | GET /foo?c=blah 100 | ``` 101 | 102 | returns 103 | 104 | ```javascript 105 | blah(...); 106 | ``` 107 | 108 | [Back to TOC](#table-of-contents) 109 | 110 | xss_override_status 111 | ------------------- 112 | **syntax:** *xss_override_status on | off* 113 | 114 | **default:** *xss_check_status on* 115 | 116 | **context:** *http, server, location, if location* 117 | 118 | Specifies whether to override 30x, 40x and 50x status to 200 119 | when the response is actually being processed. 120 | 121 | [Back to TOC](#table-of-contents) 122 | 123 | xss_check_status 124 | ----------------- 125 | **syntax:** *xss_check_status on | off* 126 | 127 | **default:** *xss_check_status on* 128 | 129 | **context:** *http, server, location, if location* 130 | 131 | By default, ngx_xss only process responses with the status code 132 | 200 or 201. 133 | 134 | [Back to TOC](#table-of-contents) 135 | 136 | xss_input_types 137 | --------------- 138 | **syntax:** *xss_input_types [mime-type]...* 139 | 140 | **default:** *xss_input_types application/json* 141 | 142 | **context:** *http, server, location, if location* 143 | 144 | Only processes the responses of the specified MIME types. 145 | 146 | Example: 147 | 148 | ```nginx 149 | xss_input_types application/json text/plain; 150 | ``` 151 | 152 | [Back to TOC](#table-of-contents) 153 | 154 | Limitations 155 | =========== 156 | 157 | * ngx_xss will not work with [ngx_echo](https://github.com/openresty/echo-nginx-module)'s 158 | subrequest interfaces, due to the underlying 159 | limitations imposed by subrequests' "postponed chain" mechanism in the nginx core. 160 | The standard ngx_addition module also falls into this category. You're recommended, 161 | however, to use [ngx_lua](https://github.com/openresty/lua-nginx-module) as the content 162 | handler to issue subrequests *and* ngx_xss 163 | to do JSONP, because [ngx_lua](https://github.com/openresty/lua-nginx-module)'s 164 | [ngx.location.capture()](https://github.com/openresty/lua-nginx-module#ngxlocationcapture) 165 | interface does not utilize the "postponed chain" mechanism, thus getting out of this 166 | limitation. We're taking this approach in production and it works great. 167 | 168 | [Back to TOC](#table-of-contents) 169 | 170 | Trouble Shooting 171 | ================ 172 | 173 | Use the "info" error log level (or lower) to get more 174 | diagnostics when things go wrong. 175 | 176 | [Back to TOC](#table-of-contents) 177 | 178 | Installation 179 | ============ 180 | 181 | You're recommended to install this module (as well as the Nginx core and many other goodies) via the [OpenResty bundle](http://openresty.org). See [the detailed instructions](http://openresty.org/#Installation) for downloading and installing OpenResty into your system. This is the easiest and most safe way to set things up. 182 | 183 | Alternatively, you can install this module manually with the Nginx source: 184 | 185 | Grab the nginx source code from [nginx.org](http://nginx.org/), for example, 186 | the version 1.13.6 (see [nginx compatibility](#compatibility)), and then build the source with this module: 187 | 188 | ```bash 189 | 190 | $ wget 'http://nginx.org/download/nginx-1.13.6.tar.gz' 191 | $ tar -xzvf nginx-1.13.6.tar.gz 192 | $ cd nginx-1.13.6/ 193 | 194 | # Here we assume you would install you nginx under /opt/nginx/. 195 | $ ./configure --prefix=/opt/nginx \ 196 | --add-module=/path/to/xss-nginx-module 197 | # Or 198 | --add-dynamic-module=../xss-nginx-module 199 | 200 | $ make -j2 201 | $ make install 202 | ``` 203 | 204 | Download the latest version of the release tarball of this module from [xss-nginx-module file list](https://github.com/openresty/xss-nginx-module/tags). 205 | 206 | Also, this module is included and enabled by default in the [OpenResty bundle](http://openresty.org). 207 | 208 | [Back to TOC](#table-of-contents) 209 | 210 | Compatibility 211 | ============= 212 | 213 | The following versions of Nginx should work with this module: 214 | 215 | * **1.13.x** (last tested: 1.13.6) 216 | * **1.12.x** 217 | * **1.11.x** (last tested: 1.11.2) 218 | * **1.10.x** 219 | * **1.9.x** (last tested: 1.9.7) 220 | * **1.8.x** 221 | * **1.7.x** (last tested: 1.7.10) 222 | * **1.6.x** 223 | * **1.5.x** 224 | * **1.4.x** (last tested: 1.4.3) 225 | * **1.2.x** (last tested: 1.2.9) 226 | * **1.0.x** (last tested: 1.0.10) 227 | * **0.9.x** (last tested: 0.9.4) 228 | * **0.8.x** (last tested: 0.8.54) 229 | * **0.7.x** >= 0.7.30 (last tested: 0.7.67) 230 | 231 | Earlier versions of Nginx like 0.6.x and 0.5.x will *not* work. 232 | 233 | If you find that any particular version of Nginx above 0.7.30 does not 234 | work with this module, please consider reporting a bug. 235 | 236 | [Back to TOC](#table-of-contents) 237 | 238 | TODO 239 | ==== 240 | 241 | * add cross-site POST support. 242 | 243 | [Back to TOC](#table-of-contents) 244 | 245 | Author 246 | ====== 247 | 248 | Yichun "agentzh" Zhang (章亦春) <agentzh@gmail@com> 249 | 250 | [Back to TOC](#table-of-contents) 251 | 252 | Copyright & License 253 | =================== 254 | 255 | The implementation of the builtin connection pool has borrowed 256 | a lot of code from Maxim Dounin's upstream_keepalive module. 257 | This part of code is copyrighted by Maxim Dounin. 258 | 259 | This module is licenced under the BSD license. 260 | 261 | Copyright (C) 2009-2018 by Yichun "agentzh" Zhang (章亦春) <agentzh@gmail.com> OpenResty Inc. 262 | 263 | All rights reserved. 264 | 265 | Redistribution and use in source and binary forms, with or without 266 | modification, are permitted provided that the following conditions 267 | are met: 268 | 269 | * Redistributions of source code must retain the above copyright 270 | notice, this list of conditions and the following disclaimer. 271 | * Redistributions in binary form must reproduce the above copyright 272 | notice, this list of conditions and the following disclaimer in the 273 | documentation and/or other materials provided with the distribution. 274 | 275 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 276 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 277 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 278 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 279 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 280 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 281 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 282 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 283 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 284 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 285 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 286 | 287 | [Back to TOC](#table-of-contents) 288 | 289 | See Also 290 | ======== 291 | 292 | * [Introduction to JSONP](http://en.wikipedia.org/wiki/JSONP) 293 | * [ngx_lua](https://github.com/openresty/lua-nginx-module) 294 | 295 | [Back to TOC](#table-of-contents) 296 | 297 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_xss_filter_module 2 | 3 | HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_xss_filter_module" 4 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_http_xss_filter_module.c $ngx_addon_dir/src/ngx_http_xss_util.c" 5 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/src/ddebug.h $ngx_addon_dir/src/ngx_http_xss_filter_module.h $ngx_addon_dir/src/ngx_http_xss_util.h" 6 | 7 | if test -n "$ngx_module_link"; then 8 | ngx_module_type=HTTP_AUX_FILTER 9 | ngx_module_name=$ngx_addon_name 10 | ngx_module_incs= 11 | ngx_module_deps="$NGX_ADDON_DEPS" 12 | ngx_module_srcs="$NGX_ADDON_SRCS" 13 | ngx_module_libs= 14 | 15 | . auto/module 16 | else 17 | HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES $ngx_addon_name" 18 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS" 19 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS" 20 | fi 21 | -------------------------------------------------------------------------------- /src/ddebug.h: -------------------------------------------------------------------------------- 1 | #ifndef DDEBUG_H 2 | #define DDEBUG_H 3 | 4 | #include 5 | #include 6 | 7 | #if defined(DDEBUG) && (DDEBUG) 8 | 9 | # if (NGX_HAVE_VARIADIC_MACROS) 10 | 11 | # define dd(...) fprintf(stderr, "xss *** "); \ 12 | fprintf(stderr, __VA_ARGS__); \ 13 | fprintf(stderr, " at %s line %d.\n", __FILE__, __LINE__) 14 | 15 | # else 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | static ngx_inline void 23 | dd(const char * fmt, ...) { 24 | } 25 | 26 | # endif 27 | 28 | #else 29 | 30 | # if (NGX_HAVE_VARIADIC_MACROS) 31 | 32 | # define dd(...) 33 | 34 | # else 35 | 36 | #include 37 | 38 | static ngx_inline void 39 | dd(const char * fmt, ...) { 40 | } 41 | 42 | # endif 43 | 44 | #endif 45 | 46 | #if defined(DDEBUG) && (DDEBUG) 47 | 48 | #define dd_check_read_event_handler(r) \ 49 | dd("r->read_event_handler = %s", \ 50 | r->read_event_handler == ngx_http_block_reading ? \ 51 | "ngx_http_block_reading" : \ 52 | r->read_event_handler == ngx_http_test_reading ? \ 53 | "ngx_http_test_reading" : \ 54 | r->read_event_handler == ngx_http_request_empty_handler ? \ 55 | "ngx_http_request_empty_handler" : "UNKNOWN") 56 | 57 | #define dd_check_write_event_handler(r) \ 58 | dd("r->write_event_handler = %s", \ 59 | r->write_event_handler == ngx_http_handler ? \ 60 | "ngx_http_handler" : \ 61 | r->write_event_handler == ngx_http_core_run_phases ? \ 62 | "ngx_http_core_run_phases" : \ 63 | r->write_event_handler == ngx_http_request_empty_handler ? \ 64 | "ngx_http_request_empty_handler" : "UNKNOWN") 65 | 66 | #else 67 | 68 | #define dd_check_read_event_handler(r) 69 | #define dd_check_write_event_handler(r) 70 | 71 | #endif 72 | 73 | #endif /* DDEBUG_H */ 74 | -------------------------------------------------------------------------------- /src/ngx_http_xss_filter_module.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) agentzh 4 | */ 5 | 6 | #ifndef DDEBUG 7 | #define DDEBUG 0 8 | #endif 9 | #include "ddebug.h" 10 | 11 | 12 | #include "ngx_http_xss_filter_module.h" 13 | #include "ngx_http_xss_util.h" 14 | 15 | #include 16 | #include 17 | 18 | 19 | #define ngx_http_xss_default_output_type "application/x-javascript" 20 | 21 | 22 | static ngx_str_t ngx_http_xss_default_types[] = { 23 | ngx_string("application/json"), 24 | ngx_null_string 25 | }; 26 | 27 | 28 | static void *ngx_http_xss_create_loc_conf(ngx_conf_t *cf); 29 | static char *ngx_http_xss_merge_loc_conf(ngx_conf_t *cf, void *parent, 30 | void *child); 31 | static ngx_int_t ngx_http_xss_filter_init(ngx_conf_t *cf); 32 | static void *ngx_http_xss_create_main_conf(ngx_conf_t *cf); 33 | static char *ngx_http_xss_get(ngx_conf_t *cf, ngx_command_t *cmd, 34 | void *conf); 35 | 36 | 37 | static volatile ngx_cycle_t *ngx_http_xss_prev_cycle = NULL; 38 | 39 | 40 | static ngx_command_t ngx_http_xss_commands[] = { 41 | 42 | { ngx_string("xss_get"), 43 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF 44 | |NGX_HTTP_LIF_CONF|NGX_CONF_FLAG, 45 | ngx_http_xss_get, 46 | NGX_HTTP_LOC_CONF_OFFSET, 47 | offsetof(ngx_http_xss_loc_conf_t, get_enabled), 48 | NULL }, 49 | 50 | { ngx_string("xss_callback_arg"), 51 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF 52 | |NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, 53 | ngx_conf_set_str_slot, 54 | NGX_HTTP_LOC_CONF_OFFSET, 55 | offsetof(ngx_http_xss_loc_conf_t, callback_arg), 56 | NULL }, 57 | 58 | { ngx_string("xss_input_types"), 59 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF 60 | |NGX_CONF_1MORE|NGX_HTTP_LIF_CONF, 61 | ngx_http_types_slot, 62 | NGX_HTTP_LOC_CONF_OFFSET, 63 | offsetof(ngx_http_xss_loc_conf_t, input_types_keys), 64 | &ngx_http_xss_default_types[0] }, 65 | 66 | { ngx_string("xss_check_status"), 67 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF 68 | |NGX_CONF_FLAG|NGX_HTTP_LIF_CONF, 69 | ngx_conf_set_flag_slot, 70 | NGX_HTTP_LOC_CONF_OFFSET, 71 | offsetof(ngx_http_xss_loc_conf_t, check_status), 72 | NULL }, 73 | 74 | { ngx_string("xss_override_status"), 75 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF 76 | |NGX_CONF_FLAG|NGX_HTTP_LIF_CONF, 77 | ngx_conf_set_flag_slot, 78 | NGX_HTTP_LOC_CONF_OFFSET, 79 | offsetof(ngx_http_xss_loc_conf_t, override_status), 80 | NULL }, 81 | 82 | { ngx_string("xss_output_type"), 83 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF 84 | |NGX_CONF_1MORE|NGX_HTTP_LIF_CONF, 85 | ngx_conf_set_str_slot, 86 | NGX_HTTP_LOC_CONF_OFFSET, 87 | offsetof(ngx_http_xss_loc_conf_t, output_type), 88 | NULL }, 89 | 90 | ngx_null_command 91 | }; 92 | 93 | 94 | static ngx_http_module_t ngx_http_xss_filter_module_ctx = { 95 | NULL, /* preconfiguration */ 96 | ngx_http_xss_filter_init, /* postconfiguration */ 97 | 98 | ngx_http_xss_create_main_conf, /* create main configuration */ 99 | NULL, /* init main configuration */ 100 | 101 | NULL, /* create server configuration */ 102 | NULL, /* merge server configuration */ 103 | 104 | ngx_http_xss_create_loc_conf, /* create location configuration */ 105 | ngx_http_xss_merge_loc_conf /* merge location configuration */ 106 | }; 107 | 108 | 109 | ngx_module_t ngx_http_xss_filter_module = { 110 | NGX_MODULE_V1, 111 | &ngx_http_xss_filter_module_ctx, /* module context */ 112 | ngx_http_xss_commands, /* module directives */ 113 | NGX_HTTP_MODULE, /* module type */ 114 | NULL, /* init master */ 115 | NULL, /* init module */ 116 | NULL, /* init process */ 117 | NULL, /* init thread */ 118 | NULL, /* exit thread */ 119 | NULL, /* exit process */ 120 | NULL, /* exit master */ 121 | NGX_MODULE_V1_PADDING 122 | }; 123 | 124 | 125 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 126 | static ngx_http_output_body_filter_pt ngx_http_next_body_filter; 127 | 128 | 129 | static ngx_int_t 130 | ngx_http_xss_header_filter(ngx_http_request_t *r) 131 | { 132 | ngx_http_xss_ctx_t *ctx; 133 | ngx_http_xss_loc_conf_t *xlcf; 134 | ngx_str_t callback; 135 | u_char *p, *src, *dst; 136 | 137 | if (r != r->main) { 138 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 139 | "xss skipped in subrequests"); 140 | 141 | return ngx_http_next_header_filter(r); 142 | } 143 | 144 | xlcf = ngx_http_get_module_loc_conf(r, ngx_http_xss_filter_module); 145 | 146 | if (!xlcf->get_enabled) { 147 | return ngx_http_next_header_filter(r); 148 | } 149 | 150 | if (r->method != NGX_HTTP_GET) { 151 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 152 | "xss skipped due to the unmatched request method: %V", 153 | &r->method_name); 154 | 155 | return ngx_http_next_header_filter(r); 156 | } 157 | 158 | if (xlcf->check_status) { 159 | 160 | if (r->headers_out.status != NGX_HTTP_OK 161 | && r->headers_out.status != NGX_HTTP_CREATED) 162 | { 163 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 164 | "xss skipped due to unmatched response status " 165 | "\"%ui\"", r->headers_out.status); 166 | 167 | return ngx_http_next_header_filter(r); 168 | } 169 | } 170 | 171 | if (xlcf->callback_arg.len == 0) { 172 | 173 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 174 | "xss: xss_get is enabled but no xss_callback_arg " 175 | "specified"); 176 | 177 | return ngx_http_next_header_filter(r); 178 | } 179 | 180 | if (ngx_http_test_content_type(r, &xlcf->input_types) == NULL) { 181 | 182 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 183 | "xss skipped due to unmatched Content-Type response " 184 | "header"); 185 | 186 | return ngx_http_next_header_filter(r); 187 | } 188 | 189 | if (ngx_http_arg(r, xlcf->callback_arg.data, xlcf->callback_arg.len, 190 | &callback) 191 | != NGX_OK) 192 | { 193 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 194 | "xss skipped: no GET argument \"%V\" specified in " 195 | "the request", &xlcf->callback_arg); 196 | 197 | return ngx_http_next_header_filter(r); 198 | } 199 | 200 | p = ngx_palloc(r->pool, callback.len); 201 | if (p == NULL) { 202 | return NGX_ERROR; 203 | } 204 | 205 | src = callback.data; dst = p; 206 | 207 | ngx_unescape_uri(&dst, &src, callback.len, NGX_UNESCAPE_URI_COMPONENT); 208 | 209 | if (src != callback.data + callback.len) { 210 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 211 | "xss: unescape uri: input data not consumed completely"); 212 | 213 | return NGX_ERROR; 214 | } 215 | 216 | callback.data = p; 217 | callback.len = dst - p; 218 | 219 | if (ngx_http_xss_test_callback(callback.data, callback.len) != NGX_OK) { 220 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 221 | "xss: bad callback argument: \"%V\"", &callback); 222 | 223 | return ngx_http_next_header_filter(r); 224 | } 225 | 226 | ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_xss_ctx_t)); 227 | if (ctx == NULL) { 228 | return NGX_ERROR; 229 | } 230 | 231 | /* 232 | * set by ngx_pcalloc(): 233 | * 234 | * ctx->callback = { 0, NULL }; 235 | * ctx->before_body_sent = 0; 236 | */ 237 | 238 | ctx->callback = callback; 239 | 240 | ngx_http_set_ctx(r, ctx, ngx_http_xss_filter_module); 241 | 242 | r->headers_out.content_type = xlcf->output_type; 243 | r->headers_out.content_type_len = xlcf->output_type.len; 244 | r->headers_out.content_type_lowcase = NULL; 245 | 246 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 247 | "xss output Content-Type header \"%V\"", 248 | &xlcf->output_type); 249 | 250 | ngx_http_clear_content_length(r); 251 | ngx_http_clear_accept_ranges(r); 252 | 253 | if (xlcf->override_status 254 | && r->headers_out.status >= NGX_HTTP_SPECIAL_RESPONSE) 255 | { 256 | r->headers_out.status = NGX_HTTP_OK; 257 | } 258 | 259 | return ngx_http_next_header_filter(r); 260 | } 261 | 262 | 263 | static ngx_int_t 264 | ngx_http_xss_body_filter(ngx_http_request_t *r, ngx_chain_t *in) 265 | { 266 | ngx_uint_t last; 267 | ngx_chain_t *cl, *orig_in; 268 | ngx_chain_t **ll = NULL; 269 | ngx_http_xss_ctx_t *ctx; 270 | size_t len; 271 | ngx_buf_t *b; 272 | 273 | if (in == NULL || r->header_only) { 274 | return ngx_http_next_body_filter(r, in); 275 | } 276 | 277 | ctx = ngx_http_get_module_ctx(r, ngx_http_xss_filter_module); 278 | 279 | if (ctx == NULL) { 280 | return ngx_http_next_body_filter(r, in); 281 | } 282 | 283 | orig_in = in; 284 | 285 | if (!ctx->before_body_sent) { 286 | ctx->before_body_sent = 1; 287 | 288 | dd("callback: %.*s", (int) ctx->callback.len, 289 | ctx->callback.data); 290 | 291 | len = ctx->callback.len + sizeof("(") - 1; 292 | 293 | b = ngx_create_temp_buf(r->pool, len); 294 | if (b == NULL) { 295 | return NGX_ERROR; 296 | } 297 | 298 | b->last = ngx_copy(b->last, ctx->callback.data, ctx->callback.len); 299 | 300 | *b->last++ = '('; 301 | 302 | cl = ngx_alloc_chain_link(r->pool); 303 | if (cl == NULL) { 304 | return NGX_ERROR; 305 | } 306 | 307 | cl->buf = b; 308 | cl->next = in; 309 | in = cl; 310 | } 311 | 312 | last = 0; 313 | 314 | for (cl = orig_in; cl; cl = cl->next) { 315 | if (cl->buf->last_buf) { 316 | cl->buf->last_buf = 0; 317 | cl->buf->sync = 1; 318 | ll = &cl->next; 319 | last = 1; 320 | } 321 | } 322 | 323 | if (last) { 324 | len = sizeof(");") - 1; 325 | 326 | b = ngx_create_temp_buf(r->pool, len); 327 | if (b == NULL) { 328 | return NGX_ERROR; 329 | } 330 | 331 | *b->last++ = ')'; 332 | *b->last++ = ';'; 333 | 334 | b->last_buf = 1; 335 | 336 | cl = ngx_alloc_chain_link(r->pool); 337 | if (cl == NULL) { 338 | return NGX_ERROR; 339 | } 340 | 341 | cl->buf = b; 342 | cl->next = NULL; 343 | *ll = cl; 344 | 345 | ngx_http_set_ctx(r, NULL, ngx_http_xss_filter_module); 346 | } 347 | 348 | return ngx_http_next_body_filter(r, in); 349 | } 350 | 351 | 352 | static ngx_int_t 353 | ngx_http_xss_filter_init(ngx_conf_t *cf) 354 | { 355 | int multi_http_blocks; 356 | ngx_http_xss_main_conf_t *xmcf; 357 | 358 | xmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_xss_filter_module); 359 | 360 | if (ngx_http_xss_prev_cycle != ngx_cycle) { 361 | ngx_http_xss_prev_cycle = ngx_cycle; 362 | multi_http_blocks = 0; 363 | 364 | } else { 365 | multi_http_blocks = 1; 366 | } 367 | 368 | if (multi_http_blocks || xmcf->requires_filter) { 369 | ngx_http_next_header_filter = ngx_http_top_header_filter; 370 | ngx_http_top_header_filter = ngx_http_xss_header_filter; 371 | 372 | ngx_http_next_body_filter = ngx_http_top_body_filter; 373 | ngx_http_top_body_filter = ngx_http_xss_body_filter; 374 | } 375 | 376 | return NGX_OK; 377 | } 378 | 379 | 380 | static void * 381 | ngx_http_xss_create_loc_conf(ngx_conf_t *cf) 382 | { 383 | ngx_http_xss_loc_conf_t *conf; 384 | 385 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_xss_loc_conf_t)); 386 | if (conf == NULL) { 387 | return NULL; 388 | } 389 | 390 | /* 391 | * set by ngx_pcalloc(): 392 | * 393 | * conf->callback_arg = { 0, NULL }; 394 | * conf->input_types = { NULL }; 395 | * conf->input_types_keys = NULL; 396 | * conf->output_type = { 0, NULL }; 397 | */ 398 | 399 | conf->get_enabled = NGX_CONF_UNSET; 400 | conf->check_status = NGX_CONF_UNSET; 401 | conf->override_status = NGX_CONF_UNSET; 402 | 403 | return conf; 404 | } 405 | 406 | 407 | static char * 408 | ngx_http_xss_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 409 | { 410 | ngx_http_xss_loc_conf_t *prev = parent; 411 | ngx_http_xss_loc_conf_t *conf = child; 412 | 413 | ngx_conf_merge_str_value(conf->callback_arg, prev->callback_arg, ""); 414 | 415 | ngx_conf_merge_value(conf->get_enabled, prev->get_enabled, 0); 416 | 417 | ngx_conf_merge_value(conf->check_status, prev->check_status, 1); 418 | 419 | ngx_conf_merge_value(conf->override_status, prev->override_status, 1); 420 | 421 | #if defined(nginx_version) && nginx_version >= 8029 422 | if (ngx_http_merge_types(cf, &conf->input_types_keys, &conf->input_types, 423 | &prev->input_types_keys, &prev->input_types, 424 | ngx_http_xss_default_types) 425 | != NGX_OK) 426 | #else /* 0.7.x or 0.8.x < 0.8.29 */ 427 | if (ngx_http_merge_types(cf, conf->input_types_keys, &conf->input_types, 428 | prev->input_types_keys, &prev->input_types, 429 | ngx_http_xss_default_types) 430 | != NGX_OK) 431 | #endif 432 | { 433 | return NGX_CONF_ERROR; 434 | } 435 | 436 | ngx_conf_merge_str_value(conf->output_type, prev->output_type, 437 | ngx_http_xss_default_output_type); 438 | 439 | return NGX_CONF_OK; 440 | } 441 | 442 | 443 | static void * 444 | ngx_http_xss_create_main_conf(ngx_conf_t *cf) 445 | { 446 | ngx_http_xss_main_conf_t *conf; 447 | 448 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_xss_main_conf_t)); 449 | if (conf == NULL) { 450 | return NULL; 451 | } 452 | 453 | /* set by ngx_pcalloc: 454 | * conf->requires_filter = 0; 455 | */ 456 | 457 | return conf; 458 | } 459 | 460 | 461 | static char * 462 | ngx_http_xss_get(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 463 | { 464 | ngx_http_xss_main_conf_t *xmcf; 465 | 466 | xmcf = ngx_http_conf_get_module_main_conf(cf, 467 | ngx_http_xss_filter_module); 468 | 469 | xmcf->requires_filter = 1; 470 | 471 | return ngx_conf_set_flag_slot(cf, cmd, conf); 472 | } 473 | -------------------------------------------------------------------------------- /src/ngx_http_xss_filter_module.h: -------------------------------------------------------------------------------- 1 | #ifndef NGX_HTTP_XSS_FILTER_MODULE_H 2 | #define NGX_HTTP_XSS_FILTER_MODULE_H 3 | 4 | 5 | #include 6 | #include 7 | 8 | 9 | typedef struct { 10 | ngx_str_t callback_arg; 11 | 12 | ngx_hash_t input_types; 13 | ngx_array_t *input_types_keys; 14 | 15 | ngx_str_t output_type; 16 | 17 | ngx_flag_t get_enabled; 18 | ngx_flag_t check_status; 19 | ngx_flag_t override_status; 20 | 21 | } ngx_http_xss_loc_conf_t; 22 | 23 | 24 | typedef struct { 25 | ngx_int_t requires_filter; 26 | 27 | } ngx_http_xss_main_conf_t; 28 | 29 | 30 | typedef struct { 31 | ngx_str_t callback; 32 | ngx_flag_t before_body_sent; 33 | 34 | } ngx_http_xss_ctx_t; 35 | 36 | 37 | #endif /* NGX_HTTP_XSS_FILTER_MODULE_H */ 38 | 39 | -------------------------------------------------------------------------------- /src/ngx_http_xss_util.c: -------------------------------------------------------------------------------- 1 | 2 | #line 1 "src/ngx_http_xss_util.rl" 3 | 4 | /* 5 | * Copyright (C) agentzh 6 | */ 7 | 8 | 9 | #ifndef DDEBUG 10 | #define DDEBUG 0 11 | #endif 12 | #include "ddebug.h" 13 | 14 | #include "ngx_http_xss_util.h" 15 | 16 | 17 | 18 | #line 16 "src/ngx_http_xss_util.rl" 19 | 20 | #line 21 "src/ngx_http_xss_util.c" 21 | static const char _javascript_key_offsets[] = { 22 | 0, 0, 6, 9, 11, 14, 18, 28 23 | }; 24 | 25 | static const char _javascript_trans_keys[] = { 26 | 36, 95, 65, 90, 97, 122, 46, 48, 27 | 57, 48, 57, 93, 48, 57, 46, 93, 28 | 48, 57, 36, 46, 91, 95, 48, 57, 29 | 65, 90, 97, 122, 0 30 | }; 31 | 32 | static const char _javascript_single_lengths[] = { 33 | 0, 2, 1, 0, 1, 2, 4, 0 34 | }; 35 | 36 | static const char _javascript_range_lengths[] = { 37 | 0, 2, 1, 1, 1, 1, 3, 0 38 | }; 39 | 40 | static const char _javascript_index_offsets[] = { 41 | 0, 0, 5, 8, 10, 13, 17, 25 42 | }; 43 | 44 | static const char _javascript_trans_targs[] = { 45 | 6, 6, 6, 6, 0, 3, 5, 0, 46 | 4, 0, 7, 4, 0, 3, 7, 5, 47 | 0, 6, 1, 2, 6, 6, 6, 6, 48 | 0, 0, 0 49 | }; 50 | 51 | static const int javascript_start = 1; 52 | 53 | 54 | 55 | #line 17 "src/ngx_http_xss_util.rl" 56 | 57 | ngx_int_t ngx_http_xss_test_callback(u_char *data, size_t len) 58 | { 59 | signed char *p = (signed char *) data; 60 | signed char *pe; 61 | int cs; 62 | 63 | pe = p + len; 64 | 65 | 66 | #line 70 "src/ngx_http_xss_util.c" 67 | { 68 | cs = javascript_start; 69 | } 70 | 71 | #line 75 "src/ngx_http_xss_util.c" 72 | { 73 | int _klen; 74 | const char *_keys; 75 | int _trans; 76 | 77 | if ( p == pe ) 78 | goto _test_eof; 79 | if ( cs == 0 ) 80 | goto _out; 81 | _resume: 82 | _keys = _javascript_trans_keys + _javascript_key_offsets[cs]; 83 | _trans = _javascript_index_offsets[cs]; 84 | 85 | _klen = _javascript_single_lengths[cs]; 86 | if ( _klen > 0 ) { 87 | const char *_lower = _keys; 88 | const char *_mid; 89 | const char *_upper = _keys + _klen - 1; 90 | while (1) { 91 | if ( _upper < _lower ) 92 | break; 93 | 94 | _mid = _lower + ((_upper-_lower) >> 1); 95 | if ( (*p) < *_mid ) 96 | _upper = _mid - 1; 97 | else if ( (*p) > *_mid ) 98 | _lower = _mid + 1; 99 | else { 100 | _trans += (unsigned int)(_mid - _keys); 101 | goto _match; 102 | } 103 | } 104 | _keys += _klen; 105 | _trans += _klen; 106 | } 107 | 108 | _klen = _javascript_range_lengths[cs]; 109 | if ( _klen > 0 ) { 110 | const char *_lower = _keys; 111 | const char *_mid; 112 | const char *_upper = _keys + (_klen<<1) - 2; 113 | while (1) { 114 | if ( _upper < _lower ) 115 | break; 116 | 117 | _mid = _lower + (((_upper-_lower) >> 1) & ~1); 118 | if ( (*p) < _mid[0] ) 119 | _upper = _mid - 2; 120 | else if ( (*p) > _mid[1] ) 121 | _lower = _mid + 2; 122 | else { 123 | _trans += (unsigned int)((_mid - _keys)>>1); 124 | goto _match; 125 | } 126 | } 127 | _trans += _klen; 128 | } 129 | 130 | _match: 131 | cs = _javascript_trans_targs[_trans]; 132 | 133 | if ( cs == 0 ) 134 | goto _out; 135 | if ( ++p != pe ) 136 | goto _resume; 137 | _test_eof: {} 138 | _out: {} 139 | } 140 | 141 | #line 38 "src/ngx_http_xss_util.rl" 142 | 143 | 144 | if (cs < 6 || p != pe) { 145 | return NGX_DECLINED; 146 | } 147 | 148 | return NGX_OK; 149 | } 150 | -------------------------------------------------------------------------------- /src/ngx_http_xss_util.h: -------------------------------------------------------------------------------- 1 | #ifndef NGX_HTTP_XSS_UTIL_H 2 | #define NGX_HTTP_XSS_UTIL_H 3 | 4 | #include 5 | #include 6 | 7 | 8 | #ifndef ngx_copy_const_str 9 | #define ngx_copy_const_str(p, s) ngx_copy(p, s, sizeof(s) - 1) 10 | #endif 11 | 12 | #ifndef NGX_UNESCAPE_URI_COMPONENT 13 | #define NGX_UNESCAPE_URI_COMPONENT 0 14 | #endif 15 | 16 | 17 | ngx_int_t ngx_http_xss_test_callback(u_char *data, size_t len); 18 | 19 | 20 | #endif /* NGX_HTTP_XSS_UTIL_H */ 21 | 22 | -------------------------------------------------------------------------------- /src/ngx_http_xss_util.rl: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) agentzh 4 | */ 5 | 6 | 7 | #ifndef DDEBUG 8 | #define DDEBUG 0 9 | #endif 10 | #include "ddebug.h" 11 | 12 | #include "ngx_http_xss_util.h" 13 | 14 | 15 | %% machine javascript; 16 | %% write data; 17 | 18 | ngx_int_t ngx_http_xss_test_callback(u_char *data, size_t len) 19 | { 20 | signed char *p = (signed char *) data; 21 | signed char *pe; 22 | int cs; 23 | 24 | pe = p + len; 25 | 26 | %%{ 27 | identifier = [$A-Za-z_] [$A-Za-z0-9_]*; 28 | 29 | index = [0-9]* '.' [0-9]+ 30 | | [0-9]+ 31 | ; 32 | 33 | main := identifier ( '.' identifier )* 34 | ('[' index ']')? ; 35 | 36 | write init; 37 | write exec; 38 | }%% 39 | 40 | if (cs < %%{ write first_final; }%% || p != pe) { 41 | return NGX_DECLINED; 42 | } 43 | 44 | return NGX_OK; 45 | } 46 | -------------------------------------------------------------------------------- /t/gzip.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (3 * blocks() + 1); 9 | 10 | #no_long_string(); 11 | log_level 'warn'; 12 | 13 | run_tests(); 14 | 15 | #no_diff(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: xss_get is on while no xss_callback_arg & xss_output_type 20 | --- config 21 | xss_get on; # enable cross-site GET support 22 | xss_callback_arg c; # use $arg_callback 23 | gzip on; 24 | gzip_types application/json application/x-javascript; 25 | 26 | location /foo { 27 | default_type "application/json"; 28 | echo -n '{"cat":32}'; 29 | } 30 | --- more_headers 31 | Accept-Encoding: gzip 32 | --- request 33 | GET /foo?c=blah 34 | --- response_headers 35 | Content-Type: application/x-javascript 36 | Content-Encoding: gzip 37 | --- response_body_like eval 38 | "\x{00}\x{00}\x{00}" 39 | 40 | -------------------------------------------------------------------------------- /t/sanity.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(1); 7 | 8 | plan tests => repeat_each() * 3 * blocks(); 9 | 10 | no_long_string(); 11 | 12 | run_tests(); 13 | 14 | #no_diff(); 15 | 16 | __DATA__ 17 | 18 | === TEST 1: skipped: no callback arg given 19 | --- config 20 | location /foo { 21 | default_type 'application/json'; 22 | xss_get on; 23 | xss_callback_arg foo; 24 | echo '[]'; 25 | } 26 | --- request 27 | GET /foo 28 | --- response_headers_like 29 | Content-Type: application/json 30 | --- response_body 31 | [] 32 | 33 | 34 | 35 | === TEST 2: sanity 36 | --- config 37 | location /foo { 38 | default_type 'application/json'; 39 | xss_get on; 40 | xss_callback_arg a; 41 | echo '[]'; 42 | } 43 | --- request 44 | GET /foo?foo=blar&a=bar 45 | --- response_headers_like 46 | Content-Type: application/x-javascript 47 | --- response_body chop 48 | bar([] 49 | ); 50 | 51 | 52 | 53 | === TEST 3: bug 54 | --- config 55 | location /foo { 56 | default_type 'application/json'; 57 | xss_get on; 58 | xss_callback_arg foo; 59 | echo '[]'; 60 | } 61 | --- request 62 | GET /foo?foo=bar 63 | --- response_headers_like 64 | Content-Type: application/x-javascript 65 | --- response_body chop 66 | bar([] 67 | ); 68 | 69 | 70 | 71 | === TEST 4: uri escaped 72 | --- config 73 | location /foo { 74 | default_type 'application/json'; 75 | xss_get on; 76 | xss_callback_arg _callback; 77 | echo '[]'; 78 | } 79 | --- request 80 | GET /foo?_callback=OpenResty.callbackMap%5b32%5D 81 | --- response_headers_like 82 | Content-Type: application/x-javascript 83 | --- response_body chop 84 | OpenResty.callbackMap[32]([] 85 | ); 86 | 87 | 88 | 89 | === TEST 5: test invalid callback 90 | --- config 91 | location /foo { 92 | default_type 'application/json'; 93 | xss_get on; 94 | xss_callback_arg _callback; 95 | echo '[]'; 96 | } 97 | --- request 98 | GET /foo?_callback=a();b 99 | --- response_headers_like 100 | Content-Type: application/json 101 | --- response_body 102 | [] 103 | --- error_code: 200 104 | 105 | 106 | 107 | === TEST 6: input type mismatch 108 | --- config 109 | location /foo { 110 | default_type 'text/plain'; 111 | xss_get on; 112 | xss_callback_arg foo; 113 | echo '[]'; 114 | } 115 | --- request 116 | GET /foo?foo=bar 117 | --- response_headers_like 118 | Content-Type: text/plain 119 | --- response_body 120 | [] 121 | 122 | 123 | 124 | === TEST 7: input type match by setting xss_input_types 125 | --- config 126 | location /foo { 127 | default_type 'text/plain'; 128 | xss_get on; 129 | xss_callback_arg foo; 130 | xss_input_types text/plain text/css; 131 | echo '[]'; 132 | } 133 | --- request 134 | GET /foo?foo=bar 135 | --- response_headers_like 136 | Content-Type: application/x-javascript 137 | --- response_body chop 138 | bar([] 139 | ); 140 | 141 | 142 | 143 | === TEST 8: set a different output type 144 | --- config 145 | location /foo { 146 | default_type 'text/plain'; 147 | xss_get on; 148 | xss_callback_arg foo; 149 | xss_input_types text/plain text/css; 150 | xss_output_type text/html; 151 | echo '[]'; 152 | } 153 | --- request 154 | GET /foo?foo=bar 155 | --- response_headers_like 156 | Content-Type: text/html 157 | --- response_body chop 158 | bar([] 159 | ); 160 | 161 | 162 | 163 | === TEST 9: xss_get is on while no xss_callback_arg 164 | --- config 165 | location /foo { 166 | default_type 'application/json'; 167 | xss_get on; 168 | #xss_callback_arg foo; 169 | echo '[]'; 170 | } 171 | --- request 172 | GET /foo?foo=bar 173 | --- response_headers_like 174 | Content-Type: application/json 175 | --- response_body 176 | [] 177 | 178 | 179 | 180 | === TEST 10: xss_get is on while no xss_callback_arg 181 | --- config 182 | location /foo { 183 | default_type "application/json"; 184 | echo '{"errcode":400,"errstr":"Bad Request"}'; 185 | 186 | xss_get on; # enable cross-site GET support 187 | xss_callback_arg callback; # use $arg_callback 188 | } 189 | --- request 190 | GET /foo?callback=blah 191 | --- response_headers 192 | Content-Type: application/x-javascript 193 | --- response_body chop 194 | blah({"errcode":400,"errstr":"Bad Request"} 195 | ); 196 | 197 | 198 | 199 | === TEST 11: xss_get is on while no xss_callback_arg & xss_output_type 200 | --- config 201 | location /foo { 202 | default_type "application/json"; 203 | echo '{"errcode":400,"errstr":"Bad Request"}'; 204 | 205 | xss_get on; # enable cross-site GET support 206 | xss_callback_arg callback; # use $arg_callback 207 | xss_output_type text/javascript; 208 | } 209 | --- request 210 | GET /foo?callback=blah 211 | --- response_headers 212 | Content-Type: text/javascript 213 | --- response_body chop 214 | blah({"errcode":400,"errstr":"Bad Request"} 215 | ); 216 | 217 | 218 | 219 | === TEST 12: check status and override status 220 | --- config 221 | location /foo { 222 | xss_get on; 223 | xss_callback_arg c; 224 | xss_check_status off; 225 | default_type application/json; 226 | 227 | content_by_lua ' 228 | ngx.status = 404 229 | ngx.print("hello") 230 | '; 231 | } 232 | --- request 233 | GET /foo?c=blah 234 | --- response_body chop 235 | blah(hello); 236 | --- error_code: 200 237 | --- response_headers 238 | Content-Type: application/x-javascript 239 | 240 | 241 | 242 | === TEST 13: check status and NO override status 243 | --- config 244 | location /foo { 245 | xss_get on; 246 | xss_callback_arg c; 247 | xss_override_status off; 248 | xss_check_status off; 249 | default_type application/json; 250 | 251 | content_by_lua ' 252 | ngx.status = 404 253 | ngx.print("hello") 254 | '; 255 | } 256 | --- request 257 | GET /foo?c=blah 258 | --- response_body chop 259 | blah(hello); 260 | --- error_code: 404 261 | --- response_headers 262 | Content-Type: application/x-javascript 263 | 264 | 265 | 266 | === TEST 14: bug: keys started by underscore 267 | --- config 268 | location /foo { 269 | default_type 'application/json'; 270 | xss_get on; 271 | xss_callback_arg _callback; 272 | echo '[]'; 273 | } 274 | --- request 275 | GET /foo?_callback=foo._bar 276 | --- response_headers_like 277 | Content-Type: application/x-javascript 278 | --- response_body chop 279 | foo._bar([] 280 | ); 281 | 282 | 283 | 284 | === TEST 15: exec 285 | --- config 286 | location /foo { 287 | default_type "application/json"; 288 | echo -n hello world; 289 | xss_get on; 290 | xss_callback_arg _c; 291 | } 292 | location /lua { 293 | echo_exec /foo $args; 294 | } 295 | --- request 296 | GET /lua?_c=bah 297 | --- response_headers 298 | Content-Type: application/x-javascript 299 | --- response_body chop 300 | bah(hello world); 301 | 302 | -------------------------------------------------------------------------------- /t/unused.t: -------------------------------------------------------------------------------- 1 | # vi:filetype= 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(2); 7 | 8 | plan tests => repeat_each() * (4 * blocks()); 9 | 10 | #no_long_string(); 11 | log_level 'warn'; 12 | 13 | run_tests(); 14 | 15 | #no_diff(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: used 20 | --- config 21 | location = /t { 22 | echo hello; 23 | xss_get on; 24 | xss_callback_arg c; 25 | } 26 | --- more_headers 27 | Accept-Encoding: gzip 28 | --- request 29 | GET /t 30 | --- stap 31 | F(ngx_http_xss_header_filter) { 32 | println("xss header filter") 33 | } 34 | F(ngx_http_xss_body_filter) { 35 | println("xss body filter") 36 | } 37 | 38 | --- stap_out 39 | xss header filter 40 | xss body filter 41 | xss body filter 42 | 43 | --- response_body 44 | hello 45 | --- no_error_log 46 | [error] 47 | 48 | 49 | 50 | === TEST 2: not used 51 | --- config 52 | location = /t { 53 | echo hello; 54 | } 55 | --- more_headers 56 | Accept-Encoding: gzip 57 | --- request 58 | GET /t 59 | --- stap 60 | F(ngx_http_xss_header_filter) { 61 | println("xss header filter") 62 | } 63 | F(ngx_http_xss_body_filter) { 64 | println("xss body filter") 65 | } 66 | 67 | --- stap_out 68 | --- response_body 69 | hello 70 | --- no_error_log 71 | [error] 72 | 73 | 74 | 75 | === TEST 3: used (multiple http {} blocks) 76 | This test case won't run with nginx 1.9.3+ since duplicate http {} blocks 77 | have been prohibited since then. 78 | --- SKIP 79 | --- config 80 | location = /t { 81 | default_type application/json; 82 | xss_callback_arg 'callback'; 83 | echo -n hello; 84 | xss_get on; 85 | } 86 | --- more_headers 87 | Accept-Encoding: gzip 88 | --- request 89 | GET /t?callback=foo 90 | --- stap 91 | F(ngx_http_xss_header_filter) { 92 | println("xss header filter") 93 | } 94 | F(ngx_http_xss_body_filter) { 95 | println("xss body filter") 96 | } 97 | 98 | --- stap_out 99 | xss header filter 100 | xss body filter 101 | xss body filter 102 | 103 | --- post_main_config 104 | http { 105 | } 106 | 107 | --- response_body chop 108 | foo(hello); 109 | --- no_error_log 110 | [error] 111 | 112 | -------------------------------------------------------------------------------- /util/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # this file is mostly meant to be used by the author himself. 4 | 5 | ragel -T1 src/ngx_http_xss_util.rl || exit 1 6 | if [ $? != 0 ]; then 7 | echo 'Failed to generate the ngx_http_xss_util.c.' 1>&2 8 | exit 1; 9 | fi 10 | 11 | ./util/fix-clang-warnings || exit 1 12 | 13 | root=`pwd` 14 | version=$1 15 | force=$2 16 | 17 | ngx-build $force $version \ 18 | --with-cc-opt="-I$PCRE_INC -I$OPENSSL_INC" \ 19 | --with-ld-opt="-L$PCRE_LIB -L$OPENSSL_LIB -Wl,-rpath,$PCRE_LIB:$OPENSSL_LIB" \ 20 | --with-http_ssl_module \ 21 | --without-mail_pop3_module \ 22 | --without-mail_imap_module \ 23 | --without-mail_smtp_module \ 24 | --without-http_upstream_ip_hash_module \ 25 | --without-http_empty_gif_module \ 26 | --without-http_memcached_module \ 27 | --without-http_referer_module \ 28 | --without-http_autoindex_module \ 29 | --without-http_auth_basic_module \ 30 | --without-http_userid_module \ 31 | --add-module=../echo-nginx-module \ 32 | --add-module=../ndk-nginx-module \ 33 | --add-module=../lua-nginx-module \ 34 | --add-module=$root $opts \ 35 | --with-debug 36 | #--with-cc-opt="-g3 -O0" 37 | #--add-module=$root/../echo-nginx-module \ 38 | #--without-http_ssi_module # we cannot disable ssi because echo_location_async depends on it (i dunno why?!) 39 | 40 | -------------------------------------------------------------------------------- /util/fix-clang-warnings: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use File::Temp 'tempfile'; 6 | 7 | my $infile = "src/ngx_http_xss_util.c"; 8 | my ($out, $outfile) = tempfile(); 9 | open my $in, $infile 10 | or die "Cannot open $infile for reading: $!\n"; 11 | 12 | my $hits = 0; 13 | while (<$in>) { 14 | if (/ \b javascript_ (?: en_main | error | first_final ) \b /x) 15 | { 16 | #warn "HIT!"; 17 | $hits++; 18 | next; 19 | } 20 | print $out $_; 21 | } 22 | 23 | close $in; 24 | close $out; 25 | 26 | if ($hits) { 27 | my $cmd = "cp $outfile $infile"; 28 | system($cmd) == 0 29 | or die "Cannot run command \"$cmd\": $!"; 30 | } 31 | #die; 32 | 33 | __END__ 34 | 35 | This script is to fix the following clang warnings when using Ragel 6.8/6.9/etc: 36 | 37 | rc/ngx_http_xss_util.c:22:18: error: unused variable 'javascript_first_final' [-Werror,-Wunused-const-variable] 38 | static const int javascript_first_final = 6; 39 | ^ 40 | src/ngx_http_xss_util.c:23:18: error: unused variable 'javascript_error' [-Werror,-Wunused-const-variable] 41 | static const int javascript_error = 0; 42 | ^ 43 | src/ngx_http_xss_util.c:25:18: error: unused variable 'javascript_en_main' [-Werror,-Wunused-const-variable] 44 | static const int javascript_en_main = 1; 45 | ^ 46 | 3 errors generated. 47 | -------------------------------------------------------------------------------- /util/update-readme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | perl util/wiki2pod.pl doc/manpage.wiki > /tmp/a.pod && pod2text /tmp/a.pod > README 4 | 5 | -------------------------------------------------------------------------------- /util/wiki2pod.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use bytes; 6 | 7 | my @nl_counts; 8 | my $last_nl_count_level; 9 | 10 | my @bl_counts; 11 | my $last_bl_count_level; 12 | 13 | sub fmt_pos ($) { 14 | (my $s = $_[0]) =~ s{\#(.*)}{/"$1"}; 15 | $s; 16 | } 17 | 18 | sub fmt_mark ($$) { 19 | my ($tag, $s) = @_; 20 | my $max_level = 0; 21 | while ($s =~ /([<>])\1*/g) { 22 | my $level = length $&; 23 | if ($level > $max_level) { 24 | $max_level = $level; 25 | } 26 | } 27 | 28 | my $times = $max_level + 1; 29 | if ($times > 1) { 30 | $s = " $s "; 31 | } 32 | return $tag . ('<' x $times) . $s . ('>' x $times); 33 | } 34 | 35 | print "=encoding utf-8\n\n"; 36 | 37 | while (<>) { 38 | if ($. == 1) { 39 | # strip the leading U+FEFF byte in MS-DOS text files 40 | my $first = ord(substr($_, 0, 1)); 41 | #printf STDERR "0x%x", $first; 42 | #my $second = ord(substr($_, 2, 1)); 43 | #printf STDERR "0x%x", $second; 44 | if ($first == 0xEF) { 45 | substr($_, 0, 1, ''); 46 | #warn "Hit!"; 47 | } 48 | } 49 | s{\[(http[^ \]]+) ([^\]]*)\]}{$2 (L<$1>)}gi; 50 | s{ \[\[ ( [^\]\|]+ ) \| ([^\]]*) \]\] }{"L<$2|" . fmt_pos($1) . ">"}gixe; 51 | s{(.*?)}{fmt_mark('C', $1)}gie; 52 | s{'''(.*?)'''}{fmt_mark('B', $1)}ge; 53 | s{''(.*?)''}{fmt_mark('I', $1)}ge; 54 | if (s{^\s*<[^>]+>\s*$}{}) { 55 | next; 56 | } 57 | 58 | if (/^\s*$/) { 59 | print "\n"; 60 | next; 61 | } 62 | 63 | =begin cmt 64 | 65 | if ($. == 1) { 66 | warn $_; 67 | for my $i (0..length($_) - 1) { 68 | my $chr = substr($_, $i, 1); 69 | warn "chr ord($i): ".ord($chr)." \"$chr\"\n"; 70 | } 71 | } 72 | 73 | =end cmt 74 | =cut 75 | 76 | if (/(=+) (.*) \1$/) { 77 | #warn "HERE! $_" if $. == 1; 78 | my ($level, $title) = (length $1, $2); 79 | collapse_lists(); 80 | 81 | print "\n=head$level $title\n\n"; 82 | } elsif (/^(\#+) (.*)/) { 83 | my ($level, $txt) = (length($1) - 1, $2); 84 | if (defined $last_nl_count_level && $level != $last_nl_count_level) { 85 | print "\n=back\n\n"; 86 | } 87 | $last_nl_count_level = $level; 88 | $nl_counts[$level] ||= 0; 89 | if ($nl_counts[$level] == 0) { 90 | print "\n=over\n\n"; 91 | } 92 | $nl_counts[$level]++; 93 | print "\n=item $nl_counts[$level].\n\n"; 94 | print "$txt\n"; 95 | } elsif (/^(\*+) (.*)/) { 96 | my ($level, $txt) = (length($1) - 1, $2); 97 | if (defined $last_bl_count_level && $level != $last_bl_count_level) { 98 | print "\n=back\n\n"; 99 | } 100 | $last_bl_count_level = $level; 101 | $bl_counts[$level] ||= 0; 102 | if ($bl_counts[$level] == 0) { 103 | print "\n=over\n\n"; 104 | } 105 | $bl_counts[$level]++; 106 | print "\n=item *\n\n"; 107 | print "$txt\n"; 108 | } else { 109 | collapse_lists(); 110 | print; 111 | } 112 | } 113 | 114 | collapse_lists(); 115 | 116 | sub collapse_lists { 117 | while (defined $last_nl_count_level && $last_nl_count_level >= 0) { 118 | print "\n=back\n\n"; 119 | $last_nl_count_level--; 120 | } 121 | undef $last_nl_count_level; 122 | undef @nl_counts; 123 | 124 | while (defined $last_bl_count_level && $last_bl_count_level >= 0) { 125 | print "\n=back\n\n"; 126 | $last_bl_count_level--; 127 | } 128 | undef $last_bl_count_level; 129 | undef @bl_counts; 130 | } 131 | 132 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:Addr4 4 | fun:ngx_init_cycle 5 | fun:ngx_master_process_cycle 6 | fun:main 7 | } 8 | { 9 | 10 | Memcheck:Leak 11 | fun:malloc 12 | fun:ngx_calloc 13 | fun:ngx_event_process_init 14 | } 15 | { 16 | 17 | Memcheck:Leak 18 | fun:malloc 19 | fun:ngx_alloc 20 | fun:ngx_event_process_init 21 | } 22 | { 23 | 24 | Memcheck:Param 25 | epoll_ctl(event) 26 | fun:epoll_ctl 27 | } 28 | { 29 | 30 | Memcheck:Cond 31 | fun:ngx_conf_flush_files 32 | } 33 | { 34 | nginx-core-process-init 35 | Memcheck:Leak 36 | fun:malloc 37 | fun:ngx_alloc 38 | fun:ngx_event_process_init 39 | } 40 | { 41 | nginx-core-crc32-init 42 | Memcheck:Leak 43 | fun:malloc 44 | fun:ngx_alloc 45 | fun:ngx_crc32_table_init 46 | fun:main 47 | } 48 | { 49 | palloc_large_for_init_request 50 | Memcheck:Leak 51 | fun:malloc 52 | fun:ngx_alloc 53 | fun:ngx_palloc_large 54 | fun:ngx_palloc 55 | fun:ngx_pcalloc 56 | fun:ngx_http_init_request 57 | fun:ngx_epoll_process_events 58 | fun:ngx_process_events_and_timers 59 | } 60 | { 61 | palloc_large_for_create_temp_buf 62 | Memcheck:Leak 63 | fun:malloc 64 | fun:ngx_alloc 65 | fun:ngx_palloc_large 66 | fun:ngx_palloc 67 | fun:ngx_create_temp_buf 68 | fun:ngx_http_init_request 69 | fun:ngx_epoll_process_events 70 | fun:ngx_process_events_and_timers 71 | } 72 | { 73 | accept_create_pool 74 | Memcheck:Leak 75 | fun:memalign 76 | fun:posix_memalign 77 | fun:ngx_memalign 78 | fun:ngx_create_pool 79 | fun:ngx_event_accept 80 | fun:ngx_epoll_process_events 81 | fun:ngx_process_events_and_timers 82 | } 83 | { 84 | create_pool_for_init_req 85 | Memcheck:Leak 86 | fun:memalign 87 | fun:posix_memalign 88 | fun:ngx_memalign 89 | fun:ngx_create_pool 90 | fun:ngx_http_init_request 91 | fun:ngx_epoll_process_events 92 | fun:ngx_process_events_and_timers 93 | } 94 | { 95 | 96 | Memcheck:Cond 97 | fun:index 98 | fun:expand_dynamic_string_token 99 | fun:_dl_map_object 100 | fun:map_doit 101 | fun:_dl_catch_error 102 | fun:do_preload 103 | fun:dl_main 104 | } 105 | { 106 | 107 | Memcheck:Leak 108 | match-leak-kinds: definite 109 | fun:malloc 110 | fun:ngx_alloc 111 | fun:ngx_set_environment 112 | fun:ngx_single_process_cycle 113 | } 114 | { 115 | 116 | Memcheck:Leak 117 | match-leak-kinds: definite 118 | fun:malloc 119 | fun:ngx_alloc 120 | fun:ngx_set_environment 121 | fun:ngx_worker_process_init 122 | fun:ngx_worker_process_cycle 123 | } 124 | --------------------------------------------------------------------------------