├── .github ├── ISSUE_TEMPLATE │ ├── bug_report-zh-cn.md │ └── bug_report.md ├── stale.yml └── workflows │ └── test.yml ├── .gitignore ├── CHANGES-ZH-CN.md ├── CHANGES.md ├── LICENSE ├── Makefile ├── README-ZH-CN.md ├── README.md ├── assets ├── logo.png ├── rules │ ├── advanced │ ├── args │ ├── cookie │ ├── ipv4 │ ├── ipv6 │ ├── post │ ├── referer │ ├── url │ ├── user-agent │ ├── white-ipv4 │ ├── white-ipv6 │ ├── white-referer │ └── white-url ├── under-attack.html └── versioning.drawio ├── bison └── parser.yacc ├── config ├── flex └── lexer.lex ├── inc ├── ngx_http_waf_module_check.h ├── ngx_http_waf_module_config.h ├── ngx_http_waf_module_core.h ├── ngx_http_waf_module_ip_trie.h ├── ngx_http_waf_module_lru_cache.h ├── ngx_http_waf_module_macro.h ├── ngx_http_waf_module_mem_pool.h ├── ngx_http_waf_module_type.h ├── ngx_http_waf_module_under_attack.h ├── ngx_http_waf_module_util.h └── ngx_http_waf_module_vm.h ├── src ├── ngx_http_waf_module_check.c ├── ngx_http_waf_module_config.c ├── ngx_http_waf_module_core.c ├── ngx_http_waf_module_ip_trie.c ├── ngx_http_waf_module_lru_cache.c ├── ngx_http_waf_module_mem_pool.c ├── ngx_http_waf_module_under_attack.c ├── ngx_http_waf_module_util.c └── ngx_http_waf_module_vm.c └── test ├── test-nginx ├── init.sh ├── start.sh ├── t │ └── .gitkeep └── template │ ├── args.t │ ├── bad-config.t │ ├── cache.t │ ├── cc.t │ ├── cookie.t │ ├── disable.t │ ├── ipv4.t │ ├── ipv6.t │ ├── mode.t │ ├── post.t │ ├── referer.t │ ├── under-attack.t │ ├── uri.t │ ├── user-agent.t │ └── var.t └── wrk └── rand.lua /.github/ISSUE_TEMPLATE/bug_report-zh-cn.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 报告 3 | about: 缺陷报告 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **提交时请删除以下内容。** 11 | 12 | 其实我不太喜欢按照固定的格式写东西,所以您也不需要,但是下面的内容可以帮助 Bug 的解决。 13 | 14 | ## 强烈建议提供的信息 15 | 16 | * 如何触发错误。 17 | * ngx_waf 的版本/分支。 18 | * `nginx -V` 命令的输出内容。 19 | * 调试日志([如何获取调试日志](#如何获取调试日志))。 20 | * 出错时 `shell` 的输出内容。 21 | 22 | ## 可以提供的信息 23 | 24 | 这些信息通常不会用到,但是必要时可能会向您询问。 25 | 26 | * 操作系统,包括名称和版本。 27 | * `nginx` 是否运行在某些虚拟环境内,比如有没有运行在 `Docker` 里。如果运行在 `Docker` 内请提供镜像名称。 28 | 29 | ## 如何获取调试日志 30 | 31 | 本文模块会条件满足的情况下提供调试日志,如果您提供这些日志将有利于 Bug 的定位。您可以按照下面的方式获取调试日志。 32 | 33 | 1. 在 `nginx` 配置文件中调整错误日志的等级为 `debug`,例如 `error_log logs/error.log debug;`。 34 | 2. 关闭 `nginx`,然后清空现有的 `error.log` 文件(必要的话可以在清空前备份),然后启动 `nginx`。 35 | 3. 触发 Bug。 36 | 4. 上传 `error.log` 文件,注意消除文件中的隐私信息。 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Please delete the following content when submitting.** 11 | 12 | I don't really like to write things in a set format, so you don't need to follow the format for this report. But please read the following, as it will help fix the bugs. 13 | 14 | ## Key Info 15 | 16 | It is strongly recommended that you provide the following information. 17 | 18 | * How to trigger this bug? 19 | * The version or branch of `ngx_waf`. 20 | * Output of `nginx -V`. 21 | * Debug log ([How to get the debug log?](how-to-get-the-debug-log)). 22 | * Output of `shell` (if any). 23 | 24 | ## Optional Info 25 | 26 | In most cases you should not provide this information, but the program maintainer will ask you for it if necessary. 27 | 28 | * The name and version of the OS. 29 | * Whether `nginx` is running in a virtual environment such as `Docker`. If it is running in `Docker`, please provide the image name. 30 | 31 | ## How to get the debug log 32 | 33 | This module will output debug logs under certain conditions to facilitate bug location.You can get the debug log by following the steps below. 34 | 35 | 1. Set the error log level to `debug` in the configuration file for `nginx`, e.g. `error_log logs/error.log debug;` 36 | 2. Shut down `nginx`, then clear `error.log` (back it up if necessary), and finally start `nginx`. 37 | 3. Triggers the bug you want to report. 38 | 4. Upload `error.log` and remember to clear the privacy information from the file. 39 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 7 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 8 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - long-term 10 | # Label to use when marking an issue as stale 11 | staleLabel: stale 12 | # Comment to post when marking an issue as stale. Set to `false` to disable 13 | markComment: | 14 | This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. 15 | 此 issue 因为最近没有任何活动已经被标记,如果在此之后的一段时间内仍没有任何活动则会被关闭。感谢您对项目的支持。 16 | 17 | # Comment to post when closing a stale issue. Set to `false` to disable 18 | closeComment: false -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - lts* 8 | - current* 9 | pull_request: 10 | schedule: 11 | - cron: '0 0 * * SUN' 12 | workflow_dispatch: 13 | 14 | 15 | defaults: 16 | run: 17 | shell: bash 18 | 19 | jobs: 20 | native: 21 | runs-on: ubuntu-latest 22 | 23 | strategy: 24 | matrix: 25 | nginx-version: 26 | - '1.18.0' 27 | - '1.20.2' 28 | - '1.22.1' 29 | - '1.24.0' 30 | - '1.26.2' 31 | - '1.27.3' 32 | install-type: ['static module', 'dynamic module'] 33 | 34 | env: 35 | LIB_INJECTION: ${{ github.workspace }}/libinjection 36 | 37 | steps: 38 | - name: Checkout sources 39 | uses: actions/checkout@v2 40 | 41 | - name: Checkout libinjection sources 42 | uses: actions/checkout@v2 43 | with: 44 | repository: 'libinjection/libinjection' 45 | path: 'libinjection-src' 46 | 47 | - uses: actions/setup-python@v2 48 | with: 49 | python-version: '3.x' 50 | architecture: 'x64' 51 | 52 | - name: Install dependencies 53 | run: | 54 | sudo apt-get --yes update 55 | sudo apt-get install --yes libsodium23 libsodium-dev build-essential zlib1g-dev libpcre3 libpcre3-dev libssl-dev libxslt1-dev libxml2-dev libgeoip-dev libgd-dev libperl-dev uthash-dev flex bison 56 | sudo apt-get remove --yes python3-urllib3 57 | sudo pip install lastversion 58 | 59 | - name: Install libinjection 60 | run: | 61 | cd libinjection-src 62 | ./autogen.sh 63 | ./configure --prefix=${{ env.LIB_INJECTION }} 64 | make -j$(nproc) 65 | sudo make install 66 | 67 | - name: Download nginx-${{ matrix.nginx-version }} 68 | run: | 69 | wget https://nginx.org/download/nginx-${{ matrix.nginx-version }}.tar.gz 70 | mkdir nginx-src 71 | tar zxf nginx-*.tar.gz --directory nginx-src --strip-components=1 72 | 73 | - name: Configure nginx-${{ matrix.install-type }} 74 | run: | 75 | make parser 76 | cd nginx-src 77 | if [ ${{ matrix.install-type }} = 'static module' ] ; then \ 78 | opt='--add-module' ;\ 79 | else \ 80 | opt='--add-dynamic-module' ;\ 81 | fi 82 | LIB_INJECTION=${{ env.LIB_INJECTION }} ./configure ${opt}=.. --with-cc-opt='-Wno-unused-but-set-variable -Wno-unused-function -fstack-protector-strong -Wno-sign-compare' --with-http_realip_module 83 | 84 | - name: Install ${{ matrix.nginx-version }} 85 | run: | 86 | cd nginx-src 87 | make -j$(nproc) 88 | sudo make install 89 | sudo useradd nginx -s /sbin/nologin -M 90 | sudo chmod 777 -R /usr/local/nginx 91 | sudo ln -s /usr/local/nginx/sbin/nginx /usr/local/bin/nginx 92 | 93 | - name: Install Test::Nginx 94 | run: | 95 | sudo cpan Test::Nginx 96 | 97 | - name: Test 98 | run: | 99 | sudo chmod 777 -R /tmp 100 | cd test/test-nginx 101 | export MODULE_TEST_PATH=/tmp/module_test 102 | sh ./init.sh 103 | exec sudo sh start.sh t/*.t 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | docs/.vuepress/dist 4 | yarn.lock 5 | package-lock.json 6 | test/test-nginx/t/servroot 7 | test/test-nginx/t/*.t 8 | inc/libinjection 9 | inc/libsodium 10 | inc/ngx_http_waf_module_lexer.h 11 | inc/ngx_http_waf_module_parser.tab.h 12 | src/ngx_http_waf_module_lexer.c 13 | src/ngx_http_waf_module_parser.tab.c 14 | *.so -------------------------------------------------------------------------------- /CHANGES-ZH-CN.md: -------------------------------------------------------------------------------- 1 | 见 [https://github.com/ADD-SP/ngx_waf-docs/blob/master/docs/zh-cn/changes/overview.md](https://github.com/ADD-SP/ngx_waf-docs/blob/master/docs/zh-cn/changes/overview.md)。 -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | See [https://github.com/ADD-SP/ngx_waf-docs/blob/master/docs/changes/overview.md](https://github.com/ADD-SP/ngx_waf-docs/blob/master/docs/changes/overview.md). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, ADD-SP 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | parser: flex/lexer.lex bison/parser.yacc 2 | @flex flex/lexer.lex 3 | @bison --defines=inc/ngx_http_waf_module_parser.tab.h -L C -o src/ngx_http_waf_module_parser.tab.c bison/parser.yacc -------------------------------------------------------------------------------- /README-ZH-CN.md: -------------------------------------------------------------------------------- 1 | # ngx_waf 2 | 3 | 4 |

5 | 6 |

