├── .gitignore ├── .travis.yml ├── README.markdown ├── README_zh.markdown ├── config ├── libinjection ├── Makefile ├── example1.c ├── fptool.c ├── html5_cli.c ├── libinjection.h ├── libinjection_html5.c ├── libinjection_html5.h ├── libinjection_sqli.c ├── libinjection_sqli.h ├── libinjection_sqli_data.h ├── libinjection_xss.c ├── libinjection_xss.h ├── reader.c ├── sqli_cli.c ├── test_speed_sqli.c ├── test_speed_xss.c └── testdriver.c ├── ngx_http_waf_module.c └── t ├── waf.t └── waf_log.t /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | os: linux 4 | 5 | compiler: 6 | # - clang 7 | - gcc 8 | 9 | addons: 10 | apt: 11 | packages: 12 | - cpanminus 13 | - lcov 14 | - libmagic-dev 15 | 16 | install: 17 | - gem install coveralls-lcov 18 | - git clone https://github.com/nginx/nginx.git ../nginx 19 | - git clone https://github.com/nginx/nginx-tests.git ../nginx-tests 20 | - rm -f ../nginx-tests/*.t 21 | - cp ./t/*.t ../nginx-tests/ 22 | 23 | before_script: 24 | - lcov --directory ../nginx --zerocounters 25 | 26 | script: 27 | - ls -ltra 28 | - cd ../nginx 29 | - ls -ltra 30 | - ./auto/configure --prefix=/tmp/nginx --add-dynamic-module=../ngx_http_waf_module --with-compat 31 | --with-cc-opt="-fprofile-arcs -ftest-coverage" --with-ld-opt="-lgcov" 32 | - make -j 33 | - make install 34 | - cd ../nginx-tests 35 | - ls -ltra 36 | - TEST_NGINX_BINARY=/tmp/nginx/sbin/nginx prove . 37 | - cd ../nginx 38 | - sudo lcov --compat-libtool --directory ./objs/addon/ngx_http_waf_module/ --capture 39 | --output-file ./coverage.info --base-directory . 40 | - ls -ltra 41 | 42 | after_success: 43 | - coveralls-lcov ./coverage.info 44 | - genhtml -s -o /tmp/waf_html coverage.info 45 | 46 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | [![travis-ci](https://travis-ci.org/vislee/ngx_http_waf_module.svg?branch=master)](https://travis-ci.org/vislee/ngx_http_waf_module) 5 | [![Coverage Status](https://coveralls.io/repos/github/vislee/ngx_http_waf_module/badge.svg?branch=master)](https://coveralls.io/github/vislee/ngx_http_waf_module?branch=master) 6 | 7 | The **ngx_http_waf_module** is an open-source high-performance simple-rule easy-extend web application firewall(WAF) module for Nginx. 8 | 9 | 10 | Table of Contents 11 | ================= 12 | * [Name](#name) 13 | * [Status](#status) 14 | * [Install](#install) 15 | * [Example Configuration](#example-configuration) 16 | * [TODO](#todo) 17 | * [Directives](#directives) 18 | * [security_rule](#security_rule) 19 | * [security_loc_rule](#security_loc_rule) 20 | * [security_waf](#security_waf) 21 | * [security_check](#security_check) 22 | * [security_log](#security_log) 23 | * [security_timeout](#security_timeout) 24 | * [New match strategy](#new-match-strategy) 25 | * [Author](#author) 26 | * [Copyright and License](#copyright-and-license) 27 | * [See Also](#see-also) 28 | 29 | 30 | Status 31 | ====== 32 | The ngx_http_waf_module is currently in active development. 33 | 34 | 35 | Install 36 | ======= 37 | 38 | ```sh 39 | ./configure --prefix=/usr/local/nginx --add-dynamic-module=github.com/vislee/ngx_http_waf_module --with-compat 40 | ``` 41 | 42 | [Back to TOC](#table-of-contents) 43 | 44 | Example Configuration 45 | ==================== 46 | 47 | ```nginx 48 | 49 | load_module ./modules/ngx_http_waf_module.so; 50 | events { 51 | ...... 52 | } 53 | 54 | http { 55 | ...... 56 | security_rule id:1001 "str:rx@^[a-z]{1,3}" "s:$BYPASS:1,$SQLI:2" z:V_HEADERS:bar|ARGS; 57 | security_rule id:1002 "str:!sw@/test.php" s:$XSS:3,$BYPASS:3 z:#URL; 58 | security_rule id:1003 "libinj:sql" "s:$SQLI:9" z:V_ARGS:foo; 59 | security_rule id:1004 "libinj:decode_url|xss" "s:$XSS:9" z:V_ARGS:foo "note:test rule by vislee"; 60 | security_rule id:1005 "str:eq@testbody" "s:$BYPASS:2" "z:BODY"; 61 | security_rule id:1006 "str:decode_base64|decode_url|ct@test file data" "s:$BYPASS:2" "z:V_BODY:input"; 62 | security_rule id:1007 "str:ct@eval" "s:$HANG:2" "z:#FILE"; 63 | security_rule id:1008 "str:ct@testphp" "s:$HANG:2" "z:X_FILE:^[a-z]{1,5}\.php$"; 64 | security_rule id:1009 "str:ct@testphp" "s:$BYPASS:2" "z:#RAW_BODY"; 65 | 66 | map $sec_result $ups { 67 | "block" block; 68 | default runtime; 69 | } 70 | 71 | server { 72 | location / { 73 | client_body_buffer_size 1m; 74 | 75 | security_waf on; 76 | security_log ./logs/waf.log; 77 | 78 | security_loc_rule wl:1003 z:V_ARGS:test; 79 | security_loc_rule id:90001 str:eq@vislee s:$BYPASS:4 z:V_HEADERS:name; 80 | 81 | security_check "$HANG>4" LOG; 82 | security_check "$BYPASS>8" $sec_result; 83 | security_check "$SQLI>8" DROP; 84 | security_check "$XSS>8" BLOCK; 85 | 86 | proxy_pass http://$ups; 87 | } 88 | } 89 | 90 | ``` 91 | 92 | [Back to TOC](#table-of-contents) 93 | 94 | TODO 95 | ========== 96 | 97 | + support content-type: json and xml. 98 | 99 | [Back to TOC](#table-of-contents) 100 | 101 | Directives 102 | ========== 103 | 104 | security_rule 105 | ------------- 106 | **syntax:** *security_rule rule* 107 | 108 | **default:** *no* 109 | 110 | **context:** *http* 111 | 112 | Set general rules. All the `location` contained in `http` is visible. 113 | 114 | The rule format: 115 | 116 | `security_rule id:number match-strategy "s:$TAG:score,$TAG2:score" "z:zones" "note:message";` 117 | 118 | + id: The ID of the rule. 119 | + match-strategy: The strategy of rule. 120 | + str:[decode_func1|decode_func2|][!]le@string 121 | + str:[decode_func1|decode_func2|][!]ge@string 122 | + str:[decode_func1|decode_func2|][!]ct@string 123 | + str:[decode_func1|decode_func2|][!]eq@string 124 | + str:[decode_func1|decode_func2|][!]sw@string 125 | + str:[decode_func1|decode_func2|][!]ew@string 126 | + str:[decode_func1|decode_func2|][!]rx@regex 127 | + libinj:[decode_func1|decode_func2|]sql 128 | + libinj:[decode_func1|decode_func2|]xss 129 | + hash:[!]md5@hashcode 130 | + hash:[!]crc32@hashcode 131 | + hash:[!]crc32_long@hashcode 132 | + libmagic:[!]mime_type@mime_type 133 | 134 | >>decode_func: decode_url or decode_base64 135 | 136 | >>!: not. eg: "!eq@test" - Is not equal to 'test'. 137 | 138 | + zones: The match zones of rule. 139 | + #URL 140 | + V_URL:string 141 | + X_URL:regex 142 | + [@ | #]ARGS 143 | + V_ARGS:string 144 | + X_ARGS:regex 145 | + [@ | #]HEADERS 146 | + V_HEADERS:string 147 | + X_HEADERS:regex 148 | + #RAW_BODY 149 | + [@ | #]BODY 150 | + V_BODY:string 151 | + X_BODY:regex 152 | + #FILE 153 | + X_FILE:regex 154 | 155 | For example: 156 | 157 | "str:eq@/index.php" "z:#URL" `curl 'http://x/index.php'` will be blocked 158 | 159 | "str:eq@bar" "z:V_ARGS:foo" `curl 'http://x/?foo=bar'` will be blocked 160 | 161 | "str:eq@bar" "z:V_HEADERS:foo" `curl -H'foo: bar' 'http://x/'` will be blocked 162 | 163 | 164 | A complete rule configuration. 165 | 166 | ```nginx 167 | security_rule id:100 "str:decode_base64|decode_url|!eq@foo bar" "s:$ATT:2,$SQLI:1" "z:V_ARGS:foo|#HEADERS" "note:test rule"; 168 | ``` 169 | 170 | 171 | [Back to TOC](#table-of-contents) 172 | 173 | security_loc_rule 174 | ----------------- 175 | **syntax:** *security_loc_rule rule* 176 | 177 | **default:** *no* 178 | 179 | **context:** *location* 180 | 181 | Set the location rules. 182 | 183 | Also, you can set the whitelist disable of the general rules of the specified IDs. 184 | 185 | The whitelist rule format: 186 | ```nginx 187 | security_loc_rule "wl:id1,id2" "z:zones" "note:test whitelist"; 188 | ``` 189 | 190 | 191 | [Back to TOC](#table-of-contents) 192 | 193 | security_waf 194 | ------------ 195 | **syntax:** *security_waf * 196 | 197 | **default:** *off* 198 | 199 | **context:** *location* 200 | 201 | Enables or disables this module. 202 | 203 | [Back to TOC](#table-of-contents) 204 | 205 | 206 | security_check 207 | -------------- 208 | **syntax:** *security_check $tag>threshold * 209 | 210 | **default:** *no* 211 | 212 | **context:** *location* 213 | 214 | Setting rules accumulating scoring thresholds and actions. 215 | 216 | The action include: 217 | 218 | + LOG: only logged. 219 | + BLOCK: refuse the request, return 403. 220 | + DROP: close the connection, Is equivalent to return 444. 221 | + ALLOW: skip the rest of the rules. 222 | + $variable: return string "block" else return nil. 223 | 224 | [Back to TOC](#table-of-contents) 225 | 226 | security_log 227 | ------------ 228 | **syntax:** *security_log [unflat]* 229 | 230 | **default:** *off* 231 | 232 | **context:** *location* 233 | 234 | A log that requests a hit rule. 235 | 236 | Logging to [syslog](http://nginx.org/en/docs/syslog.html) can be configured by specifying the “syslog:” prefix. 237 | 238 | [Back to TOC](#table-of-contents) 239 | 240 | security_timeout 241 | ---------------- 242 | **syntax:** *security_timeout time* 243 | 244 | **default:** *60s* 245 | 246 | **context:** *location* 247 | 248 | Defines a timeout for waf-rule filter. If filter is not finish, the filter return pass. 249 | 250 | [Back to TOC](#table-of-contents) 251 | 252 | New match strategy 253 | =========== 254 | 255 | If you want to expand `match-strategy`. Only need to implement two function: 256 | The function of parse directive and The matching-strategy callback function. 257 | 258 | And then the parse directive function registered into the array of `ngx_http_waf_rule_parser_item`. 259 | 260 | For example, the `libinj:xss` and `libinj:sql` expand. 261 | 262 | ```c 263 | // The parse directive function 264 | static ngx_int_t 265 | ngx_http_waf_parse_rule_libinj(ngx_conf_t *cf, ngx_str_t *str, 266 | ngx_http_waf_rule_parser_t *parser, ngx_http_waf_rule_opt_t *opt) 267 | { 268 | u_char *p, *e; 269 | ngx_int_t offset; 270 | 271 | static ngx_str_t sql = ngx_string("sql"); 272 | static ngx_str_t xss = ngx_string("xss"); 273 | 274 | if (str->len - parser->prefix.len < 3) { 275 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 276 | "invalid libinj in arguments \"%V\"", str); 277 | return NGX_ERROR; 278 | } 279 | 280 | p = str->data + parser->prefix.len; 281 | e = str->data + str->len; 282 | 283 | offset = ngx_http_waf_parse_rule_decode(cf, opt->p_rule->decode_handlers, 284 | p, e - 4); 285 | if (offset == NGX_ERROR) { 286 | return NGX_ERROR; 287 | } 288 | 289 | p += offset; 290 | 291 | if ((size_t)(e - p) == sql.len 292 | && ngx_strncmp(p, sql.data, sql.len) == 0) 293 | { 294 | opt->p_rule->str = sql; 295 | opt->p_rule->handler = ngx_http_waf_rule_str_sqli_handler; 296 | 297 | } else if ((size_t)(e - p) == xss.len 298 | && ngx_strncmp(p, xss.data, xss.len) == 0) 299 | { 300 | opt->p_rule->str = xss; 301 | opt->p_rule->handler = ngx_http_waf_rule_str_xss_handler; 302 | 303 | } else { 304 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 305 | "invalid libinj args in arguments \"%V\"", str); 306 | return NGX_ERROR; 307 | } 308 | 309 | return NGX_OK; 310 | } 311 | 312 | // matching-strategy callback function 313 | static ngx_int_t 314 | ngx_http_waf_rule_str_sqli_handler(ngx_http_waf_public_rule_t *pr, 315 | ngx_str_t *s) 316 | { 317 | ngx_int_t issqli; 318 | struct libinjection_sqli_state state; 319 | 320 | if (s == NULL || s->data == NULL || s->len == 0) { 321 | return NGX_ERROR; 322 | } 323 | 324 | libinjection_sqli_init(&state, (const char *)s->data, s->len, FLAG_NONE); 325 | issqli = libinjection_is_sqli(&state); 326 | if (!issqli) { 327 | return NGX_ERROR; 328 | } 329 | 330 | return NGX_OK; 331 | } 332 | 333 | // registered parse directive function 334 | static ngx_http_waf_rule_parser_t ngx_http_waf_rule_parser_item[] = { 335 | ...... 336 | {ngx_string("libinj:"), ngx_http_waf_parse_rule_libinj}, 337 | ...... 338 | } 339 | ``` 340 | 341 | [Back to TOC](#table-of-contents) 342 | 343 | 344 | Author 345 | ====== 346 | 347 | wenqiang li(vislee) 348 | 349 | [Back to TOC](#table-of-contents) 350 | 351 | Copyright and License 352 | ===================== 353 | 354 | This module is licensed under the [GPL](http://www.gnu.org/licenses/licenses.en.html) license. 355 | 356 | Copyright (C) 2018-2019, by vislee. 357 | 358 | All rights reserved. 359 | 360 | 361 | [Back to TOC](#table-of-contents) 362 | 363 | 364 | See Also 365 | ======== 366 | 367 | + nginx: ngx_http_waf_module based on nginx. 368 | + naxsi: ngx_http_waf_module has learned a lot of from it. 369 | + [libinjection](https://github.com/client9/libinjection): ngx_http_waf_module refer to this library. 370 | + [Hyperscan](https://github.com/intel/hyperscan): replace the PCRE. 371 | + libmagic: ngx_http_waf_module refer to this library. 372 | 373 | [Back to TOC](#table-of-contents) 374 | 375 | -------------------------------------------------------------------------------- /README_zh.markdown: -------------------------------------------------------------------------------- 1 | Name 2 | ===== 3 | 4 | [![travis-ci](https://travis-ci.org/vislee/ngx_http_waf_module.svg?branch=master)](https://travis-ci.org/vislee/ngx_http_waf_module) 5 | [![Coverage Status](https://coveralls.io/repos/github/vislee/ngx_http_waf_module/badge.svg?branch=master)](https://coveralls.io/github/vislee/ngx_http_waf_module?branch=master) 6 | 7 | **ngx_http_waf_module** 是一个开源的、高效的、规则简单、策略易扩展的nginx WAF模块。 8 | 9 | 10 | Table of Contents 11 | ================= 12 | * [Name](#name) 13 | * [Status](#status) 14 | * [Install](#install) 15 | * [Example Configuration](#example-configuration) 16 | * [Directives](#directives) 17 | * [security_rule](#security_rule) 18 | * [security_loc_rule](#security_loc_rule) 19 | * [security_waf](#security_waf) 20 | * [security_check](#security_check) 21 | * [security_log](#security_log) 22 | * [security_timeout](#security_timeout) 23 | * [New match strategy](#new-match-strategy) 24 | * [Author](#author) 25 | * [Copyright and License](#copyright-and-license) 26 | * [See Also](#see-also) 27 | 28 | Status 29 | ====== 30 | ngx_http_waf_module 还处在早期的开发阶段。 31 | 32 | 33 | Install 34 | ======= 35 | 36 | ```sh 37 | ./configure --prefix=/usr/local/nginx --add-dynamic-module=github.com/vislee/ngx_http_waf_module --with-compat 38 | ``` 39 | 40 | [Back to TOC](#table-of-contents) 41 | 42 | 43 | Example Configuration 44 | ==================== 45 | 46 | ```nginx 47 | 48 | load_module ./modules/ngx_http_waf_module.so; 49 | events { 50 | ...... 51 | } 52 | 53 | http { 54 | ...... 55 | security_rule id:1001 "str:rx@^[a-z]{1,3}" "s:$BYPASS:1,$SQLI:2" z:V_HEADERS:bar|ARGS; 56 | security_rule id:1002 "str:!sw@/test.php" s:$XSS:3,$BYPASS:3 z:#URL; 57 | security_rule id:1003 "libinj:sql" "s:$SQLI:9" z:V_ARGS:foo; 58 | security_rule id:1004 "libinj:decode_url|xss" "s:$XSS:9" z:V_ARGS:foo "note:test rule by vislee"; 59 | security_rule id:1005 "str:eq@testbody" "s:$BYPASS:2" "z:BODY"; 60 | security_rule id:1006 "str:decode_base64|decode_url|ct@test file data" "s:$BYPASS:2" "z:V_BODY:input"; 61 | security_rule id:1007 "str:ct@eval" "s:$HANG:2" "z:#FILE"; 62 | security_rule id:1008 "str:ct@testphp" "s:$HANG:2" "z:X_FILE:^[a-z]{1,5}\.php$"; 63 | security_rule id:1009 "str:ct@testphp" "s:$BYPASS:2" "z:#RAW_BODY"; 64 | 65 | map $sec_result $ups { 66 | "block" block; 67 | default runtime; 68 | } 69 | 70 | server { 71 | location / { 72 | client_body_buffer_size 1m; 73 | 74 | security_waf on; 75 | security_log ./logs/waf.log; 76 | 77 | security_loc_rule wl:1003 z:V_ARGS:test; 78 | security_loc_rule id:90001 str:eq@vislee s:$BYPASS:4 z:V_HEADERS:name; 79 | 80 | security_check "$HANG>4" LOG; 81 | security_check "$BYPASS>8" $sec_result; 82 | security_check "$SQLI>8" DROP; 83 | security_check "$XSS>8" BLOCK; 84 | 85 | proxy_pass http://$ups; 86 | } 87 | } 88 | 89 | ``` 90 | 91 | [Back to TOC](#table-of-contents) 92 | 93 | 94 | Directives 95 | ========== 96 | 97 | security_rule 98 | ------------- 99 | **语法** *security_rule rule* 100 | 101 | **默认:** *no* 102 | 103 | **环境:** *http* 104 | 105 | 配置一条通用的规则,对该`http`下的所有`location`均可见。 106 | 规则的格式: `security_rule id:number strategy "s:$TAG:score,$TAG2:score" "z:zones" "note:message";` 107 | 108 | + id: number取值为数字,规则的唯一编码,唯一代表一条规则。在`白名单`和`日志`记录中使用。 109 | + strategy: 规则策略,有以下几种策略。 110 | 以`str:`开始的是字符串匹配策略。 111 | 以`libinj:`开始的是调用了第三方 (libinjection)[https://github.com/client9/libinjection] 库的策略。 112 | 以`hash:`开始的是计算hash值的策略。 113 | 以`libmagic:`开始的是调用了libmagic库通过检测文件魔数获取文件类型。 114 | 115 | + "str:[decode_func1|decode_func2][!]le@string": [经过decode函数处理后的]字符串[不]小于等于string(字典顺序) 116 | + "str:[decode_func1|decode_func2][!]ge@string": [经过decode函数处理后的]字符串[不]大于等于string(字典顺序) 117 | + "str:[decode_func1|decode_func2][!]ct@string": [经过decode函数处理后的]字符串[不]包含string 118 | + "str:[decode_func1|decode_func2][!]eq@string": [经过decode函数处理后的]字符串[不]等于string 119 | + "str:[decode_func1|decode_func2][!]sw@string": [经过decode函数处理后的]字符串[不是]以string开始 120 | + "str:[decode_func1|decode_func2][!]ew@string": [经过decode函数处理后的]字符串[不是]以string结束 121 | + "str:[decode_func1|decode_func2][!]rx@regex": [经过decode函数处理后的]字符串[不]符合正则表达式 122 | + "libinj:[decode_func1|decode_func2][!]sql": [经过decode函数处理后的]字符串[不]存在sql注入 123 | + "libinj:[decode_func1|decode_func2][!]xss": [经过decode函数处理后的]字符串[不]存在xss攻击 124 | + "hash:[!]md5@hashcode": 字符串的md5值[不]等于hashcode 125 | + "hash:[!]crc32@hashcode": 字符串的crc32[不]等于hashcode 126 | + "hash:[!]crc32_long@hashcode": 字符串的crc32_long[不]等于hashcode 127 | + "libmagic:[!]mime_type@type": 内容的魔数识别[不]等于type 128 | 129 | >>**decode_func**: 支持`decode_url`和`decode_base64`函数。通过管道符号(`|`)支持多次decode操作。 130 | 131 | >>**!**: 取反。 132 | 133 | + "s:$TAG:score,$TAG2:score": 规则标签打分,通过该配置多条规则可以加权作用。如果不配置,命中该规则请求被BLOCK。 134 | 135 | + zones: 规则策略检测的区域,有以下多种取值。其中`@`代表仅检测对应的key,`#`代表仅检测对应的value。 136 | + "z:#URL": 检测请求的URL。 137 | + "z:V_URL:string": 检测是string的URL。 138 | + "z:X_URL:regex": 检测满足regex正则表达式的URL。 139 | + "z:[@ | #]ARGS": 检测请求的URL参数,@ARGS表示仅检测参数的key,#ARGS表示仅检测参数的value。 140 | + "z:V_ARGS:string": 检测请求参数是string参的value。 141 | + "z:X_ARGS:regex": 检测请求参数满足regex正则表达式的value。 142 | + "z:[@ | #]HEADERS": 检测请求的头。 143 | + "z:V_HEADERS:string": 检测请求头是string的value。 144 | + "z:X_HEADERS:regex": 检测请求头符合regex正则表达式的value。 145 | + "z:#RAW_BODY": 检测请求的原始(未解码)body。 146 | + "z:[@ | #]BODY": 检测请求解析后的body,支持urldecode和multipart的解析。 147 | + "z:V_BODY:string": 检测解析后的body的string的value。 148 | + "z:X_BODY:regex": 检测body解析后的复合正则表达式的value。 149 | + "z:#FILE": 检测表单上传的文件内容。 150 | + "z:X_FILE:regex": 检测表单上传文件名满足正则表达式的内容。 151 | 152 | 例如: 153 | ```nginx 154 | security_rule id:100 "str:decode_base64|decode_url|!eq@foo bar" "s:$ATT:2,$SQLI:1" "z:V_ARGS:foo|#HEADERS" "note:test rule"; 155 | ``` 156 | 157 | 一个请求的 158 | URL参数foo的value如果先base64解码然后再URL解码的结果不等于"foo bar",则会对`$ATT` 这个变量累加2分,对`$SQLI`这个变量累加1分。 159 | 任一请求头的value如果先base64解码然后再URL解码的结果不等于"foo bar",则会对`$ATT` 这个变量累加2分,对`$SQLI`这个变量累加1分。 160 | 161 | [Back to TOC](#table-of-contents) 162 | 163 | security_loc_rule 164 | ----------------- 165 | **语法** *security_loc_rule rule* 166 | 167 | **默认:** *no* 168 | 169 | **环境:** *location* 170 | 171 | 配置仅对当前`location`可见的规则。 172 | 173 | 也可以配置屏蔽全局规则的白名单。 174 | 白名单的格式:`security_loc_rule "wl:id1,id2" "z:zones" "note:test whitelist";` 175 | 176 | [Back to TOC](#table-of-contents) 177 | 178 | security_waf 179 | ------------ 180 | **语法** *security_waf * 181 | 182 | **默认:** *off* 183 | 184 | **环境:** *location* 185 | 186 | 在对应的`location`开启规则检测。 187 | 188 | [Back to TOC](#table-of-contents) 189 | 190 | 191 | security_check 192 | -------------- 193 | **语法** *security_check $tag>threshold * 194 | 195 | **默认:** *no* 196 | 197 | **环境:** *location* 198 | 199 | 设置规则打分的阈值和对应的动作。 200 | 201 | 支持的动作有: 202 | LOG: 仅记录一条日志。 203 | BLOCK: 拒绝请求,返回403. 204 | DROP: 关闭连接。 205 | ALLOW: 跳过剩余规则。 206 | $variable: 满足条件该变量的值为"block",和map指令配合使用。 207 | 208 | 例如: 209 | `security_check "$ATT>5" BLOCK;` 当ATT这个变量的分数累加超过5时,请求被BLOCK。 210 | 211 | [Back to TOC](#table-of-contents) 212 | 213 | security_log 214 | ------------ 215 | **语法** *security_log [unflat]* 216 | 217 | **默认:** *off* 218 | 219 | **环境:** *location* 220 | 221 | 以json格式记录命中的规则和请求。 222 | 223 | 支持以`syslog:`开始的配置,把日志通过[syslog](http://nginx.org/en/docs/syslog.html)的形式记录。 224 | 225 | [Back to TOC](#table-of-contents) 226 | 227 | security_timeout 228 | ---------------- 229 | **语法:** *security_timeout time* 230 | 231 | **默认:** *60s* 232 | 233 | **环境:** *location* 234 | 235 | 单个请求规则过滤时长。 236 | 237 | [Back to TOC](#table-of-contents) 238 | 239 | New match strategy 240 | ================== 241 | 242 | 如果现有策略不满足需求,新的策略扩展也是非常容易的。仅需要开发两个函数,注册到模块中就可以了。例如调用了libinjection这个库扩展sql注入检测。 243 | 244 | ```c 245 | // The parse directive function 246 | static ngx_int_t 247 | ngx_http_waf_parse_rule_libinj(ngx_conf_t *cf, ngx_str_t *str, 248 | ngx_http_waf_rule_parser_t *parser, ngx_http_waf_rule_opt_t *opt) 249 | { 250 | u_char *p, *e; 251 | ngx_int_t offset; 252 | 253 | static ngx_str_t sql = ngx_string("sql"); 254 | static ngx_str_t xss = ngx_string("xss"); 255 | 256 | if (str->len - parser->prefix.len < 3) { 257 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 258 | "invalid libinj in arguments \"%V\"", str); 259 | return NGX_ERROR; 260 | } 261 | 262 | p = str->data + parser->prefix.len; 263 | e = str->data + str->len; 264 | 265 | offset = ngx_http_waf_parse_rule_decode(cf, opt->p_rule->decode_handlers, 266 | p, e - 4); 267 | if (offset == NGX_ERROR) { 268 | return NGX_ERROR; 269 | } 270 | 271 | p += offset; 272 | 273 | if ((size_t)(e - p) == sql.len 274 | && ngx_strncmp(p, sql.data, sql.len) == 0) 275 | { 276 | opt->p_rule->str = sql; 277 | opt->p_rule->handler = ngx_http_waf_rule_str_sqli_handler; 278 | 279 | } else if ((size_t)(e - p) == xss.len 280 | && ngx_strncmp(p, xss.data, xss.len) == 0) 281 | { 282 | opt->p_rule->str = xss; 283 | opt->p_rule->handler = ngx_http_waf_rule_str_xss_handler; 284 | 285 | } else { 286 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 287 | "invalid libinj args in arguments \"%V\"", str); 288 | return NGX_ERROR; 289 | } 290 | 291 | return NGX_OK; 292 | } 293 | 294 | // matching-strategy callback function 295 | static ngx_int_t 296 | ngx_http_waf_rule_str_sqli_handler(ngx_http_waf_public_rule_t *pr, 297 | ngx_str_t *s) 298 | { 299 | ngx_int_t issqli; 300 | struct libinjection_sqli_state state; 301 | 302 | if (s == NULL || s->data == NULL || s->len == 0) { 303 | return NGX_ERROR; 304 | } 305 | 306 | libinjection_sqli_init(&state, (const char *)s->data, s->len, FLAG_NONE); 307 | issqli = libinjection_is_sqli(&state); 308 | if (!issqli) { 309 | return NGX_ERROR; 310 | } 311 | 312 | return NGX_OK; 313 | } 314 | 315 | // registered parse directive function 316 | static ngx_http_waf_rule_parser_t ngx_http_waf_rule_parser_item[] = { 317 | ...... 318 | {ngx_string("libinj:"), ngx_http_waf_parse_rule_libinj}, 319 | ...... 320 | } 321 | ``` 322 | 323 | [Back to TOC](#table-of-contents) 324 | 325 | 326 | Author 327 | ====== 328 | 329 | wenqiang li(vislee) 330 | 331 | [Back to TOC](#table-of-contents) 332 | 333 | Copyright and License 334 | ===================== 335 | 336 | This module is licensed under the [GPL](http://www.gnu.org/licenses/licenses.zh-cn.html 337 | ) license. 338 | 339 | Copyright (C) 2018-2019, by vislee. 340 | 341 | All rights reserved. 342 | 343 | [Back to TOC](#table-of-contents) 344 | 345 | 346 | See Also 347 | ======== 348 | 349 | + nginx: ngx_http_waf_module 是nginx的一个模块. 350 | + naxsi: ngx_http_waf_module 从naxsi吸取了很多灵感. 351 | + [libinjection](https://github.com/client9/libinjection): ngx_http_waf_module sqli和xss调用了该库. 352 | + [Hyperscan](https://github.com/intel/hyperscan): 若安装了该库,则会用该库替换掉libPCRE这个默认的正则库. 353 | + libmagic: ngx_http_waf_module 引用该库用来识别文件类型. 354 | 355 | [Back to TOC](#table-of-contents) -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_waf_libs="" 2 | 3 | if [ "$NGX_PLATFORM" != win32 ]; then 4 | #echo "libmagic check ..." 5 | ngx_feature="libmagic library" 6 | ngx_feature_name="NGX_WAF_MAGIC" 7 | ngx_feature_run=no 8 | ngx_feature_incs="#include " 9 | ngx_feature_path= 10 | ngx_feature_libs="-lmagic" 11 | ngx_feature_test="magic_t cookie = magic_open(MAGIC_MIME_TYPE); 12 | if (cookie == NULL) return 1; 13 | char *x = \"GIF89axxxx\"; 14 | magic_buffer(cookie, (const void*)x, 10); 15 | magic_close(cookie);" 16 | . auto/feature 17 | 18 | if [ $ngx_found = yes ]; then 19 | ngx_waf_libs="$ngx_waf_libs $ngx_feature_libs" 20 | ngx_found=no 21 | fi 22 | 23 | #echo "Hyperscan check ..." 24 | ngx_feature="Hyperscan library" 25 | ngx_feature_name="NGX_WAF_HS" 26 | ngx_feature_run=yes 27 | ngx_feature_incs="#include " 28 | ngx_feature_path= 29 | ngx_feature_libs="-lhs" 30 | ngx_feature_test="if (hs_valid_platform() == HS_ARCH_ERROR) return 1;" 31 | . auto/feature 32 | 33 | if [ $ngx_found = yes ]; then 34 | ngx_waf_libs="$ngx_waf_libs $ngx_feature_libs" 35 | ngx_found=no 36 | fi 37 | fi 38 | 39 | 40 | ngx_addon_name=ngx_http_waf_module 41 | ngx_feature_libs="-lm" 42 | 43 | _HTTP_WAF_SRCS="\ 44 | $ngx_addon_dir/ngx_http_waf_module.c \ 45 | $ngx_addon_dir/libinjection/libinjection_sqli.c \ 46 | $ngx_addon_dir/libinjection/libinjection_xss.c \ 47 | $ngx_addon_dir/libinjection/libinjection_html5.c \ 48 | " 49 | 50 | if test -n "$ngx_module_link"; then 51 | ngx_module_type=HTTP 52 | ngx_module_name=$ngx_addon_name 53 | ngx_module_srcs="$_HTTP_WAF_SRCS" 54 | ngx_module_libs="$ngx_feature_libs $ngx_waf_libs" 55 | . auto/module 56 | else 57 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $_HTTP_WAF_SRCS" 58 | CORE_LIBS="$CORE_LIBS $ngx_feature_libs $ngx_waf_libs" 59 | CORE_INCS="$CORE_INCS $ngx_module_incs" 60 | HTTP_MODULES="$HTTP_MODULES $ngx_addon_name" 61 | fi 62 | -------------------------------------------------------------------------------- /libinjection/Makefile: -------------------------------------------------------------------------------- 1 | 2 | AR=ar -r 3 | # MAC 4 | # AR=libtool -static 5 | 6 | CC=cc 7 | LD=ld 8 | CFLAGS=-Wall -Wextra -Werror -pedantic -ansi -g -O3 -fPIC 9 | 10 | INSTALL=install 11 | PREFIX=/usr/local 12 | INCLUDE_DIR=${PREFIX}/include 13 | LIB_DIR=${PREFIX}/lib 14 | 15 | LIBNAME=injection 16 | STATICLIB_SUFFIX=a 17 | STATICLIB=lib${LIBNAME}.${STATICLIB_SUFFIX} 18 | 19 | SHAREDLIB_SUFFIX=so 20 | SHAREDLIB=lib${LIBNAME}.${SHAREDLIB_SUFFIX} 21 | 22 | HEADERS=libinjection.h libinjection_sqli.h libinjection_sqli_data.h libinjection_html5.h libinjection_xss.h 23 | 24 | SOURCES=libinjection_sqli.c \ 25 | libinjection_html5.c \ 26 | libinjection_xss.c 27 | 28 | OBJECTS=libinjection_sqli.o \ 29 | libinjection_html5.o \ 30 | libinjection_xss.o 31 | 32 | SAMPLES=html5 sqli fptool 33 | TESTDRIVERS=reader testdriver testspeedxss testspeedsqli 34 | 35 | all: ${SHAREDLIB} ${STATICLIB} 36 | #all: ${STATICLIB} 37 | 38 | samples: ${SAMPLES} 39 | .PHONY: samples 40 | 41 | fingerprints.txt: make_parens.py 42 | ./make_parens.py < fingerprints.txt > fingerprints2.txt 43 | mv fingerprints2.txt fingerprints.txt 44 | 45 | fingerprints: 46 | ./make_parens.py < fingerprints.txt > fingerprints2.txt 47 | mv fingerprints2.txt fingerprints.txt 48 | .PHONY: fingerprints 49 | 50 | sqlparse_data.json: sqlparse_map.py fingerprints 51 | ./sqlparse_map.py > sqlparse_data.json 52 | 53 | libinjection_sqli_data.h: sqlparse2c.py sqlparse_data.json 54 | ./sqlparse2c.py < sqlparse_data.json > libinjection_sqli_data.h 55 | 56 | libinjection_sqli.o: libinjection.h libinjection_sqli.c libinjection_sqli.h libinjection_sqli_data.h 57 | ${CC} ${CFLAGS} -c -o libinjection_sqli.o libinjection_sqli.c 58 | 59 | libinjection_xss.o: libinjection.h libinjection_xss.c libinjection_xss.h libinjection_html5.h 60 | ${CC} ${CFLAGS} -c -o libinjection_xss.o libinjection_xss.c 61 | 62 | libinjection_html5.o: libinjection.h libinjection_html5.c libinjection_html5.h 63 | ${CC} ${CFLAGS} -c -o libinjection_html5.o libinjection_html5.c 64 | 65 | ${SHAREDLIB}: ${OBJECTS} 66 | $(CC) $+ -shared -lc -o $@ 67 | 68 | ${STATICLIB}: ${OBJECTS} 69 | rm -f ${STATICLIB} 70 | ${AR} ${STATICLIB} ${OBJECTS} 71 | 72 | check: ${TESTDRIVERS} 73 | @./test-driver.sh test-unit.sh 74 | @./test-driver.sh test-samples-sqli-negative.sh 75 | @./test-driver.sh test-samples-sqli-positive.sh 76 | @./test-driver.sh test-samples-xss-positive.sh 77 | 78 | # remove speed checks for now 79 | # @./test-driver.sh test-speed-xss.sh 80 | # @./test-driver.sh test-speed-sqli.sh 81 | 82 | test: check 83 | .PHONY: check test 84 | 85 | reader: reader.c ${STATICLIB} 86 | ${CC} ${CFLAGS} -o reader reader.c -L. ${STATICLIB} 87 | 88 | sqli: sqli_cli.c ${STATICLIB} 89 | rm -f sqli 90 | ${CC} ${CFLAGS} -o sqli sqli_cli.c -L. ${STATICLIB} 91 | 92 | html5: html5_cli.c ${STATICLIB} 93 | ${CC} ${CFLAGS} -o html5 html5_cli.c -L. ${STATICLIB} 94 | 95 | testdriver: testdriver.c ${STATICLIB} 96 | ${CC} ${CFLAGS} -o testdriver testdriver.c -L. ${STATICLIB} 97 | 98 | testspeedsqli: test_speed_sqli.c ${STATICLIB} 99 | ${CC} ${CFLAGS} -o testspeedsqli test_speed_sqli.c -L. ${STATICLIB} 100 | 101 | testspeedxss: test_speed_xss.c ${STATICLIB} 102 | ${CC} ${CFLAGS} -o testspeedxss test_speed_xss.c -L. ${STATICLIB} 103 | 104 | fptool: fptool.c ${STATICLIB} 105 | ${CC} ${CFLAGS} -o fptool fptool.c -L. ${STATICLIB} 106 | 107 | install: ${STATICLIB} 108 | ${INSTALL} -d ${INCLUDE_DIR} 109 | ${INSTALL} -c ${HEADERS} ${INCLUDE_DIR} 110 | ${INSTALL} -d ${LIB_DIR} 111 | ${INSTALL} -c ${STATICLIB} ${LIB_DIR} 112 | 113 | .PHONY: install 114 | 115 | clean: 116 | @rm -f *.so *.a *.o *.dylib *.lo *.gch 117 | @rm -f *~ 118 | @rm -f *.log 119 | @rm -f ${SAMPLES} ${TESTDRIVERS} 120 | @rm -f *.gcno 121 | @rm -rf *.dSYM 122 | 123 | 124 | #---- CLANG STATIC ANALYZER 125 | # 126 | # 127 | # 128 | # We are not using scan-analyze since it depends on perl 129 | # Using clang directly produces text output which is fine for 130 | # automated testing. Oddly -Werror doesn't work here, and 131 | # we have to test if any output occurred or not. This done by 132 | # piping output to a file and then checking what it contains. 133 | # 134 | # Open to better ideas here. 135 | # 136 | 137 | # ubuntu adds "-3.7" suffix 138 | CLANG_VERSION= 139 | 140 | analyze: 141 | rm -f /tmp/libinjection-analyze.txt 142 | clang${CLANG_VERSION} \ 143 | -I. --analyze \ 144 | -Xanalyzer -analyzer-checker=alpha.core.BoolAssignment \ 145 | -Xanalyzer -analyzer-checker=alpha.core.CastSize \ 146 | -Xanalyzer -analyzer-checker=alpha.core.FixedAddr \ 147 | -Xanalyzer -analyzer-checker=alpha.core.IdenticalExpr \ 148 | -Xanalyzer -analyzer-checker=alpha.core.PointerArithm \ 149 | -Xanalyzer -analyzer-checker=alpha.core.PointerSub \ 150 | -Xanalyzer -analyzer-checker=alpha.core.SizeofPtr \ 151 | -Xanalyzer -analyzer-checker=alpha.core.TestAfterDivZero \ 152 | -Xanalyzer -analyzer-checker=alpha.security.ArrayBound \ 153 | -Xanalyzer -analyzer-checker=alpha.security.MallocOverflow \ 154 | -Xanalyzer -analyzer-checker=alpha.security.ReturnPtrRange \ 155 | -Xanalyzer -analyzer-checker=alpha.unix.cstring.BufferOverlap \ 156 | -Xanalyzer -analyzer-checker=alpha.unix.cstring.OutOfBounds \ 157 | libinjection*.c 2>&1 | tee /tmp/libinjection-analyze.txt 158 | test ! -s /tmp/libinjection-analyze.txt 159 | 160 | .PHONY: analyze 161 | -------------------------------------------------------------------------------- /libinjection/example1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "libinjection.h" 4 | 5 | int main(int argc, const char* argv[]) 6 | { 7 | char fingerprint[8]; 8 | const char* input; 9 | size_t slen; 10 | int issqli; 11 | 12 | if (argc < 2) { 13 | fprintf(stderr, "Usage: %s inputstring\n", argv[0]); 14 | return -1; 15 | } 16 | 17 | input = argv[1]; 18 | slen = strlen(input); 19 | 20 | 21 | issqli = libinjection_sqli(input, slen, fingerprint); 22 | if (issqli) { 23 | printf("sqli with fingerprint of '%s'\n", fingerprint); 24 | } else { 25 | printf("not sqli\n"); 26 | } 27 | 28 | 29 | return issqli; 30 | } 31 | -------------------------------------------------------------------------------- /libinjection/fptool.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012, 2013 Nick Galbreath 3 | * nickg@client9.com 4 | * BSD License -- see COPYING.txt for details 5 | * 6 | * This is for testing against files in ../data/ *.txt 7 | * Reads from stdin or a list of files, and emits if a line 8 | * is a SQLi attack or not, and does basic statistics 9 | * 10 | */ 11 | #include 12 | #include 13 | #include 14 | 15 | 16 | #include "libinjection.h" 17 | #include "libinjection_sqli.h" 18 | 19 | int main(int argc, const char* argv[]) 20 | { 21 | size_t slen; 22 | int ok; 23 | int single = 0; 24 | int offset = 1; 25 | 26 | sfilter sf; 27 | if (argc < 2) { 28 | fprintf(stderr, "need more args\n"); 29 | return 1; 30 | } 31 | while (1) { 32 | if (strcmp(argv[offset], "-0") == 0) { 33 | single = 1; 34 | offset += 1; 35 | } else { 36 | break; 37 | } 38 | } 39 | 40 | slen = strlen(argv[offset]); 41 | 42 | if (slen == 0) { 43 | return 1; 44 | } 45 | 46 | /* 47 | * "plain" context.. test string "as-is" 48 | */ 49 | libinjection_sqli_init(&sf, argv[offset], slen, 0); 50 | 51 | if (single) { 52 | libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_NONE | FLAG_SQL_ANSI); 53 | libinjection_sqli_check_fingerprint(&sf); 54 | fprintf(stdout, "%s\n", sf.fingerprint); 55 | return 0; 56 | } 57 | 58 | libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_NONE | FLAG_SQL_ANSI); 59 | ok = libinjection_sqli_check_fingerprint(&sf); 60 | fprintf(stdout, "plain-asni\t%s\t%s\n", sf.fingerprint, ok ? "true": "false"); 61 | 62 | libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_NONE | FLAG_SQL_MYSQL); 63 | ok = libinjection_sqli_check_fingerprint(&sf); 64 | fprintf(stdout, "plain-mysql\t%s\t%s\n", sf.fingerprint, ok ? "true": "false"); 65 | 66 | libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_SINGLE | FLAG_SQL_ANSI); 67 | ok = libinjection_sqli_check_fingerprint(&sf); 68 | fprintf(stdout, "single-ansi\t%s\t%s\n", sf.fingerprint, ok ? "true": "false"); 69 | 70 | libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_SINGLE | FLAG_SQL_MYSQL); 71 | ok = libinjection_sqli_check_fingerprint(&sf); 72 | fprintf(stdout, "single-mysql\t%s\t%s\n", sf.fingerprint, ok ? "true": "false"); 73 | 74 | libinjection_sqli_fingerprint(&sf, FLAG_QUOTE_DOUBLE | FLAG_SQL_MYSQL); 75 | ok = libinjection_sqli_check_fingerprint(&sf); 76 | fprintf(stdout, "double-mysql\t%s\t%s\n", sf.fingerprint, ok ? "true": "false"); 77 | 78 | return 0; 79 | } 80 | -------------------------------------------------------------------------------- /libinjection/html5_cli.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012, 2013, 2014 Nick Galbreath 3 | * nickg@client9.com 4 | * BSD License -- see COPYING.txt for details 5 | * 6 | * This is for testing against files in ../data/ *.txt 7 | * Reads from stdin or a list of files, and emits if a line 8 | * is a SQLi attack or not, and does basic statistics 9 | * 10 | */ 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "libinjection_html5.h" 17 | #include "libinjection_xss.h" 18 | #include "libinjection.h" 19 | 20 | int urlcharmap(char ch); 21 | size_t modp_url_decode(char* dest, const char* s, size_t len); 22 | const char* h5_type_to_string(enum html5_type x); 23 | void print_html5_token(h5_state_t* hs); 24 | 25 | int urlcharmap(char ch) { 26 | switch (ch) { 27 | case '0': return 0; 28 | case '1': return 1; 29 | case '2': return 2; 30 | case '3': return 3; 31 | case '4': return 4; 32 | case '5': return 5; 33 | case '6': return 6; 34 | case '7': return 7; 35 | case '8': return 8; 36 | case '9': return 9; 37 | case 'a': case 'A': return 10; 38 | case 'b': case 'B': return 11; 39 | case 'c': case 'C': return 12; 40 | case 'd': case 'D': return 13; 41 | case 'e': case 'E': return 14; 42 | case 'f': case 'F': return 15; 43 | default: 44 | return 256; 45 | } 46 | } 47 | 48 | size_t modp_url_decode(char* dest, const char* s, size_t len) 49 | { 50 | const char* deststart = dest; 51 | 52 | size_t i = 0; 53 | int d = 0; 54 | while (i < len) { 55 | switch (s[i]) { 56 | case '+': 57 | *dest++ = ' '; 58 | i += 1; 59 | break; 60 | case '%': 61 | if (i+2 < len) { 62 | d = (urlcharmap(s[i+1]) << 4) | urlcharmap(s[i+2]); 63 | if ( d < 256) { 64 | *dest = (char) d; 65 | dest++; 66 | i += 3; /* loop will increment one time */ 67 | } else { 68 | *dest++ = '%'; 69 | i += 1; 70 | } 71 | } else { 72 | *dest++ = '%'; 73 | i += 1; 74 | } 75 | break; 76 | default: 77 | *dest++ = s[i]; 78 | i += 1; 79 | } 80 | } 81 | *dest = '\0'; 82 | return (size_t)(dest - deststart); /* compute "strlen" of dest */ 83 | } 84 | 85 | const char* h5_type_to_string(enum html5_type x) 86 | { 87 | switch (x) { 88 | case DATA_TEXT: return "DATA_TEXT"; 89 | case TAG_NAME_OPEN: return "TAG_NAME_OPEN"; 90 | case TAG_NAME_CLOSE: return "TAG_NAME_CLOSE"; 91 | case TAG_NAME_SELFCLOSE: return "TAG_NAME_SELFCLOSE"; 92 | case TAG_DATA: return "TAG_DATA"; 93 | case TAG_CLOSE: return "TAG_CLOSE"; 94 | case ATTR_NAME: return "ATTR_NAME"; 95 | case ATTR_VALUE: return "ATTR_VALUE"; 96 | case TAG_COMMENT: return "TAG_COMMENT"; 97 | case DOCTYPE: return "DOCTYPE"; 98 | default: 99 | assert(0); 100 | } 101 | } 102 | 103 | void print_html5_token(h5_state_t* hs) 104 | { 105 | char* tmp = (char*) malloc(hs->token_len + 1); 106 | memcpy(tmp, hs->token_start, hs->token_len); 107 | /* TODO.. encode to be printable */ 108 | tmp[hs->token_len] = '\0'; 109 | 110 | printf("%s,%d,%s\n", 111 | h5_type_to_string(hs->token_type), 112 | (int) hs->token_len, 113 | tmp); 114 | 115 | free(tmp); 116 | } 117 | 118 | int main(int argc, const char* argv[]) 119 | { 120 | size_t slen; 121 | h5_state_t hs; 122 | char* copy; 123 | int offset = 1; 124 | int flag = 0; 125 | int urldecode = 0; 126 | 127 | if (argc < 2) { 128 | fprintf(stderr, "need more args\n"); 129 | return 1; 130 | } 131 | 132 | while (offset < argc) { 133 | if (strcmp(argv[offset], "-u") == 0) { 134 | offset += 1; 135 | urldecode = 1; 136 | 137 | } else if (strcmp(argv[offset], "-f") == 0) { 138 | offset += 1; 139 | flag = atoi(argv[offset]); 140 | offset += 1; 141 | } else { 142 | break; 143 | } 144 | } 145 | 146 | /* ATTENTION: argv is a C-string, null terminated. We copy this 147 | * to it's own location, WITHOUT null byte. This way, valgrind 148 | * can see if we run past the buffer. 149 | */ 150 | 151 | slen = strlen(argv[offset]); 152 | copy = (char* ) malloc(slen); 153 | memcpy(copy, argv[offset], slen); 154 | if (urldecode) { 155 | slen = modp_url_decode(copy, copy, slen); 156 | } 157 | 158 | libinjection_h5_init(&hs, copy, slen, (enum html5_flags) flag); 159 | while (libinjection_h5_next(&hs)) { 160 | print_html5_token(&hs); 161 | } 162 | 163 | if (libinjection_is_xss(copy, slen, flag)) { 164 | printf("is injection!\n"); 165 | } 166 | free(copy); 167 | return 0; 168 | } 169 | -------------------------------------------------------------------------------- /libinjection/libinjection.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2016 Nick Galbreath 3 | * nickg@client9.com 4 | * BSD License -- see COPYING.txt for details 5 | * 6 | * https://libinjection.client9.com/ 7 | * 8 | */ 9 | 10 | #ifndef LIBINJECTION_H 11 | #define LIBINJECTION_H 12 | 13 | #ifdef __cplusplus 14 | # define LIBINJECTION_BEGIN_DECLS extern "C" { 15 | # define LIBINJECTION_END_DECLS } 16 | #else 17 | # define LIBINJECTION_BEGIN_DECLS 18 | # define LIBINJECTION_END_DECLS 19 | #endif 20 | 21 | LIBINJECTION_BEGIN_DECLS 22 | 23 | /* 24 | * Pull in size_t 25 | */ 26 | #include 27 | 28 | /* 29 | * Version info. 30 | * 31 | * This is moved into a function to allow SWIG and other auto-generated 32 | * binding to not be modified during minor release changes. We change 33 | * change the version number in the c source file, and not regenerated 34 | * the binding 35 | * 36 | * See python's normalized version 37 | * http://www.python.org/dev/peps/pep-0386/#normalizedversion 38 | */ 39 | const char* libinjection_version(void); 40 | 41 | /** 42 | * Simple API for SQLi detection - returns a SQLi fingerprint or NULL 43 | * is benign input 44 | * 45 | * \param[in] s input string, may contain nulls, does not need to be null-terminated 46 | * \param[in] slen input string length 47 | * \param[out] fingerprint buffer of 8+ characters. c-string, 48 | * \return 1 if SQLi, 0 if benign. fingerprint will be set or set to empty string. 49 | */ 50 | int libinjection_sqli(const char* s, size_t slen, char fingerprint[]); 51 | 52 | /** ALPHA version of xss detector. 53 | * 54 | * NOT DONE. 55 | * 56 | * \param[in] s input string, may contain nulls, does not need to be null-terminated 57 | * \param[in] slen input string length 58 | * \return 1 if XSS found, 0 if benign 59 | * 60 | */ 61 | int libinjection_xss(const char* s, size_t slen); 62 | 63 | LIBINJECTION_END_DECLS 64 | 65 | #endif /* LIBINJECTION_H */ 66 | -------------------------------------------------------------------------------- /libinjection/libinjection_html5.c: -------------------------------------------------------------------------------- 1 | #include "libinjection_html5.h" 2 | 3 | #include 4 | #include 5 | 6 | #ifdef DEBUG 7 | #include 8 | #define TRACE() printf("%s:%d\n", __FUNCTION__, __LINE__) 9 | #else 10 | #define TRACE() 11 | #endif 12 | 13 | 14 | #define CHAR_EOF -1 15 | #define CHAR_NULL 0 16 | #define CHAR_BANG 33 17 | #define CHAR_DOUBLE 34 18 | #define CHAR_PERCENT 37 19 | #define CHAR_SINGLE 39 20 | #define CHAR_DASH 45 21 | #define CHAR_SLASH 47 22 | #define CHAR_LT 60 23 | #define CHAR_EQUALS 61 24 | #define CHAR_GT 62 25 | #define CHAR_QUESTION 63 26 | #define CHAR_RIGHTB 93 27 | #define CHAR_TICK 96 28 | 29 | /* prototypes */ 30 | 31 | static int h5_skip_white(h5_state_t* hs); 32 | static int h5_is_white(char c); 33 | static int h5_state_eof(h5_state_t* hs); 34 | static int h5_state_data(h5_state_t* hs); 35 | static int h5_state_tag_open(h5_state_t* hs); 36 | static int h5_state_tag_name(h5_state_t* hs); 37 | static int h5_state_tag_name_close(h5_state_t* hs); 38 | static int h5_state_end_tag_open(h5_state_t* hs); 39 | static int h5_state_self_closing_start_tag(h5_state_t* hs); 40 | static int h5_state_attribute_name(h5_state_t* hs); 41 | static int h5_state_after_attribute_name(h5_state_t* hs); 42 | static int h5_state_before_attribute_name(h5_state_t* hs); 43 | static int h5_state_before_attribute_value(h5_state_t* hs); 44 | static int h5_state_attribute_value_double_quote(h5_state_t* hs); 45 | static int h5_state_attribute_value_single_quote(h5_state_t* hs); 46 | static int h5_state_attribute_value_back_quote(h5_state_t* hs); 47 | static int h5_state_attribute_value_no_quote(h5_state_t* hs); 48 | static int h5_state_after_attribute_value_quoted_state(h5_state_t* hs); 49 | static int h5_state_comment(h5_state_t* hs); 50 | static int h5_state_cdata(h5_state_t* hs); 51 | 52 | 53 | /* 12.2.4.44 */ 54 | static int h5_state_bogus_comment(h5_state_t* hs); 55 | static int h5_state_bogus_comment2(h5_state_t* hs); 56 | 57 | /* 12.2.4.45 */ 58 | static int h5_state_markup_declaration_open(h5_state_t* hs); 59 | 60 | /* 8.2.4.52 */ 61 | static int h5_state_doctype(h5_state_t* hs); 62 | 63 | /** 64 | * public function 65 | */ 66 | void libinjection_h5_init(h5_state_t* hs, const char* s, size_t len, enum html5_flags flags) 67 | { 68 | memset(hs, 0, sizeof(h5_state_t)); 69 | hs->s = s; 70 | hs->len = len; 71 | 72 | switch (flags) { 73 | case DATA_STATE: 74 | hs->state = h5_state_data; 75 | break; 76 | case VALUE_NO_QUOTE: 77 | hs->state = h5_state_before_attribute_name; 78 | break; 79 | case VALUE_SINGLE_QUOTE: 80 | hs->state = h5_state_attribute_value_single_quote; 81 | break; 82 | case VALUE_DOUBLE_QUOTE: 83 | hs->state = h5_state_attribute_value_double_quote; 84 | break; 85 | case VALUE_BACK_QUOTE: 86 | hs->state = h5_state_attribute_value_back_quote; 87 | break; 88 | } 89 | } 90 | 91 | /** 92 | * public function 93 | */ 94 | int libinjection_h5_next(h5_state_t* hs) 95 | { 96 | assert(hs->state != NULL); 97 | return (*hs->state)(hs); 98 | } 99 | 100 | /** 101 | * Everything below here is private 102 | * 103 | */ 104 | 105 | 106 | static int h5_is_white(char ch) 107 | { 108 | /* 109 | * \t = horizontal tab = 0x09 110 | * \n = newline = 0x0A 111 | * \v = vertical tab = 0x0B 112 | * \f = form feed = 0x0C 113 | * \r = cr = 0x0D 114 | */ 115 | return strchr(" \t\n\v\f\r", ch) != NULL; 116 | } 117 | 118 | static int h5_skip_white(h5_state_t* hs) 119 | { 120 | char ch; 121 | while (hs->pos < hs->len) { 122 | ch = hs->s[hs->pos]; 123 | switch (ch) { 124 | case 0x00: /* IE only */ 125 | case 0x20: 126 | case 0x09: 127 | case 0x0A: 128 | case 0x0B: /* IE only */ 129 | case 0x0C: 130 | case 0x0D: /* IE only */ 131 | hs->pos += 1; 132 | break; 133 | default: 134 | return ch; 135 | } 136 | } 137 | return CHAR_EOF; 138 | } 139 | 140 | static int h5_state_eof(h5_state_t* hs) 141 | { 142 | /* eliminate unused function argument warning */ 143 | (void)hs; 144 | return 0; 145 | } 146 | 147 | static int h5_state_data(h5_state_t* hs) 148 | { 149 | const char* idx; 150 | 151 | TRACE(); 152 | assert(hs->len >= hs->pos); 153 | idx = (const char*) memchr(hs->s + hs->pos, CHAR_LT, hs->len - hs->pos); 154 | if (idx == NULL) { 155 | hs->token_start = hs->s + hs->pos; 156 | hs->token_len = hs->len - hs->pos; 157 | hs->token_type = DATA_TEXT; 158 | hs->state = h5_state_eof; 159 | if (hs->token_len == 0) { 160 | return 0; 161 | } 162 | } else { 163 | hs->token_start = hs->s + hs->pos; 164 | hs->token_type = DATA_TEXT; 165 | hs->token_len = (size_t)(idx - hs->s) - hs->pos; 166 | hs->pos = (size_t)(idx - hs->s) + 1; 167 | hs->state = h5_state_tag_open; 168 | if (hs->token_len == 0) { 169 | return h5_state_tag_open(hs); 170 | } 171 | } 172 | return 1; 173 | } 174 | 175 | /** 176 | * 12 2.4.8 177 | */ 178 | static int h5_state_tag_open(h5_state_t* hs) 179 | { 180 | char ch; 181 | 182 | TRACE(); 183 | if (hs->pos >= hs->len) { 184 | return 0; 185 | } 186 | ch = hs->s[hs->pos]; 187 | if (ch == CHAR_BANG) { 188 | hs->pos += 1; 189 | return h5_state_markup_declaration_open(hs); 190 | } else if (ch == CHAR_SLASH) { 191 | hs->pos += 1; 192 | hs->is_close = 1; 193 | return h5_state_end_tag_open(hs); 194 | } else if (ch == CHAR_QUESTION) { 195 | hs->pos += 1; 196 | return h5_state_bogus_comment(hs); 197 | } else if (ch == CHAR_PERCENT) { 198 | /* this is not in spec.. alternative comment format used 199 | by IE <= 9 and Safari < 4.0.3 */ 200 | hs->pos += 1; 201 | return h5_state_bogus_comment2(hs); 202 | } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { 203 | return h5_state_tag_name(hs); 204 | } else if (ch == CHAR_NULL) { 205 | /* IE-ism NULL characters are ignored */ 206 | return h5_state_tag_name(hs); 207 | } else { 208 | /* user input mistake in configuring state */ 209 | if (hs->pos == 0) { 210 | return h5_state_data(hs); 211 | } 212 | hs->token_start = hs->s + hs->pos - 1; 213 | hs->token_len = 1; 214 | hs->token_type = DATA_TEXT; 215 | hs->state = h5_state_data; 216 | return 1; 217 | } 218 | } 219 | /** 220 | * 12.2.4.9 221 | */ 222 | static int h5_state_end_tag_open(h5_state_t* hs) 223 | { 224 | char ch; 225 | 226 | TRACE(); 227 | 228 | if (hs->pos >= hs->len) { 229 | return 0; 230 | } 231 | ch = hs->s[hs->pos]; 232 | if (ch == CHAR_GT) { 233 | return h5_state_data(hs); 234 | } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { 235 | return h5_state_tag_name(hs); 236 | } 237 | 238 | hs->is_close = 0; 239 | return h5_state_bogus_comment(hs); 240 | } 241 | /* 242 | * 243 | */ 244 | static int h5_state_tag_name_close(h5_state_t* hs) 245 | { 246 | TRACE(); 247 | hs->is_close = 0; 248 | hs->token_start = hs->s + hs->pos; 249 | hs->token_len = 1; 250 | hs->token_type = TAG_NAME_CLOSE; 251 | hs->pos += 1; 252 | if (hs->pos < hs->len) { 253 | hs->state = h5_state_data; 254 | } else { 255 | hs->state = h5_state_eof; 256 | } 257 | 258 | return 1; 259 | } 260 | 261 | /** 262 | * 12.2.4.10 263 | */ 264 | static int h5_state_tag_name(h5_state_t* hs) 265 | { 266 | char ch; 267 | size_t pos; 268 | 269 | TRACE(); 270 | pos = hs->pos; 271 | while (pos < hs->len) { 272 | ch = hs->s[pos]; 273 | if (ch == 0) { 274 | /* special non-standard case */ 275 | /* allow nulls in tag name */ 276 | /* some old browsers apparently allow and ignore them */ 277 | pos += 1; 278 | } else if (h5_is_white(ch)) { 279 | hs->token_start = hs->s + hs->pos; 280 | hs->token_len = pos - hs->pos; 281 | hs->token_type = TAG_NAME_OPEN; 282 | hs->pos = pos + 1; 283 | hs->state = h5_state_before_attribute_name; 284 | return 1; 285 | } else if (ch == CHAR_SLASH) { 286 | hs->token_start = hs->s + hs->pos; 287 | hs->token_len = pos - hs->pos; 288 | hs->token_type = TAG_NAME_OPEN; 289 | hs->pos = pos + 1; 290 | hs->state = h5_state_self_closing_start_tag; 291 | return 1; 292 | } else if (ch == CHAR_GT) { 293 | hs->token_start = hs->s + hs->pos; 294 | hs->token_len = pos - hs->pos; 295 | if (hs->is_close) { 296 | hs->pos = pos + 1; 297 | hs->is_close = 0; 298 | hs->token_type = TAG_CLOSE; 299 | hs->state = h5_state_data; 300 | } else { 301 | hs->pos = pos; 302 | hs->token_type = TAG_NAME_OPEN; 303 | hs->state = h5_state_tag_name_close; 304 | } 305 | return 1; 306 | } else { 307 | pos += 1; 308 | } 309 | } 310 | 311 | hs->token_start = hs->s + hs->pos; 312 | hs->token_len = hs->len - hs->pos; 313 | hs->token_type = TAG_NAME_OPEN; 314 | hs->state = h5_state_eof; 315 | return 1; 316 | } 317 | 318 | /** 319 | * 12.2.4.34 320 | */ 321 | static int h5_state_before_attribute_name(h5_state_t* hs) 322 | { 323 | int ch; 324 | 325 | TRACE(); 326 | ch = h5_skip_white(hs); 327 | switch (ch) { 328 | case CHAR_EOF: { 329 | return 0; 330 | } 331 | case CHAR_SLASH: { 332 | hs->pos += 1; 333 | return h5_state_self_closing_start_tag(hs); 334 | } 335 | case CHAR_GT: { 336 | hs->state = h5_state_data; 337 | hs->token_start = hs->s + hs->pos; 338 | hs->token_len = 1; 339 | hs->token_type = TAG_NAME_CLOSE; 340 | hs->pos += 1; 341 | return 1; 342 | } 343 | default: { 344 | return h5_state_attribute_name(hs); 345 | } 346 | } 347 | } 348 | 349 | static int h5_state_attribute_name(h5_state_t* hs) 350 | { 351 | char ch; 352 | size_t pos; 353 | 354 | TRACE(); 355 | pos = hs->pos + 1; 356 | while (pos < hs->len) { 357 | ch = hs->s[pos]; 358 | if (h5_is_white(ch)) { 359 | hs->token_start = hs->s + hs->pos; 360 | hs->token_len = pos - hs->pos; 361 | hs->token_type = ATTR_NAME; 362 | hs->state = h5_state_after_attribute_name; 363 | hs->pos = pos + 1; 364 | return 1; 365 | } else if (ch == CHAR_SLASH) { 366 | hs->token_start = hs->s + hs->pos; 367 | hs->token_len = pos - hs->pos; 368 | hs->token_type = ATTR_NAME; 369 | hs->state = h5_state_self_closing_start_tag; 370 | hs->pos = pos + 1; 371 | return 1; 372 | } else if (ch == CHAR_EQUALS) { 373 | hs->token_start = hs->s + hs->pos; 374 | hs->token_len = pos - hs->pos; 375 | hs->token_type = ATTR_NAME; 376 | hs->state = h5_state_before_attribute_value; 377 | hs->pos = pos + 1; 378 | return 1; 379 | } else if (ch == CHAR_GT) { 380 | hs->token_start = hs->s + hs->pos; 381 | hs->token_len = pos - hs->pos; 382 | hs->token_type = ATTR_NAME; 383 | hs->state = h5_state_tag_name_close; 384 | hs->pos = pos; 385 | return 1; 386 | } else { 387 | pos += 1; 388 | } 389 | } 390 | /* EOF */ 391 | hs->token_start = hs->s + hs->pos; 392 | hs->token_len = hs->len - hs->pos; 393 | hs->token_type = ATTR_NAME; 394 | hs->state = h5_state_eof; 395 | hs->pos = hs->len; 396 | return 1; 397 | } 398 | 399 | /** 400 | * 12.2.4.36 401 | */ 402 | static int h5_state_after_attribute_name(h5_state_t* hs) 403 | { 404 | int c; 405 | 406 | TRACE(); 407 | c = h5_skip_white(hs); 408 | switch (c) { 409 | case CHAR_EOF: { 410 | return 0; 411 | } 412 | case CHAR_SLASH: { 413 | hs->pos += 1; 414 | return h5_state_self_closing_start_tag(hs); 415 | } 416 | case CHAR_EQUALS: { 417 | hs->pos += 1; 418 | return h5_state_before_attribute_value(hs); 419 | } 420 | case CHAR_GT: { 421 | return h5_state_tag_name_close(hs); 422 | } 423 | default: { 424 | return h5_state_attribute_name(hs); 425 | } 426 | } 427 | } 428 | 429 | /** 430 | * 12.2.4.37 431 | */ 432 | static int h5_state_before_attribute_value(h5_state_t* hs) 433 | { 434 | int c; 435 | TRACE(); 436 | 437 | c = h5_skip_white(hs); 438 | 439 | if (c == CHAR_EOF) { 440 | hs->state = h5_state_eof; 441 | return 0; 442 | } 443 | 444 | if (c == CHAR_DOUBLE) { 445 | return h5_state_attribute_value_double_quote(hs); 446 | } else if (c == CHAR_SINGLE) { 447 | return h5_state_attribute_value_single_quote(hs); 448 | } else if (c == CHAR_TICK) { 449 | /* NON STANDARD IE */ 450 | return h5_state_attribute_value_back_quote(hs); 451 | } else { 452 | return h5_state_attribute_value_no_quote(hs); 453 | } 454 | } 455 | 456 | 457 | static int h5_state_attribute_value_quote(h5_state_t* hs, char qchar) 458 | { 459 | const char* idx; 460 | 461 | TRACE(); 462 | 463 | /* skip initial quote in normal case. 464 | * don't do this "if (pos == 0)" since it means we have started 465 | * in a non-data state. given an input of '>pos > 0) { 469 | hs->pos += 1; 470 | } 471 | 472 | 473 | idx = (const char*) memchr(hs->s + hs->pos, qchar, hs->len - hs->pos); 474 | if (idx == NULL) { 475 | hs->token_start = hs->s + hs->pos; 476 | hs->token_len = hs->len - hs->pos; 477 | hs->token_type = ATTR_VALUE; 478 | hs->state = h5_state_eof; 479 | } else { 480 | hs->token_start = hs->s + hs->pos; 481 | hs->token_len = (size_t)(idx - hs->s) - hs->pos; 482 | hs->token_type = ATTR_VALUE; 483 | hs->state = h5_state_after_attribute_value_quoted_state; 484 | hs->pos += hs->token_len + 1; 485 | } 486 | return 1; 487 | } 488 | 489 | static 490 | int h5_state_attribute_value_double_quote(h5_state_t* hs) 491 | { 492 | TRACE(); 493 | return h5_state_attribute_value_quote(hs, CHAR_DOUBLE); 494 | } 495 | 496 | static 497 | int h5_state_attribute_value_single_quote(h5_state_t* hs) 498 | { 499 | TRACE(); 500 | return h5_state_attribute_value_quote(hs, CHAR_SINGLE); 501 | } 502 | 503 | static 504 | int h5_state_attribute_value_back_quote(h5_state_t* hs) 505 | { 506 | TRACE(); 507 | return h5_state_attribute_value_quote(hs, CHAR_TICK); 508 | } 509 | 510 | static int h5_state_attribute_value_no_quote(h5_state_t* hs) 511 | { 512 | char ch; 513 | size_t pos; 514 | 515 | TRACE(); 516 | pos = hs->pos; 517 | while (pos < hs->len) { 518 | ch = hs->s[pos]; 519 | if (h5_is_white(ch)) { 520 | hs->token_type = ATTR_VALUE; 521 | hs->token_start = hs->s + hs->pos; 522 | hs->token_len = pos - hs->pos; 523 | hs->pos = pos + 1; 524 | hs->state = h5_state_before_attribute_name; 525 | return 1; 526 | } else if (ch == CHAR_GT) { 527 | hs->token_type = ATTR_VALUE; 528 | hs->token_start = hs->s + hs->pos; 529 | hs->token_len = pos - hs->pos; 530 | hs->pos = pos; 531 | hs->state = h5_state_tag_name_close; 532 | return 1; 533 | } 534 | pos += 1; 535 | } 536 | TRACE(); 537 | /* EOF */ 538 | hs->state = h5_state_eof; 539 | hs->token_start = hs->s + hs->pos; 540 | hs->token_len = hs->len - hs->pos; 541 | hs->token_type = ATTR_VALUE; 542 | return 1; 543 | } 544 | 545 | /** 546 | * 12.2.4.41 547 | */ 548 | static int h5_state_after_attribute_value_quoted_state(h5_state_t* hs) 549 | { 550 | char ch; 551 | 552 | TRACE(); 553 | if (hs->pos >= hs->len) { 554 | return 0; 555 | } 556 | ch = hs->s[hs->pos]; 557 | if (h5_is_white(ch)) { 558 | hs->pos += 1; 559 | return h5_state_before_attribute_name(hs); 560 | } else if (ch == CHAR_SLASH) { 561 | hs->pos += 1; 562 | return h5_state_self_closing_start_tag(hs); 563 | } else if (ch == CHAR_GT) { 564 | hs->token_start = hs->s + hs->pos; 565 | hs->token_len = 1; 566 | hs->token_type = TAG_NAME_CLOSE; 567 | hs->pos += 1; 568 | hs->state = h5_state_data; 569 | return 1; 570 | } else { 571 | return h5_state_before_attribute_name(hs); 572 | } 573 | } 574 | 575 | /** 576 | * 12.2.4.43 577 | */ 578 | static int h5_state_self_closing_start_tag(h5_state_t* hs) 579 | { 580 | char ch; 581 | 582 | TRACE(); 583 | if (hs->pos >= hs->len) { 584 | return 0; 585 | } 586 | ch = hs->s[hs->pos]; 587 | if (ch == CHAR_GT) { 588 | assert(hs->pos > 0); 589 | hs->token_start = hs->s + hs->pos -1; 590 | hs->token_len = 2; 591 | hs->token_type = TAG_NAME_SELFCLOSE; 592 | hs->state = h5_state_data; 593 | hs->pos += 1; 594 | return 1; 595 | } else { 596 | return h5_state_before_attribute_name(hs); 597 | } 598 | } 599 | 600 | /** 601 | * 12.2.4.44 602 | */ 603 | static int h5_state_bogus_comment(h5_state_t* hs) 604 | { 605 | const char* idx; 606 | 607 | TRACE(); 608 | idx = (const char*) memchr(hs->s + hs->pos, CHAR_GT, hs->len - hs->pos); 609 | if (idx == NULL) { 610 | hs->token_start = hs->s + hs->pos; 611 | hs->token_len = hs->len - hs->pos; 612 | hs->pos = hs->len; 613 | hs->state = h5_state_eof; 614 | } else { 615 | hs->token_start = hs->s + hs->pos; 616 | hs->token_len = (size_t)(idx - hs->s) - hs->pos; 617 | hs->pos = (size_t)(idx - hs->s) + 1; 618 | hs->state = h5_state_data; 619 | } 620 | 621 | hs->token_type = TAG_COMMENT; 622 | return 1; 623 | } 624 | 625 | /** 626 | * 12.2.4.44 ALT 627 | */ 628 | static int h5_state_bogus_comment2(h5_state_t* hs) 629 | { 630 | const char* idx; 631 | size_t pos; 632 | 633 | TRACE(); 634 | pos = hs->pos; 635 | while (1) { 636 | idx = (const char*) memchr(hs->s + pos, CHAR_PERCENT, hs->len - pos); 637 | if (idx == NULL || (idx + 1 >= hs->s + hs->len)) { 638 | hs->token_start = hs->s + hs->pos; 639 | hs->token_len = hs->len - hs->pos; 640 | hs->pos = hs->len; 641 | hs->token_type = TAG_COMMENT; 642 | hs->state = h5_state_eof; 643 | return 1; 644 | } 645 | 646 | if (*(idx +1) != CHAR_GT) { 647 | pos = (size_t)(idx - hs->s) + 1; 648 | continue; 649 | } 650 | 651 | /* ends in %> */ 652 | hs->token_start = hs->s + hs->pos; 653 | hs->token_len = (size_t)(idx - hs->s) - hs->pos; 654 | hs->pos = (size_t)(idx - hs->s) + 2; 655 | hs->state = h5_state_data; 656 | hs->token_type = TAG_COMMENT; 657 | return 1; 658 | } 659 | } 660 | 661 | /** 662 | * 8.2.4.45 663 | */ 664 | static int h5_state_markup_declaration_open(h5_state_t* hs) 665 | { 666 | size_t remaining; 667 | 668 | TRACE(); 669 | remaining = hs->len - hs->pos; 670 | if (remaining >= 7 && 671 | /* case insensitive */ 672 | (hs->s[hs->pos + 0] == 'D' || hs->s[hs->pos + 0] == 'd') && 673 | (hs->s[hs->pos + 1] == 'O' || hs->s[hs->pos + 1] == 'o') && 674 | (hs->s[hs->pos + 2] == 'C' || hs->s[hs->pos + 2] == 'c') && 675 | (hs->s[hs->pos + 3] == 'T' || hs->s[hs->pos + 3] == 't') && 676 | (hs->s[hs->pos + 4] == 'Y' || hs->s[hs->pos + 4] == 'y') && 677 | (hs->s[hs->pos + 5] == 'P' || hs->s[hs->pos + 5] == 'p') && 678 | (hs->s[hs->pos + 6] == 'E' || hs->s[hs->pos + 6] == 'e') 679 | ) { 680 | return h5_state_doctype(hs); 681 | } else if (remaining >= 7 && 682 | /* upper case required */ 683 | hs->s[hs->pos + 0] == '[' && 684 | hs->s[hs->pos + 1] == 'C' && 685 | hs->s[hs->pos + 2] == 'D' && 686 | hs->s[hs->pos + 3] == 'A' && 687 | hs->s[hs->pos + 4] == 'T' && 688 | hs->s[hs->pos + 5] == 'A' && 689 | hs->s[hs->pos + 6] == '[' 690 | ) { 691 | hs->pos += 7; 692 | return h5_state_cdata(hs); 693 | } else if (remaining >= 2 && 694 | hs->s[hs->pos + 0] == '-' && 695 | hs->s[hs->pos + 1] == '-') { 696 | hs->pos += 2; 697 | return h5_state_comment(hs); 698 | } 699 | 700 | return h5_state_bogus_comment(hs); 701 | } 702 | 703 | /** 704 | * 12.2.4.48 705 | * 12.2.4.49 706 | * 12.2.4.50 707 | * 12.2.4.51 708 | * state machine spec is confusing since it can only look 709 | * at one character at a time but simply it's comments end by: 710 | * 1) EOF 711 | * 2) ending in --> 712 | * 3) ending in -!> 713 | */ 714 | static int h5_state_comment(h5_state_t* hs) 715 | { 716 | char ch; 717 | const char* idx; 718 | size_t pos; 719 | size_t offset; 720 | const char* end = hs->s + hs->len; 721 | 722 | TRACE(); 723 | pos = hs->pos; 724 | while (1) { 725 | 726 | idx = (const char*) memchr(hs->s + pos, CHAR_DASH, hs->len - pos); 727 | 728 | /* did not find anything or has less than 3 chars left */ 729 | if (idx == NULL || idx > hs->s + hs->len - 3) { 730 | hs->state = h5_state_eof; 731 | hs->token_start = hs->s + hs->pos; 732 | hs->token_len = hs->len - hs->pos; 733 | hs->token_type = TAG_COMMENT; 734 | return 1; 735 | } 736 | offset = 1; 737 | 738 | /* skip all nulls */ 739 | while (idx + offset < end && *(idx + offset) == 0) { 740 | offset += 1; 741 | } 742 | if (idx + offset == end) { 743 | hs->state = h5_state_eof; 744 | hs->token_start = hs->s + hs->pos; 745 | hs->token_len = hs->len - hs->pos; 746 | hs->token_type = TAG_COMMENT; 747 | return 1; 748 | } 749 | 750 | ch = *(idx + offset); 751 | if (ch != CHAR_DASH && ch != CHAR_BANG) { 752 | pos = (size_t)(idx - hs->s) + 1; 753 | continue; 754 | } 755 | 756 | /* need to test */ 757 | #if 0 758 | /* skip all nulls */ 759 | while (idx + offset < end && *(idx + offset) == 0) { 760 | offset += 1; 761 | } 762 | if (idx + offset == end) { 763 | hs->state = h5_state_eof; 764 | hs->token_start = hs->s + hs->pos; 765 | hs->token_len = hs->len - hs->pos; 766 | hs->token_type = TAG_COMMENT; 767 | return 1; 768 | } 769 | #endif 770 | 771 | offset += 1; 772 | if (idx + offset == end) { 773 | hs->state = h5_state_eof; 774 | hs->token_start = hs->s + hs->pos; 775 | hs->token_len = hs->len - hs->pos; 776 | hs->token_type = TAG_COMMENT; 777 | return 1; 778 | } 779 | 780 | 781 | ch = *(idx + offset); 782 | if (ch != CHAR_GT) { 783 | pos = (size_t)(idx - hs->s) + 1; 784 | continue; 785 | } 786 | offset += 1; 787 | 788 | /* ends in --> or -!> */ 789 | hs->token_start = hs->s + hs->pos; 790 | hs->token_len = (size_t)(idx - hs->s) - hs->pos; 791 | hs->pos = (size_t)(idx + offset - hs->s); 792 | hs->state = h5_state_data; 793 | hs->token_type = TAG_COMMENT; 794 | return 1; 795 | } 796 | } 797 | 798 | static int h5_state_cdata(h5_state_t* hs) 799 | { 800 | const char* idx; 801 | size_t pos; 802 | 803 | TRACE(); 804 | pos = hs->pos; 805 | while (1) { 806 | idx = (const char*) memchr(hs->s + pos, CHAR_RIGHTB, hs->len - pos); 807 | 808 | /* did not find anything or has less than 3 chars left */ 809 | if (idx == NULL || idx > hs->s + hs->len - 3) { 810 | hs->state = h5_state_eof; 811 | hs->token_start = hs->s + hs->pos; 812 | hs->token_len = hs->len - hs->pos; 813 | hs->token_type = DATA_TEXT; 814 | return 1; 815 | } else if ( *(idx+1) == CHAR_RIGHTB && *(idx+2) == CHAR_GT) { 816 | hs->state = h5_state_data; 817 | hs->token_start = hs->s + hs->pos; 818 | hs->token_len = (size_t)(idx - hs->s) - hs->pos; 819 | hs->pos = (size_t)(idx - hs->s) + 3; 820 | hs->token_type = DATA_TEXT; 821 | return 1; 822 | } else { 823 | pos = (size_t)(idx - hs->s) + 1; 824 | } 825 | } 826 | } 827 | 828 | /** 829 | * 8.2.4.52 830 | * http://www.w3.org/html/wg/drafts/html/master/syntax.html#doctype-state 831 | */ 832 | static int h5_state_doctype(h5_state_t* hs) 833 | { 834 | const char* idx; 835 | 836 | TRACE(); 837 | hs->token_start = hs->s + hs->pos; 838 | hs->token_type = DOCTYPE; 839 | 840 | idx = (const char*) memchr(hs->s + hs->pos, CHAR_GT, hs->len - hs->pos); 841 | if (idx == NULL) { 842 | hs->state = h5_state_eof; 843 | hs->token_len = hs->len - hs->pos; 844 | } else { 845 | hs->state = h5_state_data; 846 | hs->token_len = (size_t)(idx - hs->s) - hs->pos; 847 | hs->pos = (size_t)(idx - hs->s) + 1; 848 | } 849 | return 1; 850 | } 851 | -------------------------------------------------------------------------------- /libinjection/libinjection_html5.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBINJECTION_HTML5 2 | #define LIBINJECTION_HTML5 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | /* pull in size_t */ 9 | 10 | #include 11 | 12 | enum html5_type { 13 | DATA_TEXT 14 | , TAG_NAME_OPEN 15 | , TAG_NAME_CLOSE 16 | , TAG_NAME_SELFCLOSE 17 | , TAG_DATA 18 | , TAG_CLOSE 19 | , ATTR_NAME 20 | , ATTR_VALUE 21 | , TAG_COMMENT 22 | , DOCTYPE 23 | }; 24 | 25 | enum html5_flags { 26 | DATA_STATE 27 | , VALUE_NO_QUOTE 28 | , VALUE_SINGLE_QUOTE 29 | , VALUE_DOUBLE_QUOTE 30 | , VALUE_BACK_QUOTE 31 | }; 32 | 33 | struct h5_state; 34 | typedef int (*ptr_html5_state)(struct h5_state*); 35 | 36 | typedef struct h5_state { 37 | const char* s; 38 | size_t len; 39 | size_t pos; 40 | int is_close; 41 | ptr_html5_state state; 42 | const char* token_start; 43 | size_t token_len; 44 | enum html5_type token_type; 45 | } h5_state_t; 46 | 47 | 48 | void libinjection_h5_init(h5_state_t* hs, const char* s, size_t len, enum html5_flags); 49 | int libinjection_h5_next(h5_state_t* hs); 50 | 51 | #ifdef __cplusplus 52 | } 53 | #endif 54 | #endif 55 | -------------------------------------------------------------------------------- /libinjection/libinjection_sqli.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2012-2016 Nick Galbreath 3 | * nickg@client9.com 4 | * BSD License -- see `COPYING.txt` for details 5 | * 6 | * https://libinjection.client9.com/ 7 | * 8 | */ 9 | 10 | #ifndef LIBINJECTION_SQLI_H 11 | #define LIBINJECTION_SQLI_H 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | /* 18 | * Pull in size_t 19 | */ 20 | #include 21 | 22 | enum sqli_flags { 23 | FLAG_NONE = 0 24 | , FLAG_QUOTE_NONE = 1 /* 1 << 0 */ 25 | , FLAG_QUOTE_SINGLE = 2 /* 1 << 1 */ 26 | , FLAG_QUOTE_DOUBLE = 4 /* 1 << 2 */ 27 | 28 | , FLAG_SQL_ANSI = 8 /* 1 << 3 */ 29 | , FLAG_SQL_MYSQL = 16 /* 1 << 4 */ 30 | }; 31 | 32 | enum lookup_type { 33 | LOOKUP_WORD = 1 34 | , LOOKUP_TYPE = 2 35 | , LOOKUP_OPERATOR = 3 36 | , LOOKUP_FINGERPRINT = 4 37 | }; 38 | 39 | struct libinjection_sqli_token { 40 | #ifdef SWIG 41 | %immutable; 42 | #endif 43 | /* 44 | * position and length of token 45 | * in original string 46 | */ 47 | size_t pos; 48 | size_t len; 49 | 50 | /* count: 51 | * in type 'v', used for number of opening '@' 52 | * but maybe used in other contexts 53 | */ 54 | int count; 55 | 56 | char type; 57 | char str_open; 58 | char str_close; 59 | char val[32]; 60 | }; 61 | 62 | typedef struct libinjection_sqli_token stoken_t; 63 | 64 | /** 65 | * Pointer to function, takes c-string input, 66 | * returns '\0' for no match, else a char 67 | */ 68 | struct libinjection_sqli_state; 69 | typedef char (*ptr_lookup_fn)(struct libinjection_sqli_state*, int lookuptype, const char* word, size_t len); 70 | 71 | struct libinjection_sqli_state { 72 | #ifdef SWIG 73 | %immutable; 74 | #endif 75 | 76 | /* 77 | * input, does not need to be null terminated. 78 | * it is also not modified. 79 | */ 80 | const char *s; 81 | 82 | /* 83 | * input length 84 | */ 85 | size_t slen; 86 | 87 | /* 88 | * How to lookup a word or fingerprint 89 | */ 90 | ptr_lookup_fn lookup; 91 | void* userdata; 92 | 93 | /* 94 | * 95 | */ 96 | int flags; 97 | 98 | /* 99 | * pos is the index in the string during tokenization 100 | */ 101 | size_t pos; 102 | 103 | #ifndef SWIG 104 | /* for SWIG.. don't use this.. use functional API instead */ 105 | 106 | /* MAX TOKENS + 1 since we use one extra token 107 | * to determine the type of the previous token 108 | */ 109 | struct libinjection_sqli_token tokenvec[8]; 110 | #endif 111 | 112 | /* 113 | * Pointer to token position in tokenvec, above 114 | */ 115 | struct libinjection_sqli_token *current; 116 | 117 | /* 118 | * fingerprint pattern c-string 119 | * +1 for ending null 120 | * Minimum of 8 bytes to add gcc's -fstack-protector to work 121 | */ 122 | char fingerprint[8]; 123 | 124 | /* 125 | * Line number of code that said decided if the input was SQLi or 126 | * not. Most of the time it's line that said "it's not a matching 127 | * fingerprint" but there is other logic that sometimes approves 128 | * an input. This is only useful for debugging. 129 | * 130 | */ 131 | int reason; 132 | 133 | /* Number of ddw (dash-dash-white) comments 134 | * These comments are in the form of 135 | * '--[whitespace]' or '--[EOF]' 136 | * 137 | * All databases treat this as a comment. 138 | */ 139 | int stats_comment_ddw; 140 | 141 | /* Number of ddx (dash-dash-[notwhite]) comments 142 | * 143 | * ANSI SQL treats these are comments, MySQL treats this as 144 | * two unary operators '-' '-' 145 | * 146 | * If you are parsing result returns FALSE and 147 | * stats_comment_dd > 0, you should reparse with 148 | * COMMENT_MYSQL 149 | * 150 | */ 151 | int stats_comment_ddx; 152 | 153 | /* 154 | * c-style comments found /x .. x/ 155 | */ 156 | int stats_comment_c; 157 | 158 | /* '#' operators or MySQL EOL comments found 159 | * 160 | */ 161 | int stats_comment_hash; 162 | 163 | /* 164 | * number of tokens folded away 165 | */ 166 | int stats_folds; 167 | 168 | /* 169 | * total tokens processed 170 | */ 171 | int stats_tokens; 172 | 173 | }; 174 | 175 | typedef struct libinjection_sqli_state sfilter; 176 | 177 | struct libinjection_sqli_token* libinjection_sqli_get_token( 178 | struct libinjection_sqli_state* sqlistate, int i); 179 | 180 | /* 181 | * Version info. 182 | * 183 | * This is moved into a function to allow SWIG and other auto-generated 184 | * binding to not be modified during minor release changes. We change 185 | * change the version number in the c source file, and not regenerated 186 | * the binding 187 | * 188 | * See python's normalized version 189 | * http://www.python.org/dev/peps/pep-0386/#normalizedversion 190 | */ 191 | const char* libinjection_version(void); 192 | 193 | /** 194 | * 195 | */ 196 | void libinjection_sqli_init(struct libinjection_sqli_state* sql_state, 197 | const char* s, size_t slen, 198 | int flags); 199 | 200 | /** 201 | * Main API: tests for SQLi in three possible contexts, no quotes, 202 | * single quote and double quote 203 | * 204 | * \param sql_state core data structure 205 | * 206 | * \return 1 (true) if SQLi, 0 (false) if benign 207 | */ 208 | int libinjection_is_sqli(struct libinjection_sqli_state* sql_state); 209 | 210 | /* FOR HACKERS ONLY 211 | * provides deep hooks into the decision making process 212 | */ 213 | void libinjection_sqli_callback(struct libinjection_sqli_state* sql_state, 214 | ptr_lookup_fn fn, 215 | void* userdata); 216 | 217 | 218 | /* 219 | * Resets state, but keeps initial string and callbacks 220 | */ 221 | void libinjection_sqli_reset(struct libinjection_sqli_state* sql_state, 222 | int flags); 223 | 224 | /** 225 | * 226 | */ 227 | 228 | /** 229 | * This detects SQLi in a single context, mostly useful for custom 230 | * logic and debugging. 231 | * 232 | * \param sql_state Main data structure 233 | * \param flags flags to adjust parsing 234 | * 235 | * \returns a pointer to sfilter.fingerprint as convenience 236 | * do not free! 237 | * 238 | */ 239 | const char* libinjection_sqli_fingerprint(struct libinjection_sqli_state* sql_state, 240 | int flags); 241 | 242 | /** 243 | * The default "word" to token-type or fingerprint function. This 244 | * uses a ASCII case-insensitive binary tree. 245 | */ 246 | char libinjection_sqli_lookup_word(struct libinjection_sqli_state* sql_state, 247 | int lookup_type, 248 | const char* s, 249 | size_t slen); 250 | 251 | /* Streaming tokenization interface. 252 | * 253 | * sql_state->current is updated with the current token. 254 | * 255 | * \returns 1, has a token, keep going, or 0 no tokens 256 | * 257 | */ 258 | int libinjection_sqli_tokenize(struct libinjection_sqli_state * sql_state); 259 | 260 | /** 261 | * parses and folds input, up to 5 tokens 262 | * 263 | */ 264 | int libinjection_sqli_fold(struct libinjection_sqli_state * sql_state); 265 | 266 | /** The built-in default function to match fingerprints 267 | * and do false negative/positive analysis. This calls the following 268 | * two functions. With this, you over-ride one part or the other. 269 | * 270 | * return libinjection_sqli_blacklist(sql_state) && 271 | * libinjection_sqli_not_whitelist(sql_state); 272 | * 273 | * \param sql_state should be filled out after libinjection_sqli_fingerprint is called 274 | */ 275 | int libinjection_sqli_check_fingerprint(struct libinjection_sqli_state * sql_state); 276 | 277 | /* Given a pattern determine if it's a SQLi pattern. 278 | * 279 | * \return TRUE if sqli, false otherwise 280 | */ 281 | int libinjection_sqli_blacklist(struct libinjection_sqli_state* sql_state); 282 | 283 | /* Given a positive match for a pattern (i.e. pattern is SQLi), this function 284 | * does additional analysis to reduce false positives. 285 | * 286 | * \return TRUE if SQLi, false otherwise 287 | */ 288 | int libinjection_sqli_not_whitelist(struct libinjection_sqli_state * sql_state); 289 | 290 | #ifdef __cplusplus 291 | } 292 | #endif 293 | 294 | #endif /* LIBINJECTION_SQLI_H */ 295 | -------------------------------------------------------------------------------- /libinjection/libinjection_xss.c: -------------------------------------------------------------------------------- 1 | 2 | #include "libinjection.h" 3 | #include "libinjection_xss.h" 4 | #include "libinjection_html5.h" 5 | 6 | #include 7 | #include 8 | 9 | typedef enum attribute { 10 | TYPE_NONE 11 | , TYPE_BLACK /* ban always */ 12 | , TYPE_ATTR_URL /* attribute value takes a URL-like object */ 13 | , TYPE_STYLE 14 | , TYPE_ATTR_INDIRECT /* attribute *name* is given in *value* */ 15 | } attribute_t; 16 | 17 | 18 | static attribute_t is_black_attr(const char* s, size_t len); 19 | static int is_black_tag(const char* s, size_t len); 20 | static int is_black_url(const char* s, size_t len); 21 | static int cstrcasecmp_with_null(const char *a, const char *b, size_t n); 22 | static int html_decode_char_at(const char* src, size_t len, size_t* consumed); 23 | static int htmlencode_startswith(const char* prefix, const char *src, size_t n); 24 | 25 | 26 | typedef struct stringtype { 27 | const char* name; 28 | attribute_t atype; 29 | } stringtype_t; 30 | 31 | 32 | static const int gsHexDecodeMap[256] = { 33 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 34 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 35 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 36 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 37 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 256, 256, 38 | 256, 256, 256, 256, 256, 10, 11, 12, 13, 14, 15, 256, 39 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 40 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 41 | 256, 10, 11, 12, 13, 14, 15, 256, 256, 256, 256, 256, 42 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 43 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 44 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 45 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 46 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 47 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 48 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 49 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 50 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 51 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 52 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 53 | 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 54 | 256, 256, 256, 256 55 | }; 56 | 57 | static int html_decode_char_at(const char* src, size_t len, size_t* consumed) 58 | { 59 | int val = 0; 60 | size_t i; 61 | int ch; 62 | 63 | if (len == 0 || src == NULL) { 64 | *consumed = 0; 65 | return -1; 66 | } 67 | 68 | *consumed = 1; 69 | if (*src != '&' || len < 2) { 70 | return (unsigned char)(*src); 71 | } 72 | 73 | 74 | if (*(src+1) != '#') { 75 | /* normally this would be for named entities 76 | * but for this case we don't actually care 77 | */ 78 | return '&'; 79 | } 80 | 81 | if (*(src+2) == 'x' || *(src+2) == 'X') { 82 | ch = (unsigned char) (*(src+3)); 83 | ch = gsHexDecodeMap[ch]; 84 | if (ch == 256) { 85 | /* degenerate case '&#[?]' */ 86 | return '&'; 87 | } 88 | val = ch; 89 | i = 4; 90 | while (i < len) { 91 | ch = (unsigned char) src[i]; 92 | if (ch == ';') { 93 | *consumed = i + 1; 94 | return val; 95 | } 96 | ch = gsHexDecodeMap[ch]; 97 | if (ch == 256) { 98 | *consumed = i; 99 | return val; 100 | } 101 | val = (val * 16) + ch; 102 | if (val > 0x1000FF) { 103 | return '&'; 104 | } 105 | ++i; 106 | } 107 | *consumed = i; 108 | return val; 109 | } else { 110 | i = 2; 111 | ch = (unsigned char) src[i]; 112 | if (ch < '0' || ch > '9') { 113 | return '&'; 114 | } 115 | val = ch - '0'; 116 | i += 1; 117 | while (i < len) { 118 | ch = (unsigned char) src[i]; 119 | if (ch == ';') { 120 | *consumed = i + 1; 121 | return val; 122 | } 123 | if (ch < '0' || ch > '9') { 124 | *consumed = i; 125 | return val; 126 | } 127 | val = (val * 10) + (ch - '0'); 128 | if (val > 0x1000FF) { 129 | return '&'; 130 | } 131 | ++i; 132 | } 133 | *consumed = i; 134 | return val; 135 | } 136 | } 137 | 138 | 139 | /* 140 | * view-source: 141 | * data: 142 | * javascript: 143 | */ 144 | static stringtype_t BLACKATTR[] = { 145 | { "ACTION", TYPE_ATTR_URL } /* form */ 146 | , { "ATTRIBUTENAME", TYPE_ATTR_INDIRECT } /* SVG allow indirection of attribute names */ 147 | , { "BY", TYPE_ATTR_URL } /* SVG */ 148 | , { "BACKGROUND", TYPE_ATTR_URL } /* IE6, O11 */ 149 | , { "DATAFORMATAS", TYPE_BLACK } /* IE */ 150 | , { "DATASRC", TYPE_BLACK } /* IE */ 151 | , { "DYNSRC", TYPE_ATTR_URL } /* Obsolete img attribute */ 152 | , { "FILTER", TYPE_STYLE } /* Opera, SVG inline style */ 153 | , { "FORMACTION", TYPE_ATTR_URL } /* HTML 5 */ 154 | , { "FOLDER", TYPE_ATTR_URL } /* Only on A tags, IE-only */ 155 | , { "FROM", TYPE_ATTR_URL } /* SVG */ 156 | , { "HANDLER", TYPE_ATTR_URL } /* SVG Tiny, Opera */ 157 | , { "HREF", TYPE_ATTR_URL } 158 | , { "LOWSRC", TYPE_ATTR_URL } /* Obsolete img attribute */ 159 | , { "POSTER", TYPE_ATTR_URL } /* Opera 10,11 */ 160 | , { "SRC", TYPE_ATTR_URL } 161 | , { "STYLE", TYPE_STYLE } 162 | , { "TO", TYPE_ATTR_URL } /* SVG */ 163 | , { "VALUES", TYPE_ATTR_URL } /* SVG */ 164 | , { "XLINK:HREF", TYPE_ATTR_URL } 165 | , { NULL, TYPE_NONE } 166 | }; 167 | 168 | /* xmlns */ 169 | /* `xml-stylesheet` > , */ 170 | 171 | /* 172 | static const char* BLACKATTR[] = { 173 | "ATTRIBUTENAME", 174 | "BACKGROUND", 175 | "DATAFORMATAS", 176 | "HREF", 177 | "SCROLL", 178 | "SRC", 179 | "STYLE", 180 | "SRCDOC", 181 | NULL 182 | }; 183 | */ 184 | 185 | static const char* BLACKTAG[] = { 186 | "APPLET" 187 | /* , "AUDIO" */ 188 | , "BASE" 189 | , "COMMENT" /* IE http://html5sec.org/#38 */ 190 | , "EMBED" 191 | /* , "FORM" */ 192 | , "FRAME" 193 | , "FRAMESET" 194 | , "HANDLER" /* Opera SVG, effectively a script tag */ 195 | , "IFRAME" 196 | , "IMPORT" 197 | , "ISINDEX" 198 | , "LINK" 199 | , "LISTENER" 200 | /* , "MARQUEE" */ 201 | , "META" 202 | , "NOSCRIPT" 203 | , "OBJECT" 204 | , "SCRIPT" 205 | , "STYLE" 206 | /* , "VIDEO" */ 207 | , "VMLFRAME" 208 | , "XML" 209 | , "XSS" 210 | , NULL 211 | }; 212 | 213 | 214 | static int cstrcasecmp_with_null(const char *a, const char *b, size_t n) 215 | { 216 | char ca; 217 | char cb; 218 | /* printf("Comparing to %s %.*s\n", a, (int)n, b); */ 219 | while (n-- > 0) { 220 | cb = *b++; 221 | if (cb == '\0') continue; 222 | 223 | ca = *a++; 224 | 225 | if (cb >= 'a' && cb <= 'z') { 226 | cb -= 0x20; 227 | } 228 | /* printf("Comparing %c vs %c with %d left\n", ca, cb, (int)n); */ 229 | if (ca != cb) { 230 | return 1; 231 | } 232 | } 233 | 234 | if (*a == 0) { 235 | /* printf(" MATCH \n"); */ 236 | return 0; 237 | } else { 238 | return 1; 239 | } 240 | } 241 | 242 | /* 243 | * Does an HTML encoded binary string (const char*, length) start with 244 | * a all uppercase c-string (null terminated), case insensitive! 245 | * 246 | * also ignore any embedded nulls in the HTML string! 247 | * 248 | * return 1 if match / starts with 249 | * return 0 if not 250 | */ 251 | static int htmlencode_startswith(const char *a, const char *b, size_t n) 252 | { 253 | size_t consumed; 254 | int cb; 255 | int first = 1; 256 | /* printf("Comparing %s with %.*s\n", a,(int)n,b); */ 257 | while (n > 0) { 258 | if (*a == 0) { 259 | /* printf("Match EOL!\n"); */ 260 | return 1; 261 | } 262 | cb = html_decode_char_at(b, n, &consumed); 263 | b += consumed; 264 | n -= consumed; 265 | 266 | if (first && cb <= 32) { 267 | /* ignore all leading whitespace and control characters */ 268 | continue; 269 | } 270 | first = 0; 271 | 272 | if (cb == 0) { 273 | /* always ignore null characters in user input */ 274 | continue; 275 | } 276 | 277 | if (cb == 10) { 278 | /* always ignore vertical tab characters in user input */ 279 | /* who allows this?? */ 280 | continue; 281 | } 282 | 283 | if (cb >= 'a' && cb <= 'z') { 284 | /* upcase */ 285 | cb -= 0x20; 286 | } 287 | 288 | if (*a != (char) cb) { 289 | /* printf(" %c != %c\n", *a, cb); */ 290 | /* mismatch */ 291 | return 0; 292 | } 293 | a++; 294 | } 295 | 296 | return (*a == 0) ? 1 : 0; 297 | } 298 | 299 | static int is_black_tag(const char* s, size_t len) 300 | { 301 | const char** black; 302 | 303 | if (len < 3) { 304 | return 0; 305 | } 306 | 307 | black = BLACKTAG; 308 | while (*black != NULL) { 309 | if (cstrcasecmp_with_null(*black, s, len) == 0) { 310 | /* printf("Got black tag %s\n", *black); */ 311 | return 1; 312 | } 313 | black += 1; 314 | } 315 | 316 | /* anything SVG related */ 317 | if ((s[0] == 's' || s[0] == 'S') && 318 | (s[1] == 'v' || s[1] == 'V') && 319 | (s[2] == 'g' || s[2] == 'G')) { 320 | /* printf("Got SVG tag \n"); */ 321 | return 1; 322 | } 323 | 324 | /* Anything XSL(t) related */ 325 | if ((s[0] == 'x' || s[0] == 'X') && 326 | (s[1] == 's' || s[1] == 'S') && 327 | (s[2] == 'l' || s[2] == 'L')) { 328 | /* printf("Got XSL tag\n"); */ 329 | return 1; 330 | } 331 | 332 | return 0; 333 | } 334 | 335 | static attribute_t is_black_attr(const char* s, size_t len) 336 | { 337 | stringtype_t* black; 338 | 339 | if (len < 2) { 340 | return TYPE_NONE; 341 | } 342 | 343 | if (len >= 5) { 344 | /* JavaScript on.* */ 345 | if ((s[0] == 'o' || s[0] == 'O') && (s[1] == 'n' || s[1] == 'N')) { 346 | /* printf("Got JavaScript on- attribute name\n"); */ 347 | return TYPE_BLACK; 348 | } 349 | 350 | 351 | 352 | /* XMLNS can be used to create arbitrary tags */ 353 | if (cstrcasecmp_with_null("XMLNS", s, 5) == 0 || cstrcasecmp_with_null("XLINK", s, 5) == 0) { 354 | /* printf("Got XMLNS and XLINK tags\n"); */ 355 | return TYPE_BLACK; 356 | } 357 | } 358 | 359 | black = BLACKATTR; 360 | while (black->name != NULL) { 361 | if (cstrcasecmp_with_null(black->name, s, len) == 0) { 362 | /* printf("Got banned attribute name %s\n", black->name); */ 363 | return black->atype; 364 | } 365 | black += 1; 366 | } 367 | 368 | return TYPE_NONE; 369 | } 370 | 371 | static int is_black_url(const char* s, size_t len) 372 | { 373 | 374 | static const char* data_url = "DATA"; 375 | static const char* viewsource_url = "VIEW-SOURCE"; 376 | 377 | /* obsolete but interesting signal */ 378 | static const char* vbscript_url = "VBSCRIPT"; 379 | 380 | /* covers JAVA, JAVASCRIPT, + colon */ 381 | static const char* javascript_url = "JAVA"; 382 | 383 | /* skip whitespace */ 384 | while (len > 0 && (*s <= 32 || *s >= 127)) { 385 | /* 386 | * HEY: this is a signed character. 387 | * We are intentionally skipping high-bit characters too 388 | * since they are not ASCII, and Opera sometimes uses UTF-8 whitespace. 389 | * 390 | * Also in EUC-JP some of the high bytes are just ignored. 391 | */ 392 | ++s; 393 | --len; 394 | } 395 | 396 | if (htmlencode_startswith(data_url, s, len)) { 397 | return 1; 398 | } 399 | 400 | if (htmlencode_startswith(viewsource_url, s, len)) { 401 | return 1; 402 | } 403 | 404 | if (htmlencode_startswith(javascript_url, s, len)) { 405 | return 1; 406 | } 407 | 408 | if (htmlencode_startswith(vbscript_url, s, len)) { 409 | return 1; 410 | } 411 | return 0; 412 | } 413 | 414 | int libinjection_is_xss(const char* s, size_t len, int flags) 415 | { 416 | h5_state_t h5; 417 | attribute_t attr = TYPE_NONE; 418 | 419 | libinjection_h5_init(&h5, s, len, (enum html5_flags) flags); 420 | while (libinjection_h5_next(&h5)) { 421 | if (h5.token_type != ATTR_VALUE) { 422 | attr = TYPE_NONE; 423 | } 424 | 425 | if (h5.token_type == DOCTYPE) { 426 | return 1; 427 | } else if (h5.token_type == TAG_NAME_OPEN) { 428 | if (is_black_tag(h5.token_start, h5.token_len)) { 429 | return 1; 430 | } 431 | } else if (h5.token_type == ATTR_NAME) { 432 | attr = is_black_attr(h5.token_start, h5.token_len); 433 | } else if (h5.token_type == ATTR_VALUE) { 434 | /* 435 | * IE6,7,8 parsing works a bit differently so 436 | * a whole ", 17 | ">" 18 | "x >", 19 | "' >", 20 | "\">", 21 | "red;", 22 | "red;}", 23 | "red;\"/>", 24 | "');}", 25 | "onerror=alert(1)>", 26 | "x onerror=alert(1);>", 27 | "x' onerror=alert(1);>", 28 | "x\" onerror=alert(1);>", 29 | "", 30 | "", 31 | "", 32 | "", 33 | "", 34 | "", 35 | "123 LIKE -1234.5678E+2;", 36 | "APPLE 19.123 'FOO' \"BAR\"", 37 | "/* BAR */ UNION ALL SELECT (2,3,4)", 38 | "1 || COS(+0X04) --FOOBAR", 39 | "dog apple @cat banana bar", 40 | "dog apple cat \"banana \'bar", 41 | "102 TABLE CLOTH", 42 | "(1001-'1') union select 1,2,3,4 from credit_cards", 43 | NULL 44 | }; 45 | const int imax = 1000000; 46 | int i, j; 47 | size_t slen; 48 | clock_t t0,t1; 49 | double total; 50 | int tps; 51 | 52 | t0 = clock(); 53 | for (i = imax, j=0; i != 0; --i, ++j) { 54 | if (s[j] == NULL) { 55 | j = 0; 56 | } 57 | 58 | slen = strlen(s[j]); 59 | libinjection_xss(s[j], slen); 60 | } 61 | 62 | t1 = clock(); 63 | total = (double) (t1 - t0) / (double) CLOCKS_PER_SEC; 64 | tps = (int)((double) imax / total); 65 | return tps; 66 | } 67 | 68 | int main() 69 | { 70 | const int mintps = 500000; 71 | int tps = testIsSQL(); 72 | 73 | printf("\nTPS : %d\n\n", tps); 74 | 75 | if (tps < 500000) { 76 | printf("FAIL: %d < %d\n", tps, mintps); 77 | /* FAIL */ 78 | return 1; 79 | } else { 80 | printf("OK: %d > %d\n", tps, mintps); 81 | /* OK */ 82 | return 0; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /libinjection/testdriver.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "libinjection.h" 7 | #include "libinjection_sqli.h" 8 | #include "libinjection_html5.h" 9 | #include "libinjection_xss.h" 10 | 11 | static char g_test[8096]; 12 | static char g_input[8096]; 13 | static char g_expected[8096]; 14 | 15 | size_t modp_rtrim(char* str, size_t len); 16 | size_t print_string(char* buf, size_t len, stoken_t* t); 17 | size_t print_var(char* buf, size_t len, stoken_t* t); 18 | size_t print_token(char* buf, size_t len, stoken_t *t); 19 | int read_file(const char* fname, int flags, int testtype); 20 | const char* h5_type_to_string(enum html5_type x); 21 | size_t print_html5_token(char* buf, size_t len, h5_state_t* hs); 22 | 23 | size_t modp_rtrim(char* str, size_t len) 24 | { 25 | while (len) { 26 | char c = str[len -1]; 27 | if (c == ' ' || c == '\n' || c == '\t' || c == '\r') { 28 | str[len -1] = '\0'; 29 | len -= 1; 30 | } else { 31 | break; 32 | } 33 | } 34 | return len; 35 | } 36 | 37 | size_t print_string(char* buf, size_t len, stoken_t* t) 38 | { 39 | int slen = 0; 40 | 41 | /* print opening quote */ 42 | if (t->str_open != '\0') { 43 | slen = sprintf(buf + len, "%c", t->str_open); 44 | assert(slen >= 0); 45 | len += (size_t) slen; 46 | } 47 | 48 | /* print content */ 49 | slen = sprintf(buf + len, "%s", t->val); 50 | assert(slen >= 0); 51 | len += (size_t) slen; 52 | 53 | /* print closing quote */ 54 | if (t->str_close != '\0') { 55 | slen = sprintf(buf + len, "%c", t->str_close); 56 | assert(slen >= 0); 57 | len += (size_t) slen; 58 | } 59 | 60 | return len; 61 | } 62 | 63 | size_t print_var(char* buf, size_t len, stoken_t* t) 64 | { 65 | int slen = 0; 66 | if (t->count >= 1) { 67 | slen = sprintf(buf + len, "%c", '@'); 68 | assert(slen >= 0); 69 | len += (size_t) slen; 70 | } 71 | if (t->count == 2) { 72 | slen = sprintf(buf + len, "%c", '@'); 73 | assert(slen >= 0); 74 | len += (size_t) slen; 75 | } 76 | return print_string(buf, len, t); 77 | } 78 | 79 | const char* h5_type_to_string(enum html5_type x) 80 | { 81 | switch (x) { 82 | case DATA_TEXT: return "DATA_TEXT"; 83 | case TAG_NAME_OPEN: return "TAG_NAME_OPEN"; 84 | case TAG_NAME_CLOSE: return "TAG_NAME_CLOSE"; 85 | case TAG_NAME_SELFCLOSE: return "TAG_NAME_SELFCLOSE"; 86 | case TAG_DATA: return "TAG_DATA"; 87 | case TAG_CLOSE: return "TAG_CLOSE"; 88 | case ATTR_NAME: return "ATTR_NAME"; 89 | case ATTR_VALUE: return "ATTR_VALUE"; 90 | case TAG_COMMENT: return "TAG_COMMENT"; 91 | case DOCTYPE: return "DOCTYPE"; 92 | default: 93 | assert(0); 94 | } 95 | return ""; 96 | } 97 | 98 | size_t print_html5_token(char* buf, size_t len, h5_state_t* hs) 99 | { 100 | int slen; 101 | char* tmp = (char*) malloc(hs->token_len + 1); 102 | memcpy(tmp, hs->token_start, hs->token_len); 103 | /* TODO.. encode to be printable */ 104 | tmp[hs->token_len] = '\0'; 105 | 106 | slen = sprintf(buf + len, "%s,%d,%s\n", 107 | h5_type_to_string(hs->token_type), 108 | (int) hs->token_len, 109 | tmp); 110 | len += (size_t) slen; 111 | free(tmp); 112 | return len; 113 | } 114 | 115 | size_t print_token(char* buf, size_t len, stoken_t *t) 116 | { 117 | int slen; 118 | 119 | slen = sprintf(buf + len, "%c ", t->type); 120 | assert(slen >= 0); 121 | len += (size_t) slen; 122 | switch (t->type) { 123 | case 's': 124 | len = print_string(buf, len, t); 125 | break; 126 | case 'v': 127 | len = print_var(buf, len, t); 128 | break; 129 | default: 130 | slen = sprintf(buf + len, "%s", t->val); 131 | assert(slen >= 0); 132 | len += (size_t) slen; 133 | } 134 | slen = sprintf(buf + len, "%c", '\n'); 135 | assert(slen >= 0); 136 | len += (size_t) slen; 137 | return len; 138 | } 139 | 140 | int read_file(const char* fname, int flags, int testtype) 141 | { 142 | int count = 0; 143 | FILE *fp = NULL; 144 | char linebuf[8192]; 145 | char g_actual[8192]; 146 | char* bufptr = NULL; 147 | size_t slen; 148 | char* copy; 149 | sfilter sf; 150 | int ok = 1; 151 | int num_tokens; 152 | int issqli; 153 | int i; 154 | 155 | g_test[0] = '\0'; 156 | g_input[0] = '\0'; 157 | g_expected[0] = '\0'; 158 | 159 | fp = fopen(fname, "r"); 160 | while(fgets(linebuf, sizeof(linebuf), fp) != NULL) { 161 | if (count == 0 && strcmp(linebuf, "--TEST--\n") == 0) { 162 | bufptr = g_test; 163 | count = 1; 164 | } else if (count == 1 && strcmp(linebuf, "--INPUT--\n") == 0) { 165 | bufptr = g_input; 166 | count = 2; 167 | } else if (count == 2 && strcmp(linebuf, "--EXPECTED--\n") == 0) { 168 | bufptr = g_expected; 169 | count = 3; 170 | } else { 171 | assert(bufptr != NULL); 172 | strcat(bufptr, linebuf); 173 | } 174 | } 175 | fclose(fp); 176 | if (count != 3) { 177 | return 1; 178 | } 179 | 180 | g_expected[modp_rtrim(g_expected, strlen(g_expected))] = '\0'; 181 | g_input[modp_rtrim(g_input, strlen(g_input))] = '\0'; 182 | 183 | 184 | slen = strlen(g_input); 185 | copy = (char* ) malloc(slen); 186 | memcpy(copy, g_input, slen); 187 | 188 | g_actual[0] = '\0'; 189 | if (testtype == 0) { 190 | /* 191 | * print SQLi tokenization only 192 | */ 193 | libinjection_sqli_init(&sf, copy, slen, flags); 194 | libinjection_sqli_callback(&sf, NULL, NULL); 195 | slen =0; 196 | while (libinjection_sqli_tokenize(&sf) == 1) { 197 | slen = print_token(g_actual, slen, sf.current); 198 | } 199 | } else if (testtype == 1) { 200 | /* 201 | * testing tokenization + folding 202 | */ 203 | libinjection_sqli_init(&sf, copy, slen, flags); 204 | libinjection_sqli_callback(&sf, NULL, NULL); 205 | slen =0; 206 | num_tokens = libinjection_sqli_fold(&sf); 207 | for (i = 0; i < num_tokens; ++i) { 208 | slen = print_token(g_actual, slen, libinjection_sqli_get_token(&sf, i)); 209 | } 210 | } else if (testtype == 2) { 211 | /** 212 | * test SQLi detection 213 | */ 214 | char buf[100]; 215 | issqli = libinjection_sqli(copy, slen, buf); 216 | if (issqli) { 217 | sprintf(g_actual, "%s", buf); 218 | } 219 | } else if (testtype == 3) { 220 | /* 221 | * test HTML 5 tokenization only 222 | */ 223 | 224 | h5_state_t hs; 225 | libinjection_h5_init(&hs, copy, slen, DATA_STATE); 226 | slen = 0; 227 | while (libinjection_h5_next(&hs)) { 228 | slen = print_html5_token(g_actual, slen, &hs); 229 | } 230 | } else if (testtype == 4) { 231 | /* 232 | * test XSS detection 233 | */ 234 | sprintf(g_actual, "%d", libinjection_xss(copy, slen)); 235 | } else { 236 | fprintf(stderr, "Got strange testtype value of %d\n", testtype); 237 | assert(0); 238 | } 239 | 240 | g_actual[modp_rtrim(g_actual, strlen(g_actual))] = '\0'; 241 | 242 | if (strcmp(g_expected, g_actual) != 0) { 243 | printf("INPUT: \n%s\n==\n", g_input); 244 | printf("EXPECTED: \n%s\n==\n", g_expected); 245 | printf("GOT: \n%s\n==\n", g_actual); 246 | ok = 0; 247 | } 248 | 249 | free(copy); 250 | return ok; 251 | } 252 | 253 | int main(int argc, char** argv) 254 | { 255 | int offset = 1; 256 | int i; 257 | int ok; 258 | int count = 0; 259 | int count_fail = 0; 260 | int flags = 0; 261 | int testtype = 0; 262 | int quiet = 0; 263 | 264 | const char* fname; 265 | while (argc > offset) { 266 | if (strcmp(argv[offset], "-q") == 0 || strcmp(argv[offset], "--quiet") == 0) { 267 | quiet = 1; 268 | offset += 1; 269 | } else { 270 | break; 271 | } 272 | } 273 | 274 | printf("%s\n", libinjection_version()); 275 | 276 | for (i = offset; i < argc; ++i) { 277 | fname = argv[i]; 278 | count += 1; 279 | if (strstr(fname, "test-tokens-")) { 280 | flags = FLAG_QUOTE_NONE | FLAG_SQL_ANSI; 281 | testtype = 0; 282 | } else if (strstr(fname, "test-folding-")) { 283 | flags = FLAG_QUOTE_NONE | FLAG_SQL_ANSI; 284 | testtype = 1; 285 | } else if (strstr(fname, "test-sqli-")) { 286 | flags = FLAG_NONE; 287 | testtype = 2; 288 | } else if (strstr(fname, "test-html5-")) { 289 | flags = FLAG_NONE; 290 | testtype = 3; 291 | } else if (strstr(fname, "test-xss-")) { 292 | flags = FLAG_NONE; 293 | testtype = 4; 294 | } else { 295 | fprintf(stderr, "Unknown test type: %s, failing\n", fname); 296 | count_fail += 1; 297 | continue; 298 | } 299 | 300 | ok = read_file(fname, flags, testtype); 301 | if (ok) { 302 | if (! quiet) { 303 | fprintf(stderr, "%s: ok\n", fname); 304 | } 305 | } else { 306 | count_fail += 1; 307 | if (! quiet) { 308 | fprintf(stderr, "%s: fail\n", fname); 309 | } 310 | } 311 | } 312 | return count > 0 && count_fail > 0; 313 | } 314 | -------------------------------------------------------------------------------- /t/waf.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # (C) vislee 4 | 5 | # Tests for http waf module. 6 | 7 | ############################################################################### 8 | 9 | use warnings; 10 | use strict; 11 | 12 | use Test::More; 13 | 14 | use Socket qw/ CRLF /; 15 | 16 | BEGIN { use FindBin; chdir($FindBin::Bin); } 17 | 18 | use lib 'lib'; 19 | use Test::Nginx; 20 | 21 | ############################################################################### 22 | 23 | select STDERR; $| = 1; 24 | select STDOUT; $| = 1; 25 | 26 | my $t = Test::Nginx->new(); 27 | 28 | $t->write_file_expand('nginx.conf', <<'EOF'); 29 | 30 | %%TEST_GLOBALS%% 31 | 32 | daemon off; 33 | 34 | load_module /tmp/nginx/modules/ngx_http_waf_module.so; 35 | 36 | events { 37 | } 38 | 39 | http { 40 | %%TEST_GLOBALS_HTTP%% 41 | 42 | security_rule id:1001 "str:sw@/test" z:#URL "note:test url"; 43 | security_rule id:1002 "str:sw@/hello" "z:X_URL:/hello/[a-z]{3,5}"; 44 | 45 | security_rule id:1010 "str:ct@testct" "z:V_ARGS:teststr"; 46 | security_rule id:1110 "str:!ct@testct" "z:V_ARGS:teststrnotct"; 47 | security_rule id:1011 "str:eq@testeq" "z:V_ARGS:teststr"; 48 | security_rule id:1111 "str:!eq@testeq" "z:V_ARGS:teststrnoteq"; 49 | security_rule id:1012 "str:sw@testsw" "z:V_ARGS:teststr"; 50 | security_rule id:1112 "str:!sw@testsw" "z:V_ARGS:teststrnotsw"; 51 | security_rule id:1013 "str:ew@testew" "z:V_ARGS:teststr"; 52 | security_rule id:1113 "str:!ew@testew" "z:V_ARGS:teststrnotew"; 53 | security_rule id:1014 "str:rx@test-[a-z]{3}-done" "z:V_ARGS:teststr"; 54 | security_rule id:1114 "str:!rx@test-[a-z]{3}-done" "z:V_ARGS:teststrnotrx"; 55 | security_rule id:1015 "libinj:sql" "z:V_ARGS:teststr"; 56 | security_rule id:1016 "libinj:xss" "z:V_ARGS:teststr"; 57 | security_rule id:1017 "str:ge@def" "z:V_ARGS:testge"; 58 | security_rule id:1117 "str:!ge@def" "z:V_ARGS:testnotge"; 59 | security_rule id:1018 "str:le@def" "z:V_ARGS:testle"; 60 | security_rule id:1118 "str:!le@def" "z:V_ARGS:testnotle"; 61 | security_rule id:1019 "libmagic:mime_type@text/plain" "z:V_ARGS:testmagic"; 62 | security_rule id:1119 "libmagic:!mime_type@text/plain" "z:V_ARGS:testmagicnot"; 63 | security_rule id:1020 "libmagic:mime_type@error" "z:V_ARGS:testmagicerror"; 64 | security_rule id:1120 "libmagic:!mime_type@error" "z:V_ARGS:testmagicerrornot"; 65 | security_rule id:1501 "hash:md5@32269ae63a25306bb46a03d6f38bd2b7" "z:V_ARGS:testmd5"; 66 | security_rule id:1502 "hash:!md5@4935f6e27eff994304a1a72768581ce5" "z:V_ARGS:testnotmd5"; 67 | security_rule id:1503 "hash:crc32@4160194954" "z:V_ARGS:testcrc32"; 68 | security_rule id:1504 "hash:crc32_long@800313341" "z:V_ARGS:testcrc32_long"; 69 | 70 | security_rule id:1601 "str:decode_url|eq@xx&yy zz" "z:V_ARGS:testdecodeurl"; 71 | security_rule id:1602 "str:decode_base64|decode_url|eq@xx&yy zz" "z:V_ARGS:testdecodebase64url"; 72 | security_rule id:1603 "str:decode_base64|decode_base64|eq@testdecodebase64base64" "z:V_ARGS:testdecodebase64base64"; 73 | security_rule id:1604 "str:decode_url|decode_url|ct@xx&yy ZZ" "z:V_ARGS:testdecodeurlurl"; 74 | security_rule id:1605 "libinj:decode_base64|xss" "z:V_ARGS:testdecodebase64xss"; 75 | security_rule id:1606 "libinj:decode_base64|decode_url|xss" "z:V_ARGS:testdecodebase64urlxss"; 76 | security_rule id:1607 "libinj:decode_url|decode_url|xss" "z:V_ARGS:testdecodeurlurlxss"; 77 | 78 | security_rule id:2001 "str:eq@argskv" "z:ARGS"; 79 | security_rule id:2002 "str:eq@argsonlyval" "z:#ARGS"; 80 | security_rule id:2003 "str:eq@argsonlykey" "z:@ARGS"; 81 | 82 | security_rule id:2004 "str:eq@argsvbar" "z:V_ARGS:foo"; 83 | security_rule id:2005 "str:eq@argsxbar" "z:X_ARGS:^x-[a-z]{2,3}-regex$"; 84 | security_rule id:2006 "str:eq@/allowurl" "s:$ALLOW:5" "z:#URL"; 85 | 86 | security_rule id:3001 "str:eq@headerkeyval" "z:HEADERS"; 87 | security_rule id:3002 "str:eq@headeronlykey" "z:@HEADERS"; 88 | security_rule id:3003 "str:eq@headeronlyval" "z:#HEADERS"; 89 | security_rule id:3004 "str:eq@headervbar" "z:V_HEADERS:foo"; 90 | security_rule id:3005 "str:eq@headerxbar" "z:X_HEADERS:^X-[A,B,C,D]{2,4}-regex$"; 91 | 92 | security_rule id:4000 "str:eq@testwl0" "s:$WL0:2" "z:ARGS"; 93 | security_rule id:4001 "str:eq@testwl1" "s:$WL1:2" "z:ARGS"; 94 | security_rule id:4002 "str:eq@testwl2" "z:V_ARGS:foo|V_ARGS:bar"; 95 | security_rule id:4003 "str:eq@testwl3" "z:V_ARGS:foo|V_ARGS:bar"; 96 | security_rule id:4004 "str:eq@testwl4" "z:HEADERS"; 97 | security_rule id:4005 "str:eq@testwl5" "z:ARGS"; 98 | security_rule id:4006 "str:eq@testwl6" "z:ARGS"; 99 | security_rule id:4007 "str:eq@testwl7" "z:ARGS"; 100 | security_rule id:4008 "str:eq@testwl8" "z:ARGS"; 101 | security_rule id:4009 "str:eq@testwl9" "z:HEADERS"; 102 | 103 | security_rule id:5001 "str:eq@testcalc" "s:$CALC1:2" "z:V_ARGS:foo|V_ARGS:bar"; 104 | 105 | security_rule id:6001 "str:eq@testvar" "s:$VAR:2" "z:V_ARGS:foo|V_ARGS:bar"; 106 | 107 | security_rule id:7001 "str:ct@testbody" "z:#RAW_BODY"; 108 | security_rule id:7002 "str:eq@testurlencodebody" "z:V_BODY:foo"; 109 | security_rule id:7003 "str:eq@multibar" "z:V_BODY:multifoo"; 110 | 111 | security_rule id:8001 "str:ct@eval" "z:#FILE"; 112 | security_rule id:8002 "str:ct@testphp" "z:X_FILE:^[a-z]{1,5}\.php$"; 113 | 114 | security_rule id:9001 "str:eq@testscorecheck" "s:$TESTCHK:10" "z:V_ARGS:foo"; 115 | 116 | 117 | server { 118 | listen 127.0.0.1:8080; 119 | server_name localhost; 120 | 121 | large_client_header_buffers 4 2k; 122 | 123 | location / { 124 | security_waf on; 125 | security_timeout 100ms; 126 | 127 | client_body_buffer_size 8k; 128 | 129 | security_loc_rule "wl:4000" "z:@ARGS"; 130 | security_loc_rule "wl:4002" "z:V_ARGS:bar"; 131 | security_loc_rule "wl:4003" "z:ARGS"; 132 | security_loc_rule "wl:4004" "z:X_HEADERS:x-[a-z]{1,5}|V_HEADERS:foo"; 133 | security_loc_rule "wl:4005" "z:ARGS"; 134 | security_loc_rule "wl:4007"; 135 | security_loc_rule "wl:4009" "z:X_HEADERS:x-[a-z]{1,5}|V_HEADERS:foo|@HEADERS"; 136 | 137 | security_check $WL0>3 BLOCK; 138 | security_check $WL1>3 BLOCK; 139 | security_check $CALC1>3 BLOCK; 140 | security_check $VAR>3 $var_res; 141 | security_check $ALLOW>1 ALLOW; 142 | 143 | security_log off; 144 | 145 | proxy_pass http://127.0.0.1:8081/$var_res; 146 | 147 | location /innerlocation/ { 148 | security_waf on; 149 | 150 | security_loc_rule id:20001 "str:eq@innerlocation" "s:$WL0:3" "z:V_ARGS:Foo_location"; 151 | 152 | security_check $WL0>4 BLOCK; 153 | 154 | proxy_pass http://127.0.0.1:8081/; 155 | } 156 | } 157 | } 158 | 159 | server { 160 | listen 127.0.0.1:8081; 161 | server_name localhost; 162 | 163 | location / { 164 | return 200 "ok"; 165 | } 166 | 167 | location /block { 168 | return "302" "http://test.com/"; 169 | } 170 | } 171 | } 172 | 173 | EOF 174 | 175 | 176 | $t->try_run('no waf')->plan(136); 177 | 178 | ############################################################################### 179 | 180 | like(http_get('/testwaf'), qr/403 Forbidden/, 'waf_1001: test url block'); 181 | like(http_get('/'), qr/200 OK/, 'waf_1001: url test ok'); 182 | 183 | like(http_get('/hello/waf'), qr/403 Forbidden/, 'waf_1002: test url block'); 184 | like(http_get('/hello/hellowaf'), qr/403 Forbidden/, 'waf_1002: test url ok'); 185 | 186 | like(http_get("/?teststr=hello testct world"), 187 | qr/403 Forbidden/, 'waf_1010: test contain block'); 188 | like(http_get("/?TESTstr=hello TESTct world"), 189 | qr/403 Forbidden/, 'waf_1010: test case contain block'); 190 | like(http_get("/?teststr=hello world"), 191 | qr/200 OK/, 'waf_1010: test contain ok'); 192 | like(http_get("/?Teststrnotct=hello world"), 193 | qr/403 Forbidden/, 'waf_1110: test not case contain ok'); 194 | like(http_get("/?Teststrnotct=hello TestCT world"), 195 | qr/200 OK/, 'waf_1110: test not case contain ok'); 196 | 197 | like(http_get("/?teststr=testeq"), 198 | qr/403 Forbidden/, 'waf_1011: test equal block'); 199 | like(http_get("/?teststr=testequal"), 200 | qr/200 OK/, 'waf_1011: test equal ok'); 201 | like(http_get("/?teststrnoteq=testeq"), 202 | qr/200 OK/, 'waf_1111: test notequal ok'); 203 | like(http_get("/?teststrNotEQ=testequal"), 204 | qr/403 Forbidden/, 'waf_1111: test notequal block'); 205 | 206 | like(http_get("/?teststr=testsw world"), 207 | qr/403 Forbidden/, 'waf_1012: test startwith block'); 208 | like(http_get("/?teststr=hello testsw world"), 209 | qr/200 OK/, 'waf_1012: test startwith ok'); 210 | like(http_get("/?teststrnotsw=testsw world"), 211 | qr/200 OK/, 'waf_1112: test startwith ok'); 212 | like(http_get("/?teststrnotsw=hello testsw world"), 213 | qr/403 Forbidden/, 'waf_1112: test startwith block'); 214 | 215 | like(http_get("/?teststr=hello testew"), 216 | qr/403 Forbidden/, 'waf_1013: test endwith block'); 217 | like(http_get("/?teststr=hello testew world"), 218 | qr/200 OK/, 'waf_1013: test endwith ok'); 219 | like(http_get("/?teststrnotew=hello testew"), 220 | qr/200 OK/, 'waf_1113: test endwith ok'); 221 | like(http_get("/?teststrnotew=hello testew world"), 222 | qr/403 Forbidden/, 'waf_1113: test endwith block'); 223 | 224 | like(http_get("/?teststr=test-abc-done"), 225 | qr/403 Forbidden/, 'waf_1014: test regex block'); 226 | like(http_get("/?teststr=test-abcd-done"), 227 | qr/200 OK/, 'waf_1014: test regex ok'); 228 | like(http_get("/?teststrnotrx=test-abc-done"), 229 | qr/200 OK/, 'waf_1114: test regex ok'); 230 | like(http_get("/?teststrnotrx=test-abcd-done"), 231 | qr/403 Forbidden/, 'waf_1114: test regex block'); 232 | 233 | like(http_get("/?teststr=1 or 1=1"), 234 | qr/403 Forbidden/, 'waf_1015: test sqli block'); 235 | like(http_get("/?teststr=1"), 236 | qr/200 OK/, 'waf_1015: test sqli ok'); 237 | like(http_get("/?teststr=\"/>" . CRLF . 607 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 608 | "Content-Disposition: form-data; name='multifoo'" . CRLF . 609 | CRLF . 610 | "multibar" . CRLF . 611 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4--" . CRLF 612 | ), qr/403 Forbidden/, 'waf_7003: test body multipart block'); 613 | 614 | 615 | like(http( 616 | "POST / HTTP/1.1" . CRLF . 617 | "Host: localhost" . CRLF . 618 | "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 619 | "Content-Length: 410" . CRLF . 620 | "Connection: close" . CRLF . 621 | CRLF . 622 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 623 | "Content-Disposition: form-data; name='MAX_FILE_SIZE'" . CRLF . 624 | CRLF . 625 | "100000" . CRLF . 626 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 627 | "Content-Disposition: form-data; name='uploaded'; filename='ttt'" . CRLF . 628 | "Content-Type: application/octet-stream" . CRLF . 629 | CRLF . 630 | "" . CRLF . 631 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 632 | "Content-Disposition: form-data; name='multifoo'" . CRLF . 633 | CRLF . 634 | "multifoo" . CRLF . 635 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4--" . CRLF 636 | ), qr/200 OK/, 'waf_7003: test body multipart block'); 637 | 638 | 639 | like(http( 640 | "POST / HTTP/1.1" . CRLF . 641 | "Host: localhost" . CRLF . 642 | "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 643 | "Content-Length: 430" . CRLF . 644 | "Connection: close" . CRLF . 645 | CRLF . 646 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 647 | "Content-Disposition: form-data; name='MAX_FILE_SIZE'" . CRLF . 648 | CRLF . 649 | "100000" . CRLF . 650 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 651 | "Content-Disposition: form-data; name='uploaded'; filename='ttt'" . CRLF . 652 | "Content-Type: application/octet-stream" . CRLF . 653 | CRLF . 654 | "" . CRLF . 655 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 656 | "Content-Disposition: form-data; name='Upload'" . CRLF . 657 | CRLF . 658 | "Upload" . CRLF . 659 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4--" . CRLF 660 | ), qr/403 Forbidden/, 'waf_8001: test body multipart block'); 661 | 662 | like(http( 663 | "POST / HTTP/1.1" . CRLF . 664 | "Host: localhost" . CRLF . 665 | "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 666 | "Content-Length: 430" . CRLF . 667 | "Connection: close" . CRLF . 668 | CRLF . 669 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 670 | "Content-Disposition: form-data; name='MAX_FILE_SIZE'" . CRLF . 671 | CRLF . 672 | "100000" . CRLF . 673 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 674 | "Content-Disposition: form-data; name='uploaded'; filename='ttt'" . CRLF . 675 | "Content-Type: application/octet-stream" . CRLF . 676 | CRLF . 677 | "" . CRLF . 678 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 679 | "Content-Disposition: form-data; name='Upload'" . CRLF . 680 | CRLF . 681 | "Upload" . CRLF . 682 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4--" . CRLF 683 | ), qr/200 OK/, 'waf_8001: test body multipart ok'); 684 | 685 | 686 | like(http( 687 | "POST / HTTP/1.1" . CRLF . 688 | "Host: localhost" . CRLF . 689 | "Content-Type: multipart/form-data; boundary= '----WebKitFormBoundaryoWJTVDAYOLw4Tlo4'" . CRLF . 690 | "Content-Length: 425" . CRLF . 691 | "Connection: close" . CRLF . 692 | CRLF . 693 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 694 | "Content-Disposition: form-data; name='MAX_FILE_SIZE'" . CRLF . 695 | CRLF . 696 | "100000" . CRLF . 697 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 698 | "Content-Disposition: form-data; filename=xxx; name='uploaded'; filename='empty'" . CRLF . 699 | "Content-Type: application/octet-stream" . CRLF . 700 | CRLF . 701 | "test eval" . CRLF . 702 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 703 | "Content-Disposition: form-data; name='Upload'" . CRLF . 704 | CRLF . 705 | "Upload" . CRLF . 706 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4--" . CRLF 707 | ), qr/403 Forbidden/, 'waf_8001: test body multipart block'); 708 | 709 | 710 | like(http( 711 | "POST / HTTP/1.1" . CRLF . 712 | "Host: localhost" . CRLF . 713 | "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoWJTVDAYOLw4Tlxx" . CRLF . 714 | "Content-Length: 430" . CRLF . 715 | "Connection: close" . CRLF . 716 | CRLF . 717 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlxx" . CRLF . 718 | "Content-Disposition: form-data; name='MAX_FILE_SIZE'" . CRLF . 719 | CRLF . 720 | "100000" . CRLF . 721 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlxx" . CRLF . 722 | "Content-Disposition: form-data; name='uploaded'; filename='ttt.php'" . CRLF . 723 | "Content-Type: application/octet-stream" . CRLF . 724 | CRLF . 725 | "hello testphp xxxxxxxxxxxxxxxxx" . CRLF . 726 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlxx" . CRLF . 727 | "Content-Disposition: form-data; name=Upload" . CRLF . 728 | CRLF . 729 | "Upload" . CRLF . 730 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlxx--" . CRLF 731 | ), qr/403 Forbidden/, 'waf_8002: test specify filename body multipart block'); 732 | 733 | 734 | like(http( 735 | "POST / HTTP/1.1" . CRLF . 736 | "Host: localhost" . CRLF . 737 | "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 738 | "Content-Length: 430" . CRLF . 739 | "Connection: close" . CRLF . 740 | CRLF . 741 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 742 | "Content-Disposition: form-data; name='MAX_FILE_SIZE'" . CRLF . 743 | CRLF . 744 | "100000" . CRLF . 745 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 746 | "Content-Disposition: form-data; name='uploaded'; filename='ttt'" . CRLF . 747 | "Content-Type: application/octet-stream" . CRLF . 748 | CRLF . 749 | "hello testphp xxxxxxxxxxxxxxxxxx" . CRLF . 750 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 751 | "Content-Disposition: form-data; name='Upload'" . CRLF . 752 | CRLF . 753 | "Upload" . CRLF . 754 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4--" . CRLF 755 | ), qr/200 OK/, 'waf_8002: test specify filename body multipart ok'); 756 | 757 | 758 | like(http( 759 | "POST / HTTP/1.1" . CRLF . 760 | "Host: localhost" . CRLF . 761 | "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 762 | "Content-Length: 3700" . CRLF . 763 | "Connection: close" . CRLF . 764 | CRLF . 765 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 766 | "Content-Disposition: form-data; name='MAX_FILE_SIZE'" . CRLF . 767 | CRLF . 768 | "100000" . CRLF . 769 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 770 | "Content-Disposition: form-data; name='uploaded'; filename=aaa; filename='empty'" . CRLF . 771 | "Content-Type: application/octet-stream" . CRLF . 772 | CRLF . 773 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 774 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 775 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 776 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 777 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 778 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 779 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 780 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 781 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 782 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 783 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 784 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 785 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 786 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 787 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 788 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 789 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 790 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 791 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 792 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 793 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 794 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 795 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 796 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 797 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 798 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 799 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 800 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 801 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 802 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 803 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 804 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 805 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 806 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 807 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 808 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 809 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 810 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 811 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 812 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 813 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 814 | "eval" . CRLF . 815 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 816 | "Content-Disposition: form-data; name='Upload'" . CRLF . 817 | CRLF . 818 | "Upload" . CRLF . 819 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4--" . CRLF 820 | ), qr/403 Forbidden/, 'waf_8001: test body multipart block'); 821 | 822 | 823 | 824 | like(http( 825 | "POST / HTTP/1.1" . CRLF . 826 | "Host: localhost" . CRLF . 827 | "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 828 | "Content-Length: 12500" . CRLF . 829 | "Connection: close" . CRLF . 830 | CRLF . 831 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 832 | "Content-Disposition: form-data; name='MAX_FILE_SIZE'" . CRLF . 833 | CRLF . 834 | "100000" . CRLF . 835 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 836 | "Content-Disposition: form-data; name='uploaded'; filename=aaa; filename='empty'" . CRLF . 837 | "Content-Type: application/octet-stream" . CRLF . 838 | CRLF . 839 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 840 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 841 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 842 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 843 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 844 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 845 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 846 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 847 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 848 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 849 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 850 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 851 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 852 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 853 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 854 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 855 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 856 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 857 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 858 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 859 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 860 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 861 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 862 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 863 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 864 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 865 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 866 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 867 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 868 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 869 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 870 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 871 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 872 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 873 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 874 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 875 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 876 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 877 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 878 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 879 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 880 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 881 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 882 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 883 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 884 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 885 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 886 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 887 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 888 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 889 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 890 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 891 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 892 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 893 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 894 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 895 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 896 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 897 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 898 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 899 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 900 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 901 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 902 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 903 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 904 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 905 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 906 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 907 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 908 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 909 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 910 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 911 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 912 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 913 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 914 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 915 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 916 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 917 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 918 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 919 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 920 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 921 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 922 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 923 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 924 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 925 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 926 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 927 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 928 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 929 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 930 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 931 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 932 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 933 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 934 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 935 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 936 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 937 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 938 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 939 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 940 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 941 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 942 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 943 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 944 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 945 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 946 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 947 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 948 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 949 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 950 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 951 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 952 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 953 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 954 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 955 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 956 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 957 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 958 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 959 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 960 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 961 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 962 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 963 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 964 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 965 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 966 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 967 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 968 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 969 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 970 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 971 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 972 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 973 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 974 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 975 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 976 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 977 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 978 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 979 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 980 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 981 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 982 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 983 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 984 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 985 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 986 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 987 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 988 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 989 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" . 990 | "eval" . CRLF . 991 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4" . CRLF . 992 | "Content-Disposition: form-data; name='Upload'" . CRLF . 993 | CRLF . 994 | "Upload" . CRLF . 995 | "------WebKitFormBoundaryoWJTVDAYOLw4Tlo4--" . CRLF 996 | ), qr/200 OK/, 'waf_8001: test body multipart overflow'); 997 | 998 | 999 | like(http_get("/?foo=testscorecheck"), 1000 | qr/200 OK/, 'waf_9001: test empty score check ok'); 1001 | ############################################################################### 1002 | -------------------------------------------------------------------------------- /t/waf_log.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # (C) vislee 4 | 5 | # Tests for http waf module. 6 | 7 | ############################################################################### 8 | 9 | use warnings; 10 | use strict; 11 | 12 | use Test::More; 13 | 14 | use IO::Select; 15 | 16 | BEGIN { use FindBin; chdir($FindBin::Bin); } 17 | 18 | use lib 'lib'; 19 | use Test::Nginx; 20 | 21 | ############################################################################### 22 | 23 | select STDERR; $| = 1; 24 | select STDOUT; $| = 1; 25 | 26 | my $t = Test::Nginx->new()->plan(6) 27 | ->write_file_expand('nginx.conf', <<'EOF'); 28 | 29 | %%TEST_GLOBALS%% 30 | 31 | daemon off; 32 | 33 | load_module /tmp/nginx/modules/ngx_http_waf_module.so; 34 | 35 | events { 36 | } 37 | 38 | http { 39 | %%TEST_GLOBALS_HTTP%% 40 | 41 | server { 42 | listen 127.0.0.1:8080; 43 | server_name localhost; 44 | 45 | 46 | location /sec/log { 47 | security_loc_rule id:1001 "str:eq@test" "z:ARGS"; 48 | security_loc_rule id:1002 "str:eq@waflog" "s:$TLOG:2" "z:ARGS"; 49 | security_loc_rule id:1003 "str:ct@/allow/url" "s:$ALLOW:2" "z:#URL"; 50 | 51 | security_waf on; 52 | 53 | security_check $TLOG>3 LOG; 54 | security_check $ALLOW>1 ALLOW; 55 | 56 | security_log %%TESTDIR%%/waf.log; 57 | 58 | proxy_pass http://127.0.0.1:8082/; 59 | } 60 | 61 | location /log/syslog { 62 | security_loc_rule id:2001 "str:eq@test" "z:ARGS"; 63 | 64 | security_waf on; 65 | security_log syslog:server=127.0.0.1:%%PORT_8985_UDP%%,tag=SEETHIS; 66 | 67 | proxy_pass http://127.0.0.1:8082/; 68 | } 69 | 70 | location /log/unflat { 71 | security_loc_rule id:3001 "str:eq@test" "z:ARGS"; 72 | security_loc_rule id:3002 "str:eq@unflat" "s:$TLOG:2" "z:ARGS"; 73 | 74 | 75 | security_waf on; 76 | security_check $TLOG>3 DROP; 77 | 78 | security_log %%TESTDIR%%/waf_unflat.log unflat; 79 | proxy_pass http://127.0.0.1:8082/; 80 | } 81 | } 82 | 83 | server { 84 | listen 127.0.0.1:8082; 85 | server_name localhost; 86 | 87 | location / { 88 | return 200 "ok"; 89 | } 90 | } 91 | } 92 | 93 | EOF 94 | 95 | $t->run(); 96 | 97 | ############################################################################### 98 | 99 | my $s = IO::Socket::INET->new( 100 | Proto => 'udp', 101 | LocalAddr => '127.0.0.1:' . port(8985) 102 | ) 103 | or die "Can't open syslog socket: $!"; 104 | 105 | 106 | http_get('/log/unflat?foo=test'); 107 | http_get('/log/unflat?foo=unflat&bar=unflat'); 108 | http_get('/sec/log?foo=test'); 109 | http_get('/sec/log?waflog=hello&hello=waflog'); 110 | http_get('/sec/log/allow/url?waflog=hello&hello=waflog'); 111 | like(get_syslog('/log/syslog?foo=test'), qr/SEETHIS:/, 'waf syslog tag'); 112 | 113 | $t->stop(); 114 | 115 | like($t->read_file('waf_unflat.log'), qr/"rule": {"id": "3001"/, 'waf unflat log'); 116 | like($t->read_file('waf_unflat.log'), qr/"result": "DROP", "TLOG": {"total": "4", "rule": /, 'waf unflat log'); 117 | like($t->read_file('waf.log'), qr/"rule_BLOCK_1001_score": "0"/, 'waf log'); 118 | like($t->read_file('waf.log'), qr/"TLOG_total": "4"/, 'waf log'); 119 | like($t->read_file('waf.log'), qr/"ALLOW_total": "2"/, 'waf log'); 120 | 121 | ############################################################################### 122 | sub get_syslog { 123 | my ($uri) = @_; 124 | my $data = ''; 125 | 126 | http_get($uri); 127 | 128 | IO::Select->new($s)->can_read(1); 129 | while (IO::Select->new($s)->can_read(0.1)) { 130 | my $buffer; 131 | sysread($s, $buffer, 4096); 132 | $data .= $buffer; 133 | } 134 | return $data; 135 | } --------------------------------------------------------------------------------