├── .gitignore ├── LICENSE ├── Makefile ├── README ├── TODO ├── config ├── src ├── ngx_yy_sec_waf.h ├── ngx_yy_sec_waf_body_processor.c ├── ngx_yy_sec_waf_conn_processor.c ├── ngx_yy_sec_waf_module.c ├── ngx_yy_sec_waf_re.c ├── ngx_yy_sec_waf_re.h ├── ngx_yy_sec_waf_re_action.c ├── ngx_yy_sec_waf_re_cache.c ├── ngx_yy_sec_waf_re_operator.c ├── ngx_yy_sec_waf_re_tfn.c ├── ngx_yy_sec_waf_re_variable.c └── ngx_yy_sec_waf_utils.c └── t ├── yy_sec_waf_action.t ├── yy_sec_waf_base.t └── yy_sec_waf_files.t /.gitignore: -------------------------------------------------------------------------------- 1 | *.svn 2 | *.git 3 | */servroot 4 | *core 5 | *conf 6 | 7 | 3rdparty 8 | nginx* 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Sophos (hnlq.sysu@gmail.com) 2 | 3 | This software is licensed under the terms of the BSD license. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NGINX_PATH=nginx-1.2.3 2 | 3 | with-debug: 4 | $(shell chmod +x ../3rdparty/*/configure ../$(NGINX_PATH)/configure) \ 5 | cd $(NGINX_PATH) && ./configure --prefix=/usr/local/nginx \ 6 | --add-module=../ \ 7 | --add-module=../3rdparty/nginx_upstream_hash \ 8 | --add-module=../3rdparty/simpl-ngx_devel_kit \ 9 | --add-module=../3rdparty/set-misc-nginx-module \ 10 | --add-module=../3rdparty/echo-nginx-module \ 11 | --add-module=../3rdparty/memc-nginx-module \ 12 | --add-module=../3rdparty/srcache-nginx-module \ 13 | --add-module=../3rdparty/redis2-nginx-module \ 14 | --add-module=../3rdparty/lua-nginx-module \ 15 | --add-module=../3rdparty/ngx_http_redis \ 16 | --add-module=../3rdparty/nginx-http-concat \ 17 | --with-zlib=../3rdparty/zlib-1.2.8 \ 18 | --with-pcre=../3rdparty/pcre-8.33 \ 19 | --with-pcre-opt="-g -O1" \ 20 | --with-pcre-jit \ 21 | --without-mail_pop3_module \ 22 | --without-mail_smtp_module \ 23 | --without-mail_imap_module \ 24 | --without-http_uwsgi_module \ 25 | --without-http_scgi_module \ 26 | --with-http_realip_module \ 27 | --with-http_addition_module \ 28 | --with-http_sub_module \ 29 | --with-http_dav_module \ 30 | --with-http_flv_module \ 31 | --with-http_mp4_module \ 32 | --with-http_gzip_static_module \ 33 | --with-http_random_index_module \ 34 | --with-http_secure_link_module \ 35 | --with-file-aio \ 36 | --with-http_stub_status_module \ 37 | --with-http_ssl_module \ 38 | --with-debug && make 39 | 40 | without-debug: 41 | $(shell chmod +x ./3rdparty/*/configure) \ 42 | cd $(NGINX_PATH) && ./configure --user=www-data --group=www-data --prefix=/usr/local/nginx \ 43 | --add-module=. \ 44 | --add-module=../3rdparty/nginx_upstream_hash \ 45 | --without-mail_pop3_module \ 46 | --without-mail_smtp_module \ 47 | --without-mail_imap_module \ 48 | --without-http_uwsgi_module \ 49 | --without-http_scgi_module \ 50 | --with-http_stub_status_module \ 51 | --with-http_ssl_module \ 52 | --with-zlib=../3rdparty/zlib-1.2.8 \ 53 | --with-openssl=../3rdparty/openssl-1.0.1e \ 54 | --with-pcre=../3rdparty/pcre-8.33 \ 55 | --with-pcre-opt="-g -O2" \ 56 | --with-pcre-jit \ 57 | --with-debug && make 58 | 59 | mac: 60 | $(shell chmod +x ./3rdparty/*/configure) \ 61 | cd $NGINX_PATH && ./configure --prefix=/usr/local/nginx \ 62 | --add-module=$(shell pwd) \ 63 | --add-module=$(shell pwd)/../echo-nginx-module \ 64 | --without-mail_pop3_module \ 65 | --without-mail_smtp_module \ 66 | --without-mail_imap_module \ 67 | --without-http_uwsgi_module \ 68 | --without-http_scgi_module \ 69 | --with-http_stub_status_module \ 70 | --with-pcre-opt="-g -O2" \ 71 | --with-pcre-jit \ 72 | --with-debug && make 73 | 74 | 75 | with-pcre-lib: 76 | cd $NGINX_PATH && ./configure --add-module=. --without-mail_pop3_module --without-mail_smtp_module --without-mail_imap_module --without-http_uwsgi_module --without-http_scgi_module --with-http_stub_status_module --with-http_ssl_module --with-pcre --with-pcre-jit && make -j6 77 | nginx: 78 | cd $NGINX_PATH && make -j6 79 | 80 | clean: 81 | cd $NGINX_PATH && make clean 82 | 83 | test: 84 | prove -r t/*.t 85 | 86 | install: 87 | cd $NGINX_PATH && make install 88 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | This software is open source, works as a WAF module in Nginx web server, and it provides 4 | a lightweight and restful framework for us to look into the content of requests from clients. 5 | 6 | It can defend amount of attacks, such as COMMAND/XSS/SQL/HEADER injection, 7 | upload file attack, slow HTTP DOS, hash consistency attack, etc. 8 | 9 | Also, it provides an effective way to monitor the Nginx web server in real time, 10 | supports IP whitelists and blacklists, and works with log system for further study. 11 | 12 | Features 13 | ======== 14 | 1. Flexible Rule Engine: 15 | 16 | A flexible rule engine sits in the heart of YY SEC WAF. 17 | 18 | 2. Effecient Regex Engine: 19 | 20 | I am please to choose PCRE as regex engine for YY SEC WAF. 21 | 22 | 3. Real-Time Monitoring and Attack Detection: 23 | 24 | YY SEC WAF can monitor the HTTP traffic in real time in order to detect attacks. 25 | In this case YY SEC WAF operates as a web intrusion detection tool, allowing you 26 | to react to suspicious events that take place at your web systems. 27 | 28 | 4. Network-Based Deployment: 29 | 30 | YY SEC WAF works equally well when deployed as part of a reverse proxy server, 31 | and many of our customers choose to do so. In this scenario, one installation 32 | of YY SEC WAF can protect any number or type of back-end web servers. 33 | 34 | 5. Embedded Deployment: 35 | 36 | YY SEC WAF is an embeddable web application firewall, which means it can be 37 | deployed as part of your existing web server infrastructure (Nginx). 38 | 39 | Install 40 | ======= 41 | To make things simpler, i just write a simple Makefile to compile source file. 42 | You may cann't use it directly, just feel free to improve it for yourselves. 43 | 44 | Dependencies: 45 | apt-get install libpcre3 libpcre3-dev 46 | apt-get install zlib1g zlib1g-dev 47 | apt-get install openssl openssl-dev 48 | 49 | make 50 | 51 | Test 52 | ==== 53 | prove -r t/ 54 | 55 | run the test case. 56 | 57 | About 58 | ===== 59 | nginx-http-yy-sec-waf-module 60 | 61 | Copyright (C) YY, Inc. 62 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 1. restruct the re var module with varibales in nginx. done 2 | 2. refine the re action module to be more beautiful 3 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_yy_sec_waf_module 2 | #HTTP_MODULES="$HTTP_MODULES ngx_http_yy_sec_waf_module" 3 | HTTP_AUX_FILTER_MODULES="$ngx_addon_name $HTTP_AUX_FILTER_MODULES" 4 | 5 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_yy_sec_waf_module.c 6 | $ngx_addon_dir/src/ngx_yy_sec_waf_utils.c 7 | $ngx_addon_dir/src/ngx_yy_sec_waf_body_processor.c 8 | $ngx_addon_dir/src/ngx_yy_sec_waf_conn_processor.c 9 | $ngx_addon_dir/src/ngx_yy_sec_waf_re.c 10 | $ngx_addon_dir/src/ngx_yy_sec_waf_re_operator.c 11 | $ngx_addon_dir/src/ngx_yy_sec_waf_re_variable.c 12 | $ngx_addon_dir/src/ngx_yy_sec_waf_re_tfn.c 13 | $ngx_addon_dir/src/ngx_yy_sec_waf_re_action.c" 14 | 15 | 16 | 17 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/src/ngx_yy_sec_waf.h \ 18 | $ngx_addon_dir/src/ngx_yy_sec_waf_re.h" 19 | -------------------------------------------------------------------------------- /src/ngx_yy_sec_waf.h: -------------------------------------------------------------------------------- 1 | #ifndef __YY_SEC_WAF_H__ 2 | #define __YY_SEC_WAF_H__ 3 | 4 | /* 5 | ** @file: ngx_yy_sec_waf.h 6 | ** @description: This is the header file for yy sec waf. 7 | ** @author: dw_liqi1 8 | ** @date: 2013.07.10 9 | ** Copyright (C) YY, Inc. 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | int ngx_yy_sec_waf_unescape(ngx_str_t *str); 20 | 21 | u_char *ngx_yy_sec_waf_itoa(ngx_pool_t *p, ngx_int_t n); 22 | u_char *ngx_yy_sec_waf_uitoa(ngx_pool_t *p, ngx_uint_t n); 23 | 24 | #define REQUEST_HEADER_PHASE 1 25 | #define REQUEST_BODY_PHASE 2 26 | #define RESPONSE_HEADER_PHASE 4 27 | #define RESPONSE_BODY_PHASE 8 28 | 29 | extern ngx_module_t ngx_http_yy_sec_waf_module; 30 | 31 | extern ngx_atomic_t *request_matched; 32 | extern ngx_atomic_t *request_blocked; 33 | extern ngx_atomic_t *request_allowed; 34 | extern ngx_atomic_t *request_logged; 35 | 36 | typedef struct ngx_http_yy_sec_waf_rule { 37 | ngx_str_t *str; /* STR */ 38 | ngx_http_regex_t *regex; /* REG */ 39 | ngx_str_t *eq; /* EQ */ 40 | ngx_str_t *gt; 41 | ngx_str_t *gids; /* GIDS */ 42 | ngx_str_t *msg; /* MSG */ 43 | ngx_int_t rule_id; 44 | ngx_int_t phase; 45 | 46 | /*target variable index array*/ 47 | ngx_array_t var_index; 48 | 49 | /* operators*/ 50 | ngx_flag_t op_negative; 51 | 52 | void *op_metadata; 53 | void *action_metadata; 54 | void *tfn_metadata; 55 | 56 | /* actions*/ 57 | ngx_flag_t action_level; 58 | ngx_uint_t status; 59 | ngx_flag_t is_chain; 60 | } ngx_http_yy_sec_waf_rule_t; 61 | 62 | typedef struct { 63 | /* ngx_http_yy_sec_waf_rule_t */ 64 | ngx_array_t *request_header_rules; 65 | ngx_array_t *request_body_rules; 66 | ngx_array_t *response_header_rules; 67 | ngx_array_t *response_body_rules; 68 | 69 | ngx_shm_zone_t *shm_zone; 70 | ngx_str_t server_ip; 71 | ngx_str_t *denied_url; 72 | ngx_flag_t enabled; 73 | ngx_flag_t conn_processor; 74 | ngx_flag_t body_processor; 75 | } ngx_http_yy_sec_waf_loc_conf_t; 76 | 77 | typedef struct { 78 | ngx_http_request_t *r; 79 | ngx_pool_t *pool; 80 | ngx_http_yy_sec_waf_loc_conf_t *cf; 81 | ngx_int_t phase; 82 | 83 | ngx_rbtree_t cache_rbtree; 84 | ngx_rbtree_node_t cache_sentinel; 85 | 86 | ngx_str_t args; 87 | 88 | ngx_str_t post_args; 89 | ngx_uint_t post_args_count; 90 | 91 | ngx_str_t *real_client_ip; 92 | ngx_str_t *server_ip; 93 | 94 | u_char *boundary; 95 | ngx_uint_t boundary_len; 96 | ngx_array_t multipart_filename; 97 | ngx_array_t multipart_name; 98 | ngx_array_t content_type; 99 | 100 | ngx_int_t process_body_error; 101 | ngx_str_t process_body_error_msg; 102 | ngx_uint_t post_args_len; 103 | ngx_uint_t conn_per_ip; 104 | ngx_int_t var_index; 105 | ngx_str_t var; 106 | 107 | /* level flags*/ 108 | ngx_flag_t action_level; 109 | ngx_uint_t status; 110 | 111 | /* state */ 112 | ngx_flag_t process_done:1; 113 | ngx_flag_t read_body_done:1; 114 | ngx_flag_t waiting_more_body:1; 115 | 116 | ngx_flag_t matched:1; 117 | ngx_int_t rule_id; 118 | ngx_str_t *gids; 119 | ngx_str_t *msg; 120 | ngx_str_t *matched_string; 121 | } ngx_http_request_ctx_t; 122 | 123 | ngx_int_t ngx_http_yy_sec_waf_process_conn(ngx_http_request_ctx_t *ctx); 124 | 125 | ngx_shm_zone_t *ngx_http_yy_sec_waf_create_shm_zone(ngx_conf_t *cf); 126 | 127 | ngx_int_t ngx_http_yy_sec_waf_process_body(ngx_http_request_t *r, 128 | ngx_http_yy_sec_waf_loc_conf_t *cf, ngx_http_request_ctx_t *ctx); 129 | 130 | 131 | #endif 132 | 133 | -------------------------------------------------------------------------------- /src/ngx_yy_sec_waf_body_processor.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** @file: ngx_yy_sec_waf_body_processor.c 3 | ** @description: This is the body processor for yy sec waf. 4 | ** @author: dw_liqi1 5 | ** @date: 2013.07.17 6 | ** Copyright (C) YY, Inc. 7 | */ 8 | 9 | #include "ngx_yy_sec_waf.h" 10 | 11 | /* 12 | ** @description: This function is called to process spliturl of the request. 13 | ** @para: ngx_http_request_t *r 14 | ** @para: ngx_str_t *str 15 | ** @para: ngx_array_t *rules 16 | ** @para: ngx_http_request_ctx_t *ctx 17 | ** @return: NGX_OK or NGX_ERROR if failed. 18 | */ 19 | 20 | static ngx_int_t 21 | ngx_http_yy_sec_waf_process_spliturl(ngx_http_request_t *r, 22 | ngx_str_t *str, ngx_http_request_ctx_t *ctx) 23 | { 24 | u_char *start, *buffer, *eq, *ev; 25 | ngx_uint_t len, arg_cnt, arg_len, nullbytes, buffer_size; 26 | ngx_str_t value; 27 | 28 | buffer = start = str->data; 29 | len = str->len; 30 | buffer_size = arg_len = 0; 31 | 32 | if (len != 0) 33 | arg_cnt = 1; 34 | 35 | while ((len != 0) && *start) { 36 | if (*start == '&') { 37 | buffer_size++; 38 | *buffer++ = '$'; 39 | arg_cnt++; 40 | start++; 41 | continue; 42 | } 43 | 44 | eq = (u_char*)ngx_strchr((char*)start, '='); 45 | ev = (u_char*)ngx_strchr((char*)start, '&'); 46 | 47 | if (eq) { 48 | if (!ev) 49 | ev = str->data + str->len; 50 | arg_len = ev - start; 51 | eq = ngx_strlchr(start, start+arg_len, '='); 52 | if (!eq) 53 | return NGX_ERROR; 54 | 55 | eq++; 56 | value.data = eq; 57 | value.len = ev - eq; 58 | } else { 59 | break; 60 | } 61 | 62 | nullbytes = ngx_yy_sec_waf_unescape(&value); 63 | 64 | if (nullbytes > 0) { 65 | ctx->process_body_error = 1; 66 | ngx_str_set(&ctx->process_body_error_msg, "UNCOMMON_HEX_ENCODING"); 67 | return NGX_ERROR; 68 | } 69 | 70 | buffer = ngx_cpymem(buffer, value.data, value.len); 71 | buffer_size += value.len; 72 | 73 | start += arg_len; 74 | } 75 | 76 | str->len = buffer_size; 77 | 78 | /* convert \r\n to blank as ' ' to improve the format of error log */ 79 | buffer = str->data; 80 | 81 | while (buffer_size-- > 0) { 82 | if (*buffer == '\n' || *buffer == '\r') 83 | *buffer = ' '; 84 | buffer++; 85 | } 86 | 87 | ctx->post_args.len = str->len; 88 | ctx->post_args.data = ngx_palloc(r->pool, str->len); 89 | if (ctx->post_args.data == NULL) { 90 | return NGX_ERROR; 91 | } 92 | 93 | ngx_memcpy(ctx->post_args.data, str->data, str->len); 94 | ctx->post_args_count = arg_cnt; 95 | ctx->post_args_len = str->len; 96 | 97 | return NGX_OK; 98 | } 99 | 100 | 101 | /* 102 | ** @description: This function is called to process the boundary of the request. 103 | ** @para: ngx_http_request_t *r 104 | ** @para: ngx_http_request_ctx_t *ctx 105 | ** @para: ngx_str_t full_body 106 | ** @return: NGX_OK or NGX_ERROR if failed. 107 | */ 108 | 109 | static ngx_int_t 110 | ngx_http_yy_sec_waf_process_boundary(ngx_http_request_t *r, 111 | u_char **boundary, ngx_uint_t *boundary_len) 112 | { 113 | u_char *start; 114 | u_char *end; 115 | 116 | start = r->headers_in.content_type->value.data + ngx_strlen("multipart/form-data;"); 117 | end = r->headers_in.content_type->value.data + r->headers_in.content_type->value.len; 118 | 119 | while (start < end && *start && (*start == ' ' || *start == '\t')) 120 | start++; 121 | 122 | if (ngx_strncmp(start, "boundary=", ngx_strlen("boundary="))) 123 | return NGX_ERROR; 124 | 125 | start += ngx_strlen("boundary="); 126 | 127 | *boundary_len = end - start; 128 | *boundary = start; 129 | 130 | if (*boundary_len > 70) 131 | return NGX_ERROR; 132 | 133 | return NGX_OK; 134 | } 135 | 136 | /* 137 | ** @description: This function is called to process the disposition of the request. 138 | ** @para: ngx_http_request_t *r 139 | ** @para: ngx_http_request_ctx_t *ctx 140 | ** @para: ngx_str_t full_body 141 | ** @return: NGX_OK or NGX_ERROR if failed. 142 | */ 143 | 144 | static ngx_int_t 145 | ngx_http_yy_sec_waf_process_disposition(ngx_http_request_t *r, 146 | u_char *str, u_char *line_end, ngx_str_t *name, ngx_str_t *filename) 147 | { 148 | u_char *name_start, *name_end, *filename_start, *filename_end; 149 | 150 | while (str < line_end) { 151 | while(str < line_end && *str && (*str == ' ' || *str == '\t')) 152 | str++; 153 | if (str < line_end && *str && *str == ';') 154 | str++; 155 | while (str < line_end && *str && (*str == ' ' || *str == '\t')) 156 | str++; 157 | 158 | if (str >= line_end || !*str) 159 | break; 160 | 161 | if (!ngx_strncmp(str, "name=\"", ngx_strlen("name=\""))) { 162 | name_start = name_end = str + ngx_strlen("name=\""); 163 | do { 164 | name_end = (u_char*) ngx_strchr(name_end, '"'); 165 | if (name_end && *(name_end - 1) != '\\') 166 | break; 167 | name_end++; 168 | } while (name_end && name_end < line_end); 169 | 170 | if (!name_end || !*name_end) 171 | return NGX_ERROR; 172 | 173 | str = name_end; 174 | 175 | if (str < line_end + 1) 176 | str++; 177 | else 178 | return NGX_ERROR; 179 | 180 | name->data = name_start; 181 | name->len = name_end - name_start; 182 | } 183 | else if (!ngx_strncmp(str, "filename=\"", ngx_strlen("filename=\""))) { 184 | filename_end = filename_start = str + ngx_strlen("filename=\""); 185 | do { 186 | /* ignore 0x00 for %00 injection situation */ 187 | filename_end = (u_char*) ngx_strlchr(filename_end, line_end, '"'); 188 | if (filename_end && *(filename_end - 1) != '\\') 189 | break; 190 | filename_end++; 191 | } while (filename_end && filename_end < line_end); 192 | 193 | if (!filename_end) 194 | return NGX_ERROR; 195 | 196 | str = filename_end; 197 | if (str < line_end + 1) 198 | str++; 199 | else 200 | return NGX_ERROR; 201 | 202 | filename->data = filename_start; 203 | filename->len = filename_end - filename_start; 204 | } 205 | else if (str == line_end - 1) 206 | break; 207 | else { 208 | return NGX_ERROR; 209 | } 210 | } 211 | 212 | if (filename_end > line_end || name_end > line_end) 213 | return NGX_ERROR; 214 | 215 | return NGX_OK; 216 | } 217 | 218 | /* 219 | ** @description: This function is called to process the multipart of the request. 220 | ** @para: ngx_http_request_t *r 221 | ** @para: ngx_http_request_ctx_t *ctx 222 | ** @para: ngx_str_t full_body 223 | ** @return: NGX_OK or NGX_ERROR if failed. 224 | */ 225 | 226 | static ngx_int_t 227 | ngx_http_yy_sec_waf_process_multipart(ngx_http_request_t *r, 228 | ngx_str_t *full_body, ngx_http_request_ctx_t *ctx) 229 | { 230 | u_char *boundary, *line_start, *line_end, *body_end, *p; 231 | ngx_uint_t boundary_len, idx, nullbytes; 232 | ngx_str_t name, filename, content_type, *tmp; 233 | 234 | boundary = NULL; 235 | boundary_len = 0; 236 | 237 | if (r == NULL || full_body == NULL || ctx == NULL) { 238 | return NGX_ERROR; 239 | } 240 | 241 | if (ngx_http_yy_sec_waf_process_boundary(r, &boundary, &boundary_len) != NGX_OK) { 242 | ctx->process_body_error = 1; 243 | ngx_str_set(&ctx->process_body_error_msg, "UNCOMMON_CONTENT_TYPE"); 244 | return NGX_ERROR; 245 | } 246 | 247 | ctx->boundary = boundary; 248 | ctx->boundary_len = boundary_len; 249 | 250 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[ysec_waf] boundary: %s", boundary); 251 | 252 | idx = 0; 253 | 254 | p = ngx_strlcasestrn(full_body->data, full_body->data+full_body->len, boundary, boundary_len-1); 255 | if (p == NULL) 256 | return NGX_ERROR; 257 | 258 | full_body->len = full_body->len - (p - full_body->data - 2); 259 | full_body->data = p - 2; 260 | 261 | while (idx < full_body->len) { 262 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[ysec_waf] request_body: %s, len: %d", full_body->data+idx, full_body->len); 263 | 264 | if (idx+boundary_len+6 == full_body->len || idx+boundary_len+4 == full_body->len) { 265 | if (ngx_strncmp(full_body->data+idx, "--", 2) 266 | || ngx_strncmp(full_body->data+idx+2, boundary, boundary_len) 267 | || ngx_strncmp(full_body->data+idx+boundary_len+2, "--", 2)) { 268 | ctx->process_body_error = 1; 269 | ngx_str_set(&ctx->process_body_error_msg, "UNCOMMON_POST_FORMAT"); 270 | return NGX_ERROR; 271 | } else 272 | break; 273 | } 274 | 275 | if ((full_body->len-idx < 4+boundary_len) 276 | || full_body->data[idx] != '-' 277 | || full_body->data[idx+1] != '-' 278 | || ngx_strncmp(full_body->data+idx+2, boundary, boundary_len) 279 | || idx+boundary_len+2+2 >= full_body->len 280 | || full_body->data[idx+boundary_len+2] != '\r' 281 | || full_body->data[idx+boundary_len+2+1] != '\n') { 282 | ctx->process_body_error = 1; 283 | ngx_str_set(&ctx->process_body_error_msg, "UNCOMMON_POST_BOUNDARY"); 284 | return NGX_ERROR; 285 | } 286 | 287 | /* plus with 4 for -- and \r\n*/ 288 | idx += boundary_len + 4; 289 | if (ngx_strncasecmp(full_body->data+idx, (u_char*)"content-disposition: form-data;", 290 | ngx_strlen("content-disposition: form-data;"))) { 291 | ctx->process_body_error = 1; 292 | ngx_str_set(&ctx->process_body_error_msg, "UNCOMMON_POST_FORMAT"); 293 | return NGX_ERROR; 294 | } 295 | 296 | idx += ngx_strlen("content-disposition: form-data;"); 297 | 298 | /* ignore 0x00 for %00 injection situation */ 299 | line_end = (u_char*) ngx_strlchr(full_body->data+idx, full_body->data+full_body->len, '\n'); 300 | if (!line_end) { 301 | ctx->process_body_error = 1; 302 | ngx_str_set(&ctx->process_body_error_msg, "UNCOMMON_POST_FORMAT"); 303 | return NGX_ERROR; 304 | } 305 | 306 | ngx_memzero(&name, sizeof(ngx_str_t)); 307 | ngx_memzero(&filename, sizeof(ngx_str_t)); 308 | ngx_memzero(&content_type, sizeof(ngx_str_t)); 309 | 310 | ngx_http_yy_sec_waf_process_disposition(r, full_body->data+idx, line_end, &name, &filename); 311 | 312 | tmp = ngx_array_push(&ctx->multipart_filename); 313 | if (tmp == NULL) 314 | return NGX_ERROR; 315 | 316 | ngx_memcpy(tmp, &filename, sizeof(ngx_str_t)); 317 | 318 | tmp = ngx_array_push(&ctx->multipart_name); 319 | if (tmp == NULL) 320 | return NGX_ERROR; 321 | 322 | ngx_memcpy(tmp, &name, sizeof(ngx_str_t)); 323 | 324 | if (filename.data) { 325 | line_start = line_end + 1; 326 | line_end = (u_char*) ngx_strchr(line_start, '\n'); 327 | if (!line_end) { 328 | ctx->process_body_error = 1; 329 | ngx_str_set(&ctx->process_body_error_msg, "UNCOMMON_POST_FORMAT"); 330 | return NGX_ERROR; 331 | } 332 | 333 | content_type.data = line_start + ngx_strlen("content-type: "); 334 | content_type.len = (line_end - 1) - content_type.data; 335 | 336 | tmp = ngx_array_push(&ctx->content_type); 337 | if (tmp == NULL) 338 | return NGX_ERROR; 339 | 340 | ngx_memcpy(tmp, &content_type, sizeof(ngx_str_t)); 341 | } 342 | 343 | idx += (u_char*)line_end - (full_body->data + idx) + 1; 344 | if (full_body->data[idx] != '\r' || full_body->data[idx+1] != '\n') { 345 | ctx->process_body_error = 1; 346 | ngx_str_set(&ctx->process_body_error_msg, "UNCOMMON_POST_FORMAT"); 347 | return NGX_ERROR; 348 | } 349 | 350 | idx += 2; 351 | body_end = NULL; 352 | 353 | while (idx < full_body->len) { 354 | body_end = (u_char*) ngx_strstr(full_body->data+idx, "\r\n--"); 355 | while(!body_end) { 356 | idx += ngx_strlen((const char*)full_body->data+idx); 357 | if (idx < full_body->len-2) { 358 | idx++; 359 | body_end = (u_char*) ngx_strstr(full_body->data+idx, "\r\n--"); 360 | } else 361 | break; 362 | } 363 | 364 | if (!body_end) { 365 | ctx->process_body_error = 1; 366 | ngx_str_set(&ctx->process_body_error_msg, "UNCOMMON_POST_FORMAT"); 367 | return NGX_ERROR; 368 | } 369 | 370 | if (!ngx_strncmp(body_end+4, boundary, boundary_len)) 371 | break; 372 | else { 373 | idx += (u_char*)body_end - (full_body->data + idx) + 1; 374 | body_end = NULL; 375 | } 376 | } 377 | 378 | if (!body_end) { 379 | return NGX_ERROR; 380 | } 381 | 382 | if (filename.data) { 383 | nullbytes = ngx_yy_sec_waf_unescape(&filename); 384 | if (nullbytes > 0) { 385 | ctx->process_body_error = 1; 386 | ngx_str_set(&ctx->process_body_error_msg, "UNCOMMON_HEX_ENCODING"); 387 | return NGX_ERROR; 388 | } 389 | 390 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 391 | "[ysec_waf] checking filename [%V]", &filename); 392 | 393 | if (content_type.data) { 394 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 395 | "[ysec_waf] checking content_type [%V]", &content_type); 396 | 397 | if (!ngx_strnstr(filename.data, ".html", filename.len) 398 | || !ngx_strnstr(filename.data, ".html", filename.len)) { 399 | if (!ngx_strncmp(content_type.data, "text/html", content_type.len)) { 400 | ctx->process_body_error = 1; 401 | ngx_str_set(&ctx->process_body_error_msg, "UNCOMMON_FILENAME"); 402 | return NGX_ERROR; 403 | } 404 | } 405 | else if (!ngx_strnstr(filename.data, ".php", filename.len) 406 | || !ngx_strnstr(filename.data, ".jsp", filename.len)) { 407 | if (!ngx_strncmp(content_type.data, "application/octet-stream", content_type.len)) { 408 | ctx->process_body_error = 1; 409 | ngx_str_set(&ctx->process_body_error_msg, "UNCOMMON_FILENAME"); 410 | return NGX_ERROR; 411 | } 412 | } 413 | } 414 | 415 | idx += (u_char*)body_end - (full_body->data + idx); 416 | } else if (name.data) { 417 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 418 | "[ysec_waf] checking name [%V]", &name); 419 | 420 | idx += (u_char*)body_end - (full_body->data + idx); 421 | } 422 | 423 | if (!ngx_strncmp(body_end, "\r\n", ngx_strlen("\r\n"))) 424 | idx += ngx_strlen("\r\n"); 425 | } 426 | 427 | return NGX_OK; 428 | } 429 | 430 | /* 431 | ** @description: This function is called to process the body of the request. 432 | ** @para: ngx_http_request_t *r 433 | ** @para: ngx_http_yy_sec_waf_loc_conf_t *cf 434 | ** @para: ngx_http_request_ctx_t *ctx 435 | ** @return: NGX_OK or NGX_ERROR if failed. 436 | */ 437 | 438 | ngx_int_t 439 | ngx_http_yy_sec_waf_process_body(ngx_http_request_t *r, 440 | ngx_http_yy_sec_waf_loc_conf_t *cf, ngx_http_request_ctx_t *ctx) 441 | { 442 | u_char *src; 443 | ngx_chain_t *bb; 444 | ngx_str_t *full_body; 445 | 446 | if (!r->request_body->bufs || !r->headers_in.content_type) { 447 | ctx->process_body_error = 1; 448 | ngx_str_set(&ctx->process_body_error_msg, "UNCOMMON_CONTENT_TYPE"); 449 | return NGX_ERROR; 450 | } 451 | 452 | if (r->request_body->temp_file) { 453 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[ysec_waf] post body is stored in temp_file."); 454 | return NGX_ERROR; 455 | } 456 | 457 | full_body = ngx_palloc(r->pool, sizeof(ngx_str_t)); 458 | if (full_body == NULL) { 459 | return NGX_ERROR; 460 | } 461 | 462 | if (r->request_body->bufs->next == NULL) { 463 | full_body->len = (ngx_uint_t) (r->request_body->bufs->buf->last 464 | - r->request_body->bufs->buf->pos); 465 | 466 | full_body->data = ngx_pcalloc(r->pool, full_body->len+1); 467 | 468 | ngx_memcpy(full_body->data, r->request_body->bufs->buf->pos, full_body->len); 469 | } else { 470 | for (full_body->len = 0, bb = r->request_body->bufs; bb; bb = bb->next) 471 | full_body->len += bb->buf->last - bb->buf->pos; 472 | 473 | full_body->data = ngx_pcalloc(r->pool, full_body->len+1); 474 | 475 | if (full_body->data == NULL) 476 | return NGX_ERROR; 477 | 478 | src = full_body->data; 479 | 480 | for (bb = r->request_body->bufs; bb; bb = bb->next) 481 | full_body->data = ngx_cpymem(full_body->data, bb->buf->pos, 482 | bb->buf->last - bb->buf->pos); 483 | 484 | full_body->data = src; 485 | } 486 | 487 | //ngx_yy_sec_waf_unescape(full_body); 488 | 489 | if (!ngx_strncasecmp(r->headers_in.content_type->value.data, 490 | (u_char*)"multipart/form-data", ngx_strlen("multipart/form-data"))) { 491 | /* MULTIPART */ 492 | ngx_http_yy_sec_waf_process_multipart(r, full_body, ctx); 493 | } else if (!ngx_strncasecmp(r->headers_in.content_type->value.data, 494 | (u_char*)"application/x-www-form-urlencoded", ngx_strlen("application/x-www-form-urlencoded"))) { 495 | /* X-WWW-FORM-URLENCODED */ 496 | ctx->post_args_len = full_body->len; 497 | 498 | ngx_http_yy_sec_waf_process_spliturl(r, full_body, ctx); 499 | } 500 | 501 | return NGX_OK; 502 | } 503 | 504 | 505 | -------------------------------------------------------------------------------- /src/ngx_yy_sec_waf_conn_processor.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** @file: ngx_yy_sec_waf_conn_processor.c 3 | ** @description: This is the connection processor for yy sec waf. 4 | ** @author: dw_liqi1 5 | ** @date: 2013.12.03 6 | ** Copyright (C) YY, Inc. 7 | */ 8 | 9 | #include "ngx_yy_sec_waf.h" 10 | 11 | typedef struct { 12 | u_char color; 13 | u_char len; 14 | u_short conn; 15 | u_char data[1]; 16 | } yy_sec_waf_conn_node_t; 17 | 18 | 19 | typedef struct { 20 | ngx_shm_zone_t *shm_zone; 21 | ngx_rbtree_node_t *node; 22 | } yy_sec_waf_conn_cleanup_t; 23 | 24 | 25 | typedef struct { 26 | ngx_rbtree_t *rbtree; 27 | ngx_int_t index; 28 | ngx_str_t var; 29 | } yy_sec_waf_conn_ctx_t; 30 | 31 | /* 32 | ** @description: This function is called to cleanup connection. 33 | ** @para: void *data 34 | ** @return: static void. 35 | */ 36 | 37 | static void 38 | yy_sec_waf_conn_cleanup(void *data) 39 | { 40 | yy_sec_waf_conn_cleanup_t *lccln = data; 41 | 42 | ngx_slab_pool_t *shpool; 43 | ngx_rbtree_node_t *node; 44 | yy_sec_waf_conn_ctx_t *ctx; 45 | yy_sec_waf_conn_node_t *lc; 46 | 47 | ctx = lccln->shm_zone->data; 48 | shpool = (ngx_slab_pool_t *) lccln->shm_zone->shm.addr; 49 | node = lccln->node; 50 | lc = (yy_sec_waf_conn_node_t *) &node->color; 51 | 52 | ngx_shmtx_lock(&shpool->mutex); 53 | 54 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, lccln->shm_zone->shm.log, 0, 55 | "[ysec_waf] conn cleanup: %08XD %d", node->key, lc->conn); 56 | 57 | lc->conn--; 58 | 59 | if (lc->conn == 0) { 60 | ngx_rbtree_delete(ctx->rbtree, node); 61 | ngx_slab_free_locked(shpool, node); 62 | } 63 | 64 | ngx_shmtx_unlock(&shpool->mutex); 65 | } 66 | 67 | /* 68 | ** @description: This function is called to cleanup all connections. 69 | ** @para: gx_pool_t *pool 70 | ** @return: static ngx_inline void. 71 | */ 72 | 73 | static ngx_inline void 74 | yy_sec_waf_conn_cleanup_all(ngx_pool_t *pool) 75 | { 76 | ngx_pool_cleanup_t *cln; 77 | 78 | cln = pool->cleanup; 79 | 80 | while (cln && cln->handler == yy_sec_waf_conn_cleanup) { 81 | yy_sec_waf_conn_cleanup(cln->data); 82 | cln = cln->next; 83 | } 84 | 85 | pool->cleanup = cln; 86 | } 87 | 88 | /* 89 | ** @description: This function is called to insert value into rbtree. 90 | ** @para: ngx_rbtree_node_t *temp 91 | ** @para: ngx_rbtree_node_t *node 92 | ** @para: ngx_rbtree_node_t *sentinel 93 | ** @return: static void. 94 | */ 95 | 96 | static void 97 | yy_sec_waf_conn_rbtree_insert_value(ngx_rbtree_node_t *temp, 98 | ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) 99 | { 100 | ngx_rbtree_node_t **p; 101 | yy_sec_waf_conn_node_t *lcn, *lcnt; 102 | 103 | for ( ;; ) { 104 | 105 | if (node->key < temp->key) { 106 | 107 | p = &temp->left; 108 | 109 | } else if (node->key > temp->key) { 110 | 111 | p = &temp->right; 112 | 113 | } else { /* node->key == temp->key */ 114 | 115 | lcn = (yy_sec_waf_conn_node_t *) &node->color; 116 | lcnt = (yy_sec_waf_conn_node_t *) &temp->color; 117 | 118 | p = (ngx_memn2cmp(lcn->data, lcnt->data, lcn->len, lcnt->len) < 0) 119 | ? &temp->left : &temp->right; 120 | } 121 | 122 | if (*p == sentinel) { 123 | break; 124 | } 125 | 126 | temp = *p; 127 | } 128 | 129 | *p = node; 130 | node->parent = temp; 131 | node->left = sentinel; 132 | node->right = sentinel; 133 | ngx_rbt_red(node); 134 | } 135 | 136 | /* 137 | ** @description: This function is called to init shm zone. 138 | ** @para: ngx_shm_zone_t *shm_zone 139 | ** @para: void *data 140 | ** @return: static ngx_int_t. 141 | */ 142 | 143 | static ngx_int_t 144 | yy_sec_waf_conn_init_zone(ngx_shm_zone_t *shm_zone, void *data) 145 | { 146 | yy_sec_waf_conn_ctx_t *octx = data; 147 | 148 | size_t len; 149 | ngx_slab_pool_t *shpool; 150 | ngx_rbtree_node_t *sentinel; 151 | yy_sec_waf_conn_ctx_t *ctx; 152 | 153 | ctx = shm_zone->data; 154 | 155 | if (octx) { 156 | ctx->rbtree = octx->rbtree; 157 | 158 | return NGX_OK; 159 | } 160 | 161 | shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; 162 | 163 | if (shm_zone->shm.exists) { 164 | ctx->rbtree = shpool->data; 165 | 166 | return NGX_OK; 167 | } 168 | 169 | ctx->rbtree = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_t)); 170 | if (ctx->rbtree == NULL) { 171 | return NGX_ERROR; 172 | } 173 | 174 | shpool->data = ctx->rbtree; 175 | 176 | sentinel = ngx_slab_alloc(shpool, sizeof(ngx_rbtree_node_t)); 177 | if (sentinel == NULL) { 178 | return NGX_ERROR; 179 | } 180 | 181 | ngx_rbtree_init(ctx->rbtree, sentinel, 182 | yy_sec_waf_conn_rbtree_insert_value); 183 | 184 | len = sizeof("[ysec_waf] in yy_sec_waf_conn_zone \"\"") + shm_zone->shm.name.len; 185 | 186 | shpool->log_ctx = ngx_slab_alloc(shpool, len); 187 | if (shpool->log_ctx == NULL) { 188 | return NGX_ERROR; 189 | } 190 | 191 | ngx_sprintf(shpool->log_ctx, "[ysec_waf] in yy_sec_waf_conn_zone \"%V\"%Z", 192 | &shm_zone->shm.name); 193 | 194 | return NGX_OK; 195 | } 196 | 197 | /* 198 | ** @description: This function is called to lookup conn node. 199 | ** @para: ngx_rbtree_t *rbtree 200 | ** @para: ngx_http_variable_value_t *vv 201 | ** @para: uint32_t hash 202 | ** @return: static ngx_rbtree_node_t *. 203 | */ 204 | 205 | static ngx_rbtree_node_t * 206 | yy_sec_waf_conn_lookup(ngx_rbtree_t *rbtree, ngx_http_variable_value_t *vv, 207 | uint32_t hash) 208 | { 209 | ngx_int_t rc; 210 | ngx_rbtree_node_t *node, *sentinel; 211 | yy_sec_waf_conn_node_t *lcn; 212 | 213 | node = rbtree->root; 214 | sentinel = rbtree->sentinel; 215 | 216 | while (node != sentinel) { 217 | 218 | if (hash < node->key) { 219 | node = node->left; 220 | continue; 221 | } 222 | 223 | if (hash > node->key) { 224 | node = node->right; 225 | continue; 226 | } 227 | 228 | /* hash == node->key */ 229 | 230 | lcn = (yy_sec_waf_conn_node_t *) &node->color; 231 | 232 | rc = ngx_memn2cmp(vv->data, lcn->data, 233 | (size_t) vv->len, (size_t) lcn->len); 234 | if (rc == 0) { 235 | return node; 236 | } 237 | 238 | node = (rc < 0) ? node->left : node->right; 239 | } 240 | 241 | return NULL; 242 | } 243 | 244 | /* 245 | ** @description: This function is called to create shm zone. 246 | ** @para: ngx_conf_t *cf 247 | ** @return: ngx_shm_zone_t *. 248 | */ 249 | 250 | ngx_shm_zone_t * 251 | ngx_http_yy_sec_waf_create_shm_zone(ngx_conf_t *cf) 252 | { 253 | ssize_t n; 254 | ngx_str_t value, name; 255 | ngx_shm_zone_t *shm_zone; 256 | yy_sec_waf_conn_ctx_t *ctx; 257 | 258 | 259 | ctx = ngx_pcalloc(cf->pool, sizeof(yy_sec_waf_conn_ctx_t)); 260 | if (ctx == NULL) { 261 | return NULL; 262 | } 263 | 264 | ngx_str_set(&name, "yy_sec_waf_conn"); 265 | ngx_str_set(&value, "10m"); 266 | ngx_str_set(&ctx->var, "binary_remote_addr"); 267 | 268 | ctx->index = ngx_http_get_variable_index(cf, &ctx->var); 269 | if (ctx->index == NGX_ERROR) { 270 | return NGX_CONF_ERROR; 271 | } 272 | 273 | n = ngx_parse_size(&value); 274 | 275 | if (n == NGX_ERROR) { 276 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 277 | "[ysec_waf] invalid size of shm_zone \"%V\"", &value); 278 | return NULL; 279 | } 280 | 281 | if (n < (ssize_t) (8 * ngx_pagesize)) { 282 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 283 | "[ysec_waf] shm_zone \"%V\" is too small", &value); 284 | return NGX_CONF_ERROR; 285 | } 286 | 287 | shm_zone = ngx_shared_memory_add(cf, &name, n, 288 | &ngx_http_yy_sec_waf_module); 289 | if (shm_zone == NULL) { 290 | return NULL; 291 | } 292 | 293 | if (shm_zone->data) { 294 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, cf->log, 0, 295 | "[ysec_waf] shm_zone \"%V\" is already bound to variable \"%Vs\"", 296 | &name, &ctx->var); 297 | ngx_pfree(cf->cycle->shared_memory.pool, shm_zone); 298 | return NULL; 299 | } 300 | 301 | shm_zone->init = yy_sec_waf_conn_init_zone; 302 | shm_zone->data = ctx; 303 | 304 | return shm_zone; 305 | } 306 | 307 | /* 308 | ** @description: This function is called to process connection counter. 309 | ** @para: ngx_http_request_ctx_t *ctx 310 | ** @return: ngx_int_t. 311 | */ 312 | 313 | ngx_int_t 314 | ngx_http_yy_sec_waf_process_conn(ngx_http_request_ctx_t *ctx) 315 | { 316 | size_t len, n; 317 | uint32_t hash; 318 | ngx_slab_pool_t *shpool; 319 | ngx_rbtree_node_t *node; 320 | ngx_pool_cleanup_t *cln; 321 | ngx_http_variable_value_t *vv; 322 | yy_sec_waf_conn_ctx_t *conn_ctx; 323 | yy_sec_waf_conn_node_t *lc; 324 | yy_sec_waf_conn_cleanup_t *lccln; 325 | 326 | 327 | if (ctx == NULL || ctx->cf == NULL 328 | || ctx->cf->shm_zone == NULL 329 | || ctx->cf->shm_zone->data == NULL) { 330 | ngx_log_error(NGX_LOG_ERR, ctx->r->connection->log, 0, "[ysec_waf] NULL pointer"); 331 | return NGX_ERROR; 332 | } 333 | 334 | conn_ctx = ctx->cf->shm_zone->data; 335 | 336 | vv = ngx_http_get_indexed_variable(ctx->r, conn_ctx->index); 337 | 338 | if (vv == NULL || vv->not_found) { 339 | return NGX_ERROR; 340 | } 341 | 342 | len = vv->len; 343 | 344 | hash = ngx_crc32_short(vv->data, vv->len); 345 | 346 | shpool = (ngx_slab_pool_t *) ctx->cf->shm_zone->shm.addr; 347 | 348 | ngx_shmtx_lock(&shpool->mutex); 349 | 350 | node = yy_sec_waf_conn_lookup(conn_ctx->rbtree, vv, hash); 351 | 352 | if (node == NULL) { 353 | 354 | n = offsetof(ngx_rbtree_node_t, color) 355 | + offsetof(yy_sec_waf_conn_node_t, data) 356 | + len; 357 | 358 | node = ngx_slab_alloc_locked(shpool, n); 359 | 360 | if (node == NULL) { 361 | ngx_shmtx_unlock(&shpool->mutex); 362 | yy_sec_waf_conn_cleanup_all(ctx->pool); 363 | return NGX_HTTP_SERVICE_UNAVAILABLE; 364 | } 365 | 366 | lc = (yy_sec_waf_conn_node_t *) &node->color; 367 | 368 | node->key = hash; 369 | lc->len = (u_char) len; 370 | lc->conn = 1; 371 | ngx_memcpy(lc->data, vv->data, len); 372 | 373 | ngx_rbtree_insert(conn_ctx->rbtree, node); 374 | 375 | } else { 376 | 377 | lc = (yy_sec_waf_conn_node_t *) &node->color; 378 | 379 | lc->conn++; 380 | } 381 | 382 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ctx->r->connection->log, 0, 383 | "[ysec_waf] conn: %08XD %d", node->key, lc->conn); 384 | 385 | // GET the connection counter. 386 | ctx->conn_per_ip = lc->conn; 387 | 388 | ngx_shmtx_unlock(&shpool->mutex); 389 | 390 | cln = ngx_pool_cleanup_add(ctx->pool, 391 | sizeof(yy_sec_waf_conn_cleanup_t)); 392 | if (cln == NULL) { 393 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 394 | } 395 | 396 | cln->handler = yy_sec_waf_conn_cleanup; 397 | lccln = cln->data; 398 | 399 | lccln->shm_zone = ctx->cf->shm_zone; 400 | lccln->node = node; 401 | 402 | return NGX_OK; 403 | } 404 | 405 | 406 | -------------------------------------------------------------------------------- /src/ngx_yy_sec_waf_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** @file: ngx_yy_sec_waf_module.c 3 | ** @description: This is the core module for yy sec waf. 4 | ** @author: dw_liqi1 5 | ** @date: 2013.07.10 6 | ** Copyright (C) YY, Inc. 7 | */ 8 | 9 | #include "ngx_yy_sec_waf.h" 10 | 11 | static ngx_int_t ngx_http_yy_sec_waf_preconfiguration(ngx_conf_t *cf); 12 | static ngx_int_t ngx_http_yy_sec_waf_init(ngx_conf_t *cf); 13 | static ngx_int_t ngx_http_yy_sec_waf_handler(ngx_http_request_t *r); 14 | static void ngx_http_yy_sec_waf_request_body_handler(ngx_http_request_t *r); 15 | static void * ngx_http_yy_sec_waf_create_loc_conf(ngx_conf_t *cf); 16 | static char * ngx_http_yy_sec_waf_merge_loc_conf(ngx_conf_t *cf, 17 | void *parent, void *child); 18 | 19 | static ngx_http_request_ctx_t* ngx_http_yy_sec_waf_create_ctx(ngx_http_request_t *r, 20 | ngx_http_yy_sec_waf_loc_conf_t *cf); 21 | 22 | extern char * ngx_http_yy_sec_waf_re_read_du_loc_conf(ngx_conf_t *cf, 23 | ngx_command_t *cmd, void *conf); 24 | extern char * ngx_http_yy_sec_waf_re_read_conf(ngx_conf_t *cf, 25 | ngx_command_t *cmd, void *conf); 26 | extern ngx_int_t ngx_http_yy_sec_waf_process_request(ngx_http_request_t *r, 27 | ngx_http_yy_sec_waf_loc_conf_t *cf, ngx_http_request_ctx_t *ctx); 28 | 29 | extern ngx_int_t ngx_http_yy_sec_waf_re_create(ngx_conf_t *cf); 30 | extern ngx_int_t yy_sec_waf_re_process_normal_rules(ngx_http_request_t *r, 31 | ngx_http_yy_sec_waf_loc_conf_t *cf, ngx_http_request_ctx_t *ctx, ngx_uint_t phase); 32 | static ngx_int_t ngx_http_yy_sec_waf_module_init(ngx_cycle_t *cycle); 33 | 34 | static ngx_atomic_t request_matched0; 35 | static ngx_atomic_t request_blocked0; 36 | static ngx_atomic_t request_allowed0; 37 | static ngx_atomic_t request_logged0; 38 | 39 | ngx_atomic_t *request_matched = &request_matched0; 40 | ngx_atomic_t *request_blocked = &request_blocked0; 41 | ngx_atomic_t *request_allowed = &request_allowed0; 42 | ngx_atomic_t *request_logged = &request_logged0; 43 | 44 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 45 | static ngx_http_output_body_filter_pt ngx_http_next_body_filter; 46 | 47 | static ngx_command_t ngx_http_yy_sec_waf_commands[] = { 48 | { ngx_string("yy_sec_waf"), 49 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 50 | ngx_conf_set_flag_slot, 51 | NGX_HTTP_LOC_CONF_OFFSET, 52 | offsetof(ngx_http_yy_sec_waf_loc_conf_t, enabled), 53 | NULL }, 54 | 55 | { ngx_string("conn_processor"), 56 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 57 | ngx_conf_set_flag_slot, 58 | NGX_HTTP_LOC_CONF_OFFSET, 59 | offsetof(ngx_http_yy_sec_waf_loc_conf_t, conn_processor), 60 | NULL }, 61 | 62 | { ngx_string("body_processor"), 63 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 64 | ngx_conf_set_flag_slot, 65 | NGX_HTTP_LOC_CONF_OFFSET, 66 | offsetof(ngx_http_yy_sec_waf_loc_conf_t, body_processor), 67 | NULL }, 68 | 69 | { ngx_string("basic_rule"), 70 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_2MORE, 71 | ngx_http_yy_sec_waf_re_read_conf, 72 | NGX_HTTP_LOC_CONF_OFFSET, 73 | 0, 74 | NULL }, 75 | 76 | { ngx_string("denied_url"), 77 | NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1, 78 | ngx_http_yy_sec_waf_re_read_du_loc_conf, 79 | NGX_HTTP_LOC_CONF_OFFSET, 80 | 0, 81 | NULL }, 82 | 83 | ngx_null_command 84 | }; 85 | 86 | 87 | static ngx_http_module_t ngx_http_yy_sec_waf_module_ctx = { 88 | ngx_http_yy_sec_waf_preconfiguration, /* preconfiguration */ 89 | ngx_http_yy_sec_waf_init, /* postconfiguration */ 90 | 91 | NULL, /* create main configuration */ 92 | NULL, /* init main configuration */ 93 | 94 | NULL, /* create server configuration */ 95 | NULL, /* merge server configuration */ 96 | 97 | ngx_http_yy_sec_waf_create_loc_conf, /* create location configuration */ 98 | ngx_http_yy_sec_waf_merge_loc_conf /* merge location configuration */ 99 | }; 100 | 101 | ngx_module_t ngx_http_yy_sec_waf_module = { 102 | NGX_MODULE_V1, 103 | &ngx_http_yy_sec_waf_module_ctx, /* module context */ 104 | ngx_http_yy_sec_waf_commands, /* module directives */ 105 | NGX_HTTP_MODULE, /* module type */ 106 | NULL, /* init master */ 107 | ngx_http_yy_sec_waf_module_init, /* init module */ 108 | NULL, /* init process */ 109 | NULL, /* init thread */ 110 | NULL, /* exit thread */ 111 | NULL, /* exit process */ 112 | NULL, /* exit master */ 113 | NGX_MODULE_V1_PADDING 114 | }; 115 | 116 | 117 | /* 118 | ** @description: This function is called to create the location configuration of yy sec waf. 119 | ** @para: ngx_conf_t *cf 120 | ** @return: conf or NGX_CONF_ERROR if failed. 121 | */ 122 | 123 | static void * 124 | ngx_http_yy_sec_waf_create_loc_conf(ngx_conf_t *cf) 125 | { 126 | ngx_http_yy_sec_waf_loc_conf_t *conf; 127 | 128 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_yy_sec_waf_loc_conf_t)); 129 | 130 | if (conf == NULL) { 131 | return NGX_CONF_ERROR; 132 | } 133 | 134 | conf->enabled = NGX_CONF_UNSET; 135 | conf->conn_processor = NGX_CONF_UNSET; 136 | conf->body_processor = NGX_CONF_UNSET; 137 | 138 | return conf; 139 | } 140 | 141 | /* 142 | ** @description: This function is called to merge the location configuration of yy sec waf. 143 | ** @para: ngx_conf_t *cf 144 | ** @para: void *parent 145 | ** @para: void *child 146 | ** @return: NGX_CONF_OK 147 | */ 148 | 149 | static char * 150 | ngx_http_yy_sec_waf_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 151 | { 152 | (void) cf; 153 | ngx_http_yy_sec_waf_loc_conf_t *prev = parent; 154 | ngx_http_yy_sec_waf_loc_conf_t *conf = child; 155 | 156 | if (conf->request_header_rules == NULL) 157 | conf->request_header_rules = prev->request_header_rules; 158 | if (conf->request_body_rules == NULL) 159 | conf->request_body_rules = prev->request_body_rules; 160 | if (conf->response_header_rules == NULL) 161 | conf->response_header_rules = prev->response_header_rules; 162 | if (conf->response_body_rules == NULL) 163 | conf->response_body_rules = prev->response_body_rules; 164 | if (conf->denied_url == NULL) 165 | conf->denied_url = prev->denied_url; 166 | if (conf->shm_zone == NULL) 167 | conf->shm_zone = prev->shm_zone; 168 | if (conf->server_ip.len == 0) 169 | conf->server_ip = prev->server_ip; 170 | 171 | ngx_conf_merge_value(conf->enabled, prev->enabled, 1); 172 | 173 | ngx_conf_merge_value(conf->conn_processor, prev->conn_processor, 0); 174 | 175 | ngx_conf_merge_value(conf->body_processor, prev->body_processor, 1); 176 | 177 | return NGX_CONF_OK; 178 | } 179 | 180 | /* 181 | ** @description: This function is called before configuration of yy sec waf. 182 | ** @para: ngx_conf_t *cf 183 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 184 | */ 185 | 186 | static ngx_int_t 187 | ngx_http_yy_sec_waf_preconfiguration(ngx_conf_t *cf) 188 | { 189 | return ngx_http_yy_sec_waf_re_create(cf); 190 | } 191 | 192 | /* 193 | ** @description: This function is called to filter header. 194 | ** @para: ngx_http_request_t *r 195 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 196 | */ 197 | 198 | static ngx_int_t 199 | ngx_http_yy_sec_waf_header_filter(ngx_http_request_t *r) 200 | { 201 | ngx_int_t rc; 202 | ngx_http_request_ctx_t *ctx; 203 | ngx_http_yy_sec_waf_loc_conf_t *cf; 204 | 205 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[ysec_waf] ngx_http_yy_sec_waf_header_filter Entry"); 206 | 207 | cf = ngx_http_get_module_loc_conf(r, ngx_http_yy_sec_waf_module); 208 | ctx = ngx_http_get_module_ctx(r, ngx_http_yy_sec_waf_module); 209 | 210 | if (cf == NULL) { 211 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "[ysec_waf] ngx_http_get_module_loc_conf failed."); 212 | return NGX_ERROR; 213 | } 214 | 215 | if (!cf->enabled) { 216 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[ysec_waf] yy sec waf isn't enabled."); 217 | return ngx_http_next_header_filter(r); 218 | } 219 | 220 | if (r != r->main && ctx && !ctx->process_done) { 221 | rc = yy_sec_waf_re_process_normal_rules(r, cf, ctx, RESPONSE_HEADER_PHASE); 222 | 223 | if (rc != NGX_DECLINED) { 224 | return ngx_http_filter_finalize_request(r, &ngx_http_yy_sec_waf_module, rc); 225 | } 226 | } 227 | 228 | return ngx_http_next_header_filter(r); 229 | } 230 | 231 | /* 232 | ** @description: This function is called to filter body. 233 | ** @para: ngx_http_request_t *r 234 | ** @para: ngx_chain_t *in 235 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 236 | */ 237 | 238 | static ngx_int_t 239 | ngx_http_yy_sec_waf_body_filter(ngx_http_request_t *r, ngx_chain_t *in) 240 | { 241 | ngx_int_t rc; 242 | ngx_http_request_ctx_t *ctx; 243 | ngx_http_yy_sec_waf_loc_conf_t *cf; 244 | 245 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[ysec_waf] ngx_http_yy_sec_waf_body_filter Entry"); 246 | 247 | cf = ngx_http_get_module_loc_conf(r, ngx_http_yy_sec_waf_module); 248 | ctx = ngx_http_get_module_ctx(r, ngx_http_yy_sec_waf_module); 249 | 250 | if (cf == NULL) { 251 | return NGX_ERROR; 252 | } 253 | 254 | if (!cf->enabled) { 255 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[ysec_waf] yy sec waf isn't enabled."); 256 | return ngx_http_next_body_filter(r, in); 257 | } 258 | 259 | if (r == r->main && ctx && !ctx->process_done) { 260 | rc = yy_sec_waf_re_process_normal_rules(r, cf, ctx, RESPONSE_BODY_PHASE); 261 | if (rc != NGX_DECLINED) { 262 | return ngx_http_filter_finalize_request(r, &ngx_http_yy_sec_waf_module, rc); 263 | } 264 | } 265 | 266 | return ngx_http_next_body_filter(r, in); 267 | } 268 | 269 | /* 270 | ** @description: This function is called to init yy sec waf in process of postconfiguration. 271 | ** @para: ngx_conf_t *cf 272 | ** @return: NGX_CONF_OK or NGX_ERROR if failed. 273 | */ 274 | 275 | static ngx_int_t 276 | ngx_http_yy_sec_waf_init(ngx_conf_t *cf) 277 | { 278 | ngx_http_handler_pt *h; 279 | ngx_http_core_main_conf_t *cmcf; 280 | 281 | cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 282 | 283 | h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers); 284 | 285 | if (h == NULL) { 286 | return NGX_ERROR; 287 | } 288 | 289 | *h = ngx_http_yy_sec_waf_handler; 290 | 291 | ngx_http_next_header_filter = ngx_http_top_header_filter; 292 | ngx_http_top_header_filter = ngx_http_yy_sec_waf_header_filter; 293 | 294 | ngx_http_next_body_filter = ngx_http_top_body_filter; 295 | ngx_http_top_body_filter = ngx_http_yy_sec_waf_body_filter; 296 | 297 | return NGX_OK; 298 | } 299 | 300 | /* 301 | ** @description: This is the function called by nginx : 302 | ** - Set up the context for the request 303 | ** - Check if the job is done and we're called again 304 | ** - if it's a POST/PUT request, setup hook for body data 305 | ** - call ngx_http_yy_sec_waf_data_parse 306 | ** - check if the request should be denied 307 | ** @para: ngx_http_request_t *r 308 | ** @return: this value should vary due to different situations. 309 | */ 310 | 311 | static ngx_int_t 312 | ngx_http_yy_sec_waf_handler(ngx_http_request_t *r) 313 | { 314 | ngx_int_t rc; 315 | ngx_http_request_ctx_t *ctx; 316 | ngx_http_yy_sec_waf_loc_conf_t *cf; 317 | 318 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[ysec_waf] ngx_http_yy_sec_waf_handler Entry"); 319 | 320 | cf = ngx_http_get_module_loc_conf(r, ngx_http_yy_sec_waf_module); 321 | ctx = ngx_http_get_module_ctx(r, ngx_http_yy_sec_waf_module); 322 | 323 | if (cf == NULL) { 324 | return NGX_ERROR; 325 | } 326 | 327 | if (ctx != NULL) { 328 | if (ctx->process_done) { 329 | return NGX_DECLINED; 330 | } 331 | 332 | if (ctx->waiting_more_body) { 333 | return NGX_DONE; 334 | } 335 | } 336 | 337 | if (!cf->enabled) { 338 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[ysec_waf] yy sec waf isn't enabled."); 339 | return NGX_DECLINED; 340 | } 341 | 342 | ctx = ngx_http_yy_sec_waf_create_ctx(r, cf); 343 | 344 | if (ctx == NULL) { 345 | return NGX_ERROR; 346 | } 347 | 348 | ngx_http_set_ctx(r, ctx, ngx_http_yy_sec_waf_module); 349 | 350 | if (cf->conn_processor) { 351 | rc = ngx_http_yy_sec_waf_process_conn(ctx); 352 | 353 | if (rc != NGX_OK) { 354 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "[ysec_waf] ngx_http_yy_sec_waf_process_conn failed"); 355 | return rc; 356 | } 357 | } 358 | 359 | 360 | /* This section is prepared for further considerations, such as checking the body of this request.*/ 361 | if ((r->method == NGX_HTTP_POST || r->method == NGX_HTTP_PUT) && !ctx->read_body_done) { 362 | rc = ngx_http_read_client_request_body(r, ngx_http_yy_sec_waf_request_body_handler); 363 | 364 | if (rc == NGX_AGAIN) { 365 | ctx->waiting_more_body = 1; 366 | return NGX_DONE; 367 | } else if (rc >= NGX_HTTP_SPECIAL_RESPONSE || rc == NGX_ERROR) { 368 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"[ysec_waf] ngx_http_read_client_request_body failed: %d", rc); 369 | return rc; 370 | } 371 | } else { 372 | ctx->read_body_done = 1; 373 | } 374 | 375 | if (r == r->main && ctx && ctx->read_body_done && !ctx->process_done) { 376 | rc = NGX_DECLINED; 377 | 378 | if (cf->body_processor 379 | && (r->method == NGX_HTTP_POST || r->method == NGX_HTTP_PUT) 380 | && r->request_body) { 381 | rc = ngx_http_yy_sec_waf_process_body(r, cf, ctx); 382 | if (rc == NGX_ERROR) { 383 | return rc; 384 | } 385 | } 386 | 387 | //Temply hack here, should hook the input filters. 388 | rc = yy_sec_waf_re_process_normal_rules(r, cf, ctx, REQUEST_HEADER_PHASE); 389 | if (rc != NGX_DECLINED) { 390 | return rc; 391 | } 392 | 393 | rc = yy_sec_waf_re_process_normal_rules(r, cf, ctx, REQUEST_BODY_PHASE); 394 | if (rc != NGX_DECLINED) { 395 | return rc; 396 | } 397 | 398 | return rc; 399 | } 400 | 401 | return NGX_DECLINED; 402 | } 403 | 404 | /* 405 | ** @description: This function is called when the body is read. 406 | ** - Will set-up flags to tell that parsing can be done, 407 | ** - and then run the core phases again 408 | ** @para: ngx_http_request_t *r 409 | ** @return: static void 410 | */ 411 | 412 | static void 413 | ngx_http_yy_sec_waf_request_body_handler(ngx_http_request_t *r) 414 | { 415 | ngx_http_request_ctx_t *ctx; 416 | 417 | ctx = ngx_http_get_module_ctx(r, ngx_http_yy_sec_waf_module); 418 | 419 | if (ctx != NULL) { 420 | ctx->read_body_done = 1; 421 | r->count--; 422 | 423 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 424 | "[ysec_waf] req body post read, c:%ud", r->count); 425 | 426 | if (ctx->waiting_more_body) { 427 | ctx->waiting_more_body = 0; 428 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[ysec_waf] ngx_http_core_run_phases Entry"); 429 | ngx_http_core_run_phases(r); 430 | } 431 | } 432 | } 433 | 434 | /* 435 | ** @description: This function is called to create ctx for this request. 436 | ** @para: ngx_http_request_t *r 437 | ** @para: ngx_http_yy_sec_waf_loc_conf_t *cf 438 | ** @return: static ngx_http_request_ctx_t* 439 | */ 440 | 441 | static ngx_http_request_ctx_t* 442 | ngx_http_yy_sec_waf_create_ctx(ngx_http_request_t *r, 443 | ngx_http_yy_sec_waf_loc_conf_t *cf) 444 | { 445 | ngx_http_request_ctx_t *ctx; 446 | 447 | ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_request_ctx_t)); 448 | if (ctx == NULL) { 449 | return NULL; 450 | } 451 | 452 | ctx->args.len = r->args.len; 453 | ctx->args.data = ngx_pcalloc(r->pool, r->args.len); 454 | if (ctx->args.data == NULL) { 455 | return NULL; 456 | } 457 | 458 | ngx_memcpy(ctx->args.data, r->args.data, ctx->args.len); 459 | 460 | ngx_yy_sec_waf_unescape(&ctx->args); 461 | 462 | ctx->process_body_error = 0; 463 | 464 | if (r->method == NGX_HTTP_POST || r->method == NGX_HTTP_PUT) { 465 | 466 | ngx_array_init(&ctx->multipart_name, r->pool, 2, sizeof(ngx_str_t)); 467 | ngx_array_init(&ctx->multipart_filename, r->pool, 2, sizeof(ngx_str_t)); 468 | ngx_array_init(&ctx->content_type, r->pool, 2, sizeof(ngx_str_t)); 469 | } 470 | 471 | ctx->r = r; 472 | ctx->cf = cf; 473 | ctx->pool = r->pool; 474 | 475 | ctx->server_ip = &cf->server_ip; 476 | 477 | ctx->real_client_ip = &ctx->r->connection->addr_text; 478 | 479 | #ifdef NGX_HTTP_X_FORWARDED_FOR 480 | 481 | #if (nginx_version < 1003014) 482 | if (ctx->r->headers_in.x_forwarded_for != NULL) { 483 | ctx->real_client_ip = &ctx->r->headers_in.x_forwarded_for->value; 484 | } 485 | #else 486 | 487 | if (ctx->r->headers_in.x_forwarded_for.nelts != 0) { 488 | ctx->real_client_ip = &((ngx_table_elt_t**)(ctx->r->headers_in.x_forwarded_for.elts))[0]->value; 489 | } 490 | #endif 491 | #endif 492 | 493 | //yy_sec_waf_re_cache_init_rbtree(&ctx->cache_rbtree, &ctx->cache_sentinel); 494 | 495 | return ctx; 496 | } 497 | 498 | /* 499 | ** @description: This function is called to init yy_sec_waf_module. 500 | ** @para: ngx_cycle_t *cycle 501 | ** @return: static ngx_int_t 502 | */ 503 | 504 | static ngx_int_t 505 | ngx_http_yy_sec_waf_module_init(ngx_cycle_t *cycle) 506 | { 507 | u_char *shared; 508 | size_t size, cl; 509 | ngx_shm_t shm; 510 | ngx_core_conf_t *ccf; 511 | 512 | ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); 513 | 514 | if (ccf->master == 0) { 515 | return NGX_OK; 516 | } 517 | 518 | /* cl should be equal to or greater than cache line size */ 519 | 520 | cl = 128; 521 | 522 | size = cl /* request_matched */ 523 | + cl /* request_blocked */ 524 | + cl /* request_allowed */ 525 | + cl; /* request_logged */ 526 | 527 | shm.size = size; 528 | shm.name.len = sizeof("yy_sec_waf_shared_zone"); 529 | shm.name.data = (u_char *) "yy_sec_waf_shared_zone"; 530 | shm.log = cycle->log; 531 | 532 | if (ngx_shm_alloc(&shm) != NGX_OK) { 533 | return NGX_ERROR; 534 | } 535 | 536 | shared = shm.addr; 537 | 538 | request_matched = (ngx_atomic_t *) (shared + 0 * cl); 539 | request_blocked = (ngx_atomic_t *) (shared + 1 * cl); 540 | request_allowed = (ngx_atomic_t *) (shared + 2 * cl); 541 | request_logged = (ngx_atomic_t *) (shared + 3 * cl); 542 | 543 | return NGX_OK; 544 | } 545 | 546 | -------------------------------------------------------------------------------- /src/ngx_yy_sec_waf_re.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** @file: ngx_yy_sec_waf_re.c 3 | ** @description: This is the rule engine for yy sec waf. 4 | ** @author: dw_liqi1 5 | ** @date: 2013.07.15 6 | ** Copyright (C) YY, Inc. 7 | */ 8 | 9 | #include "ngx_yy_sec_waf_re.h" 10 | 11 | static yy_sec_waf_re_t *rule_engine; 12 | 13 | extern ngx_int_t ngx_local_addr(const char *eth, ngx_str_t *s); 14 | 15 | /* 16 | ** @description: This function is called to resolve tfns in hash. 17 | ** @para: ngx_str_t *action 18 | ** @return: static re_action_metadata * 19 | */ 20 | 21 | re_tfns_metadata * 22 | yy_sec_waf_re_resolve_tfn_in_hash(ngx_str_t *tfn) 23 | { 24 | ngx_uint_t key; 25 | re_tfns_metadata *metadata; 26 | 27 | if (tfn == NULL) { 28 | return NULL; 29 | } 30 | 31 | key = ngx_hash_key_lc(tfn->data, tfn->len); 32 | ngx_strlow(tfn->data, tfn->data, tfn->len); 33 | 34 | metadata = (re_tfns_metadata *)ngx_hash_find( 35 | &rule_engine->tfns_in_hash, key, tfn->data, tfn->len); 36 | 37 | return metadata; 38 | } 39 | 40 | /* 41 | ** @description: This function is called to resolve operators in hash. 42 | ** @para: ngx_str_t *operator 43 | ** @return: static re_op_metadata * 44 | */ 45 | 46 | static re_op_metadata * 47 | yy_sec_waf_re_resolve_operator_in_hash(ngx_str_t *operator) 48 | { 49 | ngx_uint_t key; 50 | re_op_metadata *metadata; 51 | 52 | if (operator == NULL) { 53 | return NULL; 54 | } 55 | 56 | key = ngx_hash_key_lc(operator->data, operator->len); 57 | ngx_strlow(operator->data, operator->data, operator->len); 58 | 59 | metadata = (re_op_metadata *)ngx_hash_find( 60 | &rule_engine->operators_in_hash, key, operator->data, operator->len); 61 | 62 | return metadata; 63 | } 64 | 65 | /* 66 | ** @description: This function is called to resolve actions in hash. 67 | ** @para: ngx_str_t *action 68 | ** @return: static re_action_metadata * 69 | */ 70 | 71 | re_action_metadata * 72 | yy_sec_waf_re_resolve_action_in_hash(ngx_str_t *action) 73 | { 74 | ngx_uint_t key; 75 | re_action_metadata *metadata; 76 | 77 | if (action == NULL) { 78 | return NULL; 79 | } 80 | 81 | key = ngx_hash_key_lc(action->data, action->len); 82 | ngx_strlow(action->data, action->data, action->len); 83 | 84 | metadata = (re_action_metadata *)ngx_hash_find( 85 | &rule_engine->actions_in_hash, key, action->data, action->len); 86 | 87 | return metadata; 88 | } 89 | 90 | /* 91 | ** @description: This function is called to redirect request url to the denied url of yy sec waf. 92 | ** @para: ngx_http_request_t *r 93 | ** @para: ngx_http_request_ctx_t *ctx 94 | ** @return: NGX_HTTP_OK or NGX_ERROR if failed. 95 | */ 96 | 97 | static ngx_int_t 98 | yy_sec_waf_output_forbidden_page(ngx_http_request_t *r, 99 | ngx_http_request_ctx_t *ctx) 100 | { 101 | ngx_str_t empty = ngx_string(""); 102 | ngx_str_t *tmp_uri; 103 | ngx_http_yy_sec_waf_loc_conf_t *cf; 104 | 105 | cf = ngx_http_get_module_loc_conf(r, ngx_http_yy_sec_waf_module); 106 | 107 | if (cf->denied_url) { 108 | tmp_uri = ngx_pcalloc(r->pool, sizeof(ngx_str_t)); 109 | if (!tmp_uri) 110 | return NGX_ERROR; 111 | 112 | tmp_uri->len = r->uri.len + (2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len, 113 | NGX_ESCAPE_ARGS)); 114 | tmp_uri->data = ngx_pcalloc(r->pool, tmp_uri->len+1); 115 | 116 | ngx_escape_uri(tmp_uri->data, r->uri.data, r->uri.len, NGX_ESCAPE_ARGS); 117 | 118 | ngx_table_elt_t *h; 119 | 120 | if (r->headers_in.headers.last) { 121 | h = ngx_list_push(&(r->headers_in.headers)); 122 | h->key.len = ngx_strlen("orig_url"); 123 | h->key.data = ngx_pcalloc(r->pool, ngx_strlen("orig_url")+1); 124 | ngx_memcpy(h->key.data, "orig_url", ngx_strlen("orig_url")); 125 | h->lowcase_key = ngx_pcalloc(r->pool, ngx_strlen("orig_url") + 1); 126 | ngx_memcpy(h->lowcase_key, "orig_url", ngx_strlen("orig_url")); 127 | h->value.len = tmp_uri->len; 128 | h->value.data = ngx_pcalloc(r->pool, tmp_uri->len+1); 129 | ngx_memcpy(h->value.data, tmp_uri->data, tmp_uri->len); 130 | 131 | h = ngx_list_push(&(r->headers_in.headers)); 132 | h->key.len = ngx_strlen("orig_args"); 133 | h->key.data = ngx_pcalloc(r->pool, ngx_strlen("orig_args")+1); 134 | ngx_memcpy(h->key.data, "orig_args", ngx_strlen("orig_args")); 135 | h->lowcase_key = ngx_pcalloc(r->pool, ngx_strlen("orig_args") + 1); 136 | ngx_memcpy(h->lowcase_key, "orig_args", ngx_strlen("orig_args")); 137 | h->value.len = r->args.len; 138 | h->value.data = ngx_pcalloc(r->pool, r->args.len+1); 139 | ngx_memcpy(h->value.data, r->args.data, r->args.len); 140 | 141 | h = ngx_list_push(&(r->headers_in.headers)); 142 | h->key.len = ngx_strlen("yy_sec_waf"); 143 | h->key.data = ngx_pcalloc(r->pool, ngx_strlen("yy_sec_waf")+1); 144 | ngx_memcpy(h->key.data, "yy_sec_waf", ngx_strlen("yy_sec_waf")); 145 | h->lowcase_key = ngx_pcalloc(r->pool, ngx_strlen("yy_sec_waf") + 1); 146 | ngx_memcpy(h->lowcase_key, "yy_sec_waf", ngx_strlen("yy_sec_waf")); 147 | h->value.len = empty.len; 148 | h->value.data = empty.data; 149 | } 150 | 151 | ngx_http_internal_redirect(r, cf->denied_url, &empty); 152 | 153 | return NGX_HTTP_OK; 154 | } else { 155 | return ctx->status? ctx->status: NGX_HTTP_PRECONDITION_FAILED; 156 | } 157 | } 158 | 159 | /* 160 | ** @description: This function is called to execute operator. 161 | ** @para: ngx_http_request_t *r 162 | ** @para: ngx_str_t *str 163 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 164 | ** @para: ngx_http_request_ctx_t *ctx 165 | ** @return: RULE_MATCH or RULE_NO_MATCH if failed. 166 | */ 167 | 168 | static ngx_int_t 169 | yy_sec_waf_re_execute_operator(ngx_http_request_t *r, 170 | ngx_http_yy_sec_waf_rule_t *rule, ngx_http_request_ctx_t *ctx) 171 | { 172 | ngx_int_t rc; 173 | 174 | if (rule->op_metadata == NULL) { 175 | return NGX_ERROR; 176 | } 177 | 178 | rc = ((re_op_metadata*)rule->op_metadata)->execute(r, &ctx->var, rule); 179 | 180 | if ((rc == RULE_MATCH && !rule->op_negative) 181 | || (rc == RULE_NO_MATCH && rule->op_negative)) { 182 | ctx->matched = 1; 183 | ctx->rule_id = rule->rule_id; 184 | ctx->action_level = rule->action_level; 185 | ctx->gids = rule->gids; 186 | ctx->msg = rule->msg; 187 | ctx->status = rule->status; 188 | return RULE_MATCH; 189 | } 190 | 191 | return RULE_NO_MATCH; 192 | } 193 | 194 | /* 195 | ** @description: This function is called to perform interception. 196 | ** @para: ngx_http_request_ctx_t *ctx 197 | ** @return: static ngx_int_t. 198 | */ 199 | 200 | static ngx_int_t 201 | yy_sec_waf_re_perform_interception(ngx_http_request_ctx_t *ctx) 202 | { 203 | 204 | ngx_atomic_fetch_add(request_matched, 1); 205 | 206 | ctx->process_done = 1; 207 | 208 | if (ctx->action_level & ACTION_LOG) 209 | ngx_atomic_fetch_add(request_logged, 1); 210 | 211 | if (ctx->action_level & ACTION_ALLOW) 212 | ngx_atomic_fetch_add(request_allowed, 1); 213 | 214 | if (ctx->action_level & ACTION_BLOCK) 215 | ngx_atomic_fetch_add(request_blocked, 1); 216 | 217 | if (ctx->action_level & ACTION_LOG) { 218 | ngx_log_error(NGX_LOG_ERR, ctx->r->connection->log, 0, 219 | "[ysec_waf] %s, id: %d, conn_per_ip: %ud," 220 | " matched: %uA, blocked: %uA, allowed: %uA, alerted: %uA," 221 | " var: %V, client_ip: %V, server_ip: %V", 222 | (ctx->action_level & ACTION_BLOCK)? "block": 223 | (ctx->action_level & ACTION_ALLOW)? "allow": "alert", 224 | ctx->rule_id, ctx->conn_per_ip, 225 | *request_matched, *request_blocked, *request_allowed, *request_logged, 226 | ctx->process_body_error? &ctx->process_body_error_msg: &ctx->var, 227 | ctx->real_client_ip, ctx->server_ip); 228 | } 229 | 230 | if (ctx->action_level & ACTION_BLOCK) 231 | return yy_sec_waf_output_forbidden_page(ctx->r, ctx); 232 | 233 | return NGX_DECLINED; 234 | } 235 | 236 | /* 237 | ** @description: This function is called to process rule for yy sec waf. 238 | ** @para: ngx_http_request_t *r 239 | ** @para: ngx_http_yy_sec_waf_loc_conf_t *cf 240 | ** @para: ngx_http_request_ctx_t *ctx 241 | ** @return: RULE_MATCH or RULE_NO_MATCH if failed. 242 | */ 243 | 244 | static ngx_int_t 245 | yy_sec_waf_re_process_rule(ngx_http_request_t *r, 246 | ngx_http_yy_sec_waf_rule_t *rule, ngx_http_request_ctx_t *ctx) 247 | { 248 | ngx_int_t rc, *var_index_p; 249 | ngx_uint_t i; 250 | ngx_http_variable_value_t *vv; 251 | 252 | if (rule == NULL) 253 | return NGX_AGAIN; 254 | 255 | var_index_p = rule->var_index.elts; 256 | 257 | for (i = 0; i < rule->var_index.nelts; i++) { 258 | 259 | vv = ngx_http_get_flushed_variable(r, var_index_p[i]); 260 | 261 | if (vv == NULL || vv->not_found || vv->len == 0) { 262 | return NGX_AGAIN; 263 | } 264 | 265 | ctx->var.data = vv->data; 266 | ctx->var.len = vv->len; 267 | 268 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "[ysec_waf] id:%d, var:%V", rule->rule_id, &ctx->var); 269 | 270 | rc = yy_sec_waf_re_execute_operator(r, rule, ctx); 271 | if (rc == NGX_ERROR || rc == RULE_MATCH) { 272 | return rc; 273 | } 274 | } 275 | 276 | return RULE_NO_MATCH; 277 | } 278 | 279 | /* 280 | ** @description: This function is called to process normal rules for yy sec waf. 281 | ** @para: ngx_http_request_t *r 282 | ** @para: ngx_http_yy_sec_waf_loc_conf_t *cf 283 | ** @para: ngx_http_request_ctx_t *ctx 284 | ** @return: RULE_MATCH or RULE_NO_MATCH if failed. 285 | */ 286 | 287 | ngx_int_t 288 | yy_sec_waf_re_process_normal_rules(ngx_http_request_t *r, 289 | ngx_http_yy_sec_waf_loc_conf_t *cf, ngx_http_request_ctx_t *ctx, ngx_uint_t phase) 290 | { 291 | ngx_uint_t i, rule_num; 292 | ngx_int_t rc, mode; 293 | ngx_array_t *rule_array; 294 | ngx_http_yy_sec_waf_rule_t *rule; 295 | 296 | if (ctx->cf == NULL) { 297 | return NGX_ERROR; 298 | } 299 | 300 | switch(phase) { 301 | case REQUEST_HEADER_PHASE: 302 | rule_array = ctx->cf->request_header_rules; 303 | break; 304 | case REQUEST_BODY_PHASE: 305 | rule_array = ctx->cf->request_body_rules; 306 | break; 307 | case RESPONSE_HEADER_PHASE: 308 | rule_array = ctx->cf->response_header_rules; 309 | break; 310 | case RESPONSE_BODY_PHASE: 311 | rule_array = ctx->cf->response_body_rules; 312 | break; 313 | default: 314 | return NGX_ERROR; 315 | } 316 | 317 | if (rule_array == NULL) { 318 | return NGX_DECLINED; 319 | } 320 | 321 | /* If we are here that means the mode is NEXT_RULE, which 322 | ** then means we have done processing any chains. 323 | */ 324 | mode = NEXT_RULE; 325 | 326 | rule = rule_array->elts; 327 | rule_num = rule_array->nelts; 328 | 329 | ctx->phase = phase; 330 | 331 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 332 | "[ysec_waf] phase: %d, rule_num: %d", phase, rule_num); 333 | 334 | for (i=0; i < rule_num; i++) { 335 | 336 | /* NEXT_CHAIN is used when one of the rules in a chain 337 | ** fails to match and then we need to skip the remaining 338 | ** rules in that chain in order to get to the next 339 | ** rule that can execute. 340 | */ 341 | 342 | if (mode == NEXT_CHAIN) { 343 | if (rule[i].is_chain == 0) { 344 | mode = NEXT_RULE; 345 | } 346 | 347 | continue; 348 | } 349 | 350 | rc = yy_sec_waf_re_process_rule(r, &rule[i], ctx); 351 | 352 | if (rc == NGX_ERROR) { 353 | 354 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "[ysec_waf] failed to execute operator"); 355 | return rc; 356 | } else if (rc == RULE_MATCH) { 357 | 358 | if (rule[i].is_chain == 1) { 359 | mode = NEXT_RULE; 360 | continue; 361 | } 362 | 363 | goto MATCH; 364 | } else if (rc == RULE_NO_MATCH || rc == NGX_AGAIN) { 365 | 366 | if (rule[i].is_chain == 1) { 367 | /* If the current rule is part of a chain then 368 | ** we need to skip over all the rules in the chain. 369 | */ 370 | mode = NEXT_CHAIN; 371 | } else { 372 | /* This rule is not part of a chain so we simply 373 | ** move to the next rule. 374 | */ 375 | mode = NEXT_RULE; 376 | } 377 | 378 | continue; 379 | } 380 | } 381 | 382 | return NGX_DECLINED; 383 | 384 | MATCH: 385 | return yy_sec_waf_re_perform_interception(ctx); 386 | } 387 | 388 | /* 389 | ** @description: This function is called to parse variables. 390 | ** @para: ngx_conf_t *cf 391 | ** @para: ngx_str_t *value 392 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 393 | ** @return: static char * 394 | */ 395 | 396 | static char * 397 | yy_sec_waf_re_parse_variables(ngx_conf_t *cf, 398 | ngx_str_t *value, ngx_http_yy_sec_waf_rule_t *rule) 399 | { 400 | ngx_str_t variable; 401 | ngx_int_t len, var_index, *var_index_p; 402 | u_char *start, *last, *end; 403 | 404 | if (value == NULL) { 405 | return NGX_CONF_ERROR; 406 | } 407 | 408 | ngx_memcpy(&variable, value, sizeof(ngx_str_t)); 409 | 410 | ngx_array_init(&rule->var_index, cf->pool, 1, sizeof(ngx_int_t)); 411 | 412 | len = variable.len; 413 | start = variable.data; 414 | last = variable.data+variable.len; 415 | 416 | while(len > 0 && *start) { 417 | 418 | if (*start == '|' || *start == '$'){ 419 | start++; 420 | continue; 421 | } 422 | 423 | end = ngx_strlchr(start, last, '|'); 424 | if (end == NULL) { 425 | end = last; 426 | } 427 | 428 | variable.data = start; 429 | variable.len = end - start; 430 | 431 | var_index = ngx_http_get_variable_index(cf, &variable); 432 | if (var_index == NGX_ERROR) { 433 | return NGX_CONF_ERROR; 434 | } 435 | 436 | var_index_p = ngx_array_push(&rule->var_index); 437 | if (var_index_p == NULL) 438 | return NGX_CONF_ERROR; 439 | 440 | *var_index_p = var_index; 441 | 442 | start = end+1; 443 | len = last - start; 444 | } 445 | 446 | return NGX_CONF_OK; 447 | } 448 | 449 | /* 450 | ** @description: This function is called to parse operator. 451 | ** @para: ngx_conf_t *cf 452 | ** @para: ngx_str_t *value 453 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 454 | ** @return: static char * 455 | */ 456 | 457 | static char * 458 | yy_sec_waf_re_parse_operator(ngx_conf_t *cf, 459 | ngx_str_t *value, ngx_http_yy_sec_waf_rule_t *rule) 460 | { 461 | u_char *pos; 462 | re_op_metadata *op_metadata; 463 | ngx_str_t operator; 464 | 465 | if (value == NULL) { 466 | return NGX_CONF_ERROR; 467 | } 468 | 469 | ngx_memcpy(&operator, value, sizeof(ngx_str_t)); 470 | 471 | if (operator.data[0] == '!') { 472 | rule->op_negative = 1; 473 | operator.data++; 474 | } 475 | 476 | pos = ngx_strlchr(operator.data, operator.data+operator.len, ':'); 477 | operator.len = pos-operator.data; 478 | 479 | rule->op_metadata = yy_sec_waf_re_resolve_operator_in_hash(&operator); 480 | 481 | if (rule->op_metadata == NULL) { 482 | return NGX_CONF_ERROR; 483 | } 484 | 485 | operator.len = value->len; 486 | if (operator.data[0] == '!') { 487 | operator.len--; 488 | } 489 | 490 | op_metadata = (re_op_metadata*) rule->op_metadata; 491 | 492 | if (op_metadata->parse(cf, &operator, rule) != NGX_CONF_OK) { 493 | return NGX_CONF_ERROR; 494 | } 495 | 496 | return NGX_CONF_OK; 497 | } 498 | 499 | /* 500 | ** @description: This function is called to read configuration of yy sec waf. 501 | ** @para: ngx_conf_t *cf 502 | ** @para: ngx_command_t *cmd 503 | ** @para: void *conf 504 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 505 | */ 506 | 507 | char * 508 | ngx_http_yy_sec_waf_re_read_conf(ngx_conf_t *cf, 509 | ngx_command_t *cmd, void *conf) 510 | { 511 | ngx_http_yy_sec_waf_loc_conf_t *p = conf; 512 | 513 | ngx_uint_t n; 514 | char *rc; 515 | ngx_str_t *value, action; 516 | ngx_shm_zone_t *shm_zone; 517 | re_action_metadata *action_metadata; 518 | ngx_http_yy_sec_waf_rule_t *rule_p, rule; 519 | 520 | value = cf->args->elts; 521 | ngx_memset(&rule, 0, sizeof(ngx_http_yy_sec_waf_rule_t)); 522 | 523 | /* variable */ 524 | rc = yy_sec_waf_re_parse_variables(cf, &value[1], &rule); 525 | if (rc == NGX_CONF_ERROR) { 526 | ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "[ysec_waf] yy_sec_waf_re_parse_variables failed"); 527 | return NGX_CONF_ERROR; 528 | } 529 | 530 | /* operator */ 531 | rc = yy_sec_waf_re_parse_operator(cf, &value[2], &rule); 532 | if (rc == NGX_CONF_ERROR) { 533 | ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "[ysec_waf] yy_sec_waf_re_parse_operator failed"); 534 | return NGX_CONF_ERROR; 535 | } 536 | 537 | /* action */ 538 | for (n = 3; n < cf->args->nelts; n++) { 539 | ngx_memcpy(&action, &value[n], sizeof(ngx_str_t)); 540 | u_char *pos = ngx_strlchr(action.data, action.data+action.len, ':'); 541 | 542 | if (pos) { 543 | action.len = pos-action.data; 544 | } 545 | 546 | rule.action_metadata = yy_sec_waf_re_resolve_action_in_hash(&action); 547 | 548 | if (rule.action_metadata == NULL) { 549 | ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "[ysec_waf] Failed to resolve action"); 550 | return NGX_CONF_ERROR; 551 | } 552 | 553 | action_metadata = (re_action_metadata*) rule.action_metadata; 554 | 555 | if (action_metadata->parse(cf, &value[n], &rule) != NGX_CONF_OK) { 556 | ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "[ysec_waf] Failed parsing '%V'", &action); 557 | return NGX_CONF_ERROR; 558 | } 559 | } 560 | 561 | if (rule.phase & REQUEST_HEADER_PHASE) { 562 | if (p->request_header_rules == NULL) { 563 | p->request_header_rules = ngx_array_create(cf->pool, 1, sizeof(ngx_http_yy_sec_waf_rule_t)); 564 | 565 | if (p->request_header_rules == NULL) 566 | return NGX_CONF_ERROR; 567 | } 568 | 569 | rule_p = ngx_array_push(p->request_header_rules); 570 | 571 | if (rule_p == NULL) 572 | return NGX_CONF_ERROR; 573 | 574 | ngx_memcpy(rule_p, &rule, sizeof(ngx_http_yy_sec_waf_rule_t)); 575 | } 576 | 577 | if (rule.phase & REQUEST_BODY_PHASE) { 578 | if (p->request_body_rules == NULL) { 579 | p->request_body_rules = ngx_array_create(cf->pool, 1, sizeof(ngx_http_yy_sec_waf_rule_t)); 580 | 581 | if (p->request_body_rules == NULL) 582 | return NGX_CONF_ERROR; 583 | } 584 | 585 | rule_p = ngx_array_push(p->request_body_rules); 586 | 587 | if (rule_p == NULL) 588 | return NGX_CONF_ERROR; 589 | 590 | ngx_memcpy(rule_p, &rule, sizeof(ngx_http_yy_sec_waf_rule_t)); 591 | } 592 | 593 | if (rule.phase & RESPONSE_HEADER_PHASE) { 594 | if (p->response_header_rules == NULL) { 595 | p->response_header_rules = ngx_array_create(cf->pool, 1, sizeof(ngx_http_yy_sec_waf_rule_t)); 596 | 597 | if (p->response_header_rules == NULL) 598 | return NGX_CONF_ERROR; 599 | } 600 | 601 | rule_p = ngx_array_push(p->response_header_rules); 602 | 603 | if (rule_p == NULL) 604 | return NGX_CONF_ERROR; 605 | 606 | ngx_memcpy(rule_p, &rule, sizeof(ngx_http_yy_sec_waf_rule_t)); 607 | } 608 | 609 | if (rule.phase & RESPONSE_BODY_PHASE) { 610 | if (p->response_body_rules == NULL) { 611 | p->response_body_rules = ngx_array_create(cf->pool, 1, sizeof(ngx_http_yy_sec_waf_rule_t)); 612 | 613 | if (p->response_body_rules == NULL) 614 | return NGX_CONF_ERROR; 615 | } 616 | 617 | rule_p = ngx_array_push(p->response_body_rules); 618 | 619 | if (rule_p == NULL) 620 | return NGX_CONF_ERROR; 621 | 622 | ngx_memcpy(rule_p, &rule, sizeof(ngx_http_yy_sec_waf_rule_t)); 623 | } 624 | 625 | // Temply hack here, create shm zone for conn processor and get server_ip. 626 | if (p->conn_processor) { 627 | shm_zone = ngx_http_yy_sec_waf_create_shm_zone(cf); 628 | 629 | if (shm_zone != NULL) { 630 | p->shm_zone = shm_zone; 631 | } 632 | } 633 | 634 | if (p->server_ip.len == 0) { 635 | 636 | p->server_ip.len = NGX_SOCKADDR_STRLEN; 637 | p->server_ip.data = ngx_pcalloc(cf->pool, NGX_SOCKADDR_STRLEN); 638 | 639 | if (p->server_ip.data == NULL) { 640 | return NGX_CONF_ERROR; 641 | } 642 | 643 | if (ngx_local_addr("eth0", &p->server_ip) != NGX_OK) { 644 | return NGX_CONF_ERROR; 645 | } 646 | } 647 | 648 | return NGX_CONF_OK; 649 | } 650 | 651 | /* 652 | ** @description: This function is called to read denied url of yy sec waf. 653 | ** @para: ngx_conf_t *cf 654 | ** @para: ngx_str_t *tmp 655 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 656 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 657 | */ 658 | 659 | char * 660 | ngx_http_yy_sec_waf_re_read_du_loc_conf(ngx_conf_t *cf, 661 | ngx_command_t *cmd, void *conf) 662 | { 663 | ngx_http_yy_sec_waf_loc_conf_t *p = conf; 664 | ngx_str_t *value; 665 | 666 | value = cf->args->elts; 667 | if (value[1].len == 0) 668 | return NGX_CONF_ERROR; 669 | 670 | p->denied_url = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); 671 | if (!p->denied_url) 672 | return NGX_CONF_ERROR; 673 | 674 | p->denied_url->data = ngx_pcalloc(cf->pool, value[1].len+1); 675 | if (!p->denied_url->data) 676 | return NGX_CONF_ERROR; 677 | 678 | ngx_memcpy(p->denied_url->data, value[1].data, value[1].len); 679 | p->denied_url->len = value[1].len; 680 | 681 | return NGX_CONF_OK; 682 | } 683 | 684 | /* 685 | ** @description: This function is called to create rule engine for yy sec waf. 686 | ** @para: ngx_conf_t *cf 687 | ** @return: NGX_OK or NGX_ERROR if failed. 688 | */ 689 | 690 | ngx_int_t 691 | ngx_http_yy_sec_waf_re_create(ngx_conf_t *cf) 692 | { 693 | rule_engine = ngx_pcalloc(cf->pool, sizeof(yy_sec_waf_re_t)); 694 | if (rule_engine == NULL) { 695 | return NGX_ERROR; 696 | } 697 | 698 | if (ngx_http_yy_sec_waf_add_variables(cf) == NGX_ERROR) 699 | return NGX_ERROR; 700 | 701 | if (ngx_http_yy_sec_waf_init_operators_in_hash(cf, 702 | &rule_engine->operators_in_hash) == NGX_ERROR) 703 | return NGX_ERROR; 704 | 705 | if (ngx_http_yy_sec_waf_init_actions_in_hash(cf, 706 | &rule_engine->actions_in_hash) == NGX_ERROR) 707 | return NGX_ERROR; 708 | 709 | if (ngx_http_yy_sec_waf_init_tfns_in_hash(cf, 710 | &rule_engine->tfns_in_hash) == NGX_ERROR) 711 | return NGX_ERROR; 712 | 713 | return NGX_OK; 714 | } 715 | 716 | -------------------------------------------------------------------------------- /src/ngx_yy_sec_waf_re.h: -------------------------------------------------------------------------------- 1 | #ifndef __YY_SEC_WAF_RE_H__ 2 | #define __YY_SEC_WAF_RE_H__ 3 | 4 | /* 5 | ** @file: ngx_yy_sec_waf_re.h 6 | ** @description: This is header file for the rule engine of yy sec waf. 7 | ** @author: dw_liqi1 8 | ** @date: 2013.11.14 9 | ** Copyright (C) YY, Inc. 10 | */ 11 | 12 | #include "ngx_yy_sec_waf.h" 13 | 14 | 15 | #define STR "str:" 16 | #define REGEX "regex:" 17 | #define EQ "eq:" 18 | #define GT "gt:" 19 | #define GIDS "gids:" 20 | #define ID "id:" 21 | #define MSG "msg:" 22 | #define POS "pos:" 23 | #define LEVEL "lev:" 24 | #define PHASE "phase:" 25 | 26 | #define TFNS "t:" 27 | 28 | /* LEV */ 29 | #define LOG "log" 30 | #define BLOCK "block" 31 | #define ALLOW "allow" 32 | 33 | #define ACTION_NONE 0 34 | #define ACTION_LOG 1 35 | #define ACTION_BLOCK 2 36 | #define ACTION_ALLOW 4 37 | 38 | #define RULE_MATCH 1 39 | #define RULE_NO_MATCH 2 40 | 41 | #define UNCOMMON_CONTENT_TYPE 10 42 | #define UNCOMMON_FILENAME 11 43 | #define UNCOMMON_FILENAME_POSTFIX 12 44 | #define UNCOMMON_HEX_ENCODING 13 45 | #define UNCOMMON_POST_BOUNDARY 14 46 | #define UNCOMMON_POST_FORMAT 15 47 | 48 | #define NEXT_CHAIN 1 49 | #define NEXT_RULE 2 50 | 51 | typedef void* (*fn_op_parse_t)(ngx_conf_t *cf, 52 | ngx_str_t *tmp, ngx_http_yy_sec_waf_rule_t *rule); 53 | typedef ngx_int_t (*fn_op_execute_t)(ngx_http_request_t *r, 54 | ngx_str_t *str, ngx_http_yy_sec_waf_rule_t *rule); 55 | 56 | typedef struct { 57 | const ngx_str_t name; 58 | fn_op_parse_t parse; 59 | fn_op_execute_t execute; 60 | } re_op_metadata; 61 | 62 | typedef ngx_int_t (*fn_tfns_execute_t)(ngx_http_variable_value_t *v); 63 | 64 | typedef struct { 65 | const ngx_str_t name; 66 | fn_tfns_execute_t execute; 67 | } re_tfns_metadata; 68 | 69 | typedef void* (*fn_action_parse_t)(ngx_conf_t *cf, 70 | ngx_str_t *tmp, ngx_http_yy_sec_waf_rule_t *rule); 71 | 72 | typedef struct { 73 | const ngx_str_t name; 74 | fn_action_parse_t parse; 75 | } re_action_metadata; 76 | 77 | typedef struct { 78 | ngx_hash_t operators_in_hash; 79 | ngx_hash_t actions_in_hash; 80 | ngx_hash_t tfns_in_hash; 81 | } yy_sec_waf_re_t; 82 | 83 | ngx_int_t ngx_http_yy_sec_waf_add_variables(ngx_conf_t *cf); 84 | 85 | ngx_int_t ngx_http_yy_sec_waf_init_operators_in_hash(ngx_conf_t *cf, 86 | ngx_hash_t *hash); 87 | 88 | ngx_int_t ngx_http_yy_sec_waf_init_actions_in_hash(ngx_conf_t *cf, 89 | ngx_hash_t *hash); 90 | 91 | ngx_int_t ngx_http_yy_sec_waf_init_tfns_in_hash(ngx_conf_t *cf, 92 | ngx_hash_t *hash); 93 | 94 | re_tfns_metadata *yy_sec_waf_re_resolve_tfn_in_hash(ngx_str_t *tfn); 95 | 96 | ngx_inline void yy_sec_waf_re_cache_init_rbtree(ngx_rbtree_t *rbtree, 97 | ngx_rbtree_node_t *sentinel); 98 | 99 | ngx_int_t yy_sec_waf_re_cache_set_value(ngx_pool_t *pool, 100 | ngx_str_t *name, ngx_array_t *value, ngx_rbtree_t *rbtree); 101 | 102 | ngx_inline ngx_str_t *yy_sec_waf_re_cache_get_value(ngx_rbtree_t *rbtree, 103 | ngx_str_t *name); 104 | 105 | #endif 106 | 107 | -------------------------------------------------------------------------------- /src/ngx_yy_sec_waf_re_action.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** @file: ngx_yy_sec_waf_re_action.c 3 | ** @description: This is the rule engine's actions for yy sec waf. 4 | ** @author: dw_liqi1 5 | ** @date: 2013.11.13 6 | ** Copyright (C) YY, Inc. 7 | */ 8 | 9 | #include "ngx_yy_sec_waf_re.h" 10 | 11 | /* 12 | ** @description: This function is called to parse gids of yy sec waf. 13 | ** @para: ngx_conf_t *cf 14 | ** @para: ngx_str_t *tmp 15 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 16 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 17 | */ 18 | 19 | static void * 20 | yy_sec_waf_parse_gids(ngx_conf_t *cf, 21 | ngx_str_t *tmp, ngx_http_yy_sec_waf_rule_t *rule) 22 | { 23 | ngx_str_t *gids; 24 | 25 | if (!rule) 26 | return NGX_CONF_ERROR; 27 | 28 | gids = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); 29 | if (!gids) 30 | return NGX_CONF_ERROR; 31 | 32 | gids->data = tmp->data + ngx_strlen(GIDS); 33 | gids->len = tmp->len - ngx_strlen(GIDS); 34 | 35 | rule->gids = gids; 36 | 37 | return NGX_CONF_OK; 38 | } 39 | 40 | /* 41 | ** @description: This function is called to parse rule id of yy sec waf. 42 | ** @para: ngx_conf_t *cf 43 | ** @para: ngx_str_t *tmp 44 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 45 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 46 | */ 47 | 48 | static void * 49 | yy_sec_waf_parse_rule_id(ngx_conf_t *cf, 50 | ngx_str_t *tmp, ngx_http_yy_sec_waf_rule_t *rule) 51 | { 52 | ngx_str_t *rule_id; 53 | 54 | if (!rule) 55 | return NGX_CONF_ERROR; 56 | 57 | rule_id = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); 58 | if (!rule_id) 59 | return NGX_CONF_ERROR; 60 | 61 | rule_id->data = tmp->data + ngx_strlen(ID); 62 | rule_id->len = tmp->len - ngx_strlen(ID); 63 | 64 | rule->rule_id = ngx_atoi(rule_id->data, rule_id->len); 65 | 66 | return NGX_CONF_OK; 67 | } 68 | 69 | /* 70 | ** @description: This function is called to parse msg of yy sec waf. 71 | ** @para: ngx_conf_t *cf 72 | ** @para: ngx_str_t *tmp 73 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 74 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 75 | */ 76 | 77 | static void * 78 | yy_sec_waf_parse_msg(ngx_conf_t *cf, 79 | ngx_str_t *tmp, ngx_http_yy_sec_waf_rule_t *rule) 80 | { 81 | ngx_str_t *msg; 82 | 83 | if (!rule) 84 | return NGX_CONF_ERROR; 85 | 86 | msg = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); 87 | if (!msg) 88 | return NGX_CONF_ERROR; 89 | 90 | msg->data = tmp->data + ngx_strlen(MSG); 91 | msg->len = tmp->len - ngx_strlen(MSG); 92 | 93 | rule->msg = msg; 94 | 95 | return NGX_CONF_OK; 96 | } 97 | 98 | /* 99 | ** @description: This function is called to parse level of yy sec waf. 100 | ** @para: ngx_conf_t *cf 101 | ** @para: ngx_str_t *tmp 102 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 103 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 104 | */ 105 | 106 | static void * 107 | yy_sec_waf_parse_level(ngx_conf_t *cf, 108 | ngx_str_t *tmp, ngx_http_yy_sec_waf_rule_t *rule) 109 | { 110 | u_char *tmp_ptr; 111 | 112 | tmp_ptr = (u_char*)tmp->data + ngx_strlen(LEVEL); 113 | 114 | while (*tmp_ptr) { 115 | if (tmp_ptr[0] == '|') 116 | tmp_ptr++; 117 | /* match global zones */ 118 | if (!ngx_strncasecmp(tmp_ptr, (u_char*)BLOCK, ngx_strlen(BLOCK))) { 119 | rule->action_level |= ACTION_BLOCK; 120 | tmp_ptr += ngx_strlen(BLOCK); 121 | continue; 122 | } else if (!ngx_strncasecmp(tmp_ptr, (u_char*)LOG, ngx_strlen(LOG))) { 123 | rule->action_level |= ACTION_LOG; 124 | tmp_ptr += ngx_strlen(LOG); 125 | continue; 126 | } else if (!ngx_strncasecmp(tmp_ptr, (u_char*)ALLOW, ngx_strlen(ALLOW))) { 127 | rule->action_level |= ACTION_ALLOW; 128 | tmp_ptr += ngx_strlen(ALLOW); 129 | continue; 130 | } else { 131 | rule->action_level &= ACTION_NONE; 132 | return (NGX_CONF_ERROR); 133 | } 134 | } 135 | 136 | return NGX_CONF_OK; 137 | } 138 | 139 | /* 140 | ** @description: This function is called to parse phase of yy sec waf. 141 | ** @para: ngx_conf_t *cf 142 | ** @para: ngx_str_t *tmp 143 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 144 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 145 | */ 146 | 147 | static void * 148 | yy_sec_waf_parse_phase(ngx_conf_t *cf, 149 | ngx_str_t *tmp, ngx_http_yy_sec_waf_rule_t *rule) 150 | { 151 | 152 | u_char *tmp_ptr; 153 | 154 | if (!rule) 155 | return NGX_CONF_ERROR; 156 | 157 | tmp_ptr = (u_char*)tmp->data + ngx_strlen(PHASE); 158 | 159 | while (*tmp_ptr) { 160 | if (tmp_ptr[0] == ',') 161 | tmp_ptr++; 162 | /* match global zones */ 163 | if (tmp_ptr[0] == '1') { 164 | rule->phase |= REQUEST_HEADER_PHASE; 165 | tmp_ptr += 1; 166 | continue; 167 | } else if (tmp_ptr[0] == '2') { 168 | rule->phase |= REQUEST_BODY_PHASE; 169 | tmp_ptr += 1; 170 | } else if (tmp_ptr[0] == '3') { 171 | rule->phase |= RESPONSE_HEADER_PHASE; 172 | tmp_ptr += 1; 173 | } else if (tmp_ptr[0] == '4') { 174 | rule->phase |= RESPONSE_BODY_PHASE; 175 | tmp_ptr += 1; 176 | } else { 177 | return NGX_CONF_ERROR; 178 | } 179 | } 180 | 181 | 182 | return NGX_CONF_OK; 183 | } 184 | 185 | /* 186 | ** @description: This function is called to parse tfn of yy sec waf. 187 | ** @para: ngx_conf_t *cf 188 | ** @para: ngx_str_t *tmp 189 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 190 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 191 | */ 192 | 193 | static void * 194 | yy_sec_waf_parse_tfn(ngx_conf_t *cf, 195 | ngx_str_t *tmp, ngx_http_yy_sec_waf_rule_t *rule) 196 | { 197 | ngx_str_t *tfn; 198 | 199 | if (!rule) 200 | return NGX_CONF_ERROR; 201 | 202 | tfn = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); 203 | if (!tfn) 204 | return NGX_CONF_ERROR; 205 | 206 | tfn->data = tmp->data + ngx_strlen(TFNS); 207 | tfn->len = tmp->len - ngx_strlen(TFNS); 208 | 209 | rule->tfn_metadata = yy_sec_waf_re_resolve_tfn_in_hash(tfn); 210 | 211 | return NGX_CONF_OK; 212 | } 213 | 214 | /* 215 | ** @description: This function is called to parse chain of yy sec waf. 216 | ** @para: ngx_conf_t *cf 217 | ** @para: ngx_str_t *tmp 218 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 219 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 220 | */ 221 | 222 | static void * 223 | yy_sec_waf_parse_chain(ngx_conf_t *cf, 224 | ngx_str_t *tmp, ngx_http_yy_sec_waf_rule_t *rule) 225 | { 226 | ngx_str_t *chain; 227 | 228 | if (!rule) 229 | return NGX_CONF_ERROR; 230 | 231 | chain = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); 232 | if (!chain) 233 | return NGX_CONF_ERROR; 234 | 235 | chain->data = tmp->data + ngx_strlen("chain:"); 236 | chain->len = tmp->len - ngx_strlen("chain:"); 237 | 238 | rule->is_chain = ngx_atoi(chain->data, chain->len); 239 | 240 | return NGX_CONF_OK; 241 | } 242 | 243 | /* 244 | ** @description: This function is called to parse status of yy sec waf. 245 | ** @para: ngx_conf_t *cf 246 | ** @para: ngx_str_t *tmp 247 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 248 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 249 | */ 250 | 251 | static void * 252 | yy_sec_waf_parse_status(ngx_conf_t *cf, 253 | ngx_str_t *tmp, ngx_http_yy_sec_waf_rule_t *rule) 254 | { 255 | ngx_str_t *status; 256 | 257 | if (!rule) 258 | return NGX_CONF_ERROR; 259 | 260 | status = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); 261 | if (!status) 262 | return NGX_CONF_ERROR; 263 | 264 | status->data = tmp->data + ngx_strlen("status:"); 265 | status->len = tmp->len - ngx_strlen("status:"); 266 | 267 | rule->status = ngx_atoi(status->data, status->len); 268 | 269 | return NGX_CONF_OK; 270 | } 271 | 272 | static re_action_metadata action_metadata[] = { 273 | { ngx_string("gids"), yy_sec_waf_parse_gids}, 274 | { ngx_string("id"), yy_sec_waf_parse_rule_id}, 275 | { ngx_string("msg"), yy_sec_waf_parse_msg}, 276 | { ngx_string("lev"), yy_sec_waf_parse_level}, 277 | { ngx_string("phase"), yy_sec_waf_parse_phase}, 278 | { ngx_string("t"), yy_sec_waf_parse_tfn}, 279 | { ngx_string("chain"), yy_sec_waf_parse_chain}, 280 | { ngx_string("status"), yy_sec_waf_parse_status}, 281 | { ngx_null_string, NULL} 282 | }; 283 | 284 | /* 285 | ** @description: This function is called to init actions. 286 | ** @para: ngx_conf_t *cf 287 | ** @para: ngx_hash_t *actions_in_hash 288 | ** @return: NGX_OK or NGX_ERROR if failed. 289 | */ 290 | 291 | ngx_int_t 292 | ngx_http_yy_sec_waf_init_actions_in_hash(ngx_conf_t *cf, 293 | ngx_hash_t *actions_in_hash) 294 | { 295 | ngx_array_t actions; 296 | ngx_hash_key_t *hk; 297 | ngx_hash_init_t hash; 298 | re_action_metadata *metadata; 299 | 300 | if (ngx_array_init(&actions, cf->temp_pool, 32, sizeof(ngx_hash_key_t)) 301 | != NGX_OK) 302 | { 303 | return NGX_ERROR; 304 | } 305 | 306 | for (metadata = action_metadata; metadata->name.len; metadata++) { 307 | hk = ngx_array_push(&actions); 308 | if (hk == NULL) { 309 | return NGX_ERROR; 310 | } 311 | 312 | hk->key = metadata->name; 313 | hk->key_hash = ngx_hash_key_lc(metadata->name.data, metadata->name.len); 314 | hk->value = metadata; 315 | } 316 | 317 | hash.hash = actions_in_hash; 318 | hash.key = ngx_hash_key_lc; 319 | hash.max_size = 512; 320 | hash.bucket_size = ngx_align(64, ngx_cacheline_size); 321 | hash.name = "actions_in_hash"; 322 | hash.pool = cf->pool; 323 | hash.temp_pool = NULL; 324 | 325 | if (ngx_hash_init(&hash, actions.elts, actions.nelts) != NGX_OK) { 326 | return NGX_ERROR; 327 | } 328 | 329 | return NGX_OK; 330 | } 331 | 332 | -------------------------------------------------------------------------------- /src/ngx_yy_sec_waf_re_cache.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** @file: ngx_yy_sec_waf_re_cache.c 3 | ** @description: This is the rule engine's caches for yy sec waf. 4 | ** @author: dw_liqi1 5 | ** @date: 2013.12.12 6 | ** Copyright (C) YY, Inc. 7 | */ 8 | 9 | #include "ngx_yy_sec_waf_re.h" 10 | 11 | typedef struct { 12 | ngx_str_node_t sn; 13 | ngx_array_t *value; 14 | } re_cache_node_t; 15 | 16 | ngx_inline void 17 | yy_sec_waf_re_cache_init_rbtree(ngx_rbtree_t *rbtree, 18 | ngx_rbtree_node_t *sentinel) 19 | { 20 | ngx_rbtree_init(rbtree, sentinel, ngx_str_rbtree_insert_value); 21 | } 22 | 23 | ngx_int_t 24 | yy_sec_waf_re_cache_set_value(ngx_pool_t *pool, 25 | ngx_str_t *name, ngx_array_t *value, ngx_rbtree_t *rbtree) 26 | { 27 | uint32_t hash; 28 | ngx_str_t *val; 29 | re_cache_node_t *cache_node; 30 | 31 | hash = ngx_crc32_long(name->data, name->len); 32 | 33 | cache_node = (re_cache_node_t *) ngx_str_rbtree_lookup(rbtree, name, hash); 34 | 35 | if (cache_node != NULL) { 36 | return NGX_OK; 37 | } 38 | 39 | cache_node = ngx_palloc(pool, sizeof(re_cache_node_t)); 40 | 41 | if (cache_node == NULL) { 42 | return NGX_ERROR; 43 | } 44 | 45 | val = ngx_palloc(pool, sizeof(ngx_str_t)); 46 | 47 | if (value == NULL) { 48 | return NGX_ERROR; 49 | } 50 | 51 | val->len = value->len; 52 | val->data = ngx_pstrdup(pool, value); 53 | if (val->data == NULL) { 54 | return NGX_ERROR; 55 | } 56 | 57 | cache_node->sn.node.key = hash; 58 | cache_node->sn.str.len = name->len; 59 | cache_node->sn.str.data = name->data; 60 | cache_node->value = value; 61 | 62 | ngx_rbtree_insert(rbtree, &cache_node->sn.node); 63 | 64 | return NGX_OK; 65 | } 66 | 67 | ngx_inline ngx_array_t * 68 | yy_sec_waf_re_cache_get_value(ngx_rbtree_t *rbtree, ngx_str_t *name) 69 | { 70 | uint32_t hash; 71 | re_cache_node_t *cache_node; 72 | 73 | hash = ngx_crc32_long(name->data, name->len); 74 | 75 | cache_node = (re_cache_node_t *) ngx_str_rbtree_lookup(rbtree, name, hash); 76 | 77 | if (cache_node != NULL) { 78 | return cache_node->value; 79 | } 80 | 81 | return NULL; 82 | } 83 | 84 | -------------------------------------------------------------------------------- /src/ngx_yy_sec_waf_re_operator.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** @file: ngx_yy_sec_waf_re_operator.c 3 | ** @description: This is the rule engine's operators for yy sec waf. 4 | ** @author: dw_liqi1 5 | ** @date: 2013.11.05 6 | ** Copyright (C) YY, Inc. 7 | */ 8 | 9 | #include "ngx_yy_sec_waf_re.h" 10 | 11 | /* 12 | ** @description: This function is called to parse str of yy sec waf. 13 | ** @para: ngx_conf_t *cf 14 | ** @para: ngx_str_t *tmp 15 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 16 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 17 | */ 18 | 19 | static void * 20 | yy_sec_waf_parse_str(ngx_conf_t *cf, 21 | ngx_str_t *tmp, ngx_http_yy_sec_waf_rule_t *rule) 22 | { 23 | ngx_str_t *str; 24 | 25 | if (!rule) 26 | return NGX_CONF_ERROR; 27 | 28 | str = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); 29 | if (!str) 30 | return NGX_CONF_ERROR; 31 | 32 | str->data = tmp->data + ngx_strlen(STR); 33 | str->len = tmp->len - ngx_strlen(STR); 34 | 35 | rule->str = str; 36 | 37 | return NGX_CONF_OK; 38 | } 39 | 40 | /* 41 | ** @description: This function is called to excute str operator. 42 | ** @para: ngx_http_request_t *r 43 | ** @para: ngx_str_t *str 44 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 45 | ** @return: RULE_MATCH or RULE_NO_MATCH if failed. 46 | */ 47 | 48 | static ngx_int_t 49 | yy_sec_waf_execute_str(ngx_http_request_t *r, 50 | ngx_str_t *str, ngx_http_yy_sec_waf_rule_t *rule) 51 | { 52 | if (str == NULL || str->data == NULL) { 53 | return NGX_ERROR; 54 | } 55 | 56 | if (rule->str != NULL) { 57 | /* STR */ 58 | if (ngx_strnstr(str->data, (char*) rule->str->data, str->len)) { 59 | return RULE_MATCH; 60 | } 61 | } 62 | 63 | return RULE_NO_MATCH; 64 | } 65 | 66 | /* 67 | ** @description: This function is called to parse regex of yy sec waf. 68 | ** @para: ngx_conf_t *cf 69 | ** @para: ngx_str_t *tmp 70 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 71 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 72 | */ 73 | 74 | static void * 75 | yy_sec_waf_parse_regex(ngx_conf_t *cf, 76 | ngx_str_t *tmp, ngx_http_yy_sec_waf_rule_t *rule) 77 | { 78 | ngx_regex_compile_t *rgc; 79 | ngx_str_t pattern; 80 | 81 | pattern.data = tmp->data + ngx_strlen(REGEX); 82 | pattern.len = tmp->len - ngx_strlen(REGEX); 83 | 84 | rgc = ngx_pcalloc(cf->pool, sizeof(ngx_regex_compile_t)); 85 | if (!rgc) 86 | return NGX_CONF_ERROR; 87 | 88 | rgc->options = PCRE_CASELESS|PCRE_MULTILINE; 89 | rgc->pattern = pattern; 90 | rgc->pool = cf->pool; 91 | rgc->err.len = 0; 92 | rgc->err.data = NULL; 93 | 94 | rule->regex = ngx_http_regex_compile(cf, rgc); 95 | if (rule->regex == NULL) 96 | return NGX_CONF_ERROR; 97 | 98 | return NGX_CONF_OK; 99 | } 100 | 101 | /* 102 | ** @description: This function is called to excute regex operator. 103 | ** @para: ngx_http_request_t *r 104 | ** @para: ngx_str_t *str 105 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 106 | ** @return: RULE_MATCH or RULE_NO_MATCH if failed. 107 | */ 108 | 109 | static ngx_int_t 110 | yy_sec_waf_execute_regex(ngx_http_request_t *r, 111 | ngx_str_t *str, ngx_http_yy_sec_waf_rule_t *rule) 112 | { 113 | int rc; 114 | 115 | if (str == NULL || str->data == NULL) { 116 | return NGX_ERROR; 117 | } 118 | 119 | if (rule->regex != NULL) { 120 | /* REGEX */ 121 | rc = ngx_http_regex_exec(r, rule->regex, str); 122 | 123 | if (rc == NGX_OK) { 124 | return RULE_MATCH; 125 | } else if (rc == NGX_DECLINED) { 126 | return RULE_NO_MATCH; 127 | } 128 | } 129 | 130 | return NGX_ERROR; 131 | } 132 | 133 | /* 134 | ** @description: This function is called to parse eq of yy sec waf. 135 | ** @para: ngx_conf_t *cf 136 | ** @para: ngx_str_t *tmp 137 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 138 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 139 | */ 140 | 141 | static void * 142 | yy_sec_waf_parse_eq(ngx_conf_t *cf, 143 | ngx_str_t *tmp, ngx_http_yy_sec_waf_rule_t *rule) 144 | { 145 | ngx_str_t *eq; 146 | 147 | if (!rule) 148 | return NGX_CONF_ERROR; 149 | 150 | eq = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); 151 | if (!eq) 152 | return NGX_CONF_ERROR; 153 | 154 | eq->data = tmp->data + ngx_strlen(EQ); 155 | eq->len = tmp->len - ngx_strlen(EQ); 156 | 157 | rule->eq = eq; 158 | 159 | return NGX_CONF_OK; 160 | } 161 | 162 | /* 163 | ** @description: This function is called to excute eq operator. 164 | ** @para: ngx_http_request_t *r 165 | ** @para: ngx_str_t *str 166 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 167 | ** @return: RULE_MATCH or RULE_NO_MATCH if failed. 168 | */ 169 | 170 | static ngx_int_t 171 | yy_sec_waf_execute_eq(ngx_http_request_t *r, 172 | ngx_str_t *str, ngx_http_yy_sec_waf_rule_t *rule) 173 | { 174 | if (str == NULL || str->data == NULL) { 175 | return NGX_ERROR; 176 | } 177 | 178 | if ((str->len == rule->eq->len) 179 | && (ngx_memcmp(str->data, rule->eq->data, str->len) == 0)) 180 | { 181 | return RULE_MATCH; 182 | } 183 | 184 | return RULE_NO_MATCH; 185 | } 186 | 187 | /* 188 | ** @description: This function is called to parse gt of yy sec waf. 189 | ** @para: ngx_conf_t *cf 190 | ** @para: ngx_str_t *tmp 191 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 192 | ** @return: NGX_CONF_OK or NGX_CONF_ERROR if failed. 193 | */ 194 | 195 | static void * 196 | yy_sec_waf_parse_gt(ngx_conf_t *cf, 197 | ngx_str_t *tmp, ngx_http_yy_sec_waf_rule_t *rule) 198 | { 199 | ngx_str_t *gt; 200 | 201 | if (!rule) 202 | return NGX_CONF_ERROR; 203 | 204 | gt = ngx_pcalloc(cf->pool, sizeof(ngx_str_t)); 205 | if (!gt) 206 | return NGX_CONF_ERROR; 207 | 208 | gt->data = tmp->data + ngx_strlen(GT); 209 | gt->len = tmp->len - ngx_strlen(GT); 210 | 211 | rule->gt = gt; 212 | 213 | return NGX_CONF_OK; 214 | } 215 | 216 | /* 217 | ** @description: This function is called to excute gt operator. 218 | ** @para: ngx_http_request_t *r 219 | ** @para: ngx_str_t *str 220 | ** @para: ngx_http_yy_sec_waf_rule_t *rule 221 | ** @return: RULE_MATCH or RULE_NO_MATCH if failed. 222 | */ 223 | 224 | static ngx_int_t 225 | yy_sec_waf_execute_gt(ngx_http_request_t *r, 226 | ngx_str_t *str, ngx_http_yy_sec_waf_rule_t *rule) 227 | { 228 | if (str == NULL || str->data == NULL) { 229 | return NGX_ERROR; 230 | } 231 | 232 | ngx_int_t test, gt; 233 | 234 | test = ngx_atoi(str->data, str->len); 235 | gt = ngx_atoi(rule->gt->data, rule->gt->len); 236 | 237 | if (test > gt) 238 | { 239 | return RULE_MATCH; 240 | } 241 | 242 | return RULE_NO_MATCH; 243 | } 244 | 245 | static re_op_metadata op_metadata[] = { 246 | { ngx_string("str"), yy_sec_waf_parse_str, yy_sec_waf_execute_str }, 247 | { ngx_string("regex"), yy_sec_waf_parse_regex, yy_sec_waf_execute_regex }, 248 | { ngx_string("eq"), yy_sec_waf_parse_eq, yy_sec_waf_execute_eq }, 249 | { ngx_string("gt"), yy_sec_waf_parse_gt, yy_sec_waf_execute_gt }, 250 | { ngx_null_string, NULL, NULL } 251 | }; 252 | 253 | /* 254 | ** @description: This function is called to init operators. 255 | ** @para: ngx_conf_t *cf 256 | ** @para: ngx_hash_t *operators_in_hash 257 | ** @return: NGX_OK or NGX_ERROR if failed. 258 | */ 259 | 260 | ngx_int_t 261 | ngx_http_yy_sec_waf_init_operators_in_hash(ngx_conf_t *cf, 262 | ngx_hash_t *operators_in_hash) 263 | { 264 | ngx_array_t operators; 265 | ngx_hash_key_t *hk; 266 | ngx_hash_init_t hash; 267 | re_op_metadata *metadata; 268 | 269 | if (ngx_array_init(&operators, cf->temp_pool, 32, sizeof(ngx_hash_key_t)) 270 | != NGX_OK) 271 | { 272 | return NGX_ERROR; 273 | } 274 | 275 | for (metadata = op_metadata; metadata->name.len; metadata++) { 276 | hk = ngx_array_push(&operators); 277 | if (hk == NULL) { 278 | return NGX_ERROR; 279 | } 280 | 281 | hk->key = metadata->name; 282 | hk->key_hash = ngx_hash_key_lc(metadata->name.data, metadata->name.len); 283 | hk->value = metadata; 284 | } 285 | 286 | hash.hash = operators_in_hash; 287 | hash.key = ngx_hash_key_lc; 288 | hash.max_size = 512; 289 | hash.bucket_size = ngx_align(64, ngx_cacheline_size); 290 | hash.name = "operators_in_hash"; 291 | hash.pool = cf->pool; 292 | hash.temp_pool = NULL; 293 | 294 | if (ngx_hash_init(&hash, operators.elts, operators.nelts) != NGX_OK) { 295 | return NGX_ERROR; 296 | } 297 | 298 | return NGX_OK; 299 | } 300 | 301 | 302 | -------------------------------------------------------------------------------- /src/ngx_yy_sec_waf_re_tfn.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** @file: ngx_yy_sec_waf_re_tfn.c 3 | ** @description: This is the rule engine's tfns for yy sec waf. 4 | ** @author: dw_liqi1 5 | ** @date: 2013.11.26 6 | ** Copyright (C) YY, Inc. 7 | */ 8 | 9 | #include "ngx_yy_sec_waf_re.h" 10 | 11 | /* 12 | ** @description: This function is called to excute urldecode tfs. 13 | ** @para: ngx_http_variable_value_t *v 14 | ** @return: RULE_MATCH or RULE_NO_MATCH if failed. 15 | */ 16 | 17 | static ngx_int_t 18 | yy_sec_waf_re_tfns_urldecode(ngx_http_variable_value_t *v) 19 | { 20 | if (v == NULL) { 21 | return NGX_ERROR; 22 | } 23 | 24 | ngx_str_t str; 25 | str.data = v->data; 26 | str.len = v->len; 27 | 28 | ngx_yy_sec_waf_unescape(&str); 29 | 30 | return NGX_OK; 31 | } 32 | 33 | static re_tfns_metadata tfns_metadata[] = { 34 | { ngx_string("urldecode"), yy_sec_waf_re_tfns_urldecode }, 35 | { ngx_null_string, NULL } 36 | }; 37 | 38 | /* 39 | ** @description: This function is called to init tfns. 40 | ** @para: ngx_conf_t *cf 41 | ** @para: ngx_hash_t *actions_in_hash 42 | ** @return: NGX_OK or NGX_ERROR if failed. 43 | */ 44 | 45 | ngx_int_t 46 | ngx_http_yy_sec_waf_init_tfns_in_hash(ngx_conf_t *cf, 47 | ngx_hash_t *tfns_in_hash) 48 | { 49 | ngx_array_t tfns; 50 | ngx_hash_key_t *hk; 51 | ngx_hash_init_t hash; 52 | re_tfns_metadata *metadata; 53 | 54 | if (ngx_array_init(&tfns, cf->temp_pool, 32, sizeof(ngx_hash_key_t)) 55 | != NGX_OK) 56 | { 57 | return NGX_ERROR; 58 | } 59 | 60 | for (metadata = tfns_metadata; metadata->name.len; metadata++) { 61 | hk = ngx_array_push(&tfns); 62 | if (hk == NULL) { 63 | return NGX_ERROR; 64 | } 65 | 66 | hk->key = metadata->name; 67 | hk->key_hash = ngx_hash_key_lc(metadata->name.data, metadata->name.len); 68 | hk->value = metadata; 69 | } 70 | 71 | hash.hash = tfns_in_hash; 72 | hash.key = ngx_hash_key_lc; 73 | hash.max_size = 512; 74 | hash.bucket_size = ngx_align(64, ngx_cacheline_size); 75 | hash.name = "tfns_in_hash"; 76 | hash.pool = cf->pool; 77 | hash.temp_pool = NULL; 78 | 79 | if (ngx_hash_init(&hash, tfns.elts, tfns.nelts) != NGX_OK) { 80 | return NGX_ERROR; 81 | } 82 | 83 | return NGX_OK; 84 | } 85 | 86 | -------------------------------------------------------------------------------- /src/ngx_yy_sec_waf_re_variable.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** @file: ngx_yy_sec_waf_re_variable.c 3 | ** @description: This is the rule engine's variables for yy sec waf. 4 | ** @author: dw_liqi1 5 | ** @date: 2013.11.08 6 | ** Copyright (C) YY, Inc. 7 | */ 8 | 9 | #include "ngx_yy_sec_waf_re.h" 10 | 11 | /* 12 | ** @description: This function is called to get args. 13 | ** @para: ngx_http_request_t *r 14 | ** @para: ngx_http_variable_value_t *v 15 | ** @para: uintptr_t data 16 | ** @return: NGX_OK or NGX_ERROR if failed. 17 | */ 18 | 19 | static ngx_int_t 20 | yy_sec_waf_get_args(ngx_http_request_t *r, 21 | ngx_http_variable_value_t *v, uintptr_t data) 22 | { 23 | ngx_http_request_ctx_t *ctx; 24 | ngx_str_t p; 25 | 26 | ctx = ngx_http_get_module_ctx(r, ngx_http_yy_sec_waf_module); 27 | 28 | if (ctx == NULL) { 29 | v->not_found = 1; 30 | return NGX_OK; 31 | } 32 | 33 | if (ctx->args.len == 0 && ctx->post_args.len == 0){ 34 | v->not_found = 1; 35 | return NGX_OK; 36 | } 37 | 38 | v->data = ctx->args.data; 39 | v->len = ctx->args.len; 40 | 41 | if (ctx->post_args.len) { 42 | p.len = ctx->args.len+ctx->post_args.len+1; 43 | p.data = ngx_palloc(r->pool, p.len); 44 | if (p.data == NULL) { 45 | return NGX_ERROR; 46 | } 47 | 48 | v->data = p.data; 49 | v->len = p.len; 50 | 51 | p.data = ngx_cpymem(p.data, ctx->args.data, ctx->args.len); 52 | p.data = ngx_cpymem(p.data, ",", 1); 53 | ngx_memcpy(p.data, ctx->post_args.data, ctx->post_args.len); 54 | } 55 | 56 | v->valid = 1; 57 | v->no_cacheable = 0; 58 | v->escape = 0; 59 | v->not_found = 0; 60 | 61 | return NGX_OK; 62 | } 63 | 64 | /* 65 | ** @description: This function is called to get post args count. 66 | ** @para: ngx_http_request_t *r 67 | ** @para: ngx_http_variable_value_t *v 68 | ** @para: uintptr_t data 69 | ** @return: NGX_OK or NGX_ERROR if failed. 70 | */ 71 | 72 | static ngx_int_t 73 | yy_sec_waf_get_post_args_count(ngx_http_request_t *r, 74 | ngx_http_variable_value_t *v, uintptr_t data) 75 | { 76 | u_char *p; 77 | ngx_http_request_ctx_t *ctx; 78 | 79 | ctx = ngx_http_get_module_ctx(r, ngx_http_yy_sec_waf_module); 80 | 81 | if (ctx == NULL) { 82 | v->not_found = 1; 83 | return NGX_OK; 84 | } 85 | 86 | if (ctx->post_args_count == 0) { 87 | v->not_found = 1; 88 | return NGX_OK; 89 | } 90 | 91 | p = ngx_yy_sec_waf_uitoa(r->pool, ctx->post_args_count); 92 | 93 | v->len = ngx_strlen(p); 94 | v->valid = 1; 95 | v->no_cacheable = 0; 96 | v->escape = 0; 97 | v->not_found = 0; 98 | v->data = p; 99 | 100 | return NGX_OK; 101 | } 102 | 103 | /* 104 | ** @description: This function is called to get process body error. 105 | ** @para: ngx_http_request_t *r 106 | ** @para: ngx_http_variable_value_t *v 107 | ** @para: uintptr_t data 108 | ** @return: NGX_OK or NGX_ERROR if failed. 109 | */ 110 | 111 | static ngx_int_t 112 | yy_sec_waf_get_process_body_error(ngx_http_request_t *r, 113 | ngx_http_variable_value_t *v, uintptr_t data) 114 | { 115 | ngx_http_request_ctx_t *ctx; 116 | 117 | ctx = ngx_http_get_module_ctx(r, ngx_http_yy_sec_waf_module); 118 | 119 | if (ctx == NULL) { 120 | v->not_found = 1; 121 | return NGX_OK; 122 | } 123 | 124 | if (ctx->process_body_error == 1) { 125 | *v = ngx_http_variable_true_value; 126 | } else { 127 | v->not_found = 1; 128 | } 129 | 130 | return NGX_OK; 131 | } 132 | 133 | /* 134 | ** @description: This function is called to get multipart name. 135 | ** @para: ngx_http_request_t *r 136 | ** @para: ngx_http_variable_value_t *v 137 | ** @para: uintptr_t data 138 | ** @return: NGX_OK or NGX_ERROR if failed. 139 | */ 140 | 141 | static ngx_int_t 142 | yy_sec_waf_get_multipart_name(ngx_http_request_t *r, 143 | ngx_http_variable_value_t *v, uintptr_t data) 144 | { 145 | ngx_uint_t i; 146 | ngx_str_t *var; 147 | u_char *p; 148 | ngx_http_request_ctx_t *ctx; 149 | 150 | ctx = ngx_http_get_module_ctx(r, ngx_http_yy_sec_waf_module); 151 | 152 | if (ctx == NULL) { 153 | v->not_found = 1; 154 | return NGX_OK; 155 | } 156 | 157 | var = ctx->multipart_name.elts; 158 | 159 | for (i = 0; i < ctx->multipart_name.nelts; i++) { 160 | v->len += var[i].len; 161 | } 162 | 163 | if (v->len == 0) { 164 | v->not_found = 1; 165 | return NGX_OK; 166 | } 167 | 168 | v->data = ngx_palloc(r->pool, v->len); 169 | 170 | p = v->data; 171 | 172 | for (i = 0; i < ctx->multipart_name.nelts; i++) { 173 | v->data = ngx_cpymem(v->data, var[i].data, var[i].len); 174 | } 175 | 176 | v->valid = 1; 177 | v->no_cacheable = 0; 178 | v->escape = 0; 179 | v->not_found = 0; 180 | v->data = p; 181 | 182 | return NGX_OK; 183 | } 184 | 185 | /* 186 | ** @description: This function is called to get multipart filename. 187 | ** @para: ngx_http_request_t *r 188 | ** @para: ngx_http_variable_value_t *v 189 | ** @para: uintptr_t data 190 | ** @return: NGX_OK or NGX_ERROR if failed. 191 | */ 192 | 193 | static ngx_int_t 194 | yy_sec_waf_get_multipart_filename(ngx_http_request_t *r, 195 | ngx_http_variable_value_t *v, uintptr_t data) 196 | { 197 | ngx_uint_t i; 198 | ngx_str_t *var; 199 | u_char *p; 200 | ngx_http_request_ctx_t *ctx; 201 | 202 | ctx = ngx_http_get_module_ctx(r, ngx_http_yy_sec_waf_module); 203 | 204 | if (ctx == NULL) { 205 | v->not_found = 1; 206 | return NGX_OK; 207 | } 208 | 209 | var = ctx->multipart_filename.elts; 210 | 211 | for (i = 0; i < ctx->multipart_filename.nelts; i++) { 212 | v->len += var[i].len; 213 | } 214 | 215 | if (v->len == 0) { 216 | v->not_found = 1; 217 | return NGX_OK; 218 | } 219 | 220 | v->data = ngx_palloc(r->pool, v->len); 221 | 222 | p = v->data; 223 | 224 | for (i = 0; i < ctx->multipart_filename.nelts; i++) { 225 | v->data = ngx_cpymem(v->data, var[i].data, var[i].len); 226 | } 227 | 228 | v->valid = 1; 229 | v->no_cacheable = 0; 230 | v->escape = 0; 231 | v->not_found = 0; 232 | v->data = p; 233 | 234 | return NGX_OK; 235 | } 236 | 237 | /* 238 | ** @description: This function is called to get connection per ip. 239 | ** @para: ngx_http_request_t *r 240 | ** @para: ngx_http_variable_value_t *v 241 | ** @para: uintptr_t data 242 | ** @return: static ngx_int_t. 243 | */ 244 | 245 | static ngx_int_t 246 | yy_sec_waf_get_conn_per_ip(ngx_http_request_t *r, 247 | ngx_http_variable_value_t *v, uintptr_t data) 248 | { 249 | u_char *p; 250 | ngx_http_request_ctx_t *ctx; 251 | 252 | ctx = ngx_http_get_module_ctx(r, ngx_http_yy_sec_waf_module); 253 | 254 | if (ctx == NULL) { 255 | v->not_found = 1; 256 | return NGX_OK; 257 | } 258 | 259 | if (ctx->conn_per_ip == 0) { 260 | v->not_found = 1; 261 | return NGX_OK; 262 | } 263 | 264 | p = ngx_yy_sec_waf_uitoa(r->pool, ctx->conn_per_ip); 265 | 266 | v->len = ngx_strlen(p); 267 | v->valid = 1; 268 | v->no_cacheable = 0; 269 | v->escape = 0; 270 | v->not_found = 0; 271 | v->data = p; 272 | 273 | return NGX_OK; 274 | } 275 | 276 | static ngx_http_variable_t var_metadata[] = { 277 | 278 | { ngx_string("ARGS"), NULL, yy_sec_waf_get_args, 279 | 0, 0, 0 }, 280 | 281 | { ngx_string("ARGS_POST"), NULL, yy_sec_waf_get_args, 282 | 0, 0, 0 }, 283 | 284 | { ngx_string("POST_ARGS_COUNT"), NULL, yy_sec_waf_get_post_args_count, 285 | 0, 0, 0 }, 286 | 287 | { ngx_string("PROCESS_BODY_ERROR"), NULL, yy_sec_waf_get_process_body_error, 288 | 0, 0, 0 }, 289 | 290 | { ngx_string("MULTIPART_NAME"), NULL, yy_sec_waf_get_multipart_name, 291 | 0, 0, 0 }, 292 | 293 | { ngx_string("MULTIPART_FILENAME"), NULL, yy_sec_waf_get_multipart_filename, 294 | 0, 0, 0 }, 295 | 296 | { ngx_string("CONN_PER_IP"), NULL, yy_sec_waf_get_conn_per_ip, 297 | 0, 0, 0 }, 298 | 299 | { ngx_null_string, NULL, NULL, 300 | 0, 0, 0 } 301 | }; 302 | 303 | ngx_int_t 304 | ngx_http_yy_sec_waf_add_variables(ngx_conf_t *cf) 305 | { 306 | ngx_http_variable_t *var, *v; 307 | 308 | for (v = var_metadata; v->name.len != 0; v++) { 309 | var = ngx_http_add_variable(cf, &v->name, v->flags); 310 | if (var == NULL) { 311 | return NGX_ERROR; 312 | } 313 | 314 | var->get_handler = v->get_handler; 315 | var->flags = v->flags; 316 | } 317 | 318 | return NGX_OK; 319 | } 320 | 321 | 322 | -------------------------------------------------------------------------------- /src/ngx_yy_sec_waf_utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** @file: ngx_yy_sec_waf_utils.c 3 | ** @description: This is the utils defined for yy sec waf. 4 | ** @author: dw_liqi1 5 | ** @date: 2013.07.10 6 | ** Copyright (C) YY, Inc. 7 | */ 8 | 9 | #include "ngx_yy_sec_waf.h" 10 | #include 11 | 12 | static int 13 | ngx_yy_sec_waf_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type); 14 | 15 | /* 16 | ** @description: Unescape routine. 17 | ** @para: ngx_str_t *str 18 | ** @return: uint (nullbytes+bad) 19 | */ 20 | 21 | int 22 | ngx_yy_sec_waf_unescape(ngx_str_t *str) { 23 | u_char *dst, *src; 24 | u_int nullbytes = 0, i; 25 | 26 | dst = str->data; 27 | src = str->data; 28 | 29 | ngx_yy_sec_waf_unescape_uri(&src, &dst, str->len, 0); 30 | 31 | str->len = src - str->data; 32 | 33 | /* tmp hack fix, avoid %00 & co (null byte) encoding :p */ 34 | for (i = 0; i < str->len; i++) { 35 | if (str->data[i] == 0x0) { 36 | nullbytes++; 37 | //str->data[i] = '0'; 38 | } 39 | } 40 | 41 | return nullbytes; 42 | } 43 | 44 | /* 45 | ** @description: Patched ngx_unescape_uri : 46 | ** The original one does not care if the character following % is in valid range. 47 | ** For example, with the original one : 48 | ** '%uff' -> 'uff' 49 | ** @para: u_char **dst 50 | ** @para: u_char **src 51 | ** @para: size_t size 52 | ** @para: ngx_uint_t type 53 | ** @return: int 54 | */ 55 | static int 56 | ngx_yy_sec_waf_unescape_uri(u_char **dst, u_char **src, size_t size, ngx_uint_t type) 57 | { 58 | u_char *d, *s, ch, c, decoded; 59 | int bad = 0; 60 | 61 | enum { 62 | sw_usual = 0, 63 | sw_quoted, 64 | sw_quoted_second 65 | } state; 66 | 67 | d = *dst; 68 | s = *src; 69 | 70 | state = 0; 71 | decoded = 0; 72 | 73 | while (size--) { 74 | 75 | ch = *s++; 76 | 77 | switch (state) { 78 | case sw_usual: 79 | if (ch == '?' 80 | && (type & (NGX_UNESCAPE_URI|NGX_UNESCAPE_REDIRECT))) 81 | { 82 | *d++ = ch; 83 | goto done; 84 | } 85 | 86 | if (ch == '%') { 87 | state = sw_quoted; 88 | break; 89 | } 90 | 91 | // Convert + into space! 92 | if (ch == '+') { 93 | ch = ' '; 94 | } 95 | 96 | *d++ = ch; 97 | break; 98 | 99 | case sw_quoted: 100 | 101 | if (ch >= '0' && ch <= '9') { 102 | decoded = (u_char) (ch - '0'); 103 | state = sw_quoted_second; 104 | break; 105 | } 106 | 107 | c = (u_char) (ch | 0x20); 108 | if (c >= 'a' && c <= 'f') { 109 | decoded = (u_char) (c - 'a' + 10); 110 | state = sw_quoted_second; 111 | break; 112 | } 113 | 114 | /* the invalid quoted character */ 115 | bad++; 116 | state = sw_usual; 117 | *d++ = '%'; 118 | *d++ = ch; 119 | break; 120 | 121 | case sw_quoted_second: 122 | 123 | state = sw_usual; 124 | 125 | if (ch >= '0' && ch <= '9') { 126 | ch = (u_char) ((decoded << 4) + ch - '0'); 127 | 128 | if (type & NGX_UNESCAPE_REDIRECT) { 129 | if (ch > '%' && ch < 0x7f) { 130 | *d++ = ch; 131 | break; 132 | } 133 | 134 | *d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1); 135 | 136 | break; 137 | } 138 | 139 | *d++ = ch; 140 | 141 | break; 142 | } 143 | 144 | c = (u_char) (ch | 0x20); 145 | if (c >= 'a' && c <= 'f') { 146 | ch = (u_char) ((decoded << 4) + c - 'a' + 10); 147 | 148 | if (type & NGX_UNESCAPE_URI) { 149 | if (ch == '?') { 150 | *d++ = ch; 151 | goto done; 152 | } 153 | 154 | *d++ = ch; 155 | break; 156 | } 157 | 158 | if (type & NGX_UNESCAPE_REDIRECT) { 159 | if (ch == '?') { 160 | *d++ = ch; 161 | goto done; 162 | } 163 | 164 | if (ch > '%' && ch < 0x7f) { 165 | *d++ = ch; 166 | break; 167 | } 168 | 169 | *d++ = '%'; *d++ = *(s - 2); *d++ = *(s - 1); 170 | break; 171 | } 172 | 173 | *d++ = ch; 174 | 175 | break; 176 | } 177 | /* the invalid quoted character */ 178 | /* as it happened in the 2nd part of quoted character, 179 | we need to restore the decoded char as well. */ 180 | *d++ = '%'; 181 | *d++ = (0 >= decoded && decoded < 10) ? decoded + '0' : 182 | decoded - 10 + 'a'; 183 | *d++ = ch; 184 | bad++; 185 | break; 186 | } 187 | } 188 | 189 | done: 190 | 191 | *dst = d; 192 | *src = s; 193 | 194 | return (bad); 195 | } 196 | 197 | /* 198 | ** @description: This function is called to convert ngx_int_t into u_char. 199 | ** @para: ngx_pool_t *p 200 | ** @para: ngx_int_t n 201 | ** @return: u_char* 202 | */ 203 | 204 | u_char* 205 | ngx_yy_sec_waf_itoa(ngx_pool_t *p, ngx_int_t n) 206 | { 207 | const int BUFFER_SIZE = sizeof(ngx_int_t) * 3 + 2; 208 | u_char *buf = ngx_palloc(p, BUFFER_SIZE); 209 | u_char *start = buf + BUFFER_SIZE - 1; 210 | int negative; 211 | 212 | if (n < 0) { 213 | negative = 1; 214 | n = -n; 215 | } else { 216 | negative = 0; 217 | } 218 | 219 | *start = 0; 220 | do { 221 | *--start = '0' + (n % 10); 222 | n /= 10; 223 | } while (n); 224 | 225 | if (negative) { 226 | *--start = '-'; 227 | } 228 | 229 | return start; 230 | } 231 | 232 | /* 233 | ** @description: This function is called to convert ngx_uint_t into u_char. 234 | ** @para: ngx_pool_t *p 235 | ** @para: ngx_uint_t n 236 | ** @return: u_char* 237 | */ 238 | 239 | u_char* 240 | ngx_yy_sec_waf_uitoa(ngx_pool_t *p, ngx_uint_t n) 241 | { 242 | const int BUFFER_SIZE = sizeof(ngx_uint_t) * 3 + 2; 243 | u_char *buf = ngx_palloc(p, BUFFER_SIZE); 244 | u_char *start = buf + BUFFER_SIZE - 1; 245 | 246 | *start = 0; 247 | do { 248 | *--start = '0' + (n % 10); 249 | n /= 10; 250 | } while (n); 251 | 252 | return start; 253 | } 254 | 255 | /* 256 | ** @description: This function is called to get local addr. 257 | ** @para: ngx_connection_t *c 258 | ** @para: const char *eth 259 | ** @para: ngx_str_t *s 260 | ** @return: ngx_int_t 261 | */ 262 | 263 | ngx_int_t 264 | ngx_local_addr(const char *eth, ngx_str_t *s) 265 | { 266 | struct sockaddr_in *addr4; 267 | struct sockaddr_in6 *addr6; 268 | struct ifaddrs *ifap0, *ifap; 269 | 270 | if (eth == NULL || s == NULL) { 271 | return NGX_ERROR; 272 | } 273 | 274 | if (getifaddrs(&ifap0)) { 275 | return NGX_ERROR; 276 | } 277 | 278 | for (ifap = ifap0; ifap != NULL; ifap = ifap->ifa_next) { 279 | if(ngx_strcmp(eth, ifap->ifa_name)!=0) 280 | continue; 281 | 282 | if(ifap->ifa_addr == NULL) 283 | continue; 284 | 285 | if(AF_INET == ifap->ifa_addr->sa_family) { 286 | addr4 = (struct sockaddr_in *)ifap->ifa_addr; 287 | 288 | if(s->len != ngx_inet_ntop(ifap->ifa_addr->sa_family, 289 | (void *)&(addr4->sin_addr), s->data, s->len)) { 290 | 291 | freeifaddrs(ifap0); 292 | return NGX_OK; 293 | } else { 294 | break; 295 | } 296 | } 297 | else if(AF_INET6 == ifap->ifa_addr->sa_family) { 298 | addr6 = (struct sockaddr_in6*) ifap->ifa_addr; 299 | 300 | if(IN6_IS_ADDR_MULTICAST(&addr6->sin6_addr)) { 301 | continue; 302 | } 303 | 304 | if(IN6_IS_ADDR_LINKLOCAL(&addr6->sin6_addr)) { 305 | continue; 306 | } 307 | 308 | if(IN6_IS_ADDR_LOOPBACK(&addr6->sin6_addr)) { 309 | continue; 310 | } 311 | 312 | if(IN6_IS_ADDR_UNSPECIFIED(&addr6->sin6_addr)) { 313 | continue; 314 | } 315 | 316 | if(IN6_IS_ADDR_SITELOCAL(&addr6->sin6_addr)) { 317 | continue; 318 | } 319 | 320 | if(s->len != ngx_inet_ntop(ifap->ifa_addr->sa_family, 321 | (void *)&(addr6->sin6_addr), s->data, s->len)) { 322 | 323 | freeifaddrs(ifap0); 324 | return NGX_OK; 325 | } 326 | else { 327 | break; 328 | } 329 | 330 | } 331 | } 332 | 333 | freeifaddrs(ifap0); 334 | 335 | return NGX_ERROR; 336 | } 337 | 338 | 339 | -------------------------------------------------------------------------------- /t/yy_sec_waf_action.t: -------------------------------------------------------------------------------- 1 | #vi:filetype=perl 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(3); 7 | 8 | plan tests => repeat_each(1) * blocks(); 9 | no_root_location(); 10 | no_long_string(); 11 | $ENV{TEST_NGINX_SERVROOT} = server_root(); 12 | run_tests(); 13 | 14 | __DATA__ 15 | === TEST 1: status 16 | --- config 17 | location / { 18 | basic_rule ARGS regex:foobar phase:1 status:500 lev:LOG|BLOCK; 19 | root $TEST_NGINX_SERVROOT/html/; 20 | index index.html index.htm; 21 | } 22 | --- request 23 | GET /?t=foobar 24 | --- error_code: 500 25 | 26 | === TEST 1: default status 27 | --- config 28 | location / { 29 | basic_rule ARGS regex:foobar phase:1 lev:LOG|BLOCK; 30 | root $TEST_NGINX_SERVROOT/html/; 31 | index index.html index.htm; 32 | } 33 | --- request 34 | GET /?t=foobar 35 | --- error_code: 412 36 | -------------------------------------------------------------------------------- /t/yy_sec_waf_base.t: -------------------------------------------------------------------------------- 1 | #vi:filetype=perl 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(3); 7 | 8 | plan tests => repeat_each(1) * blocks(); 9 | no_root_location(); 10 | no_long_string(); 11 | $ENV{TEST_NGINX_SERVROOT} = server_root(); 12 | run_tests(); 13 | 14 | __DATA__ 15 | === TEST 1: Basic GET request 16 | --- config 17 | location / { 18 | basic_rule ARGS regex:script phase:2 id:1001 msg:test gids:XSS lev:LOG|BLOCK; 19 | 20 | root $TEST_NGINX_SERVROOT/html/; 21 | index index.html index.htm; 22 | } 23 | --- request 24 | GET / 25 | --- error_code: 200 26 | 27 | === TEST 2: DENY: Short Char Rule 28 | --- config 29 | location / { 30 | basic_rule ARGS str:script phase:2 id:1001 msg:test gids:XSS lev:LOG|BLOCK; 31 | root $TEST_NGINX_SERVROOT/html/; 32 | index index.html index.htm; 33 | } 34 | --- request 35 | GET /?a="" 36 | --- error_code: 412 37 | 38 | === TEST 3: Regex 39 | --- config 40 | location / { 41 | basic_rule ARGS regex:script phase:2 id:1001 msg:test gids:XSS lev:LOG|BLOCK; 42 | root $TEST_NGINX_SERVROOT/html/; 43 | index index.html index.htm; 44 | } 45 | --- request 46 | GET /?a="" 47 | --- error_code: 412 48 | 49 | === TEST 4: Multi Rules 50 | --- config 51 | location / { 52 | basic_rule ARGS regex:script phase:2 id:1001 msg:test gids:XSS lev:LOG|BLOCK; 53 | basic_rule ARGS regex:test phase:2 id:1002 msg:test gids:XSS lev:LOG|BLOCK; 54 | root $TEST_NGINX_SERVROOT/html/; 55 | index index.html index.htm; 56 | } 57 | --- request 58 | GET /?a="" 59 | --- error_code: 412 60 | 61 | === TEST 6: LEV, log 62 | --- config 63 | location / { 64 | basic_rule ARGS regex:script phase:2 id:1001 msg:test gids:XSS lev:LOG; 65 | root $TEST_NGINX_SERVROOT/html/; 66 | index index.html index.htm; 67 | } 68 | --- request 69 | GET /?a="" 70 | --- error_code: 200 71 | 72 | === TEST 7: LEV, block 73 | --- config 74 | location / { 75 | basic_rule ARGS regex:script phase:2 id:1001 msg:test gids:XSS lev:BLOCK; 76 | root $TEST_NGINX_SERVROOT/html/; 77 | index index.html index.htm; 78 | } 79 | --- request 80 | GET /?a="" 81 | --- error_code: 412 82 | 83 | === TEST 8: yy_sec_waf, off 84 | --- config 85 | location / { 86 | yy_sec_waf off; 87 | basic_rule ARGS regex:script phase:2 id:1001 msg:test gids:XSS lev:BLOCK; 88 | root $TEST_NGINX_SERVROOT/html/; 89 | index index.html index.htm; 90 | } 91 | --- request 92 | GET /?a="" 93 | --- error_code: 200 94 | 95 | === TEST 9: basic post 96 | --- user_files 97 | >>> foobar 98 | eh yo 99 | --- config 100 | location / { 101 | basic_rule ARGS regex:script phase:2 id:1001 msg:test gids:XSS lev:LOG|BLOCK; 102 | root $TEST_NGINX_SERVROOT/html/; 103 | index index.html index.htm; 104 | error_page 405 = $uri; 105 | } 106 | --- more_headers 107 | Content-Type: application/x-www-form-urlencoded 108 | --- request eval 109 | use URI::Escape; 110 | "POST / 111 | foo1=%3Cscript%3E&foo2=bar2" 112 | --- error_code: 412 113 | -------------------------------------------------------------------------------- /t/yy_sec_waf_files.t: -------------------------------------------------------------------------------- 1 | #vi:filetype=perl 2 | 3 | use lib 'lib'; 4 | use Test::Nginx::Socket; 5 | 6 | repeat_each(3); 7 | 8 | plan tests => repeat_each(1) * blocks(); 9 | no_root_location(); 10 | no_long_string(); 11 | $ENV{TEST_NGINX_SERVROOT} = server_root(); 12 | run_tests(); 13 | 14 | #=== TEST 1: multipart, legit file 15 | 16 | __DATA__ 17 | 18 | === TEST 1: multipart, bad file 19 | --- user_files 20 | >>> foobar 21 | eh yo 22 | --- config 23 | location / { 24 | basic_rule MULTIPART_FILENAME "regex:\.jsp|\.php|\.html|\.htm|\.\./" "msg:uncommon filename" phase:2 id:1201 gids:UPLOAD lev:LOG|BLOCK; 25 | root $TEST_NGINX_SERVROOT/html/; 26 | index index.html index.htm; 27 | error_page 405 = $uri; 28 | } 29 | --- raw_request eval 30 | "POST /foobar HTTP/1.1\r 31 | Host: 127.0.0.1\r 32 | Connection: Close\r 33 | User-Agent: Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10\r 34 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r 35 | Accept-Language: en-us,en;q=0.5\r 36 | Accept-Encoding: gzip, deflate\r 37 | Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r 38 | Referer: http://127.0.0.1/\r 39 | Content-Type: multipart/form-data; boundary=---------------------------1919886344942015258287623957\r 40 | Content-Length: 378\r 41 | \r 42 | -----------------------------1919886344942015258287623957\r 43 | Content-Disposition: form-data; name=\"textline\"\r 44 | \r 45 | valid text and small file\r 46 | -----------------------------1919886344942015258287623957\r 47 | Content-Disposition: form-data; name=\"datafile\"; filename=\"bla.txt\"\r 48 | Content-Type: text/plain\r 49 | \r 50 | buibuibubi 51 | buibuibuib 52 | \r 53 | -----------------------------1919886344942015258287623957--\r 54 | " 55 | --- error_code: 200 56 | 57 | === TEST 2: multipart, %00 injection 58 | --- user_files 59 | >>> foobar 60 | eh yo 61 | --- config 62 | location / { 63 | basic_rule MULTIPART_FILENAME "regex:\.jsp|\.php|\.html|\.htm|\.\./" "msg:uncommon filename" phase:2 id:1201 gids:UPLOAD lev:LOG|BLOCK; 64 | root $TEST_NGINX_SERVROOT/html/; 65 | index index.html index.htm; 66 | error_page 405 = $uri; 67 | } 68 | --- raw_request eval 69 | "POST /index.html HTTP/1.1\r 70 | Host: 127.0.0.1\r 71 | Connection: Close\r 72 | User-Agent: Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10\r 73 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r 74 | Accept-Language: en-us,en;q=0.5\r 75 | Accept-Encoding: gzip, deflate\r 76 | Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r 77 | Referer: http://127.0.0.1/index.html\r 78 | Content-Type: multipart/form-data; boundary=---------------------------210801732320936925511223486236\r 79 | Content-Length: 3272\r 80 | \r 81 | -----------------------------210801732320936925511223486236\r 82 | Content-Disposition: form-data; name=\"textline\"\r 83 | \r 84 | valid text bad file\r 85 | -----------------------------210801732320936925511223486236\r 86 | Content-Disposition: form-data; name=\"datafile\"; filename=\"bla_m%00ed.jpg.php\"\r 87 | Content-Type: application/x-httpd-php\r 88 | \r 89 | 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000\r 90 | -----------------------------210801732320936925511223486236--\r 91 | " 92 | --- error_code: 412 93 | 94 | === TEST 3: multipart, ../ injection 95 | --- user_files 96 | >>> foobar 97 | eh yo 98 | --- config 99 | location / { 100 | basic_rule MULTIPART_FILENAME "regex:\.jsp|\.php|\.html|\.htm|\.\./" "msg:uncommon filename" phase:2 id:1201 gids:UPLOAD lev:LOG|BLOCK; 101 | root $TEST_NGINX_SERVROOT/html/; 102 | index index.html index.htm; 103 | error_page 405 = $uri; 104 | } 105 | --- raw_request eval 106 | "POST /index.html HTTP/1.1\r 107 | Host: 127.0.0.1\r 108 | Connection: Close\r 109 | User-Agent: Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10\r 110 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r 111 | Accept-Language: en-us,en;q=0.5\r 112 | Accept-Encoding: gzip, deflate\r 113 | Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r 114 | Referer: http://127.0.0.1/index.html\r 115 | Content-Type: multipart/form-data; boundary=---------------------------210801732320936925511223486236\r 116 | Content-Length: 3272\r 117 | \r 118 | -----------------------------210801732320936925511223486236\r 119 | Content-Disposition: form-data; name=\"textline\"\r 120 | \r 121 | valid text bad file\r 122 | -----------------------------210801732320936925511223486236\r 123 | Content-Disposition: form-data; name=\"datafile\"; filename=\"../bla_med.jpg.php\"\r 124 | Content-Type: application/x-httpd-php\r 125 | \r 126 | 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000\r 127 | -----------------------------210801732320936925511223486236--\r 128 | " 129 | --- error_code: 412 130 | 131 | --------------------------------------------------------------------------------