7 | 8 | 9 | [![test](https://github.com/ADD-SP/ngx_waf/actions/workflows/test.yml/badge.svg)](https://github.com/ADD-SP/ngx_waf/actions/workflows/test.yml) 10 | 11 | [![Notification](https://img.shields.io/badge/Notification-Telegram%20Channel-blue)](https://t.me/ngx_waf) 12 | [![Discussion EN](https://img.shields.io/badge/Discussion%20EN-Telegram%20Group-blue)](https://t.me/group_ngx_waf) 13 | [![Discussion CN](https://img.shields.io/badge/Discussion%20CN-Telegram%20Group-blue)](https://t.me/group_ngx_waf_cn) 14 | 15 | [English](README.md) | 简体中文 16 | 17 | 方便且高性能的 Nginx 防火墙模块。 18 | 19 | ## 为什么选择 ngx_waf 20 | 21 | * 基础防护:如 IP 或 IP 网段的黑白名单、URI 黑白名单和请求体黑名单等。 22 | * 使用简单:配置文件和规则文件书写简单,可读性强。 23 | * 高性能:使用高效的 IP 检查算法和缓存机制。 24 | * 高级防护:兼容 [ModSecurity](https://github.com/SpiderLabs/ModSecurity),因此你可以使用[开放式网络应用安全项目(OWASP)® 的核心规则库](https://owasp.org/www-project-modsecurity-core-rule-set/)。 25 | * 友好爬虫验证:支持验证 Google、Bing、Baidu 和 Yandex 的爬虫并自动放行,避免错误拦截。 26 | * 验证码:支持三种验证码:hCaptcha、reCAPTCHAv2 和 reCAPTCHAv3。 27 | 28 | ## 功能 29 | 30 | * 兼容 [ModSecurity](https://github.com/SpiderLabs/ModSecurity)。此功能仅限最新的 Current 版本。 31 | * SQL 注入防护(Powered By [libinjection](https://github.com/libinjection/libinjection))。 32 | * XSS 攻击防护(Powered By [libinjection](https://github.com/libinjection/libinjection))。 33 | * 支持 IPV4 和 IPV6。 34 | * 支持开启验证码(CAPTCHA),支持 [hCaptcha](https://www.hcaptcha.com/)、[reCAPTCHAv2](https://developers.google.com/recaptcha) 和 [reCAPTCHAv3](https://developers.google.com/recaptcha)。此功能仅限最新的 Current 版本。 35 | * 支持识别友好爬虫(如 BaiduSpider)并自动放行(基于 User-Agent 和 IP 的识别)。此功能仅限最新的 Current 版本。 36 | * CC 防御,超出限制后自动拉黑对应 IP 一段时间。 37 | * IP 黑白名单,同时支持类似 `192.168.0.0/16` 和 `fe80::/10`,即支持点分十进制和冒号十六进制表示法和网段划分。 38 | * POST 黑名单。 39 | * URL 黑白名单 40 | * 查询字符串(Query String)黑名单。 41 | * UserAgent 黑名单。 42 | * Cookie 黑名单。 43 | * Referer 黑白名单。 44 | 45 | ## 使用文档 46 | 47 | * 推荐链接:[https://docs.addesp.com/ngx_waf/zh-cn/](https://docs.addesp.com/ngx_waf/zh-cn/) 48 | * 备用链接 1:[https://add-sp.github.io/ngx_waf-docs/zh-cn/](https://add-sp.github.io/ngx_waf-docs/zh-cn/) 49 | * 备用链接 2:[https://ngx-waf-docs.pages.dev/zh-cn/](https://ngx-waf-docs.pages.dev/zh-cn/) 50 | 51 | ## 联系方式 52 | 53 | * Telegram 频道: [https://t.me/ngx_waf](https://t.me/ngx_waf) 54 | * Telegram 群组(英文): [https://t.me/group_ngx_waf](https://t.me/group_ngx_waf) 55 | * Telegram 群组(中文):[https://t.me/group_ngx_waf_cn](https://t.me/group_ngx_waf_cn) 56 | 57 | ## 打赏 58 | 59 | 打赏就算了,如果您愿意,您可以帮助宣传一下本项目。比如发个贴,推荐给身边有需求的人什么的。 60 | 61 | 我从来没碰过钱,我对钱没有兴趣。 62 | 63 | ## 测试套件 64 | 65 | 本项目使用一个 Perl 开发的数据驱动型的测试套件进行测试。 66 | 感谢项目 [Test::Nginx](http://search.cpan.org/perldoc?Test::Nginx) 及其开发者们。 67 | 68 | 你可以通过下列命令来运行测试。 69 | 70 | ```shell 71 | # 这行命令的执行时间比较长,但是以后再测试的时候就不需要运行了。 72 | cpan Test::Nginx 73 | 74 | # 你需要指定一个临时目录。 75 | # 如果目录不存在会自动创建。 76 | # 如果目录已经会被存在则会先**删除**再创建。 77 | export MODULE_TEST_PATH=/path/to/temp/dir 78 | 79 | # 如果你安装了动态模块则需要指定动态模块的绝对路径,反之则无需执行这行命令。 80 | export MODULE_PATH=/path/to/ngx_http_waf_module.so 81 | 82 | cd ./test/test-nginx 83 | sh ./init.sh 84 | sh ./start.sh ./t/*.t 85 | ``` 86 | 87 | 88 | ## 开源许可证 89 | 90 | [BSD 3-Clause License](LICENSE) 91 | 92 | ## 感谢 93 | 94 | * [ModSecurity](https://github.com/SpiderLabs/ModSecurity):开源且跨平台的 WAF 引擎。 95 | * [uthash](https://github.com/troydhanson/uthash):C 语言的哈希表、数组、链表等容器库。 96 | * [libcurl](https://curl.se/libcurl/):支持多种协议文件传输库。 97 | * [cJSON](https://github.com/DaveGamble/cJSON):C 语言的轻量级 JSON 解析库。 98 | * [libinjection](https://github.com/libinjection/libinjection):SQL 注入检测库。 99 | * [libsodium](https://github.com/jedisct1/libsodium):C 语言密码函数库。 100 | * [test-nginx](https://github.com/openresty/test-nginx): 数据驱动的 nginx 测试套件,可用于 nginx C 模块的开发和 OpenResty Lua 库的开发。 101 | * [lastversion](https://github.com/dvershinin/lastversion):一个轻巧的命令行工具,帮助你下载或安装一个项目的特定版本。 102 | * [ngx_lua_waf](https://github.com/loveshell/ngx_lua_waf):一个基于 lua-nginx-module (openresty) 的 web 应用防火墙。 103 | * [nginx-book](https://github.com/taobao/nginx-book):Nginx开发从入门到精通 104 | * [nginx-development-guide](https://github.com/baishancloud/nginx-development-guide):Nginx 开发指南。 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ngx_waf 2 | 3 |

4 | 5 |

6 | 7 | [![test](https://github.com/ADD-SP/ngx_waf/workflows/test/badge.svg)](https://github.com/ADD-SP/ngx_waf/actions?query=workflow%3Atest) 8 | 9 | [![Notification](https://img.shields.io/badge/Notification-Telegram%20Channel-blue)](https://t.me/ngx_waf) 10 | [![Discussion EN](https://img.shields.io/badge/Discussion%20EN-Telegram%20Group-blue)](https://t.me/group_ngx_waf) 11 | [![Discussion CN](https://img.shields.io/badge/Discussion%20CN-Telegram%20Group-blue)](https://t.me/group_ngx_waf_cn) 12 | 13 | English | [简体中文](README-ZH-CN.md) 14 | 15 | Handy, High performance Nginx firewall module. 16 | 17 | ## Why ngx_waf 18 | 19 | * Basic protection: such as black and white list of IPs or IP range, uri black and white list, and request body black list, etc. 20 | * Easy to use: configuration files and rule files are easy to write and readable. 21 | * High performance: Efficient algorithms and caching. 22 | * Advanced protection: [ModSecurity](https://github.com/SpiderLabs/ModSecurity) compatible, you can use [OWASP(Open Web Application Security Project®) ModSecurity Core Rule Set](https://owasp.org/www-project-modsecurity-core-rule-set/). 23 | * Friendly crawler verification: Supports verifying Google, Bing, Baidu and Yandex crawlers and allowing them automatically to avoid false positives. 24 | * Captcha: Supports three kinds of captchas: hCaptcha, reCAPTCHAv2 and reCAPTCHAv3. 25 | 26 | ## Features 27 | 28 | * [ModSecurity](https://github.com/SpiderLabs/ModSecurity) compatible. This feature is only available in the latest `Current` version. 29 | * Rules that are compatible with [ModSecurity](https://github.com/SpiderLabs/ModSecurity). 30 | * Anti SQL injection (powered by [libinjection](https://github.com/libinjection/libinjection)). 31 | * Anti XSS (powered by [libinjection](https://github.com/libinjection/libinjection)). 32 | * IPV4 and IPV6 support. 33 | * Support for enabling CAPTCHAs, including [hCaptcha](https://www.hcaptcha.com/), [reCAPTCHAv2](https://developers.google.com/recaptcha) and [reCAPTCHAv3](https://developers.google.com/recaptcha). This feature is only available in the latest `Current` version. 34 | * Support authentication-friendly crawlers (based on user agent and IP identification) to avoid blocking of these crawlers (e.g. GoogleBot). This feature is only available in the latest `Current` version. 35 | * Anti Challenge Collapsar, it can automatically block malicious IP. 36 | * Exceptional allow on specific IP address. 37 | * Block the specified IP address. 38 | * Block the specified request body. 39 | * Exceptional allow on specific URL. 40 | * Block the specified URL. 41 | * Block the specified query string. 42 | * Block the specified UserAgent. 43 | * Block the specified Cookie. 44 | * Exceptional allow on specific Referer. 45 | * Block the specified Referer. 46 | 47 | ## Docs 48 | 49 | * Recommended link: [https://docs.addesp.com/ngx_waf/](https://docs.addesp.com/ngx_waf/) 50 | * Alternate link 1: [https://add-sp.github.io/ngx_waf-docs/](https://add-sp.github.io/ngx_waf-docs/) 51 | * Alternate link 2: [https://ngx-waf-docs.pages.dev/](https://ngx-waf-docs.pages.dev/) 52 | 53 | ## Contact 54 | 55 | * Telegram Channel: [https://t.me/ngx_waf](https://t.me/ngx_waf) 56 | * Telegram Group (English): [https://t.me/group_ngx_waf](https://t.me/group_ngx_waf) 57 | * Telegram Group (Chinese): [https://t.me/group_ngx_waf_cn](https://t.me/group_ngx_waf_cn) 58 | 59 | ## Sponsor 60 | 61 | Hope you can help promote this project. The more stars got, the better this project is. :) 62 | 63 | ## Test Suite 64 | 65 | This module comes with a Perl-driven test suite. The test cases are declarative too. 66 | Thanks to the [Test::Nginx](http://search.cpan.org/perldoc?Test::Nginx) module in the Perl world. 67 | 68 | To run it on your side: 69 | 70 | ```shell 71 | ## It will take a lot of time, but it only needs to be run once. 72 | cpan Test::Nginx 73 | 74 | # You need to specify a temporary directory. 75 | # If the directory does not exist it will be created automatically. 76 | # If the directory already exists it will be **removed** first and then created. 77 | export MODULE_TEST_PATH=/path/to/temp/dir 78 | 79 | # You need to specify the absolute path to the dynamic module if you have it installed, 80 | # otherwise you do not need to run this line. 81 | export MODULE_PATH=/path/to/ngx_http_waf_module.so 82 | 83 | cd ./test/test-nginx 84 | sh ./init.sh 85 | sh ./start.sh ./t/*.t 86 | ``` 87 | 88 | Some parts of the test suite requires standard modules proxy, rewrite and SSI to be enabled as well when building Nginx. 89 | 90 | ## License 91 | 92 | [BSD 3-Clause License](LICENSE) 93 | 94 | ## Thanks 95 | 96 | * [ModSecurity](https://github.com/SpiderLabs/ModSecurity): An open source, cross platform web application firewall (WAF) engine. 97 | * [uthash](https://github.com/troydhanson/uthash): C macros for hash tables and more. 98 | * [libcurl](https://curl.se/libcurl/): The multiprotocol file transfer library . 99 | * [cJSON](https://github.com/DaveGamble/cJSON): Ultralightweight JSON parser in ANSI C. 100 | * [libinjection](https://github.com/libinjection/libinjection): SQL / SQLI tokenizer parser analyzer. 101 | * [libsodium](https://github.com/jedisct1/libsodium): A modern, portable, easy to use crypto library. 102 | * [test-nginx](https://github.com/openresty/test-nginx): Data-driven test scaffold for Nginx C module and OpenResty Lua library development. 103 | * [lastversion](https://github.com/dvershinin/lastversion): A command line tool that helps you download or install a specific version of a project. 104 | * [ngx_lua_waf](https://github.com/loveshell/ngx_lua_waf): A web application firewall based on the lua-nginx-module (openresty). 105 | * [nginx-book](https://github.com/taobao/nginx-book): The Chinese language development guide for nginx. 106 | * [nginx-development-guide](https://github.com/baishancloud/nginx-development-guide): The Chinese language development guide for nginx. -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADD-SP/ngx_waf/458b6a62e26c201bc72e1366e87e775fab7d9efa/assets/logo.png -------------------------------------------------------------------------------- /assets/rules/advanced: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADD-SP/ngx_waf/458b6a62e26c201bc72e1366e87e775fab7d9efa/assets/rules/advanced -------------------------------------------------------------------------------- /assets/rules/args: -------------------------------------------------------------------------------- 1 | \.\./ 2 | \:\$ 3 | \$\{ 4 | (?i)(?:select.+(from|limit)) 5 | (?i)(?:(?:union(.*?)select)) 6 | (?i)(?:having|rongjitest) 7 | (?i)(?:sleep\((\s*)(\d*)(\s*)\)) 8 | (?i)(?:benchmark\((.*)\,(.*)\)) 9 | (?i)(?:base64_decode\() 10 | (?i)(?:(?:from\W+information_schema\W)) 11 | (?i)(?:(?:(?:current_)user|database|schema|connection_id)\s*\() 12 | (?i)(?:(?:etc\/\W*passwd)) 13 | (?i)(?:into(\s+)+(?:dump|out)file\s*) 14 | (?i)(?:group\s+by.+\() 15 | (?i)(?:xwork.MethodAccessor) 16 | (?i)(?:(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\() 17 | (?i)(?:xwork\.MethodAccessor) 18 | (?i)(?:(?:gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\:\/) 19 | (?i)(?:java\.lang) 20 | (?i)(?:\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\[) 21 | (?i)(?:\<(?:iframe|script|body|img|layer|div|meta|style|base|object|input)) 22 | (?i)(?:(?:onmouseover|onerror|onload)\=) -------------------------------------------------------------------------------- /assets/rules/cookie: -------------------------------------------------------------------------------- 1 | \.\./ 2 | \:\$ 3 | \$\{ 4 | select.+(from|limit) 5 | (?:(union(.*?)select)) 6 | having|rongjitest 7 | sleep\((\s*)(\d*)(\s*)\) 8 | benchmark\((.*)\,(.*)\) 9 | base64_decode\( 10 | (?:from\W+information_schema\W) 11 | (?:(?:current_)user|database|schema|connection_id)\s*\( 12 | (?:etc\/\W*passwd) 13 | into(\s+)+(?:dump|out)file\s* 14 | group\s+by.+\( 15 | xwork.MethodAccessor 16 | (?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\( 17 | xwork\.MethodAccessor 18 | (gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\:\/ 19 | java\.lang 20 | \$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\[ -------------------------------------------------------------------------------- /assets/rules/ipv4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADD-SP/ngx_waf/458b6a62e26c201bc72e1366e87e775fab7d9efa/assets/rules/ipv4 -------------------------------------------------------------------------------- /assets/rules/ipv6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADD-SP/ngx_waf/458b6a62e26c201bc72e1366e87e775fab7d9efa/assets/rules/ipv6 -------------------------------------------------------------------------------- /assets/rules/post: -------------------------------------------------------------------------------- 1 | (?i)(?:select.+(?:from|limit)) 2 | (?i)(?:(?:(union(.*?)select))) 3 | (?i)(?:having|rongjitest) 4 | (?i)(?:sleep\((\s*)(\d*)(\s*)\)) 5 | (?i)(?:benchmark\((.*)\,(.*)\)) 6 | (?i)(?:base64_decode\() 7 | (?i)(?:(?:from\W+information_schema\W)) 8 | (?i)(?:(?:(?:current_)user|database|schema|connection_id)\s*\() 9 | (?i)(?:(?:etc\/\W*passwd)) 10 | (?i)(?:into(\s+)+(?:dump|out)file\s*) 11 | (?i)(?:group\s+by.+\() 12 | (?i)(?:xwork.MethodAccessor) 13 | (?i)(?:(?:define|eval|file_get_contents|include|require|require_once|shell_exec|phpinfo|system|passthru|preg_\w+|execute|echo|print|print_r|var_dump|(fp)open|alert|showmodaldialog)\() 14 | (?i)(?:xwork\.MethodAccessor) 15 | (?i)(?:(?:gopher|doc|php|glob|file|phar|zlib|ftp|ldap|dict|ogg|data)\:\/) 16 | (?i)(?:java\.lang) 17 | (?i)(?:\$_(GET|post|cookie|files|session|env|phplib|GLOBALS|SERVER)\[) 18 | (?i)(?:\<(?:iframe|script|body|img|layer|div|meta|style|base|object|input)) 19 | (?i)(?:(?:onmouseover|onerror|onload)\=) -------------------------------------------------------------------------------- /assets/rules/referer: -------------------------------------------------------------------------------- 1 | (?i)(?:\.(?:svn|htaccess|bash_history)) 2 | (?i)(?:\.(?:bak|inc|old|mdb|sql|backup|java|class))$ 3 | (?i)(?:vhost|bbs|host|wwwroot|www|site|root|hytop|flashfxp).*\.rar 4 | (?i)(?:phpmyadmin|jmx-console|jmxinvokerservlet) 5 | (?i)(?:java\.lang) 6 | (?i)(?:/(?:attachments|upimg|images|css|uploadfiles|html|uploads|templets|static|template|data|inc|forumdata|upload|includes|cache|avatar)/(?:\\w+).(?:php|jsp)) -------------------------------------------------------------------------------- /assets/rules/url: -------------------------------------------------------------------------------- 1 | (?i)(?:\.(?:svn|htaccess|bash_history)) 2 | (?i)(?:\.(?:bak|inc|old|mdb|sql|backup|java|class))$ 3 | (?i)(?:vhost|bbs|host|wwwroot|www|site|root|hytop|flashfxp).*\.rar 4 | (?i)(?:phpmyadmin|jmx-console|jmxinvokerservlet) 5 | (?i)(?:java\.lang) 6 | (?i)(?:/(?:attachments|upimg|images|css|uploadfiles|html|uploads|templets|static|template|data|inc|forumdata|upload|includes|cache|avatar)/(?:\w+)\.(?:php|jsp)) -------------------------------------------------------------------------------- /assets/rules/user-agent: -------------------------------------------------------------------------------- 1 | (?i)(?:zgrab) 2 | (?i)(?:embeddedwb) 3 | (?i)(?:NSPlayer) 4 | (?i)(?:WMFSDK) 5 | (?i)(?:HTTrack) 6 | (?i)(?:harvest) 7 | (?i)(?:audit) 8 | (?i)(?:dirbuster) 9 | (?i)(?:pangolin) 10 | (?i)(?:nmap) 11 | (?i)(?:sqln) 12 | (?i)(?:-scan) 13 | (?i)(?:hydra) 14 | (?i)(?:libwww) 15 | (?i)(?:BBBike) 16 | (?i)(?:sqlmap) 17 | (?i)(?:w3af) 18 | (?i)(?:owasp) 19 | (?i)(?:Nikto) 20 | (?i)(?:fimap) 21 | (?i)(?:havij) 22 | (?i)(?:PycURL) 23 | (?i)(?:zmeu) 24 | (?i)(?:BabyKrokodil) 25 | (?i)(?:netsparker) 26 | (?i)(?:httperf) 27 | (?i)(?:bench) 28 | (?i)(?: SF/) -------------------------------------------------------------------------------- /assets/rules/white-ipv4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADD-SP/ngx_waf/458b6a62e26c201bc72e1366e87e775fab7d9efa/assets/rules/white-ipv4 -------------------------------------------------------------------------------- /assets/rules/white-ipv6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADD-SP/ngx_waf/458b6a62e26c201bc72e1366e87e775fab7d9efa/assets/rules/white-ipv6 -------------------------------------------------------------------------------- /assets/rules/white-referer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADD-SP/ngx_waf/458b6a62e26c201bc72e1366e87e775fab7d9efa/assets/rules/white-referer -------------------------------------------------------------------------------- /assets/rules/white-url: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADD-SP/ngx_waf/458b6a62e26c201bc72e1366e87e775fab7d9efa/assets/rules/white-url -------------------------------------------------------------------------------- /assets/under-attack.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Please wait 5 seconds. 8 | 15 | 16 | 17 |

18 | Your browser will jump to the page you visited after 5 seconds. 19 |

20 |

21 | 您的浏览器将在五秒后跳转到您访问的页面。 22 |

23 | 24 | -------------------------------------------------------------------------------- /assets/versioning.drawio: -------------------------------------------------------------------------------- 1 | 7VpNc5swEP01ProDyAZzjEmcHppOZ9xpzwQUUIORCyK2++srQDIIyYmTACYtuQRW0krse/shyRPgbPa3ibsN77APo4mh+fsJuJ4Yhr6Yz+m/XHIoJaY2KwVBgnzWqRKs0R/IhBqTZsiHqdCRYBwRtBWFHo5j6BFB5iYJ3ondHnAkzrp1AygJ1p4bydKfyCdhKV0YViX/DFEQ8pl10y5b7l3vMUhwFrP5JgZ4KP7K5o3LdbEPTUPXx7uaCNxMgJNgTMqnzd6BUW5bbrZy3OpE63HdCYzJWQMW5YgnN8ogX7IZ0bHLe/oQ5A9c8ICpTrpkcmBmMn9nmDdM0wLEK9qBzr+vGrmWL9/XXBFdTqlL1E/FtTkNYSqjsCnMV63R5l2ICFxvXS9v3VEOUllINhF90/MFoShycISTYizQNMfR8mEpSfAj5C0xjiFbfa0zg6uUM2bq1nFJddMyaz/BhMB9TcRMfQvxBpLkQLuwVmPGhhyOjlK+7yqS6TajUlgjGGB0cRmvg6PqClv6wOA9AbXZF9ROlhRGuhDcANj2avVauFuA96LozkdH7s+RG0ibfQJtnQQ6NKp+HLUIBq53mNJPSxGOUxnOBim8owGrjscUVoneTJ5iOS/zJ/+UDghkF3+nCNRKhNdeYsac51yBGfr7mZF3HZkxVGbMzsgOQO8qaNhj7u829zdTAtBUOUHl+S3Aa8zG5N9j8r8k1Dy/jEF+iEHePIcaXQV5VRBoGA/G/lV+PFJ9dM1YpVH4eQf9yiXtvkL5fNey31FzmtbV0l49ZznoC+csst3qdZEmm4XLEhi5BD2JpzMqW7EZvmFU0JnBAhaf5gIuM61h7xRniQfZsPp5SUOTZWrPKyJuEkAiKSqwO373WXCe3tC9L4T/KGMBV5ZmUopAXPB1qtecEikcVTFa8uoGB6krERXxmgG95rpM5EYoiOmrR2kGqXyZOyby3OiKNWyQ7+fTKMOCGDhacPapZQtcUO0CNQWp2/B1VT3Xrq+7CeHDMZ2Gy1gfGbaPFA+au7T5G6NBU0+H0UBXbe6Gg/ivbLNlZZb5AQjQEv49wg8GDf/g8W4L8B4R776c+5cRN1tCvKmnQ8QN+S7OOXgU62ZZpijd7oRKTV2VoYGXZB1s1aeGWKLxa9d6iTZTkNBooUQDsgOz0hvFwfAhACZ/Z4tU3C2//pTMNERvUgACNEMGxGoDkCHsqMb91GlnXVgCOZT3p11tqMDp2/E+ybEcN9zPbbhFgihP1zojiHzven52psAaL4L4H+bnubm4XH5+T7k1gimDqV8STPlA7FXeOVbPCkBBf4DS1+pHj+XeqfplKbj5Cw== -------------------------------------------------------------------------------- /bison/parser.yacc: -------------------------------------------------------------------------------- 1 | %{ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | int ngx_http_waf_lex (void); 10 | void ngx_http_waf_error (UT_array* array, ngx_pool_t* pool, const char* msg); 11 | void ngx_http_waf_gen_push_str_code(UT_array* array, char* str); 12 | void ngx_http_waf_gen_push_client_ip_code(UT_array* array); 13 | void ngx_http_waf_gen_push_url_code(UT_array* array); 14 | void ngx_http_waf_gen_push_user_agent_code(UT_array* array); 15 | void ngx_http_waf_gen_push_referer_code(UT_array* array); 16 | void ngx_http_waf_gen_push_query_string_code(UT_array* array, char* index); 17 | void ngx_http_waf_gen_push_header_in_code(UT_array* array, char* index); 18 | void ngx_http_waf_gen_push_cookie_code(UT_array* array, char* index); 19 | void ngx_http_waf_gen_int_code(UT_array* array, int num); 20 | void ngx_http_waf_gen_push_op_not_code(UT_array* array); 21 | void ngx_http_waf_gen_op_and_code(UT_array* array); 22 | void ngx_http_waf_gen_op_or_code(UT_array* array); 23 | void ngx_http_waf_gen_op_matches_code(UT_array* array); 24 | void ngx_http_waf_gen_op_contains_code(UT_array* array); 25 | void ngx_http_waf_gen_op_equals_code(UT_array* array); 26 | void ngx_http_waf_gen_op_belong_to_code(UT_array* array); 27 | void ngx_http_waf_gen_op_sqli_detn_code(UT_array* array); 28 | void ngx_http_waf_gen_op_xss_detn_code(UT_array* array); 29 | void ngx_http_waf_gen_act_ret_code(UT_array* array, int http_status); 30 | void ngx_http_waf_gen_act_allow_code(UT_array* array); 31 | %} 32 | 33 | %define api.prefix {ngx_http_waf_} 34 | %parse-param {UT_array* array} {ngx_pool_t* pool} 35 | 36 | // %nterm id_rule if_rule do_rule conditon conditon_ex primary rule 37 | 38 | %nterm str_operand 39 | %nterm str_op ip_op 40 | 41 | %token token_id 42 | %token token_str token_index 43 | %token token_int 44 | 45 | %token keyword_id keyword_if keyword_do keyword_or keyword_allow 46 | %token keyword_and keyword_matches keyword_equals 47 | %token token_break_line token_blank keyword_not 48 | %token keyword_url keyword_contains keyword_return 49 | %token keyword_query_string keyword_user_agent keyword_belong_to 50 | %token keyword_referer keyword_client_ip keyword_header_in 51 | %token keyword_sqli_detn keyword_xss_detn keyword_cookie 52 | 53 | %union { 54 | int int_val; 55 | unsigned int uint_val; 56 | char id_val[256]; 57 | char str_val[256]; 58 | void (*push_op_code_pt)(UT_array* array); 59 | struct { 60 | int argc; 61 | void (*no_str_pt)(UT_array* array); 62 | void (*one_str_pt)(UT_array* array, char* str); 63 | char* argv[4]; 64 | } push_str_code_info; 65 | } 66 | 67 | 68 | %% 69 | 70 | 71 | 72 | base: 73 | rule end 74 | {} 75 | 76 | | %empty 77 | {} 78 | ; 79 | 80 | rule: 81 | id_rule if_rule do_rule 82 | {} 83 | ; 84 | end: 85 | token_break_line token_break_line rule { } 86 | | token_break_line token_break_line { } 87 | | %empty { } 88 | ; 89 | 90 | 91 | id_rule: 92 | keyword_id ':' token_blank token_id token_break_line 93 | { ngx_http_waf_gen_push_str_code(array, $4); } 94 | ; 95 | 96 | if_rule: 97 | keyword_if ':' token_blank conditon token_break_line 98 | { } 99 | ; 100 | 101 | do_rule: 102 | keyword_do ':' token_blank keyword_return '(' token_int ')' 103 | { 104 | ngx_http_waf_gen_act_ret_code(array, $6); 105 | } 106 | 107 | | keyword_do ':' token_blank keyword_allow 108 | { 109 | ngx_http_waf_gen_act_allow_code(array); 110 | } 111 | ; 112 | 113 | conditon: 114 | conditon keyword_and conditon_ex 115 | { 116 | ngx_http_waf_gen_op_and_code(array); 117 | } 118 | 119 | | conditon_ex 120 | { } 121 | ; 122 | 123 | conditon_ex: 124 | conditon_ex keyword_or primary 125 | { 126 | ngx_http_waf_gen_op_or_code(array); 127 | } 128 | | primary 129 | { } 130 | ; 131 | primary: 132 | '(' conditon ')' { } 133 | 134 | | keyword_not token_blank '(' conditon ')' 135 | { 136 | ngx_http_waf_gen_push_op_not_code(array); 137 | } 138 | 139 | | str_operand str_op str_operand 140 | { 141 | switch ($3.argc) { 142 | case 0: 143 | $3.no_str_pt(array); 144 | break; 145 | case 1: 146 | $3.one_str_pt(array, $3.argv[0]); 147 | break; 148 | default: 149 | YYABORT; 150 | } 151 | 152 | switch ($1.argc) { 153 | case 0: 154 | $1.no_str_pt(array); 155 | break; 156 | case 1: 157 | $1.one_str_pt(array, $1.argv[0]); 158 | break; 159 | default: 160 | YYABORT; 161 | } 162 | $2(array); 163 | } 164 | 165 | | str_operand token_blank keyword_not str_op str_operand 166 | { 167 | switch ($5.argc) { 168 | case 0: 169 | $5.no_str_pt(array); 170 | break; 171 | case 1: 172 | $5.one_str_pt(array, $5.argv[0]); 173 | break; 174 | default: 175 | YYABORT; 176 | } 177 | 178 | switch ($1.argc) { 179 | case 0: 180 | $1.no_str_pt(array); 181 | break; 182 | case 1: 183 | $1.one_str_pt(array, $1.argv[0]); 184 | break; 185 | default: 186 | YYABORT; 187 | } 188 | $4(array); 189 | ngx_http_waf_gen_push_op_not_code(array); 190 | } 191 | 192 | | keyword_sqli_detn token_blank str_operand 193 | { 194 | switch ($3.argc) { 195 | case 0: 196 | $3.no_str_pt(array); 197 | break; 198 | case 1: 199 | $3.one_str_pt(array, $3.argv[0]); 200 | break; 201 | default: 202 | YYABORT; 203 | } 204 | ngx_http_waf_gen_op_sqli_detn_code(array); 205 | } 206 | 207 | | keyword_xss_detn token_blank str_operand 208 | { 209 | switch ($3.argc) { 210 | case 0: 211 | $3.no_str_pt(array); 212 | break; 213 | case 1: 214 | $3.one_str_pt(array, $3.argv[0]); 215 | break; 216 | default: 217 | YYABORT; 218 | } 219 | ngx_http_waf_gen_op_xss_detn_code(array); 220 | } 221 | 222 | | keyword_client_ip ip_op str_operand 223 | { 224 | switch ($3.argc) { 225 | case 0: 226 | $3.no_str_pt(array); 227 | break; 228 | case 1: 229 | $3.one_str_pt(array, $3.argv[0]); 230 | break; 231 | default: 232 | YYABORT; 233 | } 234 | ngx_http_waf_gen_push_client_ip_code(array); 235 | $2(array); 236 | } 237 | 238 | | keyword_client_ip token_blank keyword_not ip_op str_operand 239 | { 240 | switch ($5.argc) { 241 | case 0: 242 | $5.no_str_pt(array); 243 | break; 244 | case 1: 245 | $5.one_str_pt(array, $5.argv[0]); 246 | break; 247 | default: 248 | YYABORT; 249 | } 250 | ngx_http_waf_gen_push_client_ip_code(array); 251 | $4(array); 252 | ngx_http_waf_gen_push_op_not_code(array); 253 | } 254 | 255 | ; 256 | 257 | str_operand: 258 | keyword_url 259 | { 260 | $$.argc = 0; 261 | $$.no_str_pt = ngx_http_waf_gen_push_url_code; 262 | } 263 | 264 | | keyword_user_agent 265 | { 266 | $$.argc = 0; 267 | $$.no_str_pt = ngx_http_waf_gen_push_user_agent_code; 268 | } 269 | 270 | | keyword_referer 271 | { 272 | $$.argc = 0; 273 | $$.no_str_pt = ngx_http_waf_gen_push_referer_code; 274 | } 275 | 276 | | token_str 277 | { 278 | $$.argc = 1; 279 | $$.one_str_pt = ngx_http_waf_gen_push_str_code; 280 | $$.argv[0] = strdup($1); 281 | } 282 | 283 | | keyword_header_in token_index 284 | { 285 | $$.argc = 1; 286 | $$.one_str_pt = ngx_http_waf_gen_push_header_in_code; 287 | $$.argv[0] = strdup($2); 288 | } 289 | 290 | | keyword_query_string token_index 291 | { 292 | $$.argc = 1; 293 | $$.one_str_pt = ngx_http_waf_gen_push_query_string_code; 294 | $$.argv[0] = strdup($2); 295 | } 296 | 297 | | keyword_cookie token_index 298 | { 299 | $$.argc = 1; 300 | $$.one_str_pt = ngx_http_waf_gen_push_cookie_code; 301 | $$.argv[0] = strdup($2); 302 | } 303 | ; 304 | 305 | str_op: 306 | keyword_contains 307 | { 308 | $$ = ngx_http_waf_gen_op_contains_code; 309 | } 310 | 311 | | keyword_matches 312 | { 313 | $$ = ngx_http_waf_gen_op_matches_code; 314 | } 315 | 316 | | keyword_equals 317 | { 318 | $$ = ngx_http_waf_gen_op_equals_code; 319 | } 320 | ; 321 | 322 | ip_op: 323 | keyword_equals 324 | { 325 | $$ = ngx_http_waf_gen_op_equals_code; 326 | } 327 | 328 | | keyword_belong_to 329 | { 330 | $$ = ngx_http_waf_gen_op_belong_to_code; 331 | } 332 | ; 333 | %% 334 | 335 | 336 | void 337 | ngx_http_waf_gen_push_str_code(UT_array* array, char* str) { 338 | vm_code_t code; 339 | 340 | code.type = VM_CODE_PUSH_STR; 341 | code.argv.argc = 1; 342 | code.argv.type[0] = VM_DATA_STR; 343 | code.argv.value[0].str_val.data = (u_char*)strdup(str); 344 | code.argv.value[0].str_val.len = strlen(str); 345 | 346 | utarray_push_back(array, &code); 347 | free(code.argv.value[0].str_val.data); 348 | } 349 | 350 | 351 | void ngx_http_waf_gen_push_query_string_code(UT_array* array, char* index) { 352 | vm_code_t code; 353 | size_t len = strlen(index); 354 | code.type = VM_CODE_PUSH_QUERY_STRING; 355 | code.argv.argc = 1; 356 | code.argv.type[0] = VM_DATA_STR; 357 | code.argv.value[0].str_val.data = (u_char*)strdup(index); 358 | code.argv.value[0].str_val.len = len; 359 | 360 | utarray_push_back(array, &code); 361 | free(code.argv.value[0].str_val.data); 362 | } 363 | 364 | 365 | void ngx_http_waf_gen_push_header_in_code(UT_array* array, char* index) { 366 | vm_code_t code; 367 | size_t len = strlen(index); 368 | code.type = VM_CODE_PUSH_HEADER_IN; 369 | code.argv.argc = 1; 370 | code.argv.type[0] = VM_DATA_STR; 371 | code.argv.value[0].str_val.data = (u_char*)strdup(index); 372 | code.argv.value[0].str_val.len = len; 373 | 374 | utarray_push_back(array, &code); 375 | free(code.argv.value[0].str_val.data); 376 | } 377 | 378 | 379 | void ngx_http_waf_gen_push_cookie_code(UT_array* array, char* index) { 380 | vm_code_t code; 381 | size_t len = strlen(index); 382 | code.type = VM_CODE_PUSH_COOKIE; 383 | code.argv.argc = 1; 384 | code.argv.type[0] = VM_DATA_STR; 385 | code.argv.value[0].str_val.data = (u_char*)strdup(index); 386 | code.argv.value[0].str_val.len = len; 387 | 388 | utarray_push_back(array, &code); 389 | free(code.argv.value[0].str_val.data); 390 | } 391 | 392 | 393 | void ngx_http_waf_gen_push_client_ip_code(UT_array* array) { 394 | vm_code_t code; 395 | code.type = VM_CODE_PUSH_CLIENT_IP; 396 | code.argv.argc = 0; 397 | utarray_push_back(array, &code); 398 | } 399 | 400 | 401 | void 402 | ngx_http_waf_gen_push_url_code(UT_array* array) { 403 | vm_code_t code; 404 | code.type = VM_CODE_PUSH_URL; 405 | code.argv.argc = 0; 406 | utarray_push_back(array, &code); 407 | } 408 | 409 | 410 | void 411 | ngx_http_waf_gen_push_user_agent_code(UT_array* array) { 412 | vm_code_t code; 413 | code.type = VM_CODE_PUSH_USER_AGENT; 414 | code.argv.argc = 0; 415 | utarray_push_back(array, &code); 416 | } 417 | 418 | 419 | void 420 | ngx_http_waf_gen_push_referer_code(UT_array* array) { 421 | vm_code_t code; 422 | code.type = VM_CODE_PUSH_REFERER; 423 | code.argv.argc = 0; 424 | utarray_push_back(array, &code); 425 | } 426 | 427 | 428 | void 429 | ngx_http_waf_gen_int_code(UT_array* array, int num) { 430 | vm_code_t code; 431 | code.type = VM_CODE_PUSH_INT; 432 | code.argv.argc = 1; 433 | code.argv.type[0] = VM_DATA_INT; 434 | code.argv.value[0].int_val = num; 435 | utarray_push_back(array, &code); 436 | } 437 | 438 | 439 | void 440 | ngx_http_waf_gen_push_op_not_code(UT_array* array) { 441 | vm_code_t code; 442 | code.type = VM_CODE_OP_NOT; 443 | code.argv.argc = 0; 444 | utarray_push_back(array, &code); 445 | } 446 | 447 | 448 | void 449 | ngx_http_waf_gen_op_and_code(UT_array* array) { 450 | vm_code_t code; 451 | code.type = VM_CODE_OP_AND; 452 | code.argv.argc = 0; 453 | utarray_push_back(array, &code); 454 | } 455 | 456 | 457 | void 458 | ngx_http_waf_gen_op_or_code(UT_array* array) { 459 | vm_code_t code; 460 | code.type = VM_CODE_OP_OR;; 461 | code.argv.argc = 0; 462 | utarray_push_back(array, &code); 463 | } 464 | 465 | void 466 | ngx_http_waf_gen_op_matches_code(UT_array* array) { 467 | vm_code_t code; 468 | code.type = VM_CODE_OP_MATCHES; 469 | code.argv.argc = 0; 470 | utarray_push_back(array, &code); 471 | } 472 | 473 | 474 | 475 | void 476 | ngx_http_waf_gen_op_contains_code(UT_array* array) { 477 | vm_code_t code; 478 | code.type = VM_CODE_OP_CONTAINS; 479 | code.argv.argc = 0; 480 | utarray_push_back(array, &code); 481 | } 482 | 483 | 484 | void 485 | ngx_http_waf_gen_op_equals_code(UT_array* array) { 486 | vm_code_t code; 487 | code.type = VM_CODE_OP_EQUALS; 488 | code.argv.argc = 0; 489 | utarray_push_back(array, &code); 490 | } 491 | 492 | 493 | void 494 | ngx_http_waf_gen_op_belong_to_code(UT_array* array) { 495 | vm_code_t code; 496 | code.type = VM_CODE_OP_BELONG_TO; 497 | code.argv.argc = 0; 498 | utarray_push_back(array, &code); 499 | } 500 | 501 | 502 | void 503 | ngx_http_waf_gen_op_sqli_detn_code(UT_array* array) { 504 | vm_code_t code; 505 | code.type = VM_CODE_OP_SQLI_DETN; 506 | code.argv.argc = 0; 507 | utarray_push_back(array, &code); 508 | } 509 | 510 | 511 | void 512 | ngx_http_waf_gen_op_xss_detn_code(UT_array* array) { 513 | vm_code_t code; 514 | code.type = VM_CODE_OP_XSS_DETN; 515 | code.argv.argc = 0; 516 | utarray_push_back(array, &code); 517 | } 518 | 519 | 520 | void 521 | ngx_http_waf_gen_act_ret_code(UT_array* array, int http_status) { 522 | vm_code_t code; 523 | code.type = VM_CODE_ACT_RETURN; 524 | code.argv.argc = 1; 525 | code.argv.type[0] = VM_DATA_INT; 526 | code.argv.value[0].int_val = http_status; 527 | utarray_push_back(array, &code); 528 | } 529 | 530 | 531 | void 532 | ngx_http_waf_gen_act_allow_code(UT_array* array) { 533 | vm_code_t code; 534 | code.type = VM_CODE_ACT_ALLOW; 535 | code.argv.argc = 0; 536 | utarray_push_back(array, &code); 537 | } -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_waf_module 2 | 3 | deps="$ngx_addon_dir/inc/ngx_http_waf_module_check.h \ 4 | $ngx_addon_dir/inc/ngx_http_waf_module_config.h \ 5 | $ngx_addon_dir/inc/ngx_http_waf_module_core.h \ 6 | $ngx_addon_dir/inc/ngx_http_waf_module_macro.h \ 7 | $ngx_addon_dir/inc/ngx_http_waf_module_type.h \ 8 | $ngx_addon_dir/inc/ngx_http_waf_module_util.h \ 9 | $ngx_addon_dir/inc/ngx_http_waf_module_ip_trie.h \ 10 | $ngx_addon_dir/inc/ngx_http_waf_module_mem_pool.h \ 11 | $ngx_addon_dir/inc/ngx_http_waf_module_lru_cache.h \ 12 | $ngx_addon_dir/inc/ngx_http_waf_module_under_attack.h \ 13 | $ngx_addon_dir/inc/ngx_http_waf_module_vm.h \ 14 | $ngx_addon_dir/inc/ngx_http_waf_module_lexer.h \ 15 | $ngx_addon_dir/inc/ngx_http_waf_module_parser.tab.h" 16 | 17 | srcs="$ngx_addon_dir/src/ngx_http_waf_module_core.c \ 18 | $ngx_addon_dir/src/ngx_http_waf_module_check.c \ 19 | $ngx_addon_dir/src/ngx_http_waf_module_config.c \ 20 | $ngx_addon_dir/src/ngx_http_waf_module_ip_trie.c \ 21 | $ngx_addon_dir/src/ngx_http_waf_module_lru_cache.c \ 22 | $ngx_addon_dir/src/ngx_http_waf_module_mem_pool.c \ 23 | $ngx_addon_dir/src/ngx_http_waf_module_under_attack.c \ 24 | $ngx_addon_dir/src/ngx_http_waf_module_util.c \ 25 | $ngx_addon_dir/src/ngx_http_waf_module_vm.c \ 26 | $ngx_addon_dir/src/ngx_http_waf_module_lexer.c \ 27 | $ngx_addon_dir/src/ngx_http_waf_module_parser.tab.c" 28 | 29 | ngx_http_waf_module_libs="" 30 | 31 | ngx_http_waf_module_inc_path="$ngx_addon_dir/inc " 32 | 33 | if [ -n "$LIB_UTHASH" ] ; then 34 | ngx_http_waf_module_inc_path="${ngx_http_waf_module_inc_path} ${LIB_UTHASH}/include" 35 | fi 36 | 37 | which flex 38 | if [ $? -ne 0 ] ; then 39 | cat << END 40 | 41 | $0: error: the $ngx_addon_name module requires the flex. 42 | 43 | Please run: 44 | On Ubuntu or Debian: 45 | apt-get update && apt-get install --yes flex 46 | On CentOS 7: 47 | yum -y install flex 48 | On Centos 8 or Fedora 33 or Fedora 34: 49 | dnf install flex 50 | On Alpine: 51 | apk update && apk add --upgrade flex 52 | On Arch: 53 | 1. Enable the core repository on /etc/pacman.conf: 54 | [core] 55 | Include = /etc/pacman.d/mirrorlist 56 | 2. Install flex xz package: 57 | pacman -Syu flex 58 | On FreeBSD 12 or FreeBSD 13: 59 | pkg install flex 60 | END 61 | exit 1 62 | 63 | fi 64 | 65 | which bison 66 | if [ $? -ne 0 ] ; then 67 | cat << END 68 | 69 | $0: error: the $ngx_addon_name module requires the bison. 70 | 71 | Please run: 72 | On Ubuntu or Debian: 73 | apt-get update && apt-get install --yes bison 74 | On CentOS 7: 75 | yum -y install bison 76 | On Centos 8 or Fedora 33 or Fedora 34: 77 | dnf install bison 78 | On Alpine: 79 | apk update && apk add --upgrade bison 80 | On Arch: 81 | 1. Enable the core repository on /etc/pacman.conf: 82 | [core] 83 | Include = /etc/pacman.d/mirrorlist 84 | 2. Install flex xz package: 85 | pacman -Syu bison 86 | On FreeBSD 12 or FreeBSD 13: 87 | pkg install bison 88 | 89 | END 90 | exit 1 91 | 92 | fi 93 | 94 | 95 | is_gen='true' 96 | 97 | if [ ! -e "${ngx_addon_dir}/inc/ngx_http_waf_module_lexer.h" ] ; then 98 | is_gen='false' 99 | elif [ ! -e "${ngx_addon_dir}/src/ngx_http_waf_module_lexer.c" ] ; then 100 | is_gen='false' 101 | elif [ ! -e "${ngx_addon_dir}/inc/ngx_http_waf_module_parser.tab.h" ] ; then 102 | is_gen='false' 103 | elif [ ! -e "${ngx_addon_dir}/src/ngx_http_waf_module_parser.tab.c" ] ; then 104 | is_gen='false' 105 | fi 106 | 107 | if [ $is_gen != 'true' ] ; then 108 | cat << END 109 | 110 | $0: error: the $ngx_addon_name module requires the following command to be run to generate the necessary files. 111 | 112 | cd $ngx_addon_dir && make && cd $(pwd) 113 | 114 | END 115 | exit 1 116 | fi 117 | 118 | 119 | # Check if the uthash library is installed. 120 | ngx_feature="uthash library" 121 | ngx_feature_name= 122 | ngx_feature_run=no 123 | ngx_feature_path=$ngx_http_waf_module_inc_path 124 | ngx_feature_incs="#include " 125 | ngx_feature_libs=$ngx_http_waf_module_libs 126 | ngx_feature_test= 127 | . auto/feature 128 | if [ $ngx_found = no ] ; then 129 | PWD=$(pwd) 130 | cat << END 131 | $0: error: the $ngx_addon_name module requires the $ngx_feature. 132 | 133 | Please run: 134 | cd /usr/local/src \\ 135 | && git clone https://github.com/troydhanson/uthash.git \\ 136 | && export LIB_UTHASH=/usr/local/src/uthash \\ 137 | && cd $PWD 138 | 139 | END 140 | PWD='' 141 | exit 1 142 | fi 143 | 144 | # Check if the C compiler supports the C99 standard. 145 | ngx_feature="C99 features" 146 | ngx_feature_name= 147 | ngx_feature_run=yes 148 | ngx_feature_path=$ngx_http_waf_module_inc_path 149 | ngx_feature_incs= 150 | ngx_feature_libs=$ngx_http_waf_module_libs 151 | ngx_feature_test=$(cat << END 152 | 153 | /* Declare variables in loops. */ 154 | for(int i = 0; i < 10; i++) {} 155 | 156 | int i = 0, j = 0; 157 | 158 | /* Short-circuit operation for logical expressions. */ 159 | if (i == 0 || !(j = 1)) {} 160 | 161 | if (j == 1) { return 1; } 162 | 163 | j = 0; 164 | 165 | if (i != 0 && !(j = 1)) {} 166 | 167 | if (j == 1) { return 1; } 168 | 169 | END 170 | ) 171 | . auto/feature 172 | if [ $ngx_found = no ] ; then 173 | cat << END 174 | $0: error: the $ngx_addon_name module requires the $ngx_feature, make sure your C compiler supports and enables the C99 standard. 175 | 176 | For gcc, you can enable the C99 standard by appending the parameter --with-cc-opt='-std=gnu99'. 177 | 178 | END 179 | exit 1 180 | fi 181 | 182 | if [ -n "$LIB_INJECTION" ] ; then 183 | ngx_http_waf_module_inc_path="${ngx_http_waf_module_inc_path} ${LIB_INJECTION}/include" 184 | ngx_http_waf_module_libs=" ${ngx_http_waf_module_libs} -L ${LIB_INJECTION}/lib -Wl,-Bstatic -l injection -Wl,-Bdynamic " 185 | else 186 | ngx_http_waf_module_libs=" ${ngx_http_waf_module_libs} -l injection " 187 | fi 188 | 189 | 190 | # Check if libinjection exists. 191 | ngx_feature="injection library" 192 | ngx_feature_name= 193 | ngx_feature_run=no 194 | ngx_feature_path=$ngx_http_waf_module_inc_path 195 | ngx_feature_incs=$(cat << END 196 | 197 | #include 198 | #include 199 | #include 200 | #include 201 | 202 | END 203 | ) 204 | 205 | ngx_feature_libs=$ngx_http_waf_module_libs 206 | ngx_feature_test= 207 | . auto/feature 208 | if [ $ngx_found = no ] ; then 209 | PWD=$(pwd) 210 | cat << END 211 | $0: error: the $ngx_addon_name module requires the $ngx_feature. 212 | 213 | Please run 214 | # You can remove directories libinjection-src and libinjection after installing the ngx_http_waf_module. 215 | git clone https://github.com/libinjection/libinjection.git libinjection-src \\ 216 | && cd libinjection-src \\ 217 | && ./autogen.sh \\ 218 | && ./configure --prefix=$PWD/libinjection \\ 219 | && make -j\$(nproc) && make install \\ 220 | && export LIB_INJECTION=$PWD/libinjection \\ 221 | && cd $PWD 222 | END 223 | PWD='' 224 | exit 1 225 | fi 226 | 227 | if [ -n "$LIB_SODIUM" ] ; then 228 | ngx_http_waf_module_inc_path="${ngx_http_waf_module_inc_path} ${LIB_SODIUM}/include" 229 | ngx_http_waf_module_libs=" ${ngx_http_waf_module_libs} -L ${LIB_SODIUM}/lib -Wl,-Bstatic -l sodium -Wl,-Bdynamic " 230 | else 231 | ngx_http_waf_module_libs=" ${ngx_http_waf_module_libs} -l sodium " 232 | fi 233 | 234 | # Check if libsodium exists. 235 | ngx_feature="sodium library" 236 | ngx_feature_name= 237 | ngx_feature_run=no 238 | ngx_feature_path=$ngx_http_waf_module_inc_path 239 | ngx_feature_incs='#include ' 240 | ngx_feature_libs=$ngx_http_waf_module_libs 241 | ngx_feature_test= 242 | . auto/feature 243 | if [ $ngx_found = no ] ; then 244 | PWD=$(pwd) 245 | cat << END 246 | $0: error: the $ngx_addon_name module requires the $ngx_feature. 247 | 248 | Please run: 249 | On Ubuntu or Debian: 250 | apt-get update && apt-get install --yes libsodium23 libsodium-dev 251 | On Alpine: 252 | apk update && apk add libsodium libsodium-dev 253 | On other OS: 254 | # You can remove directories libsodium-src and libsodium after installing the ngx_http_waf_module. 255 | git clone https://github.com/jedisct1/libsodium.git --branch stable libsodium-src \\ 256 | && cd libsodium-src \\ 257 | && ./configure --prefix=$PWD/libsodium --with-pic \\ 258 | && make -j\$(nproc) && make check -j \$(nproc) && make install \\ 259 | && export LIB_SODIUM=$PWD/libsodium \\ 260 | && cd $PWD 261 | 262 | END 263 | PWD='' 264 | exit 1 265 | fi 266 | 267 | 268 | 269 | # Clear these variables to avoid affecting the installation of other modules. 270 | ngx_feature= 271 | ngx_feature_name= 272 | ngx_feature_run= 273 | ngx_feature_path= 274 | ngx_feature_incs= 275 | ngx_feature_libs= 276 | ngx_feature_test= 277 | 278 | 279 | if test -n "$ngx_module_link"; then 280 | ngx_module_type=HTTP 281 | ngx_module_name=$ngx_addon_name 282 | ngx_module_deps=$deps 283 | ngx_module_incs=$ngx_http_waf_module_inc_path 284 | ngx_module_srcs=$srcs 285 | ngx_module_libs=$ngx_http_waf_module_libs 286 | 287 | # Let ngx_http_access_module initialize before this module, 288 | # so that this module can take effect after ngx_http_access_module, 289 | # because the initialization order and the effective order are reversed. 290 | ngx_module_order="${ngx_addon_name} ngx_http_access_module" 291 | 292 | . auto/module 293 | else 294 | HTTP_MODULES="$HTTP_MODULES ${ngx_addon_name}" 295 | HTTP_DEPS-"$HTTP_DEPS $deps" 296 | HTTP_INCS="$HTTP_INCS -I $ngx_addon_dir/inc $ngx_addon_dir/inc/libinjection/src" 297 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $srcs" 298 | fi -------------------------------------------------------------------------------- /flex/lexer.lex: -------------------------------------------------------------------------------- 1 | %option noyywrap 2 | %option noinput 3 | %option nounput 4 | %option yylineno 5 | %option outfile="src/ngx_http_waf_module_lexer.c" header-file="inc/ngx_http_waf_module_lexer.h" 6 | %option prefix="ngx_http_waf_" 7 | 8 | %{ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | // #define VM_DEBUG 15 | void ngx_http_waf_error(UT_array* array, ngx_pool_t* pool, const char* msg); 16 | %} 17 | 18 | 19 | KEYWORD_ID ^(?i:id) 20 | 21 | KEYWORD_IF ^(?i:if) 22 | 23 | KEYWORD_DO ^(?i:do) 24 | 25 | KEYWORD_URL (?i:url) 26 | 27 | KEYWORD_QUERY_STRING (?i:query_string) 28 | 29 | KEYWORD_USER_AGENT (?i:user_agent) 30 | 31 | KEYWORD_REFERER (?i:referer) 32 | 33 | KEYWORD_CLIENT_IP (?i:client_ip) 34 | 35 | KEYWORD_HEADER_IN (?i:header_in) 36 | 37 | KEYWORD_COOKIE (?i:cookie) 38 | 39 | KEYWORD_CONTAINS [[:blank:]]+(?i:contains)[[:blank:]]+ 40 | 41 | KEYWORD_MATCHES [[:blank:]]+(?i:matches)[[:blank:]]+ 42 | 43 | KEYWORD_EQUALS [[:blank:]]+(?i:equals)[[:blank:]]+ 44 | 45 | KEYWORD_BELONG_TO [[:blank:]]+(?i:belong_to)[[:blank:]]+ 46 | 47 | KEYWORD_SQLI_DETN (?i:sqli_detn) 48 | 49 | KEYWORD_XSS_DETN (?i:xss_detn) 50 | 51 | KEYWORD_RETURN (?i:return) 52 | 53 | KEYWORD_ALLOW (?i:allow) 54 | 55 | KEYWORD_NOT (?i:not) 56 | 57 | KEYWORD_OR [[:blank:]]+(?i:or)[[:blank:]]+ 58 | 59 | KEYWORD_AND [[:blank:]]+(?i:and)[[:blank:]]+ 60 | 61 | INDEX \[((-|_|[[:alnum:]])+)\] 62 | 63 | ID (_|[[:alpha:]])((_|[[:alnum:]]){1,49}) 64 | 65 | STRING (\"[^\"]*\")|('[^']*') 66 | 67 | INTEGER (-)?[[:digit:]]+ 68 | 69 | BREAK_LINE (\r)?\n 70 | 71 | 72 | %% 73 | 74 | 75 | {KEYWORD_ID} { 76 | #ifdef VM_DEBUG 77 | printf("Lexer - KEYWORD_ID\n"); 78 | #endif 79 | return keyword_id; 80 | } 81 | 82 | {KEYWORD_IF} { 83 | #ifdef VM_DEBUG 84 | printf("Lexer - KEYWORD_IF\n"); 85 | #endif 86 | return keyword_if; 87 | } 88 | 89 | {KEYWORD_DO} { 90 | #ifdef VM_DEBUG 91 | printf("Lexer - KEYWORD_DO\n"); 92 | #endif 93 | return keyword_do; 94 | } 95 | 96 | 97 | {KEYWORD_URL} { 98 | #ifdef VM_DEBUG 99 | printf("Lexer - KEYWORD_URL\n"); 100 | #endif 101 | return keyword_url; 102 | } 103 | 104 | {KEYWORD_QUERY_STRING} { 105 | #ifdef VM_DEBUG 106 | printf("Lexer - KEYWORD_QUERY_STRING\n"); 107 | #endif 108 | return keyword_query_string; 109 | } 110 | 111 | 112 | {KEYWORD_USER_AGENT} { 113 | #ifdef VM_DEBUG 114 | printf("Lexer - KEYWORD_USER_AGENT\n"); 115 | #endif 116 | return keyword_user_agent; 117 | } 118 | 119 | {KEYWORD_REFERER} { 120 | #ifdef VM_DEBUG 121 | printf("Lexer - KEYWORD_REFERER\n"); 122 | #endif 123 | return keyword_referer; 124 | } 125 | 126 | 127 | {KEYWORD_CLIENT_IP} { 128 | #ifdef VM_DEBUG 129 | printf("Lexer - KEYWORD_CLIENT_IP\n"); 130 | #endif 131 | return keyword_client_ip; 132 | } 133 | 134 | 135 | {KEYWORD_HEADER_IN} { 136 | #ifdef VM_DEBUG 137 | printf("Lexer - KEYWORD_HEADER_IN\n"); 138 | #endif 139 | return keyword_header_in; 140 | } 141 | 142 | {KEYWORD_COOKIE} { 143 | #ifdef VM_DEBUG 144 | printf("Lexer - KEYWORD_COOKIE\n"); 145 | #endif 146 | return keyword_cookie; 147 | } 148 | 149 | {KEYWORD_CONTAINS} { 150 | #ifdef VM_DEBUG 151 | printf("Lexer - KEYWORD_CONTAINS\n"); 152 | #endif 153 | return keyword_contains; 154 | } 155 | 156 | {KEYWORD_MATCHES} { 157 | #ifdef VM_DEBUG 158 | printf("Lexer - KEYWORD_MATCHES\n"); 159 | #endif 160 | return keyword_matches; 161 | } 162 | 163 | {KEYWORD_EQUALS} { 164 | #ifdef VM_DEBUG 165 | printf("Lexer - KEYWORD_EQUALS\n"); 166 | #endif 167 | return keyword_equals; 168 | } 169 | 170 | {KEYWORD_BELONG_TO} { 171 | #ifdef VM_DEBUG 172 | printf("Lexer - KEYWORD_BELONG_TO"); 173 | #endif 174 | return keyword_belong_to; 175 | } 176 | 177 | {KEYWORD_SQLI_DETN} { 178 | #ifdef VM_DEBUG 179 | printf("Lexer - KEYWORD_SQLI_DETN\n"); 180 | #endif 181 | return keyword_sqli_detn; 182 | } 183 | 184 | {KEYWORD_XSS_DETN} { 185 | #ifdef VM_DEBUG 186 | printf("Lexer - KEYWORD_XSS_DETN\n"); 187 | #endif 188 | return keyword_xss_detn; 189 | } 190 | 191 | {KEYWORD_RETURN} { 192 | #ifdef VM_DEBUG 193 | printf("Lexer - KEYWORD_RETURN\n"); 194 | #endif 195 | return keyword_return; 196 | } 197 | 198 | {KEYWORD_ALLOW} { 199 | #ifdef VM_DEBUG 200 | printf("Lexer - KEYWORD_ALLOW\n"); 201 | #endif 202 | return keyword_allow; 203 | } 204 | 205 | {INDEX} { 206 | strcpy(ngx_http_waf_lval.str_val, yytext + 1); 207 | ngx_http_waf_lval.str_val[yyleng - 2] = '\0'; 208 | #ifdef VM_DEBUG 209 | printf("Lexer - INDEX: %s\n", yytext); 210 | #endif 211 | return token_index; 212 | } 213 | 214 | 215 | {BREAK_LINE} { 216 | #ifdef VM_DEBUG 217 | printf("Lexer - BREAK_LINE\n"); 218 | #endif 219 | return token_break_line; 220 | } 221 | 222 | {KEYWORD_NOT} { 223 | #ifdef VM_DEBUG 224 | printf("Lexer - KEYWORD_NOT\n"); 225 | #endif 226 | return keyword_not; 227 | } 228 | 229 | {KEYWORD_OR} { 230 | #ifdef VM_DEBUG 231 | printf("Lexer - KEYWORD_or\n"); 232 | #endif 233 | return keyword_or; 234 | } 235 | 236 | {KEYWORD_AND} { 237 | #ifdef VM_DEBUG 238 | printf("Lexer - KEYWORD_and\n"); 239 | #endif 240 | return keyword_and; 241 | } 242 | 243 | {STRING} { 244 | #ifdef VM_DEBUG 245 | printf("Lexer - STRING: %s\n", yytext); 246 | #endif 247 | strcpy(ngx_http_waf_lval.str_val, yytext + 1); 248 | ngx_http_waf_lval.str_val[yyleng - 2] = '\0'; 249 | return token_str; 250 | } 251 | 252 | {ID} { 253 | #ifdef VM_DEBUG 254 | printf("Lexer - ID: %s\n", yytext); 255 | #endif 256 | strcpy(ngx_http_waf_lval.id_val, yytext); 257 | return token_id; 258 | } 259 | 260 | {INTEGER} { 261 | #ifdef VM_DEBUG 262 | printf("Lexer - INTEGER: %s\n", yytext); 263 | #endif 264 | ngx_http_waf_lval.int_val = atoi(yytext); 265 | return token_int; 266 | } 267 | 268 | [[:blank:]]+ { 269 | #ifdef VM_DEBUG 270 | printf("Lexer - [[:blank:]]+\n"); 271 | #endif 272 | return token_blank; 273 | } 274 | 275 | . { 276 | #ifdef VM_DEBUG 277 | printf("Lexer - Other: %s\n", yytext); 278 | #endif 279 | return *yytext; 280 | } 281 | 282 | %% 283 | 284 | void ngx_http_waf_error(UT_array* array, ngx_pool_t* pool, const char* msg) { 285 | printf("error: %s in line %d\n", msg, yylineno); 286 | } -------------------------------------------------------------------------------- /inc/ngx_http_waf_module_check.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_http_waf_module_check.h 3 | * @brief 检查诸如 IP,URL 等是否命中规则。 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #ifndef NGX_HTTP_WAF_MODLULE_CHECK_H 18 | #define NGX_HTTP_WAF_MODLULE_CHECK_H 19 | 20 | 21 | /** 22 | * @brief 用来挂载到清理请求资源的函数,主要用来存储和获取 ngx_http_waf_ctx_t。 23 | */ 24 | void ngx_http_waf_handler_cleanup(void *data); 25 | 26 | /** 27 | * @defgroup check 规则匹配模块 28 | * @brief 检查诸如 IP,URL 等是否命中规则。 29 | * @addtogroup check 规则匹配模块 30 | * @{ 31 | */ 32 | 33 | 34 | /** 35 | * @brief 检查客户端 IP 地址是否在白名单中。 36 | * @param[out] out_http_status 当出发规则时需要返回的 HTTP 状态码。 37 | * @return 如果在返回 MATCHED,反之返回 NOT_MATCHED。 38 | * @retval MATCHED IP 地址在白名单中。 39 | * @retval NOT_MATCHED IP 地址不在白名单中。 40 | */ 41 | ngx_int_t ngx_http_waf_handler_check_white_ip(ngx_http_request_t* r, ngx_int_t* out_http_status); 42 | 43 | 44 | /** 45 | * @brief 检查客户端 IP 地址是否在黑名单中。 46 | * @param[out] out_http_status 当触发规则时需要返回的 HTTP 状态码。 47 | * @return 如果在返回 MATCHED,反之返回 NOT_MATCHED。 48 | * @retval MATCHED IP 地址在黑名单中。 49 | * @retval NOT_MATCHED IP 地址不在黑名单中。 50 | */ 51 | ngx_int_t ngx_http_waf_handler_check_black_ip(ngx_http_request_t* r, ngx_int_t* out_http_status); 52 | 53 | 54 | /** 55 | * @brief 检查客户端 IP 地址的访问频次(60 秒内)是否超出了限制。 56 | * @param[out] out_http_status 当触发规则时需要返回的 HTTP 状态码。 57 | * @return 如果超出 MATCHED,反之返回 NOT_MATCHED。 58 | * @retval MATCHED 超出限制。 59 | * @retval NOT_MATCHED 未超出限制。 60 | */ 61 | ngx_int_t ngx_http_waf_handler_check_cc(ngx_http_request_t* r, ngx_int_t* out_http_status); 62 | 63 | 64 | /** 65 | * @brief 检查 URL 是否在白名单中。 66 | * @param[out] out_http_status 当触发规则时需要返回的 HTTP 状态码。 67 | * @return 如果在返回 MATCHED,反之返回 NOT_MATCHED。 68 | * @retval MATCHED 在白名单中。 69 | * @retval NOT_MATCHED 不在白名单中 70 | */ 71 | ngx_int_t ngx_http_waf_handler_check_white_url(ngx_http_request_t* r, ngx_int_t* out_http_status); 72 | 73 | 74 | /** 75 | * @brief 检查 URL 是否在黑名单中 76 | * @param[out] out_http_status 当触发规则时需要返回的 HTTP 状态码。 77 | * @return 如果在返回 MATCHED,反之返回 NOT_MATCHED。 78 | * @retval MATCHED 在黑名单中。 79 | * @retval NOT_MATCHED 不在黑名单中 80 | */ 81 | ngx_int_t ngx_http_waf_handler_check_black_url(ngx_http_request_t* r, ngx_int_t* out_http_status); 82 | 83 | 84 | /** 85 | * @brief 检查请求参数是否在黑名单中 86 | * @param[out] out_http_status 当触发规则时需要返回的 HTTP 状态码。 87 | * @return 如果在返回 MATCHED,反之返回 NOT_MATCHED。 88 | * @retval MATCHED 在黑名单中。 89 | * @retval NOT_MATCHED 不在黑名单中 90 | */ 91 | ngx_int_t ngx_http_waf_handler_check_black_args(ngx_http_request_t* r, ngx_int_t* out_http_status); 92 | 93 | 94 | /** 95 | * @brief 检查 UserAgent 是否在黑名单中 96 | * @param[out] out_http_status 当触发规则时需要返回的 HTTP 状态码。 97 | * @return 如果在返回 MATCHED,反之返回 NOT_MATCHED。 98 | * @retval MATCHED 在黑名单中。 99 | * @retval NOT_MATCHED 不在黑名单中 100 | */ 101 | ngx_int_t ngx_http_waf_handler_check_black_user_agent(ngx_http_request_t* r, ngx_int_t* out_http_status); 102 | 103 | 104 | /** 105 | * @brief 检查 Referer 是否在白名单中 106 | * @param[out] out_http_status 当触发规则时需要返回的 HTTP 状态码。 107 | * @return 如果在返回 MATCHED,反之返回 NOT_MATCHED。 108 | * @retval MATCHED 在白名单中。 109 | * @retval NOT_MATCHED 不在白黑名单中 110 | */ 111 | ngx_int_t ngx_http_waf_handler_check_white_referer(ngx_http_request_t* r, ngx_int_t* out_http_status); 112 | 113 | 114 | /** 115 | * @brief 检查 Referer 是否在黑名单中 116 | * @param[out] out_http_status 当触发规则时需要返回的 HTTP 状态码。 117 | * @return 如果在返回 MATCHED,反之返回 NOT_MATCHED。 118 | * @retval MATCHED 在黑名单中。 119 | * @retval NOT_MATCHED 不在黑名单中 120 | */ 121 | ngx_int_t ngx_http_waf_handler_check_black_referer(ngx_http_request_t* r, ngx_int_t* out_http_status); 122 | 123 | 124 | /** 125 | * @brief 检查 Cookie 是否在黑名单中 126 | * @param[out] out_http_status 当触发规则时需要返回的 HTTP 状态码。 127 | * @return 如果在返回 MATCHED,反之返回 NOT_MATCHED。 128 | * @retval MATCHED 在黑名单中。 129 | * @retval NOT_MATCHED 不在黑名单中 130 | */ 131 | ngx_int_t ngx_http_waf_handler_check_black_cookie(ngx_http_request_t* r, ngx_int_t* out_http_status); 132 | 133 | 134 | /** 135 | * @brief 检查请求体内容是否存在于黑名单中,存在则拦截,反之放行。 136 | */ 137 | ngx_int_t ngx_http_waf_handler_check_black_post(ngx_http_request_t* r, ngx_int_t* out_http_status); 138 | 139 | 140 | /** 141 | * @brief 获取模块上下文和 server 块配置。 142 | */ 143 | void ngx_http_waf_get_ctx_and_conf(ngx_http_request_t* r, ngx_http_waf_loc_conf_t** conf, ngx_http_waf_ctx_t** ctx); 144 | 145 | 146 | /** 147 | * @brief 测试数组内的所有正则 148 | * @param[in] str 被测试的字符串 149 | * @param[in] array 包含若干个正则的数组 150 | * @param[in] rule_type 触发规则时的规则类型 151 | * @param[in] cache 检测时所使用的缓存管理器 152 | * @return 如果匹配到返回 NGX_HTTP_WAF_MATCHED,反之则为 NGX_HTTP_WAF_NOT_MATCHED。 153 | */ 154 | ngx_int_t ngx_http_waf_regex_exec_arrray_sqli_xss(ngx_http_request_t* r, 155 | ngx_str_t* str, 156 | ngx_array_t* array, 157 | const u_char* rule_type, 158 | lru_cache_t* cache, 159 | int check_sql_injection, 160 | int check_xss); 161 | 162 | /** 163 | * @} 164 | */ 165 | 166 | 167 | #endif 168 | -------------------------------------------------------------------------------- /inc/ngx_http_waf_module_config.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_http_waf_module_config.h 3 | * @brief 读取 nginx.conf 内的配置以及规则文件。 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | #ifndef __STDC_WANT_LIB_EXT1__ 18 | #define __STDC_WANT_LIB_EXT1__ 1 19 | #endif 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #ifndef NGX_HTTP_WAF_MODULE_CONFIG_H 26 | #define NGX_HTTP_WAF_MODULE_CONFIG_H 27 | 28 | 29 | ngx_int_t ngx_http_waf_handler_access_phase(ngx_http_request_t* r); 30 | 31 | /** 32 | * @defgroup config 配置读取和处理模块 33 | * @brief 读取 nginx.conf 内的配置以及规则文件。 34 | * @addtogroup config 配置读取和处理模块 35 | * @{ 36 | */ 37 | 38 | 39 | /** 40 | * @brief 读取配置项 waf,该项表示是否启用模块。 41 | */ 42 | char* ngx_http_waf_conf(ngx_conf_t* cf, ngx_command_t* cmd, void* conf); 43 | 44 | 45 | /** 46 | * @brief 读取配置项 waf_rule_path,该项表示存有配置文件的文件夹的绝对路径,必须以 '/' 结尾。 47 | */ 48 | char* ngx_http_waf_rule_path_conf(ngx_conf_t* cf, ngx_command_t* cmd, void* conf); 49 | 50 | 51 | /** 52 | * @brief 读取配置项 waf_mode,该项表示拦截模式。 53 | */ 54 | char* ngx_http_waf_mode_conf(ngx_conf_t* cf, ngx_command_t* cmd, void* conf); 55 | 56 | 57 | /** 58 | * @brief 读取配置项 waf_cc_deny,该项表示最高的访问频次以及超出后的拉黑时间。 59 | */ 60 | char* ngx_http_waf_cc_deny_conf(ngx_conf_t* cf, ngx_command_t* cmd, void* conf); 61 | 62 | 63 | /** 64 | * @brief 读取配置项 waf_cache,该项表示缓存相关的参数。 65 | */ 66 | char* ngx_http_waf_cache_conf(ngx_conf_t* cf, ngx_command_t* cmd, void* conf); 67 | 68 | 69 | /** 70 | * @brief 读取配置项 waf_under_attack,该项用来设置五秒盾相关的参数。 71 | */ 72 | char* ngx_http_waf_under_attack_conf(ngx_conf_t* cf, ngx_command_t* cmd, void* conf); 73 | 74 | 75 | /** 76 | * @brief 读取配置项 waf_priority,该项用来设置检查项目的优先级。 77 | */ 78 | char* ngx_http_waf_priority_conf(ngx_conf_t* cf, ngx_command_t* cmd, void* conf); 79 | 80 | 81 | /** 82 | * @brief 读取配置项 waf_http_status,该项用来设置检查项目的优先级。 83 | */ 84 | char* ngx_http_waf_http_status_conf(ngx_conf_t* cf, ngx_command_t* cmd, void* conf); 85 | 86 | 87 | /** 88 | * @brief 当读取 waf_log 变量时的回调函数,这个变量当启动检查时不为空,反之为空字符串。 89 | */ 90 | ngx_int_t ngx_http_waf_log_get_handler(ngx_http_request_t* r, ngx_http_variable_value_t* v, uintptr_t data); 91 | 92 | 93 | /** 94 | * @brief 当读取 waf_blocking_log 变量时的回调函数,这个变量当拦截时不为空,反之为空字符串。 95 | */ 96 | ngx_int_t ngx_http_waf_blocking_log_get_handler(ngx_http_request_t* r, ngx_http_variable_value_t* v, uintptr_t data); 97 | 98 | 99 | /** 100 | * @brief 当读取 waf_blocked 变量时的回调函数,这个变量当请求被拦截的时候是 "true",反之是 "false"。 101 | */ 102 | ngx_int_t ngx_http_waf_blocked_get_handler(ngx_http_request_t* r, ngx_http_variable_value_t* v, uintptr_t data); 103 | 104 | 105 | /** 106 | * @brief 当读取 waf_rule_type 变量时的回调函数,这个变量会显示触发了的规则类型。 107 | */ 108 | ngx_int_t ngx_http_waf_rule_type_get_handler(ngx_http_request_t* r, ngx_http_variable_value_t* v, uintptr_t data); 109 | 110 | 111 | /** 112 | * @brief 当读取 waf_rule_deatils 变量时的回调函数,这个变量会显示触发了的规则的细节。 113 | */ 114 | ngx_int_t ngx_http_waf_rule_deatils_handler(ngx_http_request_t* r, ngx_http_variable_value_t* v, uintptr_t data); 115 | 116 | 117 | /** 118 | * @brief 当读取 waf_spend 变量时的回调函数,这个变量表示本次检查花费的时间(毫秒)。 119 | */ 120 | ngx_int_t ngx_http_waf_spend_handler(ngx_http_request_t* r, ngx_http_variable_value_t* v, uintptr_t data); 121 | 122 | 123 | /** 124 | * @brief 初始化结构体 ngx_http_waf_main_conf_t 125 | */ 126 | void* ngx_http_waf_create_main_conf(ngx_conf_t* cf); 127 | 128 | 129 | /** 130 | * @brief 初始化结构体 ngx_http_waf_loc_conf_t 131 | */ 132 | void* ngx_http_waf_create_loc_conf(ngx_conf_t* cf); 133 | 134 | 135 | /** 136 | * @brief 合并各个配置段的 ngx_http_waf_loc_conf_t 137 | */ 138 | char* ngx_http_waf_merge_loc_conf(ngx_conf_t *cf, void *prev, void *conf); 139 | 140 | 141 | /** 142 | * @brief 在读取完全部配置后进行一些操作。 143 | * @li 将处理函数挂载到对应的请求处理阶段。 144 | * @li 初始化相关的 nginx 变量。 145 | */ 146 | ngx_int_t ngx_http_waf_init_after_load_config(ngx_conf_t* cf); 147 | 148 | 149 | /** 150 | * @brief 用于 CC 防护的共享内存的初始时的回调函数 151 | * @param[in] zone 正在初始化的共享内存 152 | * @param[in] data ngx_http_waf_loc_conf_t 153 | */ 154 | ngx_int_t ngx_http_waf_shm_zone_cc_deny_init(ngx_shm_zone_t *zone, void *data); 155 | 156 | 157 | /** 158 | * @brief 初始化结构体 ngx_http_waf_loc_conf_t 159 | */ 160 | ngx_http_waf_loc_conf_t* ngx_http_waf_init_conf(ngx_conf_t* cf); 161 | 162 | 163 | /** 164 | * @brief 初始化用于 CC 防护的共享内存。 165 | */ 166 | ngx_int_t ngx_http_waf_init_cc_shm(ngx_conf_t* cf, ngx_http_waf_loc_conf_t* conf); 167 | 168 | 169 | /** 170 | * @brief 初始化 LRU 缓存。 171 | */ 172 | ngx_int_t ngx_http_waf_init_lru_cache(ngx_conf_t* cf, ngx_http_waf_loc_conf_t* conf); 173 | 174 | 175 | /** 176 | * @brief 读取所有的规则。 177 | */ 178 | ngx_int_t ngx_http_waf_load_all_rule(ngx_conf_t* cf, ngx_http_waf_loc_conf_t* conf); 179 | 180 | 181 | /** 182 | * @brief 读取指定文件的内容到容器中。 183 | * @param[in] file_name 要读取的配置文件完整路径。 184 | * @param[out] container 存放读取结果的容器。 185 | * @param[in] mode 读取模式 186 | * @li 当 mode = 0 时会将读取到文本编译成正则表达式再存储。容器类型为 ngx_array_t。 187 | * @li 当 mode = 1 时会将读取到的文本转化为 ipv4_t 再存储。容器类型为 ip_trie_t。 188 | * @li 当 mode = 2 时会将读取到的文本转化为 ipv6_t 再存储。容器类型为 ip_trie_t。 189 | * @return 读取操作的结果。 190 | * @retval NGX_HTTP_WAF_SUCCESS 读取成功。 191 | * @retval FAIL 读取中发生错误。 192 | */ 193 | ngx_int_t load_into_container(ngx_conf_t* cf, const char* file_name, void* container, ngx_int_t mode); 194 | 195 | 196 | /** 197 | * @brief 分配用于存放规则的内存。 198 | * @note 如果已经分配则什么都不做。 199 | */ 200 | ngx_int_t ngx_http_waf_alloc_memory(ngx_conf_t* cf, ngx_http_waf_loc_conf_t* conf); 201 | 202 | 203 | /** 204 | * @brief 释放用于存储规则的内存。 205 | * @note 如果已经释放或者从未分配则什么都不做。 206 | */ 207 | ngx_int_t ngx_http_waf_free_memory(ngx_conf_t* cf, ngx_http_waf_loc_conf_t* conf); 208 | 209 | /** 210 | * @} 211 | */ 212 | 213 | #endif // !NGX_HTTP_WAF_MODULE_CONFIG_H 214 | -------------------------------------------------------------------------------- /inc/ngx_http_waf_module_core.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_http_waf_module_core.h 3 | * @brief 配置块的初始化和请求检测函数。 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | 19 | #ifndef NGX_HTTP_WAF_MODULE_CORE_H 20 | #define NGX_HTTP_WAF_MODULE_CORE_H 21 | 22 | 23 | /** 24 | * @defgroup core 核心模块 25 | * @brief 配置块的初始化和请求检测函数。 26 | * @addtogroup core 核心模块 27 | * @{ 28 | */ 29 | 30 | 31 | /** 32 | * @brief 当 Worker 进程启动时调用的函数,用于重置随机数种子。 33 | */ 34 | ngx_int_t ngx_http_waf_init_process(ngx_cycle_t *cycle); 35 | 36 | 37 | /** 38 | * @brief NGX_HTTP_ACCESS_PHASE 阶段的处理函数 39 | */ 40 | ngx_int_t ngx_http_waf_handler_access_phase(ngx_http_request_t* r); 41 | 42 | 43 | /** 44 | * @brief 执行全部的检查项目 45 | * @param r 本次要处理的请求 46 | * @param is_check_cc 是否执行 CC 防护逻辑 47 | * @return http 状态码或者 nginx 控制量 48 | * @retval NGX_DECLINED 放行本次请求 49 | * @retval NGX_DONE 将在其它地方进行检查,通常是因为执行了 POST 检测 50 | */ 51 | ngx_int_t ngx_http_waf_check_all(ngx_http_request_t* r, ngx_int_t is_check_cc); 52 | 53 | 54 | void ngx_http_waf_handler_cleanup(void *data); 55 | 56 | /** 57 | * @} 58 | */ 59 | 60 | #endif // !NGX_HTTP_WAF_MODULE_CORE_H 61 | -------------------------------------------------------------------------------- /inc/ngx_http_waf_module_ip_trie.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_http_waf_module_ip_trie.h 3 | * @brief IP 前缀树。 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #ifndef NGX_HTTP_WAF_MODULE_IP_TRIE_h 11 | #define NGX_HTTP_WAF_MODULE_IP_TRIE_h 12 | 13 | /** 14 | * @defgroup ip_trie IP 前缀树 15 | * @addtogroup ip_trie IP 前缀树 16 | * @{ 17 | */ 18 | 19 | /** 20 | * @brief 初始化一个前缀树。 21 | * @param[out] trie 要初始化的前缀树。 22 | * @param[in] memory_pool 初始化、添加、删除节点所用的内存池。 23 | * @param[in] ip_type 存储的 IP 地址类型。 24 | * @return 返回 NGX_HTTP_WAF_SUCCESS 表示初始化成功,反之为 NGX_HTTP_WAF_FAIL。 25 | */ 26 | ngx_int_t ip_trie_init(ip_trie_t* trie, mem_pool_type_e pool_type, void* native_pool, int ip_type); 27 | 28 | 29 | /** 30 | * @brief 插入一个 IP 地址。 31 | * @param[in] trie 要操作的前缀树。 32 | * @param[in] inx_addr IP 地址。 33 | * @param[in] suffix_num IP 网段长度。 34 | * @param[in] text IP 的字符串形式。 35 | * @return 返回 NGX_HTTP_WAF_SUCCESS 表示成功,反之为 NGX_HTTP_WAF_FAIL。 36 | */ 37 | ngx_int_t ip_trie_add(ip_trie_t* trie, inx_addr_t* inx_addr, uint32_t suffix_num, void* data, size_t data_byte_length); 38 | 39 | /** 40 | * @brief 查找 IP 是否存在。 41 | * @param[in] trie 要操作的前缀树。 42 | * @param[in] inx_addr IP 地址。 43 | * @param[out] ip_trie_node 找到之后此指针将指向对应的节点。 44 | * @return 返回 NGX_HTTP_WAF_SUCCESS 表示找到,反之为 FAIL。 45 | */ 46 | ngx_int_t ip_trie_find(ip_trie_t* trie, inx_addr_t* inx_addr, ip_trie_node_t** ip_trie_node); 47 | 48 | /** 49 | * @brief 删除一个 IP 地址。 50 | * @param[in] trie 要操作的前缀树。 51 | * @param[in] inx_addr IP 地址。 52 | * @return 成功返回 NGX_HTTP_WAF_SUCCESS,反之则不是 53 | * @warning 不会释放节点所在占用的内存,但是会释放节点的 data 域所指向的内存。 54 | */ 55 | // static ngx_int_t ip_trie_delete(ip_trie_t* trie, inx_addr_t* inx_addr); 56 | 57 | /** 58 | * @brief 清空整个树,除根节点以外的全部节点的内存和 data 域指向的内存。 59 | * @param[in] trie 要操作的前缀树。 60 | * @return 成功返回 NGX_HTTP_WAF_SUCCESS,反之则不是 61 | */ 62 | // static ngx_int_t ip_trie_clear(ip_trie_t* trie); 63 | 64 | 65 | /** 66 | * @brief 先序遍历,并将每个节点存入 head 为头的链表里。 67 | * @param[in] node 开始遍历的节点 68 | * @param[out] head 链表头 69 | */ 70 | // static void _ip_trie_traversal(ip_trie_node_t* node, circular_doublly_linked_list_t** head); 71 | 72 | /** 73 | * @} 74 | */ 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /inc/ngx_http_waf_module_lru_cache.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_http_waf_module_lru_cache.h.h 3 | * @brief LRU 缓存管理器 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #ifndef __NGX_HTTP_WAF_MODULE_LRU_CACHE_H__ 11 | #define __NGX_HTTP_WAF_MODULE_LRU_CACHE_H__ 12 | 13 | 14 | void lru_cache_init(lru_cache_t** lru, size_t capacity, mem_pool_type_e pool_type, void* native_pool); 15 | 16 | 17 | lru_cache_add_result_t lru_cache_add(lru_cache_t* lru, void* key, size_t key_len); 18 | 19 | 20 | lru_cache_find_result_t lru_cache_find(lru_cache_t* lru, void* key, size_t key_len); 21 | 22 | 23 | void* lru_cache_calloc(lru_cache_t* lru, size_t size); 24 | 25 | 26 | void lru_cache_free(lru_cache_t* lru, void* addr); 27 | 28 | 29 | void lru_cache_delete(lru_cache_t* lru, void* key, size_t key_len); 30 | 31 | 32 | void lru_cache_eliminate(lru_cache_t* lru, size_t count); 33 | 34 | 35 | void lru_cache_destory(lru_cache_t* lru); 36 | 37 | 38 | #endif -------------------------------------------------------------------------------- /inc/ngx_http_waf_module_macro.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_http_waf_module_macro.h 3 | * @brief 定义一些必要的宏 4 | */ 5 | 6 | #ifndef NGX_HTTP_WAF_MODULE_MACRO_H 7 | #define NGX_HTTP_WAF_MODULE_MACRO_H 8 | 9 | /* 对应配置文件的文件名 */ 10 | #define NGX_HTTP_WAF_IPV4_FILE ("ipv4") 11 | #define NGX_HTTP_WAF_IPV6_FILE ("ipv6") 12 | #define NGX_HTTP_WAF_URL_FILE ("url") 13 | #define NGX_HTTP_WAF_ARGS_FILE ("args") 14 | #define NGX_HTTP_WAF_UA_FILE ("user-agent") 15 | #define NGX_HTTP_WAF_REFERER_FILE ("referer") 16 | #define NGX_HTTP_WAF_COOKIE_FILE ("cookie") 17 | #define NGX_HTTP_WAF_POST_FILE ("post") 18 | #define NGX_HTTP_WAF_WHITE_IPV4_FILE ("white-ipv4") 19 | #define NGX_HTTP_WAF_WHITE_IPV6_FILE ("white-ipv6") 20 | #define NGX_HTTP_WAF_WHITE_URL_FILE ("white-url") 21 | #define NGX_HTTP_WAF_WHITE_REFERER_FILE ("white-referer") 22 | #define NGX_HTTP_WAF_ADVANCED_FILE ("advanced") 23 | 24 | 25 | #define NGX_HTTP_WAF_FALSE (0) 26 | 27 | #define NGX_HTTP_WAF_FAIL (0) 28 | 29 | #define NGX_HTTP_WAF_NOT_MATCHED (0) 30 | 31 | #define NGX_HTTP_WAF_TRUE (1) 32 | 33 | #define NGX_HTTP_WAF_SUCCESS (1) 34 | 35 | #define NGX_HTTP_WAF_MATCHED (1) 36 | 37 | #define NGX_HTTP_WAF_PROCESSING (2) 38 | 39 | #define NGX_HTTP_WAF_MALLOC_ERROR (3) 40 | 41 | #define NGX_HTTP_WAF_KEY_EXISTS (4) 42 | 43 | #define NGX_HTTP_WAF_KEY_NOT_EXISTS (5) 44 | 45 | #define NGX_HTTP_WAF_BAD (6) 46 | 47 | 48 | /** 49 | * @def NGX_HTTP_WAF_RULE_MAX_LEN 50 | * @brief 每条规则的占用的最大字节数。 51 | */ 52 | #define NGX_HTTP_WAF_RULE_MAX_LEN (256 * 4 * 8) 53 | 54 | /** 55 | * @def NGX_HTTP_WAF_INITIAL_SIZE 56 | * @brief 初始化配置块内存池时的初始内存池大小。 57 | */ 58 | #define NGX_HTTP_WAF_INITIAL_SIZE (1024 * 1024 * 5) 59 | 60 | #define NGX_HTTP_WAF_MAX_ALLOC_TIMES (100000) 61 | 62 | /** 63 | * @def NGX_HTTP_WAF_SHARE_MEMORY_NAME 64 | * @brief 用于 CC 防护的共享内存的名称 65 | */ 66 | #define NGX_HTTP_WAF_SHARE_MEMORY_CC_DNEY_NAME ("__ADD-SP_NGX_WAF_CC_DENY_SHM__") 67 | 68 | /** 69 | * @def NGX_HTTP_WAF_SHATE_MEMORY_CC_DENY_MIN_SIZE 70 | * @brief 用于 CC 防护的共享内存的最小大小(字节) 71 | */ 72 | #define NGX_HTTP_WAF_SHARE_MEMORY_CC_DENY_MIN_SIZE (1024 * 1024 * 20) 73 | 74 | 75 | #define NGX_HTTP_WAF_UNDER_ATTACH_UID_LEN (64) 76 | 77 | 78 | #define NGX_HTTP_WAF_UNDER_ATTACH_TIME_LEN (16) 79 | 80 | 81 | #define NGX_HTTP_WAF_SHA256_HEX_LEN (crypto_hash_sha256_BYTES * 2) 82 | 83 | 84 | /** 85 | * @def CACHE_ITEM_MIN_SIZE 86 | * @brief 用于设置缓存项数的上限 87 | */ 88 | #define NGX_HTTP_WAF_CACHE_ITEM_MIN_NUM (50) 89 | 90 | /** 91 | * @def NGX_HTTP_WAF_MODE_INSPECT_GET 92 | * @brief 对 GET 请求进行检查 93 | */ 94 | #define NGX_HTTP_WAF_MODE_INSPECT_GET NGX_HTTP_GET 95 | 96 | /** 97 | * @def NGX_HTTP_WAF_MODE_INSPECT_HEAD 98 | * @brief 对 HEAD 请求进行检查 99 | */ 100 | #define NGX_HTTP_WAF_MODE_INSPECT_HEAD NGX_HTTP_HEAD 101 | 102 | /** 103 | * @def NGX_HTTP_WAF_MODE_INSPECT_POST 104 | * @brief 对 POST 请求进行检查 105 | */ 106 | #define NGX_HTTP_WAF_MODE_INSPECT_POST NGX_HTTP_POST 107 | 108 | /** 109 | * @def NGX_HTTP_WAF_MODE_INSPECT_PUT 110 | * @brief 对 PUT 请求进行检查 111 | */ 112 | #define NGX_HTTP_WAF_MODE_INSPECT_PUT NGX_HTTP_PUT 113 | 114 | /** 115 | * @def NGX_HTTP_WAF_MODE_INSPECT_DELETE 116 | * @brief 对 DELETE 请求进行检查 117 | */ 118 | #define NGX_HTTP_WAF_MODE_INSPECT_DELETE NGX_HTTP_DELETE 119 | 120 | /** 121 | * @def NGX_HTTP_WAF_MODE_INSPECT_MKCOL 122 | * @brief 对 MKCOL 请求进行检查 123 | */ 124 | #define NGX_HTTP_WAF_MODE_INSPECT_MKCOL NGX_HTTP_MKCOL 125 | 126 | /** 127 | * @def NGX_HTTP_WAF_MODE_INSPECT_COPY 128 | * @brief 对 COPY 请求进行检查 129 | */ 130 | #define NGX_HTTP_WAF_MODE_INSPECT_COPY NGX_HTTP_COPY 131 | 132 | /** 133 | * @def NGX_HTTP_WAF_MODE_INSPECT_MOVE 134 | * @brief 对 MOVE 请求进行检查 135 | */ 136 | #define NGX_HTTP_WAF_MODE_INSPECT_MOVE NGX_HTTP_MOVE 137 | 138 | /** 139 | * @def NGX_HTTP_WAF_MODE_INSPECT_OPTIONS 140 | * @brief 对 OPTIONS 请求进行检查 141 | */ 142 | #define NGX_HTTP_WAF_MODE_INSPECT_OPTIONS NGX_HTTP_OPTIONS 143 | 144 | /** 145 | * @def NGX_HTTP_WAF_MODE_INSPECT_PROPFIND 146 | * @brief 对 PROPFIND 请求进行检查 147 | */ 148 | #define NGX_HTTP_WAF_MODE_INSPECT_PROPFIND NGX_HTTP_PROPFIND 149 | 150 | /** 151 | * @def NGX_HTTP_WAF_MODE_INSPECT_PROPPATCH 152 | * @brief 对 PROPPATCH 请求进行检查 153 | */ 154 | #define NGX_HTTP_WAF_MODE_INSPECT_PROPPATCH NGX_HTTP_PROPPATCH 155 | 156 | /** 157 | * @def NGX_HTTP_WAF_MODE_INSPECT_LOCK 158 | * @brief 对 LOCK 请求进行检查 159 | */ 160 | #define NGX_HTTP_WAF_MODE_INSPECT_LOCK NGX_HTTP_LOCK 161 | 162 | /** 163 | * @def NGX_HTTP_WAF_MODE_INSPECT_UNLOCK 164 | * @brief 对 UNLOCK 请求进行检查 165 | */ 166 | #define NGX_HTTP_WAF_MODE_INSPECT_UNLOCK NGX_HTTP_UNLOCK 167 | 168 | /** 169 | * @def NGX_HTTP_WAF_MODE_INSPECT_PATCH 170 | * @brief 对 PATCH 请求进行检查 171 | */ 172 | #define NGX_HTTP_WAF_MODE_INSPECT_PATCH NGX_HTTP_PATCH 173 | 174 | /** 175 | * @def NGX_HTTP_WAF_MODE_INSPECT_TRACE 176 | * @brief 对 TRACE 请求进行检查 177 | */ 178 | #define NGX_HTTP_WAF_MODE_INSPECT_TRACE NGX_HTTP_TRACE 179 | 180 | /** 181 | * @def NGX_HTTP_WAF_MODE_INSPECT_IP 182 | * @brief 启用 IP 检查规则 183 | */ 184 | #define NGX_HTTP_WAF_MODE_INSPECT_IP (NGX_HTTP_WAF_MODE_INSPECT_TRACE << 1) 185 | 186 | /** 187 | * @def NGX_HTTP_WAF_MODE_INSPECT_URL 188 | * @brief 启用 URL 检查规则 189 | */ 190 | #define NGX_HTTP_WAF_MODE_INSPECT_URL (NGX_HTTP_WAF_MODE_INSPECT_IP << 1) 191 | 192 | /** 193 | * @def NGX_HTTP_WAF_MODE_INSPECT_RB 194 | * @brief 启用 Request Body 检查规则 195 | */ 196 | #define NGX_HTTP_WAF_MODE_INSPECT_RB (NGX_HTTP_WAF_MODE_INSPECT_URL << 1) 197 | 198 | /** 199 | * @def NGX_HTTP_WAF_MODE_INSPECT_ARGS 200 | * @brief 启用 ARGS(GET 请求参数) 检查规则 201 | */ 202 | #define NGX_HTTP_WAF_MODE_INSPECT_ARGS (NGX_HTTP_WAF_MODE_INSPECT_RB << 1) 203 | 204 | /** 205 | * @def NGX_HTTP_WAF_MODE_INSPECT_UA 206 | * @brief 启用 UserAgent 检查规则 207 | */ 208 | #define NGX_HTTP_WAF_MODE_INSPECT_UA (NGX_HTTP_WAF_MODE_INSPECT_ARGS << 1) 209 | 210 | /** 211 | * @def NGX_HTTP_WAF_MODE_INSPECT_COOKIE 212 | * @brief 启用 COOKIE 检查规则 213 | */ 214 | #define NGX_HTTP_WAF_MODE_INSPECT_COOKIE (NGX_HTTP_WAF_MODE_INSPECT_UA << 1) 215 | 216 | /** 217 | * @def NGX_HTTP_WAF_MODE_INSPECT_REFERER 218 | * @brief 启用 Referer 检查规则 219 | */ 220 | #define NGX_HTTP_WAF_MODE_INSPECT_REFERER (NGX_HTTP_WAF_MODE_INSPECT_COOKIE << 1) 221 | 222 | /** 223 | * @def NGX_HTTP_WAF_MODE_INSPECT_CC 224 | * @brief 启用 CC 防御 225 | */ 226 | #define NGX_HTTP_WAF_MODE_INSPECT_CC (NGX_HTTP_WAF_MODE_INSPECT_REFERER << 1) 227 | 228 | 229 | /** 230 | * @def NGX_HTTP_WAF_MODE_INSPECT_ADV 231 | * @brief 启用高级规则 232 | */ 233 | #define NGX_HTTP_WAF_MODE_INSPECT_ADV (NGX_HTTP_WAF_MODE_INSPECT_CC << 1) 234 | 235 | 236 | /** 237 | * @def NGX_HTTP_WAF_MODE_EXTRA_CACHE 238 | * @brief 启用缓存,但是不缓存 POST 检查。 239 | */ 240 | #define NGX_HTTP_WAF_MODE_EXTRA_CACHE (NGX_HTTP_WAF_MODE_INSPECT_ADV << 1) 241 | 242 | 243 | /** 244 | * @def NGX_HTTP_WAF_MODE_LIB_INJECTION_SQLI 245 | * @brief 启用 libinjection 进行 SQL 注入检查。 246 | */ 247 | #define NGX_HTTP_WAF_MODE_LIB_INJECTION_SQLI (NGX_HTTP_WAF_MODE_EXTRA_CACHE << 1) 248 | 249 | 250 | /** 251 | * @def NGX_HTTP_WAF_MODE_LIB_INJECTION_XSS 252 | * @brief 启用 libinjection 进行 XSS 检查。 253 | */ 254 | #define NGX_HTTP_WAF_MODE_LIB_INJECTION_XSS (NGX_HTTP_WAF_MODE_LIB_INJECTION_SQLI << 1) 255 | 256 | 257 | /** 258 | * @def NGX_HTTP_WAF_MODE_LIB_INJECTION 259 | * @brief 启用 libinjection 进行 SQL 注入检查和 XSS 检查。 260 | */ 261 | #define NGX_HTTP_WAF_MODE_LIB_INJECTION (NGX_HTTP_WAF_MODE_LIB_INJECTION_SQLI \ 262 | | NGX_HTTP_WAF_MODE_LIB_INJECTION_XSS) 263 | 264 | 265 | /** 266 | * @def NGX_HTTP_WAF_MODE_CMN_METH 267 | * @brief 常见的请求方法 268 | */ 269 | #define NGX_HTTP_WAF_MODE_CMN_METH (NGX_HTTP_WAF_MODE_INSPECT_GET \ 270 | | NGX_HTTP_WAF_MODE_INSPECT_POST \ 271 | | NGX_HTTP_WAF_MODE_INSPECT_HEAD) 272 | 273 | 274 | /** 275 | * @def NGX_HTTP_WAF_MODE_ALL_METH 276 | * @brief 所有的 277 | */ 278 | #define NGX_HTTP_WAF_MODE_ALL_METH (NGX_HTTP_WAF_MODE_INSPECT_GET \ 279 | | NGX_HTTP_WAF_MODE_INSPECT_HEAD \ 280 | | NGX_HTTP_WAF_MODE_INSPECT_POST \ 281 | | NGX_HTTP_WAF_MODE_INSPECT_PUT \ 282 | | NGX_HTTP_WAF_MODE_INSPECT_DELETE \ 283 | | NGX_HTTP_WAF_MODE_INSPECT_MKCOL \ 284 | | NGX_HTTP_WAF_MODE_INSPECT_COPY \ 285 | | NGX_HTTP_WAF_MODE_INSPECT_MOVE \ 286 | | NGX_HTTP_WAF_MODE_INSPECT_OPTIONS \ 287 | | NGX_HTTP_WAF_MODE_INSPECT_PROPFIND \ 288 | | NGX_HTTP_WAF_MODE_INSPECT_PROPPATCH \ 289 | | NGX_HTTP_WAF_MODE_INSPECT_LOCK \ 290 | | NGX_HTTP_WAF_MODE_INSPECT_UNLOCK \ 291 | | NGX_HTTP_WAF_MODE_INSPECT_PATCH \ 292 | | NGX_HTTP_WAF_MODE_INSPECT_TRACE) 293 | 294 | 295 | 296 | /** 297 | * @def MODE_STD 298 | * @brief 标准工作模式 299 | */ 300 | #define NGX_HTTP_WAF_MODE_STD (NGX_HTTP_WAF_MODE_INSPECT_IP \ 301 | | NGX_HTTP_WAF_MODE_INSPECT_URL \ 302 | | NGX_HTTP_WAF_MODE_INSPECT_RB \ 303 | | NGX_HTTP_WAF_MODE_INSPECT_ARGS \ 304 | | NGX_HTTP_WAF_MODE_INSPECT_UA \ 305 | | NGX_HTTP_WAF_MODE_CMN_METH \ 306 | | NGX_HTTP_WAF_MODE_INSPECT_CC \ 307 | | NGX_HTTP_WAF_MODE_EXTRA_CACHE \ 308 | | NGX_HTTP_WAF_MODE_LIB_INJECTION_SQLI) 309 | /** 310 | * @def MODE_STATIC 311 | * @brief 适用于静态站点的工作模式 312 | */ 313 | #define NGX_HTTP_WAF_MODE_STATIC (NGX_HTTP_WAF_MODE_INSPECT_IP \ 314 | | NGX_HTTP_WAF_MODE_INSPECT_URL \ 315 | | NGX_HTTP_WAF_MODE_INSPECT_UA \ 316 | | NGX_HTTP_WAF_MODE_INSPECT_GET \ 317 | | NGX_HTTP_WAF_MODE_INSPECT_HEAD \ 318 | | NGX_HTTP_WAF_MODE_INSPECT_CC \ 319 | | NGX_HTTP_WAF_MODE_EXTRA_CACHE) 320 | 321 | /** 322 | * @def MODE_DYNAMIC 323 | * @brief 适用于动态站点的工作模式 324 | */ 325 | #define NGX_HTTP_WAF_MODE_DYNAMIC (NGX_HTTP_WAF_MODE_INSPECT_IP \ 326 | | NGX_HTTP_WAF_MODE_INSPECT_URL \ 327 | | NGX_HTTP_WAF_MODE_INSPECT_RB \ 328 | | NGX_HTTP_WAF_MODE_INSPECT_ARGS \ 329 | | NGX_HTTP_WAF_MODE_INSPECT_UA \ 330 | | NGX_HTTP_WAF_MODE_INSPECT_COOKIE \ 331 | | NGX_HTTP_WAF_MODE_CMN_METH \ 332 | | NGX_HTTP_WAF_MODE_INSPECT_CC \ 333 | | NGX_HTTP_WAF_MODE_EXTRA_CACHE \ 334 | | NGX_HTTP_WAF_MODE_LIB_INJECTION_SQLI \ 335 | | NGX_HTTP_WAF_MODE_INSPECT_ADV) 336 | 337 | 338 | /** 339 | * @def MODE_FULL 340 | * @brief 启用所有的模式 341 | */ 342 | #define NGX_HTTP_WAF_MODE_FULL (NGX_HTTP_WAF_MODE_INSPECT_IP \ 343 | | NGX_HTTP_WAF_MODE_INSPECT_URL \ 344 | | NGX_HTTP_WAF_MODE_INSPECT_RB \ 345 | | NGX_HTTP_WAF_MODE_INSPECT_ARGS \ 346 | | NGX_HTTP_WAF_MODE_INSPECT_UA \ 347 | | NGX_HTTP_WAF_MODE_INSPECT_COOKIE \ 348 | | NGX_HTTP_WAF_MODE_INSPECT_REFERER \ 349 | | NGX_HTTP_WAF_MODE_ALL_METH \ 350 | | NGX_HTTP_WAF_MODE_INSPECT_CC \ 351 | | NGX_HTTP_WAF_MODE_EXTRA_CACHE \ 352 | | NGX_HTTP_WAF_MODE_INSPECT_ADV \ 353 | | NGX_HTTP_WAF_MODE_LIB_INJECTION) 354 | 355 | 356 | /* 检查对应文件是否存在,如果存在则根据 mode 的值将数据处理后存入容器中 */ 357 | /** 358 | * @def ngx_http_waf_check_and_load_conf(cf, folder, end, filename, container, mode) 359 | * @brief 检查对应文件是否存在,如果存在则根据 mode 的值将数据处理后存入数组中。 360 | * @param[in] folder 配置文件所在文件夹的绝对路径。 361 | * @param[in] end folder 字符数组的 '\0' 的地址。 362 | * @param[in] filename 配置文件名。 363 | * @param[out] container 存储配置读取结果的容器。 364 | * @param[in] mode 配置读取模式。 365 | * @warning 当文件不存在的时候会直接执行 @code return NGX_CONF_ERROR; @endcode 语句。 366 | */ 367 | #define ngx_http_waf_check_and_load_conf(cf, folder, end, filename, container, mode) { \ 368 | strcat((folder), (filename)); \ 369 | if (access((folder), R_OK) != 0) { \ 370 | ngx_conf_log_error(NGX_LOG_ERR, (cf), 0, "ngx_waf: %s: %s", (folder), "No such file or directory"); \ 371 | return NGX_HTTP_WAF_FAIL; \ 372 | } \ 373 | if (load_into_container((cf), (folder), (container), (mode)) == NGX_HTTP_WAF_FAIL) { \ 374 | ngx_conf_log_error(NGX_LOG_ERR, (cf), 0, "ngx_waf: %s: %s", (folder), "Cannot read configuration."); \ 375 | return NGX_HTTP_WAF_FAIL; \ 376 | } \ 377 | *(end) = '\0'; \ 378 | } 379 | 380 | /** 381 | * @def ngx_http_waf_check_flag(origin, flag) 382 | * @brief 检查 flag 是否存在于 origin 中,即位操作。 383 | * @return 存在则返回 NGX_HTTP_WAF_TRUE,反之返回 NGX_HTTP_WAF_FALSE。 384 | * @retval NGX_HTTP_WAF_TRUE 存在。 385 | * @retval NGX_HTTP_WAF_FALSE 不存在。 386 | */ 387 | #define ngx_http_waf_check_flag(origin, flag) (((origin) & (flag)) == (flag) ? NGX_HTTP_WAF_TRUE : NGX_HTTP_WAF_FALSE) 388 | 389 | 390 | /** 391 | * @def ngx_http_waf_check_bit(origin, bit_index) 392 | * @brief 检查 origin 的某一位是否为 1。 393 | * @return 如果为一则返回 NGX_HTTP_WAF_TRUE,反之返回 NGX_HTTP_WAF_FALSE。 394 | * @retval NGX_HTTP_WAF_TRUE 被测试的位为一。 395 | * @retval NGX_HTTP_WAF_FALSE 被测试的位为零。 396 | * @note bit_index 从 0 开始计数,其中 0 代表最低位。 397 | */ 398 | #define ngx_http_waf_check_bit(origin, bit_index) (ngx_http_waf_check_flag((origin), 1 << (bit_index))) 399 | 400 | 401 | #define ngx_http_waf_make_utarray_ngx_str_icd() { sizeof(ngx_str_t), NULL, ngx_http_waf_utarray_ngx_str_ctor, ngx_http_waf_utarray_ngx_str_dtor } 402 | 403 | #define ngx_http_waf_make_utarray_vm_code_icd() { sizeof(vm_code_t), NULL, ngx_http_waf_utarray_vm_code_ctor, ngx_http_waf_utarray_vm_code_dtor } 404 | 405 | #define ngx_strdup(s) ((u_char*)strdup((char*)(s))); 406 | 407 | #define ngx_strcpy(d, s) (strcpy((char*)d, (const char*)s)) 408 | 409 | 410 | #endif // !NGX_HTTP_WAF_MODULE_MACRO_H 411 | -------------------------------------------------------------------------------- /inc/ngx_http_waf_module_mem_pool.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_http_waf_module_memory_pool.h 3 | * @brief 内存池 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | 10 | #ifndef __NGX_HTTP_WAF_MODULE_MEMORY_POOL_H__ 11 | #define __NGX_HTTP_WAF_MODULE_MEMORY_POOL_H__ 12 | 13 | 14 | /** 15 | * @brief 初始化一个内存池 16 | * @param[out] pool 要初始化的内存池 17 | * @param[in] type 内存池类型 18 | * @param[in] native_pool 内存池 19 | * @return 如果成功返回 NGX_HTTP_WAF_SUCCESS,反之则不是。 20 | */ 21 | ngx_int_t mem_pool_init(mem_pool_t* pool, mem_pool_type_e type, void* native_pool); 22 | 23 | /** 24 | * @brief 申请一段连续的内存 25 | * @param[in] pool 要操作的内存池 26 | * @param[in] byte_size 内存的字节数 27 | * @return 成功则返回内存首地址,反之为 NULL。 28 | */ 29 | void* mem_pool_calloc(mem_pool_t* pool, ngx_uint_t byte_size); 30 | 31 | /** 32 | * @brief 释放一段连续的内存 33 | * @param[in] pool 要操作的内存池 34 | * @param[in] buffer 内存的首地址 35 | * @return 成功则返回 NGX_HTTP_WAF_SUCCESS,反之则不是。 36 | */ 37 | ngx_int_t mem_pool_free(mem_pool_t* pool, void* buffer); 38 | 39 | #endif -------------------------------------------------------------------------------- /inc/ngx_http_waf_module_type.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_http_waf_module_type.h 3 | * @brief 相关结构体的定义 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | // #include 10 | // #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | 17 | #ifndef NGX_HTTP_WAF_MODULE_TYPE_H 18 | #define NGX_HTTP_WAF_MODULE_TYPE_H 19 | 20 | /** 21 | * @typedef ngx_http_waf_check 22 | * @brief 请求检查函数的函数指针 23 | * @param[out] out_http_status 当触发规则时需要返回的 HTTP 状态码。 24 | */ 25 | typedef ngx_int_t (*ngx_http_waf_check_pt)(ngx_http_request_t* r, ngx_int_t* out_http_status); 26 | 27 | 28 | /** 29 | * @struct inx_addr_t 30 | * @brief 代表 ipv4 或 ipv6 地址。 31 | */ 32 | typedef union inx_addr_u { 33 | struct in_addr ipv4; 34 | #if (NGX_HAVE_INET6) 35 | struct in6_addr ipv6; 36 | #endif 37 | } inx_addr_t; 38 | 39 | 40 | /** 41 | * @struct singly_linked_list_t 42 | * @brief 单链表 43 | */ 44 | typedef struct singly_linked_list_s { 45 | void *data; /**< 链表的数据项 */ 46 | size_t data_byte_length; /**< data 指针指向的内存的长度(字节) */ 47 | struct singly_linked_list_s *next; /**< utlist 关键成员 */ 48 | } singly_linked_list_t; 49 | 50 | 51 | /** 52 | * @struct circular_doublly_linked_list_t 53 | * @brief 双向循环链表 54 | */ 55 | typedef struct circular_doublly_linked_list_s { 56 | void *data; /**< 链表的数据项 */ 57 | size_t data_byte_length; /**< data 指针指向的内存的长度(字节) */ 58 | struct circular_doublly_linked_list_s *prev; /**< utlist 关键成员 */ 59 | struct circular_doublly_linked_list_s *next; /**< utlist 关键成员 */ 60 | } circular_doublly_linked_list_t; 61 | 62 | 63 | /** 64 | * @struct ip_statis_t 65 | * @brief 用于记录 CC 防护信息 66 | */ 67 | typedef struct ip_statis_s { 68 | ngx_int_t count; /**< 访问次数 */ 69 | ngx_int_t is_blocked; /**< 是否已经被拦截 */ 70 | time_t record_time; /**< 何时开始记录 */ 71 | time_t block_time; /**< 何时开始拦截 */ 72 | } ip_statis_t; 73 | 74 | 75 | /** 76 | * @struct check_result_t 77 | * @brief 规则减价结果 78 | */ 79 | typedef struct check_result_s { 80 | ngx_int_t is_matched; /**< 是否被某条规则匹配到 */ 81 | u_char *detail; /**< 匹配到的规则的详情 */ 82 | } check_result_t; 83 | 84 | 85 | /** 86 | * @enum memory_pool_type_e 87 | * @brief 内存池类型 88 | */ 89 | typedef enum { 90 | std, /**< malloc */ 91 | gernal_pool, /**< ngx_pool_t */ 92 | slab_pool /**< ngx_slab_pool_t */ 93 | } mem_pool_type_e; 94 | 95 | 96 | /** 97 | * @enum vm_code_type_e 98 | * @brief 虚拟机指令类型 99 | */ 100 | typedef enum { 101 | VM_CODE_NOP, /**< 空指令,什么都不做,继续执行下一条指令。 */ 102 | VM_CODE_PUSH_INT, /**< 将一个整数压入栈中。 */ 103 | VM_CODE_PUSH_STR, /**< 将一个字符串压入栈中。 */ 104 | VM_CODE_PUSH_CLIENT_IP, /**< 将客户端 IP(struct in_addr 或 struct_in6addr)压入栈中。 */ 105 | VM_CODE_PUSH_URL, /**< 将 URL 压入栈中。 */ 106 | VM_CODE_PUSH_QUERY_STRING, /**< 将查询字符串的 key 对应的 value 压入栈中。 */ 107 | VM_CODE_PUSH_REFERER, /**< 将 referer 压入栈中。 */ 108 | VM_CODE_PUSH_USER_AGENT, /**< 将 user-agent 压入栈中。 */ 109 | VM_CODE_PUSH_HEADER_IN, /**< 将请求头中的 key 对应的 value 压入栈中。 */ 110 | VM_CODE_PUSH_COOKIE, /**< 将 cookie 中的 key 对应的 value 压入栈中。 */ 111 | // VM_CODE_POP, /**< */ 112 | // VM_CODE_TOP, /**< */ 113 | VM_CODE_OP_NOT, /**< 将栈顶的布尔值反转 */ 114 | VM_CODE_OP_AND, /**< 弹出两个布尔值,逻辑与后压入栈中。 */ 115 | VM_CODE_OP_OR, /**< 弹出两个布尔值,逻辑或后压入栈中 */ 116 | VM_CODE_OP_CONTAINS, /**< 弹出两个字符串,判断第一个弹出的字符串是否是第二个弹出的字符串的子串,并将结果压入栈中 */ 117 | VM_CODE_OP_MATCHES, /**< 弹出两个字符串,将第一个字符串编译为正则表达式对第二个字符串进行正则匹配,并将结果压入栈中。 */ 118 | VM_CODE_OP_EQUALS, /**< 弹出两个字符串判断两个字符串是否相等,并将结果压入栈中。 */ 119 | VM_CODE_OP_BELONG_TO, /**< 依次弹出一个 IP 块和一个 IP,判断 IP 是否包含在 IP 块中,并将结果压入栈中。 */ 120 | VM_CODE_OP_SQLI_DETN, /**< 弹出一个字符串检测其中是否存在 SQL 注入,并将结果压入栈中。 */ 121 | VM_CODE_OP_XSS_DETN, /**< 弹出一个字符串检测其中是否存在 XSS 攻击,并将结果压入栈中。 */ 122 | VM_CODE_ACT_RETURN, /**< 如果栈顶的布尔值为真则返回指定的 http 状态码。 */ 123 | VM_CODE_ACT_ALLOW /**< 如果栈顶的布尔值为真则放行本次请求。 */ 124 | } vm_code_type_e; 125 | 126 | 127 | /** 128 | * @enum vm_data_type_e 129 | * @brief 虚拟机数据类型 130 | */ 131 | typedef enum { 132 | VM_DATA_VOID, /**< 无类型数据,应该被忽略。 */ 133 | VM_DATA_STR, /**< 字符串类型 */ 134 | VM_DATA_INT, /**< 整数类型 */ 135 | VM_DATA_BOOL, /**< 布尔类型 */ 136 | VM_DATA_IPV4, /**< IPV4 */ 137 | #if (NGX_HAVE_INET6) 138 | VM_DATA_IPV6 /**< IPV6 */ 139 | #endif 140 | } vm_data_type_e; 141 | 142 | 143 | /** 144 | * @struct key_value_t 145 | * @brief 哈希表(字符串 -> 字符串) 146 | */ 147 | typedef struct key_value_s { 148 | ngx_str_t key; /**< 键 */ 149 | ngx_str_t value; /**< 值 */ 150 | UT_hash_handle hh; /**< uthash 关键成员 */ 151 | } key_value_t; 152 | 153 | 154 | /** 155 | * @struct mem_pool_t 156 | * @brief 包含常规内存池或 slab 内存池 157 | */ 158 | typedef struct memo_pool_s { 159 | mem_pool_type_e type; /**< 标识内存池的类型 */ 160 | size_t used_mem; /**< 正在使用的内存大小(字节) */ 161 | union { 162 | ngx_pool_t *gernal_pool; /**< 常规内存池 */ 163 | ngx_slab_pool_t *slab_pool; /**< slab 内存池 */ 164 | } native_pool; /**< 内存池 */ 165 | } mem_pool_t; 166 | 167 | 168 | /** 169 | * @struct lru_cache_result_t 170 | * @brief LRU 操作结果 171 | */ 172 | typedef struct lru_cache_result_s { 173 | int status; 174 | void **data; 175 | } lru_cache_result_t; 176 | 177 | 178 | typedef lru_cache_result_t lru_cache_add_result_t; 179 | 180 | typedef lru_cache_result_t lru_cache_find_result_t; 181 | 182 | 183 | /** 184 | * @struct lru_cache_item_t 185 | * @brief LRU 缓存项 186 | */ 187 | typedef struct lru_cache_item_s { 188 | u_char *key_ptr; /**< 用于哈希的关键字 */ 189 | size_t key_byte_length; /**< 关键字占用的字节数 */ 190 | void *data; /**< 缓存项的具体数据 */ 191 | struct lru_cache_item_s *prev; /**< utlist 关键成员 */ 192 | struct lru_cache_item_s *next; /**< utlist 关键成员 */ 193 | UT_hash_handle hh; /**< uthash 关键成员 */ 194 | } lru_cache_item_t; 195 | 196 | 197 | /** 198 | * @struct lru_cache_t 199 | * @brief LRU 缓存管理器 200 | */ 201 | typedef struct lru_cache_s { 202 | time_t last_eliminate; /**< 最后一次批量淘汰缓存的时间 */ 203 | mem_pool_t pool; /**< 内存池 */ 204 | size_t capacity; /**< 最多嫩容纳多少个缓存项 */ 205 | lru_cache_item_t *hash_head; /**< uthash 的表头 */ 206 | lru_cache_item_t *chain_head; /**< utlist 的表头 */ 207 | } lru_cache_t; 208 | 209 | 210 | /** 211 | * @struct token_bucket_t 212 | * @brief 令牌桶 213 | */ 214 | typedef struct token_bucket_s{ 215 | inx_addr_t inx_addr; /**< 作为哈希表中的 key */ 216 | ngx_uint_t count; /**< 令牌剩余量 */ 217 | ngx_int_t is_ban; /**< 令牌桶是否暂时被禁止 */ 218 | time_t last_ban_time; /**< 最后一次开始禁止令牌桶的时间 */ 219 | UT_hash_handle hh; /**< uthash 关键成员 */ 220 | } token_bucket_t; 221 | 222 | 223 | /** 224 | * @struct token_bucket_set_t 225 | * @brief 令牌桶集合 226 | */ 227 | typedef struct token_bucket_set_s{ 228 | mem_pool_t pool; /**< 使用的内存池 */ 229 | ngx_uint_t ban_duration; /**< 当令牌桶为空时自动禁止该桶一段时间(分钟)*/ 230 | time_t last_put; /**< 上次集中添加令牌的时间 */ 231 | time_t last_clear; /**< 上次清空令牌桶的时间 */ 232 | ngx_uint_t init_count; /**< 令牌桶内初始的令牌数量 */ 233 | ngx_uint_t bucket_count; /**< 已经有多少个令牌桶 */ 234 | token_bucket_t *head; /**< 哈希表标头 */ 235 | } token_bucket_set_t; 236 | 237 | 238 | /** 239 | * @struct ip_trie_node_t 240 | * @brief 前缀树节点。 241 | */ 242 | typedef struct ip_trie_node_s { 243 | int is_ip; /**< 如果为 TRUE 则代表此节点也代表一个 IP,反之则为 FALSE */ 244 | struct ip_trie_node_s *left; /**< 左子树代表当前位为零 */ 245 | struct ip_trie_node_s *right; /**< 右子树代表当前位为一 */ 246 | void *data; 247 | size_t data_byte_length; 248 | } ip_trie_node_t; 249 | 250 | 251 | /** 252 | * @struct ip_trie_t 253 | * @brief 前缀树。 254 | */ 255 | typedef struct ip_trie_s { 256 | int ip_type; /**< 存储的 IP 地址的类型。 */ 257 | ip_trie_node_t *root; /**< 前缀树树根。 */ 258 | int match_all; /**< 当遇到前缀长度为零(0.0.0.0/0)的地址时为真,代表所有查询均返回真。 */ 259 | size_t size; /**< 已经存储的 IP 数量。 */ 260 | mem_pool_t pool; /**< 使用的内存池 */ 261 | } ip_trie_t; 262 | 263 | 264 | /** 265 | * @struct ngx_http_waf_ctx_t 266 | * @brief 每个请求的上下文 267 | */ 268 | typedef struct ngx_http_waf_ctx_s { 269 | ngx_int_t checked; /**< 是否启动了检测流程 */ 270 | ngx_int_t blocked; /**< 是否拦截了本次请求 */ 271 | double spend; /**< 本次检查花费的时间(毫秒) */ 272 | u_char rule_type[128]; /**< 触发的规则类型 */ 273 | u_char rule_deatils[NGX_HTTP_WAF_RULE_MAX_LEN]; /**< 触发的规则内容 */ 274 | ngx_int_t read_body_done; 275 | ngx_int_t waiting_more_body; /**< 是否等待读取更多请求体 */ 276 | ngx_int_t has_req_body; /**< 字段 req_body 是否以己经存储了请求体 */ 277 | ngx_buf_t req_body; /**< 请求体 */ 278 | } ngx_http_waf_ctx_t; 279 | 280 | 281 | /** 282 | * @struct ngx_http_waf_loc_conf_t 283 | */ 284 | typedef struct ngx_http_waf_main_conf_s { 285 | ngx_array_t *local_caches; /**< 已经启用的所有的缓存管理器数组 */ 286 | } ngx_http_waf_main_conf_t; 287 | 288 | 289 | /** 290 | * @struct ngx_http_waf_loc_conf_t 291 | * @brief 每个 server 块的配置块 292 | */ 293 | typedef struct ngx_http_waf_loc_conf_s { 294 | struct ngx_http_waf_loc_conf_s *parent; /**< 上层配置,用来定位 CC 防护所使用的共享内存 */ 295 | u_char random_str[129]; /**< 随机字符串 */ 296 | ngx_str_t waf_under_attack_uri; /**< 五秒盾的 URI */ 297 | ngx_int_t waf_under_attack; /**< 是否启用五秒盾 */ 298 | ngx_int_t is_alloc; /**< 是否已经分配的存储规则的容器的内存 */ 299 | ngx_int_t waf; /**< 是否启用本模块 */ 300 | ngx_str_t waf_rule_path; /**< 配置文件所在目录 */ 301 | uint_fast64_t waf_mode; /**< 检测模式 */ 302 | ngx_int_t waf_cc_deny_limit; /**< CC 防御的限制频率 */ 303 | ngx_int_t waf_cc_deny_duration; /**< CC 防御的拉黑时长(秒) */ 304 | ngx_int_t waf_cc_deny_shm_zone_size; /**< CC 防御所使用的共享内存的大小(字节) */ 305 | ngx_int_t waf_inspection_capacity; /**< 用于缓存检查结果的共享内存的大小(字节) */ 306 | ngx_int_t waf_http_status; /**< 常规检测项目拦截后返回的状态码 */ 307 | ngx_int_t waf_http_status_cc; /**< CC 防护出发后返回的状态码 */ 308 | ip_trie_t *black_ipv4; /**< IPV4 黑名单 */ 309 | #if (NGX_HAVE_INET6) 310 | ip_trie_t *black_ipv6; /**< IPV6 黑名单 */ 311 | #endif 312 | ngx_array_t *black_url; /**< URL 黑名单 */ 313 | ngx_array_t *black_args; /**< args 黑名单 */ 314 | ngx_array_t *black_ua; /**< user-agent 黑名单 */ 315 | ngx_array_t *black_referer; /**< Referer 黑名单 */ 316 | ngx_array_t *black_cookie; /**< Cookie 黑名单 */ 317 | ngx_array_t *black_post; /**< 请求体内容黑名单 */ 318 | ip_trie_t *white_ipv4; /**< IPV4 白名单 */ 319 | #if (NGX_HAVE_INET6) 320 | ip_trie_t *white_ipv6; /**< IPV6 白名单 */ 321 | #endif 322 | ngx_array_t *white_url; /**< URL 白名单 */ 323 | ngx_array_t *white_referer; /**< Referer 白名单 */ 324 | UT_array *advanced_rule; /**< 高级规则表 */ 325 | ngx_shm_zone_t *shm_zone_cc_deny; /**< 共享内存 */ 326 | lru_cache_t *ip_access_statistics; /**< IP 访问频率统计表 */ 327 | lru_cache_t *black_url_inspection_cache; /**< URL 黑名单检查缓存 */ 328 | lru_cache_t *black_args_inspection_cache; /**< ARGS 黑名单检查缓存 */ 329 | lru_cache_t *black_ua_inspection_cache; /**< User-Agent 黑名单检查缓存 */ 330 | lru_cache_t *black_referer_inspection_cache; /**< Referer 黑名单检查缓存 */ 331 | lru_cache_t *black_cookie_inspection_cache; /**< Cookie 黑名单检查缓存 */ 332 | lru_cache_t *white_url_inspection_cache; /**< URL 白名单检查缓存 */ 333 | lru_cache_t *white_referer_inspection_cache; /**< Referer 白名单检查缓存 */ 334 | ngx_int_t is_custom_priority; /**< 用户是否自定义了优先级 */ 335 | ngx_http_waf_check_pt check_proc[20]; /**< 各种检测流程的启动函数 */ 336 | } ngx_http_waf_loc_conf_t; 337 | 338 | 339 | /** 340 | * @struct ipv4_t 341 | * @brief 格式化后的 IPV4 342 | * @note 注意,无论是 prefix 还是 suffix 都是网络字节序,即大端字节序。 343 | */ 344 | typedef struct ipv4_s { 345 | u_char text[32]; /**< 点分十进制表示法 */ 346 | uint32_t prefix; /**< 相当于 192.168.1.0/24 中的 192.168.1.0 的整数形式 */ 347 | uint32_t suffix; /**< 相当于 192.168.1.0/24 中的 24 的位表示(网络字节序) */ 348 | uint32_t suffix_num; /**< 相当于 192.168.1.0/24 中的 24 */ 349 | } ipv4_t; 350 | 351 | 352 | /** 353 | * @struct ipv6_t 354 | * @brief 格式化后的 IPV6 355 | * @note 注意,无论是 prefix[16] 还是 suffix[16],他们中的每一项都是网络字节序。 356 | * 数组的下标同理,下标零代表最高位,下标十五代表最低位。 357 | */ 358 | #if (NGX_HAVE_INET6) 359 | typedef struct ipv6_s { 360 | u_char text[64]; /**< 冒号十六进制表示法 */ 361 | uint8_t prefix[16]; /**< 相当于 ffff::ffff/64 中的 ffff::ffff 的整数形式 */ 362 | uint8_t suffix[16]; /**< 相当于 ffff::ffff/64 中的 64 的位表示(网络字节序) */ 363 | uint32_t suffix_num; /**< 相当于 ffff::ffff/64 中的 64 */ 364 | } ipv6_t; 365 | #endif 366 | 367 | 368 | 369 | /** 370 | * @struct vm_stack_arg_s 371 | * @brief 虚拟机指令参数 372 | */ 373 | typedef struct vm_stack_arg_s { 374 | vm_data_type_e type[4]; /**< 每个参数的类型 */ 375 | size_t argc; /**< 参数的数量 */ 376 | union { 377 | int int_val; 378 | ngx_str_t str_val; 379 | uint8_t bool_val; 380 | ipv4_t ipv4_val; 381 | #if (NGX_HAVE_INET6) 382 | ipv6_t ipv6_val; 383 | #endif 384 | inx_addr_t inx_addr_val; 385 | } value[4]; /**< 每个参数的值 */ 386 | struct vm_stack_arg_s *utstack_handle; /**< utstack 关键成员 */ 387 | } vm_stack_arg_t; 388 | 389 | 390 | 391 | /** 392 | * @struct vm_code_t 393 | * @brief 虚拟机指令 394 | */ 395 | typedef struct vm_code_s { 396 | vm_code_type_e type; /**< 指令类型 */ 397 | struct vm_stack_arg_s argv; /**< 指令参数 */ 398 | } vm_code_t; 399 | 400 | #endif // !NGX_HTTP_WAF_MODULE_TYPE_H -------------------------------------------------------------------------------- /inc/ngx_http_waf_module_under_attack.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #ifndef __NGX_HTTP_WAF_MODULE_UNDER_ATTACK_H__ 6 | #define __NGX_HTTP_WAF_MODULE_UNDER_ATTACK_H__ 7 | 8 | 9 | extern ngx_module_t ngx_http_waf_module; /**< 模块详情 */ 10 | 11 | /** 12 | * @brief 进行五秒盾检测 13 | */ 14 | ngx_int_t ngx_http_waf_check_under_attack(ngx_http_request_t* r, ngx_int_t* out_http_status); 15 | 16 | 17 | /** 18 | * @brief 生成用于验证五秒盾的三个 Cookie 19 | */ 20 | ngx_int_t ngx_http_waf_gen_cookie(ngx_http_request_t *r); 21 | 22 | 23 | /** 24 | * @brief 生成 Cookie 完整性校验码 25 | * @param[in] uid 对应 Cookie __waf_under_attack_uid 26 | * @param[in] uid_len 不包括结尾的 \0 27 | * @param[out] dst 对应 Cookie __waf_under_attack_verification,生成的校验码将保存到此处。 28 | * @param[in] dst_len 不包括结尾的 \0 29 | * @param[in] now 对应 Cookie __waf_under_attack_time 30 | * @param[in] now_len 不包括结尾的 \0 31 | */ 32 | ngx_int_t ngx_http_waf_gen_verification(ngx_http_request_t *r, 33 | u_char* uid, 34 | size_t uid_len, 35 | u_char* dst, 36 | size_t dst_len, 37 | u_char* now, 38 | size_t now_len); 39 | 40 | 41 | void ngx_http_waf_gen_ctx_and_header_location(ngx_http_request_t *r); 42 | 43 | 44 | #endif -------------------------------------------------------------------------------- /inc/ngx_http_waf_module_util.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_http_waf_module_util.h 3 | * @brief IPV4 字符串解析,nginx 风格转化为 C 风格字符串。 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #ifndef NGX_HTTP_WAF_MODULE_UTIL_H 13 | #define NGX_HTTP_WAF_MODULE_UTIL_H 14 | 15 | 16 | /** 17 | * @defgroup util 工具代码 18 | * @addtogroup util 工具代码 19 | * @brief IPV4 字符串解析,nginx 风格转化为 C 风格字符串。 20 | * @{ 21 | */ 22 | 23 | /** 24 | * @brief 将一个字符串形式的 IPV4 地址转化为 ipv4_t。 25 | * @param[in] text 要转换的字符串 26 | * @param[out] ipv4 转换完成后的格式化的 ipv4 27 | * @return 成功返回 SUCCESS,失败返回 FAIL。 28 | * @retval SUCCESS 转换成功 29 | * @retval FAIL 转化错误 30 | */ 31 | ngx_int_t ngx_http_waf_parse_ipv4(ngx_str_t text, ipv4_t* ipv4); 32 | 33 | 34 | /** 35 | * @brief 将一个字符串形式的 IPV6 地址转化为 ipv6_t。 36 | * @param[in] text 要转换的字符串 37 | * @param[out] ipv6 转换完成后的格式化的 ipv6 38 | * @return 成功返回 SUCCESS,失败返回 FAIL。 39 | * @retval SUCCESS 转换成功 40 | * @retval FAIL 转化错误 41 | */ 42 | #if (NGX_HAVE_INET6) 43 | ngx_int_t ngx_http_waf_parse_ipv6(ngx_str_t text, ipv6_t* ipv6); 44 | #endif 45 | 46 | 47 | /** 48 | * @brief 将一个形如 10s 10m 10h 10d 这样的字符串转化为整数,单位是秒。 49 | * @param[in] str 要解析的字符串 50 | * @return 失败返回 NGX_ERROR,反之则不是。 51 | */ 52 | ngx_int_t ngx_http_waf_parse_time(u_char* str); 53 | 54 | 55 | /** 56 | * @brief 将一个形如 10k 10m 10g 这样的字符串转化为整数,单位是字节。 57 | * @param[in] str 要解析的字符串 58 | * @return 失败返回 NGX_ERROR,反之则不是。 59 | */ 60 | ngx_int_t ngx_http_waf_parse_size(u_char* str); 61 | 62 | 63 | /** 64 | * @brief 将一个 Cookie 字符串分割为一个一个的键值对。 65 | * @param[in] cookies 字符串形式的 Cookie 66 | * @param[out] array 保存解析结果的数组 67 | * @return 成功则返回 SUCCESS,反之则不是。 68 | * @note 数组内容格式为 [key, value, key, value, ......] 69 | * @warning 使用完毕后请自行释放数组所占用内存。 70 | */ 71 | ngx_int_t ngx_http_waf_parse_cookie(ngx_str_t* native_cookie, UT_array** array); 72 | 73 | 74 | /** 75 | * @brief 将一个 Query String 字符串解析为哈希表 76 | * @param[in] native_query_string 字符串形式的 Cookie 77 | * @param[out] hash_head 保存解析结果的哈希表 78 | * @return 成功则返回 SUCCESS,反之则不是。 79 | * @warning 使用完毕后请自行释放数组所占用内存。 80 | */ 81 | ngx_int_t ngx_http_waf_parse_query_string(ngx_str_t* native_query_string, key_value_t** hash_head); 82 | 83 | 84 | /** 85 | * @brief 将一个 Header 列表解析为哈希表 86 | * @param[in] native_header Header 列表 87 | * @param[out] hash_head 保存解析结果的哈希表 88 | * @return 成功则返回 SUCCESS,反之则不是。 89 | * @warning 使用完毕后请自行释放数组所占用内存。 90 | */ 91 | ngx_int_t ngx_http_waf_parse_header(ngx_list_t* native_header, key_value_t** hash_head); 92 | 93 | 94 | /** 95 | * @brief 字符串分割 96 | * @param[in] str 要分割的字符串 97 | * @param[in] sep 分隔符 98 | * @param[out] max_len 分割后单个字符串的最大长度 99 | * @param[out] array 存放分割结果的数组 100 | * @return 成功则返回 SUCCESS,反之则不是。 101 | * @warning 使用完毕后请自行释放数组所占用内存。 102 | */ 103 | ngx_int_t ngx_http_waf_str_split(ngx_str_t* str, u_char sep, size_t max_len, UT_array** array); 104 | 105 | 106 | /** 107 | * @brief IPV4 网段比较 108 | * @param[in] ip 某个 IP 109 | * @param[in] ipv4 某个 IP 或者某个网段 110 | * @return 网段匹配则返回 MATCHED,反之则为 NOT_MATCHED。 111 | * @note 所有参数均为网络字节序 112 | */ 113 | ngx_int_t ngx_http_waf_ipv4_netcmp(uint32_t ip, const ipv4_t* ipv4); 114 | 115 | 116 | /** 117 | * @brief IPV4 网段比较 118 | * @param[in] ip 某个 IP 119 | * @param[in] ipv6 某个 IP 或者某个网段 120 | * @return 网段匹配则返回 MATCHED,反之则为 NOT_MATCHED。 121 | * @note 所有参数均为网络字节序 122 | */ 123 | #if (NGX_HAVE_INET6) 124 | ngx_int_t ngx_http_waf_ipv6_netcmp(uint8_t ip[16], const ipv6_t* ipv6); 125 | #endif 126 | 127 | 128 | /** 129 | * @brief 字符串分割 130 | * @param[in] str 要分割的字符串 131 | * @param[in] sep 分隔符 132 | * @param[out] max_len 分割后单个字符串的最大长度 133 | * @param[out] array 存放分割结果的数组 134 | * @return 成功则返回 SUCCESS,反之则不是。 135 | * @warning 使用完毕后请自行释放数组所占用内存。 136 | */ 137 | // ngx_int_t str_split(u_char* str, u_char sep, size_t max_len, UT_array** array); 138 | 139 | 140 | /** 141 | * @brief 将 ngx_str 转化为 C 风格的字符串 142 | * @param[out] destination 存储 C 风格字符串的字符数组 143 | * @param[in] ngx_str 要转换的 nginx 风格的字符串 144 | * @return 转换成功则返回 C 风格字符串的结尾的 '\0' 的地址,反之返回 NULL。 145 | * @retval !NULL C 风格字符串的结尾的 '\0' 的地址 146 | * @retval NULL 转换失败 147 | */ 148 | char* ngx_http_waf_to_c_str(u_char* destination, ngx_str_t ngx_str); 149 | 150 | 151 | /** 152 | * @brief 生成一个 C 风格的随机字符串 153 | * @param[out] dest 存储 C 风格字符串的字符数组 154 | * @param[in] len 要生成的字符串的长度,不包含结尾的 \0 。 155 | * @return 成功返回 NGX_HTTP_WAF_SUCCESS,反之则不是。 156 | */ 157 | ngx_int_t ngx_http_waf_rand_str(u_char* dest, size_t len); 158 | 159 | 160 | /** 161 | * @brief 计算 SHA256 并返回 16 进制字符串。 162 | * @param[out] dst 存储 SHA256 字符串的缓冲区 163 | * @param[in] dst_len 不包含结尾的 \0 164 | * @param[in] buf 用来计算数据所在的缓冲区 165 | * @param[in] buf_len 缓冲区长度 166 | * @return 成功返回 NGX_HTTP_WAF_SUCCESS,反之则不是。 167 | */ 168 | ngx_int_t ngx_http_waf_sha256(u_char* dst, size_t dst_len, const u_char* buf, size_t buf_len); 169 | 170 | 171 | void ngx_http_waf_utarray_ngx_str_ctor(void *dst, const void *src); 172 | 173 | 174 | void ngx_http_waf_utarray_ngx_str_dtor(void* elt); 175 | 176 | 177 | void ngx_http_waf_utarray_vm_code_ctor(void *dst, const void *src); 178 | 179 | 180 | void ngx_http_waf_utarray_vm_code_dtor(void* elt); 181 | 182 | 183 | /** 184 | * @} 185 | */ 186 | 187 | 188 | #endif -------------------------------------------------------------------------------- /inc/ngx_http_waf_module_vm.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifndef __NGX_HTTP_WAF_MODULE_VM_H__ 11 | #define __NGX_HTTP_WAF_MODULE_VM_H__ 12 | 13 | void ngx_http_waf_print_code(UT_array* array); 14 | 15 | 16 | /** 17 | * @brief 执行高级规则 18 | * @param[out] out_http_status 要返回的 HTTP 状态码 19 | * @return 如果命中规则则返回 NGX_HTTP_WAF_MATCHED,反之则为 NGX_HTTP_WAF_NOT_MATCHED。 20 | */ 21 | ngx_int_t ngx_http_waf_vm_exec(ngx_http_request_t* r, ngx_int_t* out_http_status); 22 | 23 | #endif -------------------------------------------------------------------------------- /src/ngx_http_waf_module_core.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static ngx_command_t ngx_http_waf_commands[] = { 9 | { 10 | ngx_string("waf"), 11 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_FLAG, 12 | ngx_http_waf_conf, 13 | NGX_HTTP_LOC_CONF_OFFSET, 14 | offsetof(ngx_http_waf_loc_conf_t, waf), 15 | NULL 16 | }, 17 | { 18 | ngx_string("waf_rule_path"), 19 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, 20 | ngx_http_waf_rule_path_conf, 21 | NGX_HTTP_LOC_CONF_OFFSET, 22 | offsetof(ngx_http_waf_loc_conf_t, waf_rule_path), 23 | NULL 24 | }, 25 | { 26 | ngx_string("waf_mode"), 27 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_1MORE, 28 | ngx_http_waf_mode_conf, 29 | NGX_HTTP_LOC_CONF_OFFSET, 30 | 0, 31 | NULL 32 | }, 33 | { 34 | ngx_string("waf_cc_deny"), 35 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE123, 36 | ngx_http_waf_cc_deny_conf, 37 | NGX_HTTP_LOC_CONF_OFFSET, 38 | 0, 39 | NULL 40 | }, 41 | { 42 | ngx_string("waf_cache"), 43 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE123, 44 | ngx_http_waf_cache_conf, 45 | NGX_HTTP_LOC_CONF_OFFSET, 46 | 0, 47 | NULL 48 | }, 49 | { 50 | ngx_string("waf_under_attack"), 51 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2, 52 | ngx_http_waf_under_attack_conf, 53 | NGX_HTTP_LOC_CONF_OFFSET, 54 | 0, 55 | NULL 56 | }, 57 | { 58 | ngx_string("waf_priority"), 59 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, 60 | ngx_http_waf_priority_conf, 61 | NGX_HTTP_LOC_CONF_OFFSET, 62 | 0, 63 | NULL 64 | }, 65 | { 66 | ngx_string("waf_http_status"), 67 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE12, 68 | ngx_http_waf_http_status_conf, 69 | NGX_HTTP_LOC_CONF_OFFSET, 70 | 0, 71 | NULL 72 | }, 73 | ngx_null_command 74 | }; 75 | 76 | 77 | static ngx_http_module_t ngx_http_waf_module_ctx = { 78 | NULL, 79 | ngx_http_waf_init_after_load_config, 80 | ngx_http_waf_create_main_conf, 81 | NULL, 82 | NULL, 83 | NULL, 84 | ngx_http_waf_create_loc_conf, 85 | ngx_http_waf_merge_loc_conf 86 | }; 87 | 88 | 89 | ngx_module_t ngx_http_waf_module = { 90 | NGX_MODULE_V1, 91 | &ngx_http_waf_module_ctx, /* module context */ 92 | ngx_http_waf_commands, /* module directives */ 93 | NGX_HTTP_MODULE, /* module type */ 94 | NULL, /* init master */ 95 | NULL, /* init module */ 96 | ngx_http_waf_init_process, /* init process */ 97 | NULL, /* init thread */ 98 | NULL, /* exit thread */ 99 | NULL, /* exit process */ 100 | NULL, /* exit master */ 101 | NGX_MODULE_V1_PADDING 102 | }; 103 | 104 | 105 | static ngx_int_t _read_request_body(ngx_http_request_t* r); 106 | 107 | 108 | static void _handler_read_request_body(ngx_http_request_t* r); 109 | 110 | 111 | ngx_int_t ngx_http_waf_init_process(ngx_cycle_t *cycle) { 112 | randombytes_stir(); 113 | return NGX_OK; 114 | } 115 | 116 | 117 | ngx_int_t ngx_http_waf_handler_access_phase(ngx_http_request_t* r) { 118 | return ngx_http_waf_check_all(r, NGX_HTTP_WAF_TRUE); 119 | } 120 | 121 | 122 | ngx_int_t ngx_http_waf_check_all(ngx_http_request_t* r, ngx_int_t is_check_cc) { 123 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 124 | "ngx_waf_debug: The scheduler has been started."); 125 | 126 | ngx_http_waf_ctx_t* ctx = NULL; 127 | ngx_http_waf_loc_conf_t* loc_conf = NULL; 128 | ngx_http_waf_get_ctx_and_conf(r, &loc_conf, &ctx); 129 | ngx_int_t is_matched = NGX_HTTP_WAF_NOT_MATCHED; 130 | ngx_int_t http_status = NGX_DECLINED; 131 | 132 | if (ctx == NULL) { 133 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 134 | "ngx_waf_debug: Start allocating memory for storage contexts."); 135 | ngx_http_cleanup_t* cln = ngx_palloc(r->pool, sizeof(ngx_http_cleanup_t)); 136 | ctx = ngx_palloc(r->pool, sizeof(ngx_http_waf_ctx_t)); 137 | if (ctx == NULL || cln == NULL) { 138 | http_status = NGX_HTTP_INTERNAL_SERVER_ERROR; 139 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, 140 | "ngx_waf: The request context could not be created because the memory allocation failed."); 141 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 142 | "ngx_waf_debug: The scheduler shutdown abnormally."); 143 | return http_status; 144 | } 145 | else { 146 | cln->handler = ngx_http_waf_handler_cleanup; 147 | cln->data = ctx; 148 | cln->next = NULL; 149 | 150 | ctx->read_body_done = NGX_HTTP_WAF_FALSE; 151 | ctx->has_req_body = NGX_HTTP_WAF_FALSE; 152 | ctx->waiting_more_body = NGX_HTTP_WAF_FALSE; 153 | ctx->checked = NGX_HTTP_WAF_FALSE; 154 | ctx->blocked = NGX_HTTP_WAF_FALSE; 155 | ctx->spend = (double)clock() / CLOCKS_PER_SEC * 1000; 156 | ctx->rule_type[0] = '\0'; 157 | ctx->rule_deatils[0] = '\0'; 158 | 159 | if (r->cleanup == NULL) { 160 | r->cleanup = cln; 161 | } else { 162 | for (ngx_http_cleanup_t* i = r->cleanup; i != NULL; i = i->next) { 163 | if (i->next == NULL) { 164 | i->next = cln; 165 | break; 166 | } 167 | } 168 | } 169 | 170 | ngx_http_set_ctx(r, ctx, ngx_http_waf_module); 171 | 172 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 173 | "ngx_waf_debug: Context initialization is complete."); 174 | } 175 | } else if (ngx_http_get_module_ctx(r, ngx_http_waf_module) == NULL) { 176 | ngx_http_set_ctx(r, ctx, ngx_http_waf_module); 177 | } 178 | 179 | if (ctx->waiting_more_body == NGX_HTTP_WAF_TRUE) { 180 | return NGX_DONE; 181 | } 182 | 183 | if (ctx->read_body_done != NGX_HTTP_WAF_TRUE) { 184 | r->request_body_in_single_buf = 1; 185 | r->request_body_in_persistent_file = 1; 186 | r->request_body_in_clean_file = 1; 187 | 188 | ngx_int_t rc = ngx_http_read_client_request_body(r, _handler_read_request_body); 189 | if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { 190 | return rc; 191 | } 192 | if (rc == NGX_AGAIN) { 193 | ctx->waiting_more_body = NGX_HTTP_WAF_TRUE; 194 | return NGX_DONE; 195 | } 196 | } 197 | 198 | if (loc_conf->waf == 0 || loc_conf->waf == NGX_CONF_UNSET) { 199 | http_status = NGX_DECLINED; 200 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 201 | "ngx_waf_debug: Skip scheduling."); 202 | 203 | } else if (ngx_http_waf_check_flag(loc_conf->waf_mode, r->method) == NGX_HTTP_WAF_FALSE) { 204 | http_status = NGX_DECLINED; 205 | 206 | } else if (r->internal != 0 && ctx->checked == NGX_HTTP_WAF_TRUE) { 207 | http_status = NGX_DECLINED; 208 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 209 | "ngx_waf_debug: Skip scheduling."); 210 | 211 | } else if (_read_request_body(r) == NGX_HTTP_WAF_BAD) { 212 | return NGX_HTTP_INTERNAL_SERVER_ERROR; 213 | 214 | } else { 215 | ctx->checked = NGX_HTTP_WAF_TRUE; 216 | ngx_http_waf_check_pt* funcs = loc_conf->check_proc; 217 | for (size_t i = 0; funcs[i] != NULL; i++) { 218 | is_matched = funcs[i](r, &http_status); 219 | if (is_matched == NGX_HTTP_WAF_MATCHED) { 220 | break; 221 | } 222 | } 223 | } 224 | 225 | if (http_status != NGX_DECLINED && http_status != NGX_DONE && http_status != NGX_ERROR) { 226 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, "ngx_waf: [%s][%s]", ctx->rule_type, ctx->rule_deatils); 227 | } 228 | 229 | if (http_status != NGX_DONE) { 230 | ctx->spend = ((double)clock() / CLOCKS_PER_SEC * 1000) - ctx->spend; 231 | } 232 | 233 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 234 | "ngx_waf_debug: The scheduler shutdown normally."); 235 | return http_status; 236 | } 237 | 238 | 239 | static ngx_int_t _read_request_body(ngx_http_request_t* r) { 240 | ngx_http_waf_ctx_t* ctx = NULL; 241 | ngx_http_waf_loc_conf_t* loc_conf = NULL; 242 | ngx_http_waf_get_ctx_and_conf(r, &loc_conf, &ctx); 243 | 244 | 245 | if (r->request_body == NULL) { 246 | return NGX_HTTP_WAF_FAIL; 247 | } 248 | 249 | if (r->request_body->bufs == NULL) { 250 | return NGX_HTTP_WAF_FAIL; 251 | } 252 | 253 | if (r->request_body->temp_file) { 254 | return NGX_HTTP_WAF_FAIL; 255 | } 256 | 257 | if (ctx->has_req_body == NGX_HTTP_WAF_TRUE) { 258 | return NGX_HTTP_WAF_SUCCESS; 259 | } 260 | 261 | ngx_chain_t* bufs = r->request_body->bufs; 262 | size_t len = 0; 263 | 264 | while (bufs != NULL) { 265 | len += (bufs->buf->last - bufs->buf->pos) * (sizeof(u_char) / sizeof(uint8_t)); 266 | bufs = bufs->next; 267 | } 268 | 269 | u_char* body = ngx_pnalloc(r->pool, len + sizeof(u_char)); 270 | if (body == NULL) { 271 | return NGX_HTTP_WAF_BAD; 272 | } 273 | 274 | ctx->has_req_body = NGX_HTTP_WAF_TRUE; 275 | ctx->req_body.pos = body; 276 | ctx->req_body.last = (u_char*)((uint8_t*)body + len); 277 | 278 | bufs = r->request_body->bufs; 279 | size_t offset = 0; 280 | while (bufs != NULL) { 281 | size_t size = bufs->buf->last - bufs->buf->pos; 282 | ngx_memcpy((uint8_t*)body + offset, bufs->buf->pos, size); 283 | offset += size; 284 | bufs = bufs->next; 285 | } 286 | return NGX_HTTP_WAF_SUCCESS; 287 | } 288 | 289 | 290 | static void _handler_read_request_body(ngx_http_request_t* r) { 291 | ngx_http_waf_ctx_t* ctx = NULL; 292 | ngx_http_waf_loc_conf_t* loc_conf = NULL; 293 | ngx_http_waf_get_ctx_and_conf(r, &loc_conf, &ctx); 294 | 295 | ctx->read_body_done = NGX_HTTP_WAF_TRUE; 296 | ngx_http_finalize_request(r, NGX_DONE); 297 | 298 | if (ctx->waiting_more_body == NGX_HTTP_WAF_TRUE) { 299 | ctx->waiting_more_body = NGX_HTTP_WAF_FALSE; 300 | ngx_http_core_run_phases(r); 301 | } 302 | } 303 | 304 | 305 | void ngx_http_waf_handler_cleanup(void *data) { 306 | return; 307 | } 308 | -------------------------------------------------------------------------------- /src/ngx_http_waf_module_ip_trie.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | ngx_int_t ip_trie_init(ip_trie_t* trie, mem_pool_type_e pool_type, void* native_pool, int ip_type) { 5 | if (trie == NULL) { 6 | return NGX_HTTP_WAF_FAIL; 7 | } 8 | 9 | 10 | if (mem_pool_init(&trie->pool, pool_type, native_pool) != NGX_HTTP_WAF_SUCCESS) { 11 | return NGX_HTTP_WAF_FAIL; 12 | } 13 | 14 | trie->ip_type = ip_type; 15 | trie->root = (ip_trie_node_t*)mem_pool_calloc(&trie->pool, sizeof(ip_trie_node_t)); 16 | trie->size = 0; 17 | trie->match_all = NGX_HTTP_WAF_FALSE; 18 | 19 | if (trie->root == NULL) { 20 | return NGX_HTTP_WAF_MALLOC_ERROR; 21 | } 22 | 23 | return NGX_HTTP_WAF_SUCCESS; 24 | } 25 | 26 | 27 | ngx_int_t ip_trie_add(ip_trie_t* trie, inx_addr_t* inx_addr, uint32_t suffix_num, void* data, size_t data_byte_length) { 28 | if (trie == NULL || inx_addr == NULL) { 29 | return NGX_HTTP_WAF_FAIL; 30 | } 31 | 32 | ip_trie_node_t* new_node = NULL; 33 | 34 | if (ip_trie_find(trie, inx_addr, &new_node) == NGX_HTTP_WAF_SUCCESS) { 35 | return NGX_HTTP_WAF_FAIL; 36 | } 37 | 38 | new_node = (ip_trie_node_t*)mem_pool_calloc(&trie->pool, sizeof(ip_trie_node_t)); 39 | if (new_node == NULL) { 40 | return NGX_HTTP_WAF_MALLOC_ERROR; 41 | } 42 | 43 | new_node->data = mem_pool_calloc(&trie->pool, data_byte_length); 44 | if (new_node->data == NULL) { 45 | return NGX_HTTP_WAF_MALLOC_ERROR; 46 | } 47 | 48 | new_node->is_ip = NGX_HTTP_WAF_TRUE; 49 | ngx_memcpy(new_node->data, data, data_byte_length); 50 | new_node->data_byte_length = data_byte_length; 51 | 52 | if (suffix_num == 0) { 53 | trie->match_all = NGX_HTTP_WAF_TRUE; 54 | mem_pool_free(&trie->pool, trie->root); 55 | trie->root = new_node; 56 | return NGX_HTTP_WAF_SUCCESS; 57 | } 58 | 59 | ip_trie_node_t* prev_node = trie->root; 60 | ip_trie_node_t* cur_node = trie->root; 61 | uint32_t bit_index = 0, uint8_index; 62 | int prev_bit = 0; 63 | 64 | if (trie->ip_type == AF_INET) { 65 | uint8_t u8_addr[4]; 66 | u8_addr[0] = (uint8_t)(inx_addr->ipv4.s_addr & 0x000000ff); 67 | u8_addr[1] = (uint8_t)((inx_addr->ipv4.s_addr & 0x0000ff00) >> 8); 68 | u8_addr[2] = (uint8_t)((inx_addr->ipv4.s_addr & 0x00ff0000) >> 16); 69 | u8_addr[3] = (uint8_t)((inx_addr->ipv4.s_addr & 0xff000000) >> 24); 70 | 71 | while (bit_index < suffix_num - 1) { 72 | uint8_index = bit_index / 8; 73 | if (cur_node == NULL) { 74 | cur_node = (ip_trie_node_t*)mem_pool_calloc(&trie->pool, sizeof(ip_trie_node_t)); 75 | if (cur_node == NULL) { 76 | return NGX_HTTP_WAF_MALLOC_ERROR; 77 | } 78 | if (prev_bit == 0) { 79 | prev_node->left = cur_node; 80 | } else { 81 | prev_node->right = cur_node; 82 | } 83 | } 84 | prev_node = cur_node; 85 | if (ngx_http_waf_check_bit(u8_addr[uint8_index], 7 - (bit_index % 8)) != NGX_HTTP_WAF_TRUE) { 86 | prev_bit = 0; 87 | cur_node = cur_node->left; 88 | } else { 89 | prev_bit = 1; 90 | cur_node = cur_node->right; 91 | } 92 | ++bit_index; 93 | } 94 | if (cur_node == NULL) { 95 | cur_node = (ip_trie_node_t*)mem_pool_calloc(&trie->pool, sizeof(ip_trie_node_t)); 96 | if (cur_node == NULL) { 97 | return NGX_HTTP_WAF_MALLOC_ERROR; 98 | } 99 | if (prev_bit == 0) { 100 | prev_node->left = cur_node; 101 | } else { 102 | prev_node->right = cur_node; 103 | } 104 | } 105 | uint8_index = bit_index / 8; 106 | if (ngx_http_waf_check_bit(u8_addr[uint8_index], 7 - (bit_index % 8)) != NGX_HTTP_WAF_TRUE) { 107 | cur_node->left = new_node; 108 | } else { 109 | cur_node->right = new_node; 110 | } 111 | 112 | } 113 | #if (NGX_HAVE_INET6) 114 | else if (trie->ip_type == AF_INET6) { 115 | while (bit_index < suffix_num - 1) { 116 | uint8_index = bit_index / 8; 117 | if (cur_node == NULL) { 118 | cur_node = (ip_trie_node_t*)mem_pool_calloc(&trie->pool, sizeof(ip_trie_node_t)); 119 | if (cur_node == NULL) { 120 | return NGX_HTTP_WAF_MALLOC_ERROR; 121 | } 122 | if (prev_bit == 0) { 123 | prev_node->left = cur_node; 124 | } else { 125 | prev_node->right = cur_node; 126 | } 127 | } 128 | prev_node = cur_node; 129 | if (ngx_http_waf_check_bit(inx_addr->ipv6.s6_addr[uint8_index], 7 - (bit_index % 8)) != NGX_HTTP_WAF_TRUE) { 130 | cur_node = cur_node->left; 131 | prev_bit = 0; 132 | } else { 133 | cur_node = cur_node->right; 134 | prev_bit = 1; 135 | } 136 | ++bit_index; 137 | } 138 | if (cur_node == NULL) { 139 | cur_node = (ip_trie_node_t*)mem_pool_calloc(&trie->pool, sizeof(ip_trie_node_t)); 140 | if (cur_node == NULL) { 141 | return NGX_HTTP_WAF_MALLOC_ERROR; 142 | } 143 | if (prev_bit == 0) { 144 | prev_node->left = cur_node; 145 | } else { 146 | prev_node->right = cur_node; 147 | } 148 | } 149 | uint8_index = bit_index / 8; 150 | if (ngx_http_waf_check_bit(inx_addr->ipv6.s6_addr[uint8_index], 7 - (bit_index % 8)) != NGX_HTTP_WAF_TRUE) { 151 | cur_node->left = new_node; 152 | } else { 153 | cur_node->right = new_node; 154 | } 155 | } 156 | #endif 157 | 158 | return NGX_HTTP_WAF_SUCCESS; 159 | } 160 | 161 | 162 | ngx_int_t ip_trie_find(ip_trie_t* trie, inx_addr_t* inx_addr, ip_trie_node_t** ip_trie_node) { 163 | if (trie == NULL || inx_addr == NULL || ip_trie_node ==NULL) { 164 | return NGX_HTTP_WAF_FAIL; 165 | } 166 | 167 | *ip_trie_node = NULL; 168 | 169 | if (trie->match_all == NGX_HTTP_WAF_TRUE) { 170 | *ip_trie_node = trie->root; 171 | return NGX_HTTP_WAF_SUCCESS; 172 | } 173 | 174 | ip_trie_node_t* cur_node = trie->root; 175 | ngx_int_t is_found = NGX_HTTP_WAF_FAIL; 176 | uint32_t bit_index = 0; 177 | 178 | if (trie->ip_type == AF_INET) { 179 | uint8_t u8_addr[4]; 180 | u8_addr[0] = (uint8_t)(inx_addr->ipv4.s_addr & 0x000000ff); 181 | u8_addr[1] = (uint8_t)((inx_addr->ipv4.s_addr & 0x0000ff00) >> 8); 182 | u8_addr[2] = (uint8_t)((inx_addr->ipv4.s_addr & 0x00ff0000) >> 16); 183 | u8_addr[3] = (uint8_t)((inx_addr->ipv4.s_addr & 0xff000000) >> 24); 184 | 185 | while (bit_index < 32 && cur_node != NULL && cur_node->is_ip != NGX_HTTP_WAF_TRUE) { 186 | int uint8_index = bit_index / 8; 187 | if (ngx_http_waf_check_bit(u8_addr[uint8_index], 7 - (bit_index % 8)) != NGX_HTTP_WAF_TRUE) { 188 | cur_node = cur_node->left; 189 | } else { 190 | cur_node = cur_node->right; 191 | } 192 | ++bit_index; 193 | } 194 | 195 | } 196 | #if (NGX_HAVE_INET6) 197 | else if (trie->ip_type == AF_INET6) { 198 | while (bit_index < 128 && cur_node != NULL && cur_node->is_ip != NGX_HTTP_WAF_TRUE) { 199 | int uint8_index = bit_index / 8; 200 | if (ngx_http_waf_check_bit(inx_addr->ipv6.s6_addr[uint8_index], 7 - (bit_index % 8)) != NGX_HTTP_WAF_TRUE) { 201 | cur_node = cur_node->left; 202 | } else { 203 | cur_node = cur_node->right; 204 | } 205 | ++bit_index; 206 | } 207 | } 208 | #endif 209 | 210 | if (cur_node != NULL && cur_node->is_ip == NGX_HTTP_WAF_TRUE) { 211 | is_found = NGX_HTTP_WAF_SUCCESS; 212 | *ip_trie_node = cur_node; 213 | } 214 | 215 | return is_found; 216 | } 217 | 218 | 219 | // ngx_int_t ip_trie_delete(ip_trie_t* trie, inx_addr_t* inx_addr) { 220 | // if (trie == NULL || inx_addr == NULL) { 221 | // return NGX_HTTP_WAF_FAIL; 222 | // } 223 | 224 | // ip_trie_node_t* node = NULL; 225 | // ngx_int_t ret = ip_trie_find(trie, inx_addr, &node); 226 | // if (ret != NGX_HTTP_WAF_TRUE) { 227 | // return ret; 228 | // } 229 | 230 | // node->data_byte_length = 0; 231 | // node->is_ip = NGX_HTTP_WAF_FALSE; 232 | 233 | // ret = mem_pool_free(&trie->pool, node->data); 234 | // if (ret != NGX_HTTP_WAF_SUCCESS) { 235 | // return ret; 236 | // } 237 | 238 | // node->data = NULL; 239 | 240 | // return NGX_HTTP_WAF_SUCCESS; 241 | // } 242 | 243 | 244 | // ngx_int_t ip_trie_clear(ip_trie_t* trie) { 245 | // circular_doublly_linked_list_t* head = NULL; 246 | 247 | // _ip_trie_traversal(trie->root, &head); 248 | // if (head == NULL) { 249 | // return NGX_HTTP_WAF_SUCCESS; 250 | // } 251 | 252 | // circular_doublly_linked_list_t* item = NULL; 253 | // ip_trie_node_t* node = NULL; 254 | 255 | // while ((item = head->next), (item != NULL && item != head)) { 256 | // node = item->data; 257 | // if (node->data != NULL) { 258 | // mem_pool_free(&trie->pool, node->data); 259 | // } 260 | // mem_pool_free(&trie->pool, item->data); 261 | // CDL_DELETE(head, item); 262 | // free(item); 263 | // } 264 | 265 | // if (item != NULL) { 266 | // node = item->data; 267 | // if (node->data != NULL) { 268 | // mem_pool_free(&trie->pool, node->data); 269 | // } 270 | // mem_pool_free(&trie->pool, head->data); 271 | // item = head; 272 | // CDL_DELETE(head, head); 273 | // free(item); 274 | // } 275 | 276 | // trie->root->left = NULL; 277 | // trie->root->right = NULL; 278 | 279 | // return NGX_HTTP_WAF_SUCCESS; 280 | // } 281 | 282 | 283 | // void _ip_trie_traversal(ip_trie_node_t* node, circular_doublly_linked_list_t** head) { 284 | // if (node == NULL) { 285 | // return; 286 | // } 287 | 288 | // circular_doublly_linked_list_t* item = NULL; 289 | 290 | // if (node->left != NULL) { 291 | // item = malloc(sizeof(circular_doublly_linked_list_t)); 292 | // if (item != NULL) { 293 | // ngx_memzero(item, sizeof(circular_doublly_linked_list_t)); 294 | // item->data = node->left; 295 | // item->data_byte_length = sizeof(ip_trie_node_t); 296 | // CDL_APPEND(*head, item); 297 | // _ip_trie_traversal(node->left, head); 298 | // } 299 | // } 300 | 301 | // if (node->right != NULL) { 302 | // item = malloc(sizeof(circular_doublly_linked_list_t)); 303 | // if (item != NULL) { 304 | // ngx_memzero(item, sizeof(circular_doublly_linked_list_t)); 305 | // item->data = node->right; 306 | // item->data_byte_length = sizeof(ip_trie_node_t); 307 | // CDL_APPEND(*head, item); 308 | // _ip_trie_traversal(node->right, head); 309 | // } 310 | // } 311 | 312 | // } 313 | -------------------------------------------------------------------------------- /src/ngx_http_waf_module_lru_cache.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | lru_cache_item_t* _lru_cache_hash_find(lru_cache_t* lru, void* key, size_t key_len); 5 | 6 | 7 | void _lru_cache_hash_add(lru_cache_t* lru, lru_cache_item_t* item); 8 | 9 | 10 | void _lru_cache_hash_delete(lru_cache_t* lru, lru_cache_item_t* item); 11 | 12 | 13 | void* _lru_cache_hash_calloc(lru_cache_t* lru, size_t n); 14 | 15 | 16 | void _lru_cache_hash_free(lru_cache_t* lru, void* addr); 17 | 18 | 19 | void lru_cache_init(lru_cache_t** lru, size_t capacity, mem_pool_type_e pool_type, void* native_pool) { 20 | assert(lru != NULL); 21 | 22 | lru_cache_t* _lru; 23 | 24 | if (pool_type != std) { 25 | assert(native_pool != NULL); 26 | } 27 | 28 | mem_pool_t pool; 29 | assert(mem_pool_init(&pool, pool_type, native_pool) == NGX_HTTP_WAF_SUCCESS); 30 | 31 | _lru = mem_pool_calloc(&pool, sizeof(lru_cache_t)); 32 | assert(_lru != NULL); 33 | 34 | ngx_memzero(_lru, sizeof(lru_cache_t)); 35 | 36 | ngx_memcpy(&_lru->pool, &pool, sizeof(mem_pool_t)); 37 | _lru->last_eliminate = time(NULL); 38 | _lru->capacity = capacity; 39 | _lru->hash_head = NULL; 40 | _lru->chain_head = NULL; 41 | 42 | *lru = _lru; 43 | } 44 | 45 | 46 | lru_cache_add_result_t lru_cache_add(lru_cache_t* lru, void* key, size_t key_len) { 47 | assert(lru != NULL); 48 | assert(key != NULL); 49 | assert(key_len != 0); 50 | 51 | lru_cache_add_result_t ret; 52 | 53 | lru_cache_item_t* item = _lru_cache_hash_find(lru, key, key_len); 54 | if (item != NULL) { 55 | CDL_DELETE(lru->chain_head, item); 56 | CDL_PREPEND(lru->chain_head, item); 57 | ret.status = NGX_HTTP_WAF_KEY_EXISTS; 58 | ret.data = &item->data; 59 | return ret; 60 | } 61 | 62 | if (HASH_COUNT(lru->hash_head) >= lru->capacity) { 63 | lru_cache_eliminate(lru, 1); 64 | } 65 | 66 | 67 | item = mem_pool_calloc(&lru->pool, sizeof(lru_cache_item_t)); 68 | while (item == NULL && HASH_COUNT(lru->hash_head) != 0) { 69 | lru_cache_eliminate(lru, 1); 70 | item = mem_pool_calloc(&lru->pool, sizeof(lru_cache_item_t)); 71 | } 72 | 73 | if (item == NULL) { 74 | ret.status = NGX_HTTP_WAF_MALLOC_ERROR; 75 | ret.data = NULL; 76 | return ret; 77 | } 78 | 79 | item->key_ptr = mem_pool_calloc(&lru->pool, key_len); 80 | while (item->key_ptr == NULL && HASH_COUNT(lru->hash_head) != 0) { 81 | lru_cache_eliminate(lru, 1); 82 | item->key_ptr = mem_pool_calloc(&lru->pool, key_len); 83 | } 84 | 85 | if (item->key_ptr == NULL) { 86 | mem_pool_free(&lru->pool, item); 87 | ret.status = NGX_HTTP_WAF_MALLOC_ERROR; 88 | ret.data = NULL; 89 | return ret; 90 | } 91 | 92 | ngx_memcpy(item->key_ptr, key, key_len); 93 | item->key_byte_length = key_len; 94 | CDL_PREPEND(lru->chain_head, item); 95 | _lru_cache_hash_add(lru, item); 96 | 97 | ret.status = NGX_HTTP_WAF_SUCCESS; 98 | ret.data = &item->data; 99 | 100 | return ret; 101 | } 102 | 103 | 104 | lru_cache_find_result_t lru_cache_find(lru_cache_t* lru, void* key, size_t key_len) { 105 | assert(lru != NULL); 106 | assert(key != NULL); 107 | assert(key_len != 0); 108 | 109 | lru_cache_find_result_t ret; 110 | 111 | lru_cache_item_t* item = _lru_cache_hash_find(lru, key, key_len); 112 | if (item != NULL) { 113 | CDL_DELETE(lru->chain_head, item); 114 | CDL_PREPEND(lru->chain_head, item); 115 | ret.status = NGX_HTTP_WAF_KEY_EXISTS; 116 | ret.data = &item->data; 117 | } else { 118 | ret.status = NGX_HTTP_WAF_KEY_NOT_EXISTS; 119 | ret.data = NULL; 120 | } 121 | 122 | return ret; 123 | } 124 | 125 | 126 | void* lru_cache_calloc(lru_cache_t* lru, size_t size) { 127 | assert(lru != NULL); 128 | assert(size != 0); 129 | return mem_pool_calloc(&lru->pool, size); 130 | } 131 | 132 | 133 | void lru_cache_free(lru_cache_t* lru, void* addr) { 134 | assert(lru != NULL); 135 | assert(addr != NULL); 136 | assert(addr != NGX_CONF_UNSET_PTR); 137 | mem_pool_free(&lru->pool, addr); 138 | } 139 | 140 | 141 | void lru_cache_delete(lru_cache_t* lru, void* key, size_t key_len) { 142 | assert(lru != NULL); 143 | assert(key != NULL); 144 | assert(key_len != 0); 145 | 146 | lru_cache_item_t* item = _lru_cache_hash_find(lru, key, key_len); 147 | if (item != NULL) { 148 | _lru_cache_hash_delete(lru, item); 149 | CDL_DELETE(lru->chain_head, item); 150 | 151 | if (item->data != NULL) { 152 | lru_cache_free(lru, item->data); 153 | } 154 | 155 | mem_pool_free(&lru->pool, item->key_ptr); 156 | mem_pool_free(&lru->pool, item); 157 | } 158 | } 159 | 160 | 161 | void lru_cache_eliminate(lru_cache_t* lru, size_t count) { 162 | assert(lru != NULL); 163 | assert(count != 0); 164 | 165 | for (size_t i = 0; i < count; i++) { 166 | if (lru->chain_head != NULL) { 167 | lru_cache_item_t* tail = lru->chain_head->prev; 168 | lru_cache_delete(lru, tail->key_ptr, tail->key_byte_length); 169 | } 170 | } 171 | } 172 | 173 | 174 | void lru_cache_destory(lru_cache_t* lru) { 175 | mem_pool_free(&lru->pool, lru); 176 | } 177 | 178 | 179 | lru_cache_item_t* _lru_cache_hash_find(lru_cache_t* lru, void* key, size_t key_len) { 180 | lru_cache_item_t* ret; 181 | HASH_FIND(hh, lru->hash_head, key, key_len, ret); 182 | return ret; 183 | } 184 | 185 | void _lru_cache_hash_add(lru_cache_t* lru, lru_cache_item_t* item) { 186 | #undef uthash_malloc 187 | #undef uthash_free 188 | #define uthash_malloc(n) _lru_cache_hash_calloc(lru, n) 189 | #define uthash_free(ptr,sz) _lru_cache_hash_free(lru, ptr) 190 | HASH_ADD_KEYPTR(hh, lru->hash_head, item->key_ptr, item->key_byte_length, item); 191 | #undef uthash_malloc 192 | #undef uthash_free 193 | #define uthash_malloc(n) malloc(n) 194 | #define uthash_free(ptr, sz) free(ptr) 195 | } 196 | 197 | 198 | void _lru_cache_hash_delete(lru_cache_t* lru, lru_cache_item_t* item) { 199 | #undef uthash_malloc 200 | #undef uthash_free 201 | #define uthash_malloc(n) _lru_cache_hash_calloc(lru, n) 202 | #define uthash_free(ptr,sz) _lru_cache_hash_free(lru, ptr) 203 | HASH_DELETE(hh, lru->hash_head, item); 204 | #undef uthash_malloc 205 | #undef uthash_free 206 | #define uthash_malloc(n) malloc(n) 207 | #define uthash_free(ptr, sz) free(ptr) 208 | } 209 | 210 | 211 | void* _lru_cache_hash_calloc(lru_cache_t* lru, size_t n) { 212 | void* ret = mem_pool_calloc(&lru->pool, n); 213 | while (ret == NULL && HASH_COUNT(lru->hash_head) != 0) { 214 | lru_cache_eliminate(lru, 1); 215 | ret = mem_pool_calloc(&lru->pool, n); 216 | } 217 | assert(ret != NULL); 218 | return ret; 219 | } 220 | 221 | 222 | void _lru_cache_hash_free(lru_cache_t* lru, void* addr) { 223 | mem_pool_free(&lru->pool, addr); 224 | } -------------------------------------------------------------------------------- /src/ngx_http_waf_module_mem_pool.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | ngx_int_t mem_pool_init(mem_pool_t* pool, mem_pool_type_e type, void* native_pool) { 4 | if (pool == NULL || (type != std && native_pool == NULL)) { 5 | return NGX_HTTP_WAF_FAIL; 6 | } 7 | 8 | pool->type = type; 9 | 10 | switch (type) { 11 | case std: break; 12 | case gernal_pool: pool->native_pool.gernal_pool = (ngx_pool_t*)native_pool; break; 13 | case slab_pool: pool->native_pool.slab_pool = (ngx_slab_pool_t*)native_pool; break; 14 | } 15 | 16 | return NGX_HTTP_WAF_SUCCESS; 17 | } 18 | 19 | void* mem_pool_calloc(mem_pool_t* pool, ngx_uint_t byte_size) { 20 | void* addr; 21 | switch (pool->type) { 22 | case std: addr = malloc(byte_size); ngx_memzero(addr, byte_size); break; 23 | case gernal_pool: addr = ngx_pcalloc(pool->native_pool.gernal_pool, byte_size); break; 24 | case slab_pool: addr = ngx_slab_calloc_locked(pool->native_pool.slab_pool, byte_size); break; 25 | default: addr = NULL; break; 26 | } 27 | return addr; 28 | } 29 | 30 | ngx_int_t mem_pool_free(mem_pool_t* pool, void* buffer) { 31 | switch (pool->type) { 32 | case std: free(buffer); break; 33 | case gernal_pool: ngx_pfree(pool->native_pool.gernal_pool, buffer); break; 34 | case slab_pool: ngx_slab_free_locked(pool->native_pool.slab_pool, buffer); break; 35 | default: return NGX_HTTP_WAF_FAIL; 36 | } 37 | return NGX_HTTP_WAF_SUCCESS; 38 | } -------------------------------------------------------------------------------- /src/ngx_http_waf_module_under_attack.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | ngx_int_t ngx_http_waf_check_under_attack(ngx_http_request_t* r, ngx_int_t* out_http_status) { 5 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 6 | "ngx_waf_debug: Enter the Under-Attack processing flow."); 7 | 8 | ngx_http_waf_ctx_t* ctx = NULL; 9 | ngx_http_waf_loc_conf_t* loc_conf = NULL; 10 | ngx_http_waf_get_ctx_and_conf(r, &loc_conf, &ctx); 11 | 12 | if (loc_conf->waf_under_attack == 0 || loc_conf->waf_under_attack == NGX_CONF_UNSET) { 13 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 14 | "ngx_waf_debug: Because this Inspection is disabled in the configuration, no Inspection is performed."); 15 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 16 | "ngx_waf_debug: Processing is complete."); 17 | return NGX_HTTP_WAF_NOT_MATCHED; 18 | } 19 | 20 | if (ngx_strncmp(r->uri.data, 21 | loc_conf->waf_under_attack_uri.data, 22 | ngx_max(r->uri.len, loc_conf->waf_under_attack_uri.len)) == 0) { 23 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 24 | "ngx_waf_debug: Because this Inspection is disabled in the configuration, no Inspection is performed."); 25 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 26 | "ngx_waf_debug: Processing is complete."); 27 | return NGX_HTTP_WAF_NOT_MATCHED; 28 | } 29 | 30 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 31 | "ngx_waf_debug: Begin the processing flow."); 32 | 33 | ngx_str_t __waf_under_attack_time = { 0, NULL }; 34 | ngx_str_t __waf_under_attack_uid = { 0, NULL }; 35 | ngx_str_t __waf_under_attack_verification = { 0, NULL }; 36 | 37 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 38 | "ngx_waf_debug: Start parsing cookies."); 39 | 40 | #if (nginx_version >= 1023000) 41 | if (r->headers_in.cookie != NULL) { 42 | ngx_table_elt_t* cookies = r->headers_in.cookie; 43 | #else 44 | if (r->headers_in.cookies.nelts > 0) { 45 | ngx_array_t* cookies = &(r->headers_in.cookies); 46 | #endif 47 | ngx_str_t key, value; 48 | 49 | 50 | ngx_str_set(&key, "__waf_under_attack_time"); 51 | ngx_str_null(&value); 52 | 53 | #if (nginx_version >= 1023000) 54 | if (ngx_http_parse_multi_header_lines(r, cookies, &key, &value) != NULL) { 55 | #else 56 | if (ngx_http_parse_multi_header_lines(cookies, &key, &value) != NGX_DECLINED) { 57 | #endif 58 | __waf_under_attack_time.data = ngx_pcalloc(r->pool, value.len + 1); 59 | __waf_under_attack_time.len = value.len; 60 | ngx_memcpy(__waf_under_attack_time.data, value.data, value.len); 61 | 62 | } 63 | 64 | ngx_str_set(&key, "__waf_under_attack_uid"); 65 | ngx_str_null(&value); 66 | 67 | #if (nginx_version >= 1023000) 68 | if (ngx_http_parse_multi_header_lines(r, cookies, &key, &value) != NULL) { 69 | #else 70 | if (ngx_http_parse_multi_header_lines(cookies, &key, &value) != NGX_DECLINED) { 71 | #endif 72 | __waf_under_attack_uid.data = ngx_pcalloc(r->pool, value.len + 1); 73 | __waf_under_attack_uid.len = value.len; 74 | ngx_memcpy(__waf_under_attack_uid.data, value.data, value.len); 75 | 76 | } 77 | 78 | ngx_str_set(&key, "__waf_under_attack_verification"); 79 | ngx_str_null(&value); 80 | 81 | #if (nginx_version >= 1023000) 82 | if (ngx_http_parse_multi_header_lines(r, cookies, &key, &value) != NULL) { 83 | #else 84 | if (ngx_http_parse_multi_header_lines(cookies, &key, &value) != NGX_DECLINED) { 85 | #endif 86 | __waf_under_attack_verification.data = ngx_pcalloc(r->pool, value.len + 1); 87 | __waf_under_attack_verification.len = value.len; 88 | ngx_memcpy(__waf_under_attack_verification.data, value.data, value.len); 89 | 90 | } 91 | } 92 | 93 | 94 | /* 如果 cookie 不完整 */ 95 | if (__waf_under_attack_time.data == NULL || __waf_under_attack_uid.data == NULL || __waf_under_attack_verification.data == NULL) { 96 | ngx_http_waf_gen_cookie(r); 97 | *out_http_status = 303; 98 | ngx_http_waf_gen_ctx_and_header_location(r); 99 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 100 | "ngx_waf_debug: Failed to parse cookies"); 101 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 102 | "ngx_waf_debug: Processing is complete."); 103 | return NGX_HTTP_WAF_MATCHED; 104 | } 105 | 106 | 107 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 108 | "ngx_waf_debug: Successfully parsed all cookies."); 109 | 110 | 111 | /* 验证 token 是否正确 */ 112 | u_char cur_verification[NGX_HTTP_WAF_SHA256_HEX_LEN + 1]; 113 | ngx_memzero(cur_verification, sizeof(u_char) * (NGX_HTTP_WAF_SHA256_HEX_LEN + 1)); 114 | ngx_http_waf_gen_verification(r, 115 | __waf_under_attack_uid.data, 116 | NGX_HTTP_WAF_UNDER_ATTACH_UID_LEN, 117 | cur_verification, 118 | NGX_HTTP_WAF_SHA256_HEX_LEN, 119 | __waf_under_attack_time.data, 120 | NGX_HTTP_WAF_UNDER_ATTACH_TIME_LEN); 121 | if (ngx_memcmp(__waf_under_attack_verification.data, cur_verification, sizeof(u_char) * NGX_HTTP_WAF_SHA256_HEX_LEN) != 0) { 122 | ngx_http_waf_gen_cookie(r); 123 | *out_http_status = 303; 124 | ngx_http_waf_gen_ctx_and_header_location(r); 125 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 126 | "ngx_waf_debug: Wrong __waf_under_attack_verification."); 127 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 128 | "ngx_waf_debug: Processing is complete."); 129 | return NGX_HTTP_WAF_MATCHED; 130 | } 131 | 132 | 133 | /* 验证时间是否超过 5 秒 */ 134 | time_t client_time = ngx_atoi(__waf_under_attack_time.data, __waf_under_attack_time.len); 135 | /* 如果 Cookie 不合法 或 已经超过 30 分钟 */ 136 | if (client_time == NGX_ERROR || difftime(time(NULL), client_time) > 60 * 30) { 137 | ngx_http_waf_gen_cookie(r); 138 | *out_http_status = 303; 139 | ngx_http_waf_gen_ctx_and_header_location(r); 140 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 141 | "ngx_waf_debug: Wrong __waf_under_attack_verification."); 142 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 143 | "ngx_waf_debug: Processing is complete."); 144 | return NGX_HTTP_WAF_MATCHED; 145 | } else if (difftime(time(NULL), client_time) < 5) { 146 | *out_http_status = 303; 147 | ngx_http_waf_gen_ctx_and_header_location(r); 148 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 149 | "ngx_waf_debug: Not five seconds have passed."); 150 | ngx_log_debug(NGX_LOG_DEBUG_CORE, r->connection->log, 0, 151 | "ngx_waf_debug: Processing is complete."); 152 | return NGX_HTTP_WAF_MATCHED; 153 | } 154 | 155 | return NGX_HTTP_WAF_NOT_MATCHED; 156 | } 157 | 158 | 159 | ngx_int_t ngx_http_waf_gen_cookie(ngx_http_request_t *r) { 160 | static size_t s_header_key_len = sizeof("Set-Cookie"); 161 | 162 | ngx_table_elt_t *__waf_under_attack_time = NULL; 163 | ngx_table_elt_t *__waf_under_attack_uid = NULL; 164 | ngx_table_elt_t *__waf_under_attack_verification = NULL; 165 | int write_len = 0; 166 | long long int now = (long long int)time(NULL); 167 | u_char now_str[NGX_HTTP_WAF_UNDER_ATTACH_TIME_LEN + 1]; 168 | ngx_memzero(now_str, sizeof(u_char) * (NGX_HTTP_WAF_UNDER_ATTACH_TIME_LEN + 1)); 169 | sprintf((char*)now_str, "%lld", now); 170 | 171 | __waf_under_attack_time = (ngx_table_elt_t *)ngx_list_push(&(r->headers_out.headers)); 172 | __waf_under_attack_time->hash = 1; 173 | __waf_under_attack_time->key.data = (u_char *)ngx_pnalloc(r->pool, sizeof(u_char) * (s_header_key_len + 1)); 174 | ngx_memzero(__waf_under_attack_time->key.data, sizeof(u_char) * (s_header_key_len + 1)); 175 | __waf_under_attack_time->key.len = s_header_key_len - 1; 176 | strcpy((char *)(__waf_under_attack_time->key.data), "Set-Cookie"); 177 | 178 | __waf_under_attack_time->value.data = (u_char *)ngx_pnalloc(r->pool, sizeof(u_char) * (NGX_HTTP_WAF_UNDER_ATTACH_TIME_LEN * 4)); 179 | ngx_memzero(__waf_under_attack_time->value.data, sizeof(u_char) * (NGX_HTTP_WAF_UNDER_ATTACH_TIME_LEN + 1)); 180 | write_len = sprintf((char *)(__waf_under_attack_time->value.data), "__waf_under_attack_time=%s; Path=/", (char*)now_str); 181 | __waf_under_attack_time->value.len = (size_t)write_len; 182 | 183 | 184 | __waf_under_attack_uid = (ngx_table_elt_t *)ngx_list_push(&(r->headers_out.headers)); 185 | __waf_under_attack_uid->hash = 1; 186 | __waf_under_attack_uid->key.data = (u_char *)ngx_pnalloc(r->pool, sizeof(u_char) * s_header_key_len); 187 | ngx_memzero(__waf_under_attack_uid->key.data, sizeof(u_char) * (s_header_key_len + 1)); 188 | __waf_under_attack_uid->key.len = s_header_key_len - 1; 189 | strcpy((char *)(__waf_under_attack_uid->key.data), "Set-Cookie"); 190 | 191 | __waf_under_attack_uid->value.data = (u_char *)ngx_pnalloc(r->pool, sizeof(u_char) * NGX_HTTP_WAF_UNDER_ATTACH_UID_LEN * 2); 192 | ngx_memzero(__waf_under_attack_uid->value.data, sizeof(u_char) * NGX_HTTP_WAF_UNDER_ATTACH_UID_LEN * 2); 193 | u_char* uid = ngx_pnalloc(r->pool, sizeof(u_char) * (NGX_HTTP_WAF_UNDER_ATTACH_UID_LEN + 1)); 194 | ngx_http_waf_rand_str(uid, NGX_HTTP_WAF_UNDER_ATTACH_UID_LEN); 195 | write_len = sprintf((char *)(__waf_under_attack_uid->value.data) 196 | , "__waf_under_attack_uid=%s; Path=/", 197 | (char *)uid); 198 | 199 | __waf_under_attack_uid->value.len = (size_t)write_len; 200 | 201 | 202 | __waf_under_attack_verification = (ngx_table_elt_t *)ngx_list_push(&(r->headers_out.headers)); 203 | __waf_under_attack_verification->hash = 1; 204 | __waf_under_attack_verification->key.data = (u_char *)ngx_pnalloc(r->pool, sizeof(u_char) * s_header_key_len); 205 | ngx_memzero(__waf_under_attack_verification->key.data, sizeof(u_char) * (s_header_key_len + 1)); 206 | __waf_under_attack_verification->key.len = s_header_key_len - 1; 207 | strcpy((char *)(__waf_under_attack_verification->key.data), "Set-Cookie"); 208 | 209 | 210 | __waf_under_attack_verification->value.data = (u_char *)ngx_pnalloc(r->pool, sizeof(u_char) * NGX_HTTP_WAF_SHA256_HEX_LEN * 2); 211 | u_char* verification = ngx_pnalloc(r->pool, sizeof(u_char) * (NGX_HTTP_WAF_SHA256_HEX_LEN + 1)); 212 | ngx_memzero(__waf_under_attack_verification->value.data, sizeof(u_char) * NGX_HTTP_WAF_SHA256_HEX_LEN * 2); 213 | ngx_memzero(verification, sizeof(u_char) * (NGX_HTTP_WAF_SHA256_HEX_LEN + 1)); 214 | ngx_http_waf_gen_verification(r, 215 | uid, 216 | NGX_HTTP_WAF_UNDER_ATTACH_UID_LEN, 217 | verification, 218 | NGX_HTTP_WAF_SHA256_HEX_LEN, 219 | now_str, 220 | NGX_HTTP_WAF_UNDER_ATTACH_TIME_LEN); 221 | write_len = sprintf((char *)(__waf_under_attack_verification->value.data), 222 | "__waf_under_attack_verification=%s; Path=/", (char*)verification); 223 | 224 | ngx_pfree(r->pool, verification); 225 | ngx_pfree(r->pool, uid); 226 | __waf_under_attack_verification->value.len = (size_t)write_len; 227 | 228 | return NGX_HTTP_WAF_TRUE; 229 | } 230 | 231 | 232 | ngx_int_t ngx_http_waf_gen_verification(ngx_http_request_t *r, 233 | u_char* uid, 234 | size_t uid_len, 235 | u_char* dst, 236 | size_t dst_len, 237 | u_char* now, 238 | size_t now_len) { 239 | ngx_http_waf_loc_conf_t* loc_conf = NULL; 240 | ngx_http_waf_get_ctx_and_conf(r, &loc_conf, NULL); 241 | size_t buf_len = sizeof(loc_conf->random_str) + sizeof(inx_addr_t) + uid_len + now_len; 242 | u_char *buf = (u_char *)ngx_pnalloc(r->pool, buf_len); 243 | ngx_memzero(buf, sizeof(u_char) * buf_len); 244 | inx_addr_t inx_addr; 245 | ngx_memzero(&inx_addr, sizeof(inx_addr)); 246 | 247 | if (r->connection->sockaddr->sa_family == AF_INET) { 248 | struct sockaddr_in *sin = (struct sockaddr_in *)r->connection->sockaddr; 249 | ngx_memcpy(&(inx_addr.ipv4), &(sin->sin_addr), sizeof(struct in_addr)); 250 | 251 | } 252 | #if (NGX_HAVE_INET6) 253 | else if (r->connection->sockaddr->sa_family == AF_INET6) { 254 | struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)r->connection->sockaddr; 255 | ngx_memcpy(&(inx_addr.ipv6), &(sin6->sin6_addr), sizeof(struct in6_addr)); 256 | } 257 | #endif 258 | 259 | 260 | size_t offset = 0; 261 | 262 | /* 写入随机字符串 */ 263 | ngx_memcpy(buf+ offset, loc_conf->random_str, sizeof(loc_conf->random_str)); 264 | offset += sizeof(loc_conf->random_str); 265 | 266 | /* 写入时间戳 */ 267 | ngx_memcpy(buf + offset, now, sizeof(u_char) * now_len); 268 | offset += now_len; 269 | 270 | /* 写入 uid */ 271 | ngx_memcpy(buf + offset, uid, sizeof(u_char) * uid_len); 272 | offset += uid_len; 273 | 274 | /* 写入 IP 地址 */ 275 | ngx_memcpy(buf + offset, &inx_addr, sizeof(inx_addr_t)); 276 | offset += sizeof(inx_addr_t); 277 | 278 | ngx_int_t ret = ngx_http_waf_sha256(dst, dst_len + 1, buf, buf_len); 279 | ngx_pfree(r->pool, buf); 280 | 281 | return ret; 282 | } 283 | 284 | 285 | void ngx_http_waf_gen_ctx_and_header_location(ngx_http_request_t *r) { 286 | size_t s_header_location_key_len = sizeof("Location"); 287 | ngx_http_waf_loc_conf_t* loc_conf = NULL; 288 | ngx_http_waf_get_ctx_and_conf(r, &loc_conf, NULL); 289 | 290 | 291 | ngx_table_elt_t* header = (ngx_table_elt_t *)ngx_list_push(&(r->headers_out.headers)); 292 | header->hash = 1; 293 | header->key.data = ngx_pnalloc(r->pool, sizeof(u_char) * (s_header_location_key_len + 1)); 294 | ngx_memzero(header->key.data, sizeof(u_char) * (s_header_location_key_len + 1)); 295 | ngx_memcpy(header->key.data, "Location", s_header_location_key_len - 1); 296 | header->key.len = s_header_location_key_len - 1; 297 | 298 | header->value.data = ngx_pnalloc(r->pool, sizeof(u_char) * (loc_conf->waf_under_attack_uri.len + r->uri.len + 32)); 299 | ngx_memzero(header->value.data, sizeof(u_char) * (loc_conf->waf_under_attack_uri.len + r->uri.len + 1)); 300 | u_char* uri = ngx_pnalloc(r->pool, sizeof(u_char) * (r->uri.len + 1)); 301 | ngx_memzero(uri, sizeof(u_char) * (r->uri.len + 1)); 302 | ngx_memcpy(uri, r->uri.data, sizeof(u_char) * r->uri.len); 303 | header->value.len = sprintf((char*)header->value.data, "%s?target=%s", 304 | (char*)loc_conf->waf_under_attack_uri.data, 305 | (char*)uri); 306 | ngx_pfree(r->pool, uri); 307 | 308 | ngx_http_waf_ctx_t* ctx = NULL; 309 | ngx_http_waf_get_ctx_and_conf(r, NULL, &ctx); 310 | ctx->blocked = NGX_HTTP_WAF_TRUE; 311 | strcpy((char*)ctx->rule_type, "UNDER-ATTACK"); 312 | ctx->rule_deatils[0] = '\0'; 313 | } -------------------------------------------------------------------------------- /src/ngx_http_waf_module_util.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | ngx_int_t ngx_http_waf_parse_ipv4(ngx_str_t text, ipv4_t* ipv4) { 4 | uint32_t prefix = 0; 5 | uint32_t suffix = 0; 6 | uint32_t suffix_num = 0; 7 | 8 | if (ipv4 == NULL) { 9 | return NGX_HTTP_WAF_FAIL; 10 | } 11 | 12 | ngx_memcpy(ipv4->text, text.data, text.len); 13 | ipv4->text[text.len] = '\0'; 14 | 15 | u_char* c = ipv4->text; 16 | ngx_uint_t prefix_len = 0; 17 | while (*c !='\0' && *c != '/') { 18 | ++prefix_len; 19 | ++c; 20 | } 21 | 22 | char prefix_text[32]; 23 | struct in_addr addr4; 24 | if (*c =='\0' && prefix_len == text.len) { 25 | ngx_memcpy(prefix_text, ipv4->text, prefix_len); 26 | prefix_text[prefix_len] = '\0'; 27 | } 28 | else if (*c == '/' && prefix_len >= 7) { 29 | /* 0.0.0.0 的长度刚好是 7 */ 30 | ngx_memcpy(prefix_text, ipv4->text, prefix_len); 31 | prefix_text[prefix_len] = '\0'; 32 | } 33 | else { 34 | return NGX_HTTP_WAF_FAIL; 35 | } 36 | 37 | if (inet_pton(AF_INET, prefix_text, &addr4) != 1) { 38 | return NGX_HTTP_WAF_FAIL; 39 | } 40 | prefix = addr4.s_addr; 41 | 42 | if (*c == '/') { 43 | ++c; 44 | } 45 | while (*c != '\0') { 46 | suffix = suffix * 10 + (*c - '0'); 47 | ++c; 48 | } 49 | if (suffix == 0) { 50 | suffix = 32; 51 | } 52 | 53 | suffix_num = suffix; 54 | 55 | uint8_t temp_suffix[4] = { 0 }; 56 | for (int i = 0; i < 4; i++) { 57 | uint8_t temp = 0; 58 | if (suffix >= 8) { 59 | suffix -=8; 60 | temp = ~0; 61 | } 62 | else { 63 | for (uint32_t j = 0; j < suffix; j++) { 64 | temp |= 0x80 >> j; 65 | } 66 | suffix = 0; 67 | } 68 | temp_suffix[i] = temp; 69 | } 70 | 71 | suffix = 0; 72 | for (int i = 0; i < 4; i++) { 73 | suffix |= ((uint32_t)temp_suffix[i]) << (i * 8); 74 | } 75 | 76 | ipv4->prefix = prefix & suffix; 77 | ipv4->suffix = suffix; 78 | ipv4->suffix_num = suffix_num; 79 | 80 | return NGX_HTTP_WAF_SUCCESS; 81 | } 82 | 83 | 84 | #if (NGX_HAVE_INET6) 85 | ngx_int_t ngx_http_waf_parse_ipv6(ngx_str_t text, ipv6_t* ipv6) { 86 | uint8_t prefix[16] = { 0 }; 87 | uint8_t suffix[16] = { 0 }; 88 | uint32_t suffix_num = 0; 89 | 90 | if (ipv6 == NULL) { 91 | return NGX_HTTP_WAF_FAIL; 92 | } 93 | 94 | ngx_memcpy(ipv6->text, text.data, text.len); 95 | 96 | ipv6->text[text.len] = '\0'; 97 | 98 | u_char* c = ipv6->text; 99 | ngx_uint_t prefix_len = 0; 100 | while (*c !='\0' && *c != '/') { 101 | ++prefix_len; 102 | ++c; 103 | } 104 | 105 | char prefix_text[64]; 106 | struct in6_addr addr6; 107 | if (*c =='\0' && prefix_len == text.len) { 108 | ngx_memcpy(prefix_text, ipv6->text, prefix_len); 109 | prefix_text[prefix_len] = '\0'; 110 | } 111 | else if (*c == '/' && prefix_len >= 2) { 112 | /* :: 的长度刚好是 2,此 IPV6 地址代表全零 */ 113 | ngx_memcpy(prefix_text, ipv6->text, prefix_len); 114 | prefix_text[prefix_len] = '\0'; 115 | } 116 | else { 117 | return NGX_HTTP_WAF_FAIL; 118 | } 119 | 120 | if (inet_pton(AF_INET6, prefix_text, &addr6) != 1) { 121 | return NGX_HTTP_WAF_FAIL; 122 | } 123 | ngx_memcpy(prefix, &addr6.s6_addr, 16); 124 | 125 | uint32_t temp_suffix = 0; 126 | if (*c == '/') { 127 | ++c; 128 | } 129 | while (*c != '\0') { 130 | temp_suffix = temp_suffix * 10 + (*c - '0'); 131 | ++c; 132 | } 133 | if (temp_suffix == 0) { 134 | temp_suffix = 128; 135 | } 136 | 137 | suffix_num = temp_suffix; 138 | for (int i = 0; i < 16; i++) { 139 | uint8_t temp = 0; 140 | if (temp_suffix >= 8) { 141 | temp_suffix -=8; 142 | temp = ~0; 143 | } 144 | else { 145 | for (uint32_t j = 0; j < temp_suffix; j++) { 146 | temp |= 0x80 >> j; 147 | } 148 | temp_suffix = 0; 149 | } 150 | suffix[i] = temp; 151 | } 152 | 153 | for (int i = 0; i < 16; i++) { 154 | prefix[i] &= suffix[i]; 155 | } 156 | 157 | ngx_memcpy(ipv6->prefix, prefix, 16); 158 | ngx_memcpy(ipv6->suffix, suffix, 16); 159 | ipv6->suffix_num = suffix_num; 160 | 161 | return NGX_HTTP_WAF_SUCCESS; 162 | } 163 | #endif 164 | 165 | 166 | ngx_int_t ngx_http_waf_parse_time(u_char* str) { 167 | ngx_int_t ret = 0; 168 | size_t len = ngx_strlen(str); 169 | if (len < 2) { 170 | return NGX_ERROR; 171 | } 172 | 173 | ret = ngx_atoi(str, len - 1); 174 | if (ret == NGX_ERROR || ret <= 0) { 175 | return NGX_ERROR; 176 | } 177 | 178 | switch (str[len - 1]) { 179 | case 's': ret *= 1; break; 180 | case 'm': ret *= 1 * 60; break; 181 | case 'h': ret *= 1 * 60 * 60; break; 182 | case 'd': ret *= 1 * 60 * 60 * 24; break; 183 | default: return NGX_ERROR; break; 184 | } 185 | 186 | return ret; 187 | } 188 | 189 | 190 | ngx_int_t ngx_http_waf_parse_size(u_char* str) { 191 | ngx_int_t ret = 0; 192 | size_t len = ngx_strlen(str); 193 | if (len < 2) { 194 | return NGX_ERROR; 195 | } 196 | 197 | ret = ngx_atoi(str, len - 1); 198 | if (ret == NGX_ERROR || ret <= 0) { 199 | return NGX_ERROR; 200 | } 201 | 202 | switch (str[len - 1]) { 203 | case 'k': ret *= 1 * 1024; break; 204 | case 'm': ret *= 1 * 1024 * 1024; break; 205 | case 'g': ret *= 1 * 1024 * 1024 * 1024; break; 206 | default: return NGX_ERROR; break; 207 | } 208 | 209 | return ret; 210 | } 211 | 212 | 213 | ngx_int_t ngx_http_waf_parse_cookie(ngx_str_t* native_cookie, UT_array** array) { 214 | if (array == NULL) { 215 | return NGX_HTTP_WAF_FAIL; 216 | } 217 | 218 | UT_icd icd = ngx_http_waf_make_utarray_ngx_str_icd(); 219 | utarray_new(*array, &icd); 220 | 221 | if (native_cookie == NULL) { 222 | return NGX_HTTP_WAF_FAIL; 223 | } 224 | 225 | 226 | UT_array* cookies = NULL; 227 | utarray_new(cookies, &icd); 228 | 229 | ngx_http_waf_str_split(native_cookie, ';', native_cookie->len, &cookies); 230 | ngx_str_t* p = NULL; 231 | 232 | while (p = (ngx_str_t*)utarray_next(cookies, p), p != NULL) { 233 | UT_array* key_and_value = NULL; 234 | ngx_str_t temp; 235 | temp.data = p->data; 236 | temp.len = p->len; 237 | if (p->data[0] == ' ') { 238 | temp.data += 1; 239 | temp.len -= 1; 240 | } 241 | 242 | ngx_http_waf_str_split(&temp, '=', native_cookie->len, &key_and_value); 243 | 244 | if (utarray_len(key_and_value) != 2) { 245 | return NGX_HTTP_WAF_FAIL; 246 | } 247 | 248 | ngx_str_t* key = NULL; 249 | ngx_str_t* value = NULL; 250 | 251 | 252 | key = (ngx_str_t*)utarray_next(key_and_value, NULL); 253 | value = (ngx_str_t*)utarray_next(key_and_value, key); 254 | 255 | 256 | utarray_push_back(*array, key); 257 | utarray_push_back(*array, value); 258 | utarray_free(key_and_value); 259 | } 260 | 261 | utarray_free(cookies); 262 | 263 | return NGX_HTTP_WAF_SUCCESS; 264 | } 265 | 266 | 267 | ngx_int_t ngx_http_waf_parse_query_string(ngx_str_t* native_query_string, key_value_t** hash_head) { 268 | if (hash_head == NULL) { 269 | return NGX_HTTP_WAF_FAIL; 270 | } 271 | 272 | if (native_query_string == NULL) { 273 | return NGX_HTTP_WAF_FAIL; 274 | } 275 | 276 | 277 | UT_array* kvs = NULL; 278 | 279 | ngx_http_waf_str_split(native_query_string, '&', native_query_string->len, &kvs); 280 | ngx_str_t* p = NULL; 281 | 282 | while (p = (ngx_str_t*)utarray_next(kvs, p), p != NULL) { 283 | UT_array* key_and_value = NULL; 284 | ngx_str_t temp; 285 | temp.data = p->data; 286 | temp.len = p->len; 287 | 288 | ngx_http_waf_str_split(&temp, '=', native_query_string->len, &key_and_value); 289 | 290 | if (utarray_len(key_and_value) != 2) { 291 | return NGX_HTTP_WAF_FAIL; 292 | } 293 | 294 | ngx_str_t* key = NULL; 295 | ngx_str_t* value = NULL; 296 | 297 | 298 | key = (ngx_str_t*)utarray_next(key_and_value, NULL); 299 | value = (ngx_str_t*)utarray_next(key_and_value, key); 300 | 301 | key_value_t* qs = malloc(sizeof(key_value_t)); 302 | ngx_memzero(qs, sizeof(key_value_t)); 303 | qs->key.data = ngx_strdup(key->data); 304 | qs->key.len = key->len; 305 | qs->value.data = ngx_strdup(value->data); 306 | qs->value.len = value->len; 307 | 308 | HASH_ADD_KEYPTR(hh, *hash_head, qs->key.data, qs->key.len * sizeof(u_char), qs); 309 | 310 | utarray_free(key_and_value); 311 | } 312 | 313 | utarray_free(kvs); 314 | return NGX_HTTP_WAF_SUCCESS; 315 | } 316 | 317 | 318 | ngx_int_t ngx_http_waf_parse_header(ngx_list_t* native_header, key_value_t** hash_head) { 319 | if (native_header == NULL || hash_head == NULL) { 320 | return NGX_HTTP_WAF_FALSE; 321 | } 322 | 323 | ngx_list_part_t* part = &(native_header->part); 324 | ngx_table_elt_t* value = part->elts; 325 | 326 | for (size_t i = 0; ; i++) { 327 | if (i >= part->nelts) { 328 | if (part->next == NULL) { 329 | break; 330 | } 331 | 332 | part = part->next; 333 | value = part->elts; 334 | i = 0; 335 | } 336 | 337 | key_value_t* temp = malloc(sizeof(key_value_t)); 338 | ngx_memzero(temp, sizeof(key_value_t)); 339 | temp->key.data = ngx_strdup(value[i].key.data); 340 | temp->key.len = value[i].key.len; 341 | ngx_strlow(temp->key.data, temp->key.data, temp->key.len); 342 | temp->value.data = ngx_strdup(value[i].value.data); 343 | temp->value.len = value[i].value.len; 344 | HASH_ADD_KEYPTR(hh, *hash_head, temp->key.data, temp->key.len * sizeof(u_char), temp); 345 | 346 | } 347 | 348 | return NGX_HTTP_WAF_TRUE; 349 | } 350 | 351 | 352 | ngx_int_t ngx_http_waf_ipv4_netcmp(uint32_t ip, const ipv4_t* ipv4) { 353 | size_t prefix = ip & ipv4->suffix; 354 | 355 | if (prefix == ipv4->prefix) { 356 | return NGX_HTTP_WAF_MATCHED; 357 | } 358 | 359 | return NGX_HTTP_WAF_NOT_MATCHED; 360 | } 361 | 362 | 363 | #if (NGX_HAVE_INET6) 364 | ngx_int_t ngx_http_waf_ipv6_netcmp(uint8_t ip[16], const ipv6_t* ipv6) { 365 | uint8_t temp_ip[16]; 366 | 367 | memcpy(temp_ip, ip, 16); 368 | 369 | for (int i = 0; i < 16; i++) { 370 | temp_ip[i] &= ipv6->suffix[i]; 371 | } 372 | 373 | if (memcmp(temp_ip, ipv6->prefix, sizeof(uint8_t) * 16) != 0) { 374 | return NGX_HTTP_WAF_NOT_MATCHED; 375 | } 376 | 377 | return NGX_HTTP_WAF_MATCHED; 378 | } 379 | #endif 380 | 381 | 382 | ngx_int_t ngx_http_waf_str_split(ngx_str_t* str, u_char sep, size_t max_len, UT_array** array) { 383 | if (array == NULL) { 384 | return NGX_HTTP_WAF_FAIL; 385 | } 386 | 387 | UT_icd icd = ngx_http_waf_make_utarray_ngx_str_icd(); 388 | utarray_new(*array,&icd); 389 | 390 | if (str == NULL) { 391 | return NGX_HTTP_WAF_FAIL; 392 | } 393 | 394 | ngx_str_t temp_str; 395 | temp_str.data = malloc(sizeof(u_char) * (max_len + 1)); 396 | ngx_memzero(temp_str.data, sizeof(u_char) * (max_len + 1)); 397 | size_t str_index = 0; 398 | 399 | for (size_t i = 0; i < str->len; i++) { 400 | u_char c = str->data[i]; 401 | if (c != sep) { 402 | if (str_index >= max_len) { 403 | free(temp_str.data); 404 | return NGX_HTTP_WAF_FAIL; 405 | } 406 | temp_str.data[str_index++] = c; 407 | } else { 408 | temp_str.data[str_index] = '\0'; 409 | temp_str.len = str_index; 410 | utarray_push_back(*array, &temp_str); 411 | str_index = 0; 412 | } 413 | } 414 | 415 | if (str_index != 0) { 416 | temp_str.data[str_index] = '\0'; 417 | temp_str.len = str_index; 418 | utarray_push_back(*array, &temp_str); 419 | str_index = 0; 420 | } 421 | 422 | free(temp_str.data); 423 | 424 | return NGX_HTTP_WAF_SUCCESS; 425 | } 426 | 427 | 428 | // ngx_int_t str_split(u_char* str, u_char sep, size_t max_len, UT_array** array) { 429 | // if (str == NULL || array == NULL) { 430 | // return NGX_HTTP_WAF_FAIL; 431 | // } 432 | 433 | // UT_icd icd = ngx_http_waf_make_utarray_ngx_str_icd(); 434 | 435 | // utarray_new(*array,&icd); 436 | // ngx_str_t temp_str; 437 | // temp_str.data = malloc(sizeof(u_char) * max_len); 438 | // ngx_memzero(temp_str.data, sizeof(u_char) * max_len); 439 | // size_t str_index = 0; 440 | 441 | // for (size_t i = 0; str[i] != '\0'; i++) { 442 | // u_char c = str[i]; 443 | // if (c != sep) { 444 | // if (str_index + 1 >= max_len) { 445 | // return NGX_HTTP_WAF_FAIL; 446 | // } 447 | // temp_str.data[str_index++] = c; 448 | // } else { 449 | // temp_str.data[str_index] = '\0'; 450 | // temp_str.len = str_index; 451 | // utarray_push_back(*array, &temp_str); 452 | // str_index = 0; 453 | // } 454 | // } 455 | 456 | // if (str_index != 0) { 457 | // temp_str.data[str_index] = '\0'; 458 | // temp_str.len = str_index; 459 | // utarray_push_back(*array, &temp_str); 460 | // str_index = 0; 461 | // } 462 | 463 | // free(temp_str.data); 464 | 465 | // return NGX_HTTP_WAF_SUCCESS; 466 | // } 467 | 468 | 469 | char* ngx_http_waf_to_c_str(u_char* destination, ngx_str_t ngx_str) { 470 | if (ngx_str.len > NGX_HTTP_WAF_RULE_MAX_LEN) { 471 | return NULL; 472 | } 473 | ngx_memcpy(destination, ngx_str.data, ngx_str.len); 474 | destination[ngx_str.len] = '\0'; 475 | return (char*)destination + ngx_str.len; 476 | } 477 | 478 | 479 | ngx_int_t ngx_http_waf_rand_str(u_char* dest, size_t len) { 480 | if (dest == NULL || len == 0) { 481 | return NGX_HTTP_WAF_FAIL; 482 | } 483 | 484 | for (size_t i = 0; i < len; i++) { 485 | uint32_t num = randombytes_uniform(52); 486 | if (num < 26) { 487 | dest[i] = (unsigned char)'A' + (unsigned char)num; 488 | } else { 489 | dest[i] = (unsigned char)'a' + (unsigned char)(num - 26); 490 | } 491 | } 492 | 493 | dest[len] = '\0'; 494 | 495 | return NGX_HTTP_WAF_SUCCESS; 496 | } 497 | 498 | 499 | ngx_int_t ngx_http_waf_sha256(u_char* dst, size_t dst_len, const u_char* buf, size_t buf_len) { 500 | if (dst == NULL || dst_len < crypto_hash_sha256_BYTES * 2 + 1 || buf == NULL || buf_len == 0) { 501 | return NGX_HTTP_WAF_FAIL; 502 | } 503 | 504 | unsigned char* out = malloc(sizeof(u_char) * crypto_hash_sha256_BYTES); 505 | ngx_memzero(out, sizeof(u_char) * crypto_hash_sha256_BYTES); 506 | 507 | crypto_hash_sha256(out, buf, buf_len); 508 | sodium_bin2hex((char*)dst, dst_len, out, crypto_hash_sha256_BYTES); 509 | 510 | free(out); 511 | 512 | return NGX_HTTP_WAF_SUCCESS; 513 | } 514 | 515 | 516 | void ngx_http_waf_utarray_ngx_str_ctor(void *dst, const void *src) { 517 | ngx_str_t* _dst = (ngx_str_t*)dst; 518 | const ngx_str_t* _src = (const ngx_str_t*)src; 519 | 520 | _dst->data = malloc(sizeof(u_char) * (_src->len + 1)); 521 | ngx_memcpy(_dst->data, _src->data, sizeof(u_char) * _src->len); 522 | _dst->data[_src->len] = '\0'; 523 | _dst->len = _src->len; 524 | } 525 | 526 | 527 | void ngx_http_waf_utarray_ngx_str_dtor(void* elt) { 528 | ngx_str_t* _elt = (ngx_str_t*)elt; 529 | free(_elt->data); 530 | } 531 | 532 | 533 | void ngx_http_waf_utarray_vm_code_ctor(void *dst, const void *src) { 534 | vm_code_t* _dst = (vm_code_t*)dst; 535 | const vm_code_t* _src = (const vm_code_t*)src; 536 | _dst->type = _src->type; 537 | _dst->argv.argc = _src->argv.argc; 538 | 539 | 540 | for (size_t i = 0; i < _src->argv.argc; i++) { 541 | _dst->argv.type[i] = _src->argv.type[i]; 542 | 543 | if (_src->argv.type[i] == VM_DATA_STR) { 544 | size_t len = _src->argv.value[i].str_val.len; 545 | _dst->argv.value[i].str_val.len = len; 546 | _dst->argv.value[i].str_val.data = (u_char*)malloc(sizeof(u_char) * (len + 1)); 547 | ngx_memcpy(_dst->argv.value[i].str_val.data, _src->argv.value[i].str_val.data, sizeof(u_char) * len); 548 | _dst->argv.value[i].str_val.data[len] = '\0'; 549 | _dst->argv.value[i].str_val.len = len; 550 | } else { 551 | ngx_memcpy(&(_dst->argv.value[i]), &(_src->argv.value[i]), sizeof(_src->argv.value[i])); 552 | } 553 | } 554 | } 555 | 556 | 557 | void ngx_http_waf_utarray_vm_code_dtor(void* elt) { 558 | vm_code_t* _elt = (vm_code_t*)elt; 559 | 560 | for (size_t i = 0; i < _elt->argv.argc; i++) { 561 | switch (_elt->argv.type[i]) { 562 | case VM_DATA_STR: 563 | free(_elt->argv.value[i].str_val.data); 564 | break; 565 | default: 566 | break; 567 | } 568 | } 569 | } -------------------------------------------------------------------------------- /test/test-nginx/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | 5 | if [ -z "$MODULE_TEST_PATH" ] ; then 6 | echo "Environment variable MODULE_TEST_PATH is not set." 7 | exit 1 8 | fi 9 | 10 | base_dir="$MODULE_TEST_PATH" 11 | origin_dir=$(pwd) 12 | 13 | rm -rf "$base_dir" 14 | mkdir -p "$base_dir" 15 | cp -r ../../assets "$base_dir/waf" 16 | 17 | templates=$(ls template) 18 | 19 | for file in $templates 20 | do 21 | eval "cat < "t/$file" 25 | done 26 | 27 | cd "$base_dir/waf" 28 | git clone https://github.com/SpiderLabs/ModSecurity.git 29 | git clone https://github.com/coreruleset/coreruleset.git 30 | 31 | mkdir -p modsec 32 | cp coreruleset/crs-setup.conf.example ./modsec/crs-setup.conf 33 | cp ModSecurity/modsecurity.conf-recommended ./modsec/modsecurity.conf 34 | cp ModSecurity/unicode.mapping ./modsec/unicode.mapping 35 | 36 | sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' ./modsec/modsecurity.conf 37 | echo "Include /usr/local/nginx/conf/waf/modsec/crs-setup.conf" >> ./modsec/modsecurity.conf 38 | echo "Include /usr/local/nginx/conf/waf/coreruleset/rules/*.conf" >> ./modsec/modsecurity.conf 39 | echo "SecRule ARGS:test \"@streq deny\" \"id:1234567,phase:2,log,auditlog,deny,status:403\"" >> ./modsec/modsecurity.conf 40 | echo "SecRule ARGS:test \"@streq redirect\" \"id:123456,phase:2,log,auditlog,redirect:/,status:302\"" >> ./modsec/modsecurity.conf 41 | 42 | 43 | echo "1.1.1.1" >> ./rules/ipv4 44 | echo "2.0.0.0/8" >> ./rules/ipv4 45 | 46 | echo "3.3.3.3" >> ./rules/white-ipv4 47 | echo "4.0.0.0/8" >> ./rules/white-ipv4 48 | 49 | echo "AAAA::" >> ./rules/ipv6 50 | echo "BBBB::/16" >> ./rules/ipv6 51 | 52 | echo "CCCC::" >> ./rules/white-ipv6 53 | echo "DDDD::/16" >> ./rules/white-ipv6 54 | 55 | echo "/white/" >> ./rules/white-url 56 | echo "/white/" >> ./rules/white-referer 57 | 58 | cd "$origin_dir" -------------------------------------------------------------------------------- /test/test-nginx/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export PATH="/usr/local/openresty/nginx/sbin:/usr/local/nginx/sbin:$PATH" 4 | 5 | if [ -e "/usr/local/nginx/modules/ngx_http_waf_module.so" ] ; then 6 | export TEST_NGINX_LOAD_MODULES=/usr/local/nginx/modules/ngx_http_waf_module.so 7 | fi 8 | 9 | if [ -n "$MODULE_PATH" ] ; then 10 | export TEST_NGINX_LOAD_MODULES="$MODULE_PATH" 11 | fi 12 | 13 | # export TEST_NGINX_LOG_LEVEL=emerg 14 | # export TEST_NGINX_SLEEP=1 15 | # export TEST_NGINX_NO_CLEAN=1 16 | # export TEST_NGINX_MASTER_PROCESS=on 17 | 18 | exec prove "$@" -------------------------------------------------------------------------------- /test/test-nginx/t/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADD-SP/ngx_waf/458b6a62e26c201bc72e1366e87e775fab7d9efa/test/test-nginx/t/.gitkeep -------------------------------------------------------------------------------- /test/test-nginx/template/args.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | run_tests(); 4 | 5 | 6 | __DATA__ 7 | 8 | === TEST: General 9 | 10 | --- config 11 | waf on; 12 | waf_mode GET ARGS; 13 | waf_rule_path ${base_dir}/waf/rules/; 14 | 15 | --- request 16 | GET /?s=test0 17 | 18 | --- error_code chomp 19 | 200 20 | 21 | === TEST: Black query string 22 | 23 | --- config 24 | waf on; 25 | waf_mode GET ARGS; 26 | waf_rule_path ${base_dir}/waf/rules/; 27 | 28 | --- pipelined_requests eval 29 | [ 30 | "GET /?s=../", 31 | "GET /?s=onload=" 32 | ] 33 | 34 | --- error_code eval 35 | [ 36 | 403, 37 | 403 38 | ] 39 | -------------------------------------------------------------------------------- /test/test-nginx/template/bad-config.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | run_tests(); 4 | 5 | 6 | __DATA__ 7 | 8 | === TEST: Bad directive waf 9 | 10 | --- config 11 | waf bad; 12 | 13 | --- must_die 14 | 15 | 16 | === TEST: Bad directive waf_rule_path 17 | 18 | --- config 19 | waf_rule_path ${base_dir}/waf/rules; 20 | 21 | --- must_die 22 | 23 | 24 | === TEST: Bad directive waf_mode 25 | 26 | --- config 27 | waf_mode BAD; 28 | 29 | --- must_die 30 | 31 | 32 | === TEST: Bad directive waf_cc_deny (1) 33 | 34 | --- config 35 | waf_cc_deny bad rate=100/m; 36 | 37 | --- must_die 38 | 39 | 40 | === TEST: Bad directive waf_cc_deny (2) 41 | 42 | --- config 43 | waf_cc_deny rate=r/m; 44 | 45 | --- must_die 46 | 47 | === TEST: Bad directive waf_cc_deny (3) 48 | 49 | --- config 50 | waf_cc_deny rate=-1r/m; 51 | 52 | --- must_die 53 | 54 | 55 | === TEST: Bad directive waf_cc_deny (4) 56 | 57 | --- config 58 | waf_cc_deny rate=100r; 59 | 60 | --- must_die 61 | 62 | 63 | === TEST: Bad directive waf_cc_deny (5) 64 | 65 | --- config 66 | waf_cc_deny rate=100r/b; 67 | 68 | --- must_die 69 | 70 | 71 | === TEST: Bad directive waf_cc_deny (6) 72 | 73 | --- config 74 | waf_cc_deny on; 75 | 76 | --- must_die 77 | 78 | 79 | === TEST: Bad directive waf_cc_deny (7) 80 | 81 | --- config 82 | waf_cc_deny rate=100r/m duration=1; 83 | 84 | --- must_die 85 | 86 | 87 | === TEST: Bad directive waf_cc_deny (8) 88 | 89 | --- config 90 | waf_cc_deny rate=100r/m duration=1b; 91 | 92 | --- must_die 93 | 94 | 95 | === TEST: Bad directive waf_cc_deny (9) 96 | 97 | --- config 98 | waf_cc_deny rate=100r/m duration=1h size=10z; 99 | 100 | --- must_die 101 | 102 | 103 | === TEST: Bad directive waf_cc_deny (10) 104 | 105 | --- config 106 | waf_cc_deny rate=100r/m duration=1h bad=bad; 107 | 108 | --- must_die 109 | 110 | 111 | === TEST: Bad directive waf_cache (1) 112 | 113 | --- config 114 | waf_cache on capacity=-1; 115 | 116 | --- must_die 117 | 118 | 119 | === TEST: Bad directive waf_cache (2) 120 | 121 | --- config 122 | waf_cache bad=bad; 123 | 124 | --- must_die 125 | 126 | 127 | === TEST: Bad directive waf_under_attack (1) 128 | 129 | --- config 130 | waf_under_attack bad; 131 | 132 | --- must_die 133 | 134 | 135 | === TEST: Bad directive waf_under_attack (2) 136 | 137 | --- config 138 | waf_under_attack on bad; 139 | 140 | --- must_die 141 | 142 | 143 | === TEST: Bad directive waf_under_attack (3) 144 | 145 | --- config 146 | waf_under_attack on bad; 147 | 148 | --- must_die 149 | 150 | 151 | === TEST: Bad directive waf_http_status 152 | 153 | --- config 154 | waf_http_status bad; 155 | 156 | --- must_die 157 | 158 | 159 | === TEST: Bad directive waf_priority 160 | 161 | --- config 162 | waf_priority "W-IP IP VERIFY-BOT CC CAPTCHA UNDER-ATTACK W-URL URL ARGS UA W-REFERER REFERER COOKIE POST" 163 | 164 | --- must_die 165 | -------------------------------------------------------------------------------- /test/test-nginx/template/cache.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | run_tests(); 4 | 5 | 6 | __DATA__ 7 | 8 | === TEST: Cache 9 | 10 | --- config 11 | waf on; 12 | waf_mode FULL !CC; 13 | waf_rule_path ${base_dir}/waf/rules/; 14 | waf_cache capacity=1; 15 | 16 | --- pipelined_requests eval 17 | [ 18 | "GET /test0", 19 | "GET /test0", 20 | "GET /test1", 21 | "GET /test1", 22 | "GET /test2", 23 | "GET /test2", 24 | "GET /test3", 25 | "GET /test3", 26 | "GET /test4", 27 | "GET /test4", 28 | ] 29 | 30 | --- error_code eval 31 | [ 32 | 404, 33 | 404, 34 | 404, 35 | 404, 36 | 404, 37 | 404, 38 | 404, 39 | 404, 40 | 404, 41 | 404 42 | ] -------------------------------------------------------------------------------- /test/test-nginx/template/cc.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | run_tests(); 4 | 5 | 6 | __DATA__ 7 | 8 | === TEST: CC wihout CAPTCHA 9 | 10 | --- config 11 | waf on; 12 | waf_mode FULL; 13 | waf_rule_path ${base_dir}/waf/rules/; 14 | waf_cc_deny rate=1r/m; 15 | 16 | location /t { 17 | waf_cc_deny rate=100r/m; 18 | } 19 | 20 | --- pipelined_requests eval 21 | [ 22 | "GET /", 23 | "GET /", 24 | "GET /", 25 | "GET /", 26 | "GET /", 27 | "GET /t", 28 | "GET /t", 29 | "GET /t", 30 | "GET /t", 31 | "GET /t" 32 | ] 33 | 34 | --- error_code eval 35 | [ 36 | 200, 37 | 503, 38 | 503, 39 | 503, 40 | 503, 41 | 404, 42 | 404, 43 | 404, 44 | 404, 45 | 404 46 | ] -------------------------------------------------------------------------------- /test/test-nginx/template/cookie.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | run_tests(); 4 | 5 | 6 | __DATA__ 7 | 8 | === TEST: General 9 | 10 | --- config 11 | waf on; 12 | waf_mode GET COOKIE !CC; 13 | waf_rule_path ${base_dir}/waf/rules/; 14 | 15 | --- request 16 | GET / 17 | 18 | --- more_headers 19 | Cookie: s=test 20 | 21 | --- error_code chomp 22 | 200 23 | 24 | === TEST: Black cookie 25 | 26 | --- config 27 | waf on; 28 | waf_mode GET COOKIE !CC; 29 | waf_rule_path ${base_dir}/waf/rules/; 30 | 31 | location /t { 32 | waf_mode FULL !COOKIE; 33 | } 34 | 35 | --- pipelined_requests eval 36 | [ 37 | "GET /", 38 | "GET /t" 39 | ] 40 | 41 | --- more_headers eval 42 | [ 43 | "Cookie: s=../", 44 | "Cookie: s=../" 45 | ] 46 | 47 | 48 | --- error_code eval 49 | [ 50 | 403, 51 | 404 52 | ] -------------------------------------------------------------------------------- /test/test-nginx/template/disable.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | run_tests(); 4 | 5 | 6 | __DATA__ 7 | 8 | === TEST: General 9 | 10 | --- config 11 | waf off; 12 | waf_mode FULL; 13 | waf_rule_path ${base_dir}/waf/rules/; 14 | waf_cc_deny rate=100r/m; 15 | waf_cache capacity=50; 16 | 17 | --- request 18 | GET /www.bak 19 | 20 | --- error_code chomp 21 | 404 -------------------------------------------------------------------------------- /test/test-nginx/template/ipv4.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | run_tests(); 4 | 5 | 6 | __DATA__ 7 | 8 | === TEST: General 9 | 10 | --- config 11 | waf on; 12 | waf_mode GET URL IP; 13 | waf_rule_path ${base_dir}/waf/rules/; 14 | 15 | set_real_ip_from 127.0.0.0/8; 16 | real_ip_header X-Real-IP; 17 | 18 | --- pipelined_requests eval 19 | [ 20 | "GET /", 21 | "GET /www.bak", 22 | ] 23 | 24 | --- error_code eval 25 | [ 26 | "200", 27 | "403", 28 | ] 29 | 30 | === TEST: White IPV4 31 | 32 | --- config 33 | waf on; 34 | waf_mode GET URL IP; 35 | waf_rule_path ${base_dir}/waf/rules/; 36 | 37 | set_real_ip_from 127.0.0.0/8; 38 | real_ip_header X-Real-IP; 39 | 40 | --- pipelined_requests eval 41 | [ 42 | "GET /www.bak", 43 | "GET /www.bak", 44 | "GET /www.bak", 45 | "GET /www.bak", 46 | "GET /www.bak" 47 | ] 48 | 49 | --- more_headers eval 50 | [ 51 | "X-Real-IP: 3.3.3.3", 52 | "X-Real-IP: 4.0.0.0", 53 | "X-Real-IP: 4.1.0.0", 54 | "X-Real-IP: 4.0.1.0", 55 | "X-Real-IP: 4.0.0.1" 56 | ] 57 | 58 | 59 | --- error_code eval 60 | [ 61 | "404", 62 | "404", 63 | "404", 64 | "404", 65 | "404" 66 | ] 67 | 68 | === TEST: Black IPV4 69 | 70 | --- config 71 | waf on; 72 | waf_mode GET URL IP; 73 | waf_rule_path ${base_dir}/waf/rules/; 74 | 75 | set_real_ip_from 127.0.0.0/8; 76 | real_ip_header X-Real-IP; 77 | 78 | --- pipelined_requests eval 79 | [ 80 | "GET /", 81 | "GET /", 82 | "GET /", 83 | "GET /", 84 | "GET /" 85 | ] 86 | 87 | --- more_headers eval 88 | [ 89 | "X-Real-IP: 1.1.1.1", 90 | "X-Real-IP: 2.0.0.0", 91 | "X-Real-IP: 2.1.0.0", 92 | "X-Real-IP: 2.0.1.0", 93 | "X-Real-IP: 2.0.0.1" 94 | ] 95 | 96 | 97 | --- error_code eval 98 | [ 99 | "403", 100 | "403", 101 | "403", 102 | "403", 103 | "403" 104 | ] 105 | 106 | -------------------------------------------------------------------------------- /test/test-nginx/template/ipv6.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | run_tests(); 4 | 5 | 6 | __DATA__ 7 | 8 | === TEST: General 9 | 10 | --- config 11 | waf on; 12 | waf_mode GET URL IP; 13 | waf_rule_path ${base_dir}/waf/rules/; 14 | 15 | set_real_ip_from 127.0.0.0/8; 16 | real_ip_header X-Real-IP; 17 | 18 | --- pipelined_requests eval 19 | [ 20 | "GET /", 21 | "GET /www.bak", 22 | ] 23 | 24 | --- more_headers eval 25 | [ 26 | "X-Real-IP: EEEE::", 27 | "X-Real-IP: FFFF::", 28 | ] 29 | 30 | --- error_code eval 31 | [ 32 | "200", 33 | "403", 34 | ] 35 | 36 | === TEST: White IPV4 37 | 38 | --- config 39 | waf on; 40 | waf_mode GET URL IP; 41 | waf_rule_path ${base_dir}/waf/rules/; 42 | 43 | set_real_ip_from 127.0.0.0/8; 44 | real_ip_header X-Real-IP; 45 | 46 | --- pipelined_requests eval 47 | [ 48 | "GET /www.bak", 49 | "GET /www.bak", 50 | "GET /www.bak", 51 | "GET /www.bak", 52 | "GET /www.bak" 53 | ] 54 | 55 | --- more_headers eval 56 | [ 57 | "X-Real-IP: CCCC::", 58 | "X-Real-IP: DDDD:1::", 59 | "X-Real-IP: DDDD::1", 60 | "X-Real-IP: DDDD::1:1", 61 | "X-Real-IP: DDDD::1:1:1", 62 | "X-Real-IP: DDDD::1:1:1:1", 63 | "X-Real-IP: DDDD::1:1:1:1:1", 64 | "X-Real-IP: DDDD::1:1:1:1:1:1", 65 | "X-Real-IP: DDDD::1:1:1:1:1:1:1" 66 | ] 67 | 68 | 69 | --- error_code eval 70 | [ 71 | "404", 72 | "404", 73 | "404", 74 | "404", 75 | "404", 76 | "404", 77 | "404", 78 | "404", 79 | "404" 80 | ] 81 | 82 | === TEST: Black IPV4 83 | 84 | --- config 85 | waf on; 86 | waf_mode GET URL IP; 87 | waf_rule_path ${base_dir}/waf/rules/; 88 | 89 | set_real_ip_from 127.0.0.0/8; 90 | real_ip_header X-Real-IP; 91 | 92 | --- pipelined_requests eval 93 | [ 94 | "GET /", 95 | "GET /", 96 | "GET /", 97 | "GET /", 98 | "GET /" 99 | ] 100 | 101 | --- more_headers eval 102 | [ 103 | "X-Real-IP: AAAA::", 104 | "X-Real-IP: BBBB:1::", 105 | "X-Real-IP: BBBB::1", 106 | "X-Real-IP: BBBB::1:1", 107 | "X-Real-IP: BBBB::1:1:1", 108 | "X-Real-IP: BBBB::1:1:1:1", 109 | "X-Real-IP: BBBB::1:1:1:1:1", 110 | "X-Real-IP: BBBB::1:1:1:1:1:1", 111 | "X-Real-IP: BBBB::1:1:1:1:1:1:1" 112 | ] 113 | 114 | 115 | --- error_code eval 116 | [ 117 | "403", 118 | "403", 119 | "403", 120 | "403", 121 | "403", 122 | "403", 123 | "403", 124 | "403", 125 | "403" 126 | ] 127 | 128 | -------------------------------------------------------------------------------- /test/test-nginx/template/mode.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | run_tests(); 4 | 5 | 6 | __DATA__ 7 | 8 | === TEST: No method GET 9 | 10 | --- config 11 | waf on; 12 | waf_mode FULL !GET !CC !CACHE; 13 | waf_rule_path ${base_dir}/waf/rules/; 14 | 15 | --- request 16 | GET /www.bak 17 | 18 | 19 | --- error_code chomp 20 | 404 21 | 22 | === TEST: No method POST 23 | 24 | --- config 25 | waf on; 26 | waf_mode FULL !POST !CC !CACHE; 27 | waf_rule_path ${base_dir}/waf/rules/; 28 | 29 | --- request 30 | POST /www.bak 31 | 32 | 33 | --- error_code chomp 34 | 404 35 | 36 | === TEST: No method HEAD 37 | 38 | --- config 39 | waf on; 40 | waf_mode FULL !HEAD !CC !CACHE; 41 | waf_rule_path ${base_dir}/waf/rules/; 42 | 43 | --- request 44 | HEAD /www.bak 45 | 46 | 47 | --- error_code chomp 48 | 404 49 | -------------------------------------------------------------------------------- /test/test-nginx/template/post.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | run_tests(); 4 | 5 | 6 | __DATA__ 7 | 8 | === TEST: General 9 | 10 | --- config 11 | waf on; 12 | waf_mode GET POST RBODY; 13 | waf_rule_path ${base_dir}/waf/rules/; 14 | 15 | location /t { 16 | } 17 | 18 | --- request 19 | POST /t 20 | s=test 21 | 22 | --- error_code chomp 23 | 404 24 | 25 | === TEST: Black POST 26 | 27 | --- config 28 | waf on; 29 | waf_mode GET POST RBODY; 30 | waf_rule_path ${base_dir}/waf/rules/; 31 | 32 | location /t { 33 | } 34 | 35 | --- request 36 | POST /t 37 | onload= 38 | 39 | --- error_code chomp 40 | 403 41 | 42 | -------------------------------------------------------------------------------- /test/test-nginx/template/referer.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | run_tests(); 4 | 5 | 6 | __DATA__ 7 | 8 | === TEST: General 9 | 10 | --- config 11 | waf on; 12 | waf_mode GET REFERER; 13 | waf_rule_path ${base_dir}/waf/rules/; 14 | 15 | --- request 16 | GET / 17 | 18 | --- more_headers 19 | Referer: /test 20 | 21 | --- error_code chomp 22 | 200 23 | 24 | 25 | === TEST: Black referer 26 | 27 | --- config 28 | waf on; 29 | waf_mode GET REFERER; 30 | waf_rule_path ${base_dir}/waf/rules/; 31 | 32 | --- pipelined_requests eval 33 | [ 34 | "GET /", 35 | "GET /" 36 | ] 37 | 38 | --- more_headers eval 39 | [ 40 | "Referer: /www.bak", 41 | "Referer: /www.bak" 42 | ] 43 | 44 | --- error_code eval 45 | [ 46 | 403, 47 | 403 48 | ] 49 | 50 | 51 | === TEST: White referer 52 | 53 | --- config 54 | waf on; 55 | waf_mode GET REFERER; 56 | waf_rule_path ${base_dir}/waf/rules/; 57 | 58 | --- pipelined_requests eval 59 | [ 60 | "GET /", 61 | "GET /" 62 | ] 63 | 64 | --- more_headers eval 65 | [ 66 | "Referer: /white/www.bak", 67 | "Referer: /white/www.bak" 68 | ] 69 | 70 | --- error_code eval 71 | [ 72 | 200, 73 | 200 74 | ] -------------------------------------------------------------------------------- /test/test-nginx/template/under-attack.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | run_tests(); 4 | 5 | 6 | __DATA__ 7 | 8 | === TEST: Under Attack Mode 9 | 10 | --- config 11 | waf on; 12 | waf_mode FULL !CC; 13 | waf_rule_path ${base_dir}/waf/rules/; 14 | waf_under_attack on uri=/; 15 | 16 | --- pipelined_requests eval 17 | [ 18 | "GET /", 19 | "GET /test" 20 | ] 21 | 22 | --- more_headers eval 23 | [ 24 | "", 25 | "Cookie: __waf_under_attack_time=123456; __waf_under_attack_uid=123456; __waf_under_attack_hmac=123456" 26 | ] 27 | 28 | --- error_code eval 29 | [ 30 | 200, 31 | 303 32 | ] -------------------------------------------------------------------------------- /test/test-nginx/template/uri.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | run_tests(); 4 | 5 | 6 | __DATA__ 7 | 8 | === TEST: General 9 | 10 | --- config 11 | waf on; 12 | waf_mode GET URL !CC; 13 | waf_rule_path ${base_dir}/waf/rules/; 14 | 15 | --- pipelined_requests eval 16 | [ 17 | "GET /", 18 | "GET /test0" 19 | ] 20 | 21 | --- error_code eval 22 | [ 23 | 200, 24 | 404 25 | ] 26 | 27 | === TEST: Black URI 28 | 29 | --- config 30 | waf on; 31 | waf_mode GET URL !CC; 32 | waf_rule_path ${base_dir}/waf/rules/; 33 | 34 | 35 | --- pipelined_requests eval 36 | [ 37 | "GET /www.bak", 38 | "GET /static/index.php" 39 | ] 40 | 41 | --- error_code eval 42 | [ 43 | 403, 44 | 403 45 | ] 46 | 47 | === TEST: White URI 48 | 49 | --- config 50 | waf on; 51 | waf_mode GET URL !CC; 52 | waf_rule_path ${base_dir}/waf/rules/; 53 | 54 | --- pipelined_requests eval 55 | [ 56 | "GET /white/www.bak", 57 | "GET /white/static/index.php" 58 | ] 59 | 60 | --- error_code eval 61 | [ 62 | 404, 63 | 404 64 | ] -------------------------------------------------------------------------------- /test/test-nginx/template/user-agent.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | run_tests(); 4 | 5 | 6 | __DATA__ 7 | 8 | === TEST: General 9 | 10 | --- config 11 | waf on; 12 | waf_mode GET UA !CC; 13 | waf_rule_path ${base_dir}/waf/rules/; 14 | 15 | --- request 16 | GET / 17 | 18 | --- more_headers 19 | User-Agent: test-user-agent 20 | 21 | --- error_code chomp 22 | 200 23 | 24 | === TEST: Black user-agent 25 | 26 | --- config 27 | waf on; 28 | waf_mode GET UA !CC; 29 | waf_rule_path ${base_dir}/waf/rules/; 30 | 31 | --- request 32 | GET / 33 | 34 | --- more_headers 35 | User-Agent: / SF/ 36 | 37 | --- error_code chomp 38 | 403 -------------------------------------------------------------------------------- /test/test-nginx/template/var.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | run_tests(); 4 | 5 | 6 | __DATA__ 7 | 8 | === TEST: Check value 9 | 10 | --- config 11 | waf on; 12 | waf_mode FULL; 13 | waf_rule_path ${base_dir}/waf/rules/; 14 | 15 | location /error { 16 | return 200 [\$waf_log][\$waf_blocking_log][\$waf_blocked][\$waf_rule_type]; 17 | } 18 | 19 | error_page 403 /error; 20 | 21 | --- pipelined_requests eval 22 | [ 23 | "GET /www.bak", 24 | "GET /?test=onload=" 25 | ] 26 | 27 | --- response_body eval 28 | [ 29 | "[true][true][true][BLACK-URL]", 30 | "[true][true][true][BLACK-ARGS]" 31 | ] 32 | 33 | --- error_code eval 34 | [ 35 | 403, 36 | 403 37 | ] 38 | 39 | 40 | === TEST: Check run 41 | 42 | --- config 43 | waf on; 44 | waf_mode FULL; 45 | waf_rule_path ${base_dir}/waf/rules/; 46 | 47 | location /t { 48 | 49 | } 50 | 51 | location /error { 52 | return 200 [\$waf_log][\$waf_blocking_log][\$waf_blocked][\$waf_rule_type][\$waf_spend][\$waf_rule_details]; 53 | } 54 | 55 | error_page 403 /error; 56 | 57 | --- pipelined_requests eval 58 | [ 59 | "GET /", 60 | "GET /www.bak", 61 | "GET /?test=onload=" 62 | ] 63 | 64 | --- error_code eval 65 | [ 66 | 200, 67 | 403, 68 | 403 69 | ] 70 | -------------------------------------------------------------------------------- /test/wrk/rand.lua: -------------------------------------------------------------------------------- 1 | -- Store all random strings read from the file 2 | texts = { } 3 | 4 | 5 | function init(args) 6 | -- Set random number seed 7 | math.randomseed(os.time()) 8 | 9 | -- Open the file and read the entire contents. 10 | local file = io.open(args[1], "r"); 11 | for line in file:lines() do 12 | table.insert(texts, line) 13 | end 14 | io.close(file) 15 | end 16 | 17 | 18 | function request() 19 | -- 20 | local path = string.format("/%s",texts[math.random(1, #texts)]) 21 | return wrk.format("GET", path, nil, nil) 22 | end --------------------------------------------------------------------------------