Squashed 'src/deps/src/modsecurity-nginx/' content from commit d59e4ad12

git-subtree-dir: src/deps/src/modsecurity-nginx
git-subtree-split: d59e4ad121df702751940fd66bcc0b3ecb51a079
This commit is contained in:
Théophile Diot 2023-06-30 15:36:31 -04:00
commit a676d333fd
33 changed files with 5802 additions and 0 deletions

19
.github/workflows/stale.yml vendored Normal file
View File

@ -0,0 +1,19 @@
#name: "Close stale issues"
#on:
# schedule:
# - cron: "0 0 * * *"
#
#jobs:
# stale:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/stale@v3
# with:
# repo-token: ${{ secrets.GITHUB_TOKEN }}
# stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
# stale-pr-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
# stale-issue-label: 'no-issue-activity'
# stale-pr-label: 'no-pr-activity'
# exempt-issue-label: 'nostale,work-in-progress'
# days-before-stale: 30
# days-before-close: 5

47
.travis.yml Normal file
View File

@ -0,0 +1,47 @@
sudo: required
dist: bionic
os: linux
language: c
compiler:
- gcc
addons:
apt:
packages:
- libyajl-dev
- libgeoip-dev
- liblmdb-dev
env:
- VER_NGINX=1.21.0
- VER_NGINX=1.20.1
before_script:
- cd ..
- git clone https://github.com/SpiderLabs/ModSecurity.git
- cd ModSecurity
- git checkout v3/master
- git submodule init
- git submodule update
- ./build.sh
- ./configure --without-lmdb
- make
- sudo make install
- cd ..
- wget http://nginx.org/download/nginx-${VER_NGINX}.tar.gz && tar -xf nginx-${VER_NGINX}.tar.gz
- cd nginx-${VER_NGINX}
- ./configure --with-http_auth_request_module --with-http_v2_module --add-module=../ModSecurity-nginx
- make
- sudo make install
- cd ..
- wget http://hg.nginx.org/nginx-tests/archive/tip.tar.gz
- tar xvzf tip.tar.gz
- cd nginx-tests-*
- cp ../ModSecurity-nginx/tests/* .
- export TEST_NGINX_BINARY=/usr/local/nginx/sbin/nginx
script:
- prove .

2
AUTHORS Normal file
View File

@ -0,0 +1,2 @@
zimmerle = Felipe Zimmerle <felipe@zimmerle.org>
defanator = Andrei Belov <defanator@gmail.com>

70
CHANGES Normal file
View File

@ -0,0 +1,70 @@
v1.0.3 - 2022-May-24
--------------------
- Support http protocol versions besides 0.9, 1.0, 1.1, 2.0
[Issue #224 - @HQuest, @martinhsv]
- Support for building with nginx configured with PCRE2
[Issue #260 - @defanator]
v1.0.2 - 2021-Jun-02
--------------------
- Fix auditlog in case of internal redirect
[Issue #90 - @AirisX, @defanator]
- Fix nginx sends response without headers
[Issue #238 - @airween, @defanator]
- Fix nginx not clearing body cache (caused by incomplete fix for #187)
[Issue #216 - @krewi1, @martinhsv]
- Fix config setting not respected: client_body_in_file_only on
[Issue #187 - @martinhsv]
- Fix audit_log not generated for disruptive actions
[Issue #170, #2220, #2237 - @victorhora]
- Exit more gracefully if uri length is zero
[@martinhsv]
v1.0.1 - 2019-Dec-16
--------------------
- Fixed obtaining of server_addr
[Issue #167, #168 - @defanator]
- Avoid processing of subrequests initiated by the error_page
[Issue #76, #164, #165 - @defanator]
- Tests: extend request body tests
[Issue #142,#143 - @defanator]
- Added basic tests over HTTP/2
[Issue #145 - @defanator]
- Module configuration refactoring
[Issue #139 - @defanator]
- Restore r->write_event_handler after reading request body
[Issue #131 - @defanator]
- Increase log level for disruptive actions to "error"
[Issue #112 - @victorhora]
- Support for generating transaction ID in nginx
[Issue #126 - @defanator]
- Extend request body tests with ARGS_POST case
[Issue #124 - @defanator]
- Fix tests after 42a472a change in library
[Issue #122 - @defanator]
- Fix processing of response body when gzip compression is enabled
[Issue #107 - @turchanov]
- Fixed processing of response body chunks in
ngx_http_modsecurity_body_filter.
[Issue #105 - @turchanov, @defanator]
- Fix incorrect handling of request/response body data chain of ngx_buf_t
buffers
[Issue #104 - @turchanov, @defanator]
- Pool pointer is now handled in ngx_http_modsecurity_config_cleanup
[Issue #87 - @AirisX, @defanator, @zimmerle]
- Fix memory leak in intervention processing
[Issue #100 - @defanator]
- Emit connector version in error log
[Issue #88 - @defanator]
- Fixed memory leak on config cleanup.
[Issue #80 - @AirisX, @defanator]
v1.0.0 - 2017-Dec-20
--------------------
- First version of ModSecurity-nginx connector

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

266
README.md Normal file
View File

@ -0,0 +1,266 @@
<img src="https://github.com/SpiderLabs/ModSecurity/raw/v3/master/others/modsec.png" width="50%">
[![Build Status](https://travis-ci.org/SpiderLabs/ModSecurity-nginx.svg?branch=master)](https://travis-ci.org/SpiderLabs/ModSecurity-nginx)
[![](https://raw.githubusercontent.com/ZenHubIO/support/master/zenhub-badge.png)](https://zenhub.com)
The ModSecurity-nginx connector is the connection point between nginx and libmodsecurity (ModSecurity v3). Said another way, this project provides a communication channel between nginx and libmodsecurity. This connector is required to use LibModSecurity with nginx.
The ModSecurity-nginx connector takes the form of an nginx module. The module simply serves as a layer of communication between nginx and ModSecurity.
Notice that this project depends on libmodsecurity rather than ModSecurity (version 2.9 or less).
### What is the difference between this project and the old ModSecurity add-on for nginx?
The old version uses ModSecurity standalone, which is a wrapper for
Apache internals to link ModSecurity to nginx. This current version is closer
to nginx, consuming the new libmodsecurity which is no longer dependent on
Apache. As a result, this current version has less dependencies, fewer bugs, and is faster. In addition, some new functionality is also provided - such as the possibility of use of global rules configuration with per directory/location customizations (e.g. SecRuleRemoveById).
# Compilation
Before compile this software make sure that you have libmodsecurity installed.
You can download it from the [ModSecurity git repository](https://github.com/SpiderLabs/ModSecurity). For information pertaining to the compilation and installation of libmodsecurity please consult the documentation provided along with it.
With libmodsecurity installed, you can proceed with the installation of the ModSecurity-nginx connector, which follows the nginx third-party module installation procedure. From the nginx source directory:
```
./configure --add-module=/path/to/ModSecurity-nginx
```
Or, to build a dynamic module:
```
./configure --add-dynamic-module=/path/to/ModSecurity-nginx --with-compat
```
Note that when building a dynamic module, your nginx source version
needs to match the version of nginx you're compiling this for.
Further information about nginx third-party add-ons support are available here:
http://wiki.nginx.org/3rdPartyModules
# Usage
ModSecurity for nginx extends your nginx configuration directives.
It adds four new directives and they are:
modsecurity
-----------
**syntax:** *modsecurity on | off*
**context:** *http, server, location*
**default:** *off*
Turns on or off ModSecurity functionality.
Note that this configuration directive is no longer related to the SecRule state.
Instead, it now serves solely as an nginx flag to enable or disable the module.
modsecurity_rules_file
----------------------
**syntax:** *modsecurity_rules_file &lt;path to rules file&gt;*
**context:** *http, server, location*
**default:** *no*
Specifies the location of the modsecurity configuration file, e.g.:
```nginx
server {
modsecurity on;
location / {
root /var/www/html;
modsecurity_rules_file /etc/my_modsecurity_rules.conf;
}
}
```
modsecurity_rules_remote
------------------------
**syntax:** *modsecurity_rules_remote &lt;key&gt; &lt;URL to rules&gt;*
**context:** *http, server, location*
**default:** *no*
Specifies from where (on the internet) a modsecurity configuration file will be downloaded.
It also specifies the key that will be used to authenticate to that server:
```nginx
server {
modsecurity on;
location / {
root /var/www/html;
modsecurity_rules_remote my-server-key https://my-own-server/rules/download;
}
}
```
modsecurity_rules
-----------------
**syntax:** *modsecurity_rules &lt;modsecurity rule&gt;*
**context:** *http, server, location*
**default:** *no*
Allows for the direct inclusion of a ModSecurity rule into the nginx configuration.
The following example is loading rules from a file and injecting specific configurations per directory/alias:
```nginx
server {
modsecurity on;
location / {
root /var/www/html;
modsecurity_rules_file /etc/my_modsecurity_rules.conf;
}
location /ops {
root /var/www/html/opts;
modsecurity_rules '
SecRuleEngine On
SecDebugLog /tmp/modsec_debug.log
SecDebugLogLevel 9
SecRuleRemoveById 10
';
}
}
```
modsecurity_transaction_id
--------------------------
**syntax:** *modsecurity_transaction_id string*
**context:** *http, server, location*
**default:** *no*
Allows to pass transaction ID from nginx instead of generating it in the library.
This can be useful for tracing purposes, e.g. consider this configuration:
```nginx
log_format extended '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" $request_id';
server {
server_name host1;
modsecurity on;
modsecurity_transaction_id "host1-$request_id";
access_log logs/host1-access.log extended;
error_log logs/host1-error.log;
location / {
...
}
}
server {
server_name host2;
modsecurity on;
modsecurity_transaction_id "host2-$request_id";
access_log logs/host2-access.log extended;
error_log logs/host2-error.log;
location / {
...
}
}
```
Using a combination of log_format and modsecurity_transaction_id you will
be able to find correlations between access log and error log entries
using the same unique identificator.
String can contain variables.
# Contributing
As an open source project we invite (and encourage) anyone from the community to contribute to our project. This may take the form of: new
functionality, bug fixes, bug reports, beginners user support, and anything else that you
are willing to help with. Thank you.
## Providing Patches
We prefer to have your patch within the GitHub infrastructure to facilitate our
review work, and our QA integration. GitHub provides an excellent
documentation on how to perform “Pull Requests”. More information available
here: https://help.github.com/articles/using-pull-requests/
Please respect the coding style in use. Pull requests can include various commits, so
provide one fix or one functionality per commit. Do not change anything outside
the scope of your target work (e.g. coding style in a function that you have
passed by).
### Dont know where to start?
Within our code there are various items marked as TODO or FIXME that may need
your attention. Check the list of items by performing a grep:
```
$ cd /path/to/modsecurity-nginx
$ egrep -Rin "TODO|FIXME" -R *
```
You may also take a look at recent bug reports and open issues to get an idea of what kind of help we are looking for.
### Testing your patch
Along with the manual testing, we strongly recommend that you to use the nginx test
utility to make sure that you patch does not adversely affect the behavior or performance of nginx.
The nginx tests are available on: http://hg.nginx.org/nginx-tests/
To use those tests, make sure you have the Perl utility prove (part of Perl 5)
and proceed with the following commands:
```
$ cp /path/to/ModSecurity-nginx/tests/* /path/to/nginx/test/repository
$ cd /path/to/nginx/test/repository
$ TEST_NGINX_BINARY=/path/to/your/nginx prove .
```
If you are facing problems getting your added functionality to pass all the nginx tests, feel free to contact us or the nginx mailing list at: http://nginx.org/en/support.html
### Debugging
We respect the nginx debugging schema. By using the configuration option
"--with-debug" during the nginx configuration you will also be enabling the
connector's debug messages. Core dumps and crashes are expected to be debugged
in the same fashion that is used to debug nginx. For further information,
please check the nginx debugging information: http://wiki.nginx.org/Debugging
## Reporting Issues
If you are facing a configuration issue or if something is not working as you
expect it to be, please use ModSecurity users mailing list. Issues on GitHub
are also welcome, but we prefer to have users question on the mailing list first,
where you can reach an entire community. Also dont forget to look for an
existing issue before opening a new one.
Lastly, If you are planning to open an issue on GitHub, please dont forget to tell us the
version of your libmodsecurity and the version of the nginx connector you are running.
### Security issue
Please do not publicly report any security issue. Instead, contact us at:
security@modsecurity.org to report the issue. Once the problem is fixed we will provide you with credit for the discovery.
## Feature Request
We would love to discuss any ideas that you may have for a new feature. Please keep in mind this is a community driven project so be sure to contact the community via the mailing list to get feedback first. Alternatively,
feel free to open GitHub issues requesting for new features. Before opening a new issue, please check if there is an existing feature request for the desired functionality.
## Packaging
Having our packages in distros on time is something we highly desire. Let us know if
there is anything we can do to facilitate your work as a packager.

192
config Normal file
View File

@ -0,0 +1,192 @@
# vim: filetype=sh
# If $NGX_IGNORE_RPATH is set to "YES", we will ignore explicit
# library path specification on resulting binary, allowing libmodsecurity.so
# to be relocated across configured library pathes (adjust /etc/ld.so.conf
# or set $LD_LIBRARY_PATH environment variable to manage them)
#
# $YAJL_LIB variable may need to be populated in case of non-standard
# path of libyajl.so's installation
ngx_feature_name=
ngx_feature_run=no
ngx_feature_incs="#include <modsecurity/modsecurity.h>"
ngx_feature_libs="-lmodsecurity"
ngx_feature_test='printf("hello");'
ngx_modsecurity_opt_I=
ngx_modsecurity_opt_L=
YAJL_EXTRA=
if test -n "$YAJL_LIB"; then
YAJL_EXTRA="-L$YAJL_LIB -lyajl"
fi
# If $MODSECURITY_INC is specified, lets use it. Otherwise lets try
# the default paths
#
if [ -n "$MODSECURITY_INC" -o -n "$MODSECURITY_LIB" ]; then
# explicitly set ModSecurity lib path
ngx_feature="ModSecurity library in \"$MODSECURITY_LIB\" and \"$MODSECURITY_INC\" (specified by the MODSECURITY_LIB and MODSECURITY_INC env)"
ngx_feature_path="$MODSECURITY_INC"
ngx_modsecurity_opt_I="-I$MODSECURITY_INC"
ngx_modsecurity_opt_L="-L$MODSECURITY_LIB $YAJL_EXTRA"
if [ $NGX_RPATH = YES ]; then
ngx_feature_libs="-R$MODSECURITY_LIB -L$MODSECURITY_LIB -lmodsecurity $YAJL_EXTRA"
elif [ "$NGX_IGNORE_RPATH" != "YES" -a $NGX_SYSTEM = "Linux" ]; then
ngx_feature_libs="-Wl,-rpath,$MODSECURITY_LIB -L$MODSECURITY_LIB -lmodsecurity $YAJL_EXTRA"
else
ngx_feature_libs="-L$MODSECURITY_LIB -lmodsecurity $YAJL_EXTRA"
fi
. auto/feature
if [ $ngx_found = no ]; then
cat << END
$0: error: ngx_http_modsecurity_module requires the ModSecurity library and MODSECURITY_LIB is defined as "$MODSECURITY_LIB" and MODSECURITY_INC (path for modsecurity.h) "$MODSECURITY_INC", but we cannot find ModSecurity there.
END
exit 1
fi
else
# auto-discovery
ngx_feature="ModSecurity library"
ngx_feature_libs="-lmodsecurity"
. auto/feature
if [ $ngx_found = no ]; then
ngx_feature="ModSecurity library in /usr/local/modsecurity"
ngx_feature_path="/usr/local/modsecurity/include"
if [ $NGX_RPATH = YES ]; then
ngx_feature_libs="-R/usr/local/modsecurity/lib -L/usr/local/modsecurity/lib -lmodsecurity"
elif [ "$NGX_IGNORE_RPATH" != "YES" -a $NGX_SYSTEM = "Linux" ]; then
ngx_feature_libs="-Wl,-rpath,/usr/local/modsecurity/lib -L/usr/local/modsecurity/lib -lmodsecurity"
else
ngx_feature_libs="-L/usr/local/modsecurity/lib -lmodsecurity"
fi
. auto/feature
fi
fi
if [ $ngx_found = no ]; then
cat << END
$0: error: ngx_http_modsecurity_module requires the ModSecurity library.
END
exit 1
fi
ngx_addon_name=ngx_http_modsecurity_module
# We must place ngx_http_modsecurity_module after ngx_http_gzip_filter_module
# in load order list to be able to read response body before it gets compressed
# (for filter modules later initialization means earlier execution).
#
# Nginx implements load ordering only for dynamic modules and only a BEFORE part
# of "ngx_module_order". So we list all of the modules that come after
# ngx_http_gzip_filter_module as a BEFORE dependency for
# ngx_http_modsecurity_module.
#
# For static compilation HTTP_FILTER_MODULES will be patched later.
modsecurity_dependency="ngx_http_postpone_filter_module \
ngx_http_ssi_filter_module \
ngx_http_charset_filter_module \
ngx_http_xslt_filter_module \
ngx_http_image_filter_module \
ngx_http_sub_filter_module \
ngx_http_addition_filter_module \
ngx_http_gunzip_filter_module \
ngx_http_userid_filter_module \
ngx_http_headers_filter_module \
ngx_http_copy_filter_module"
if test -n "$ngx_module_link"; then
ngx_module_type=HTTP_FILTER
ngx_module_name="$ngx_addon_name"
ngx_module_srcs="$ngx_addon_dir/src/ngx_http_modsecurity_module.c \
$ngx_addon_dir/src/ngx_http_modsecurity_pre_access.c \
$ngx_addon_dir/src/ngx_http_modsecurity_header_filter.c \
$ngx_addon_dir/src/ngx_http_modsecurity_body_filter.c \
$ngx_addon_dir/src/ngx_http_modsecurity_log.c \
$ngx_addon_dir/src/ngx_http_modsecurity_rewrite.c \
"
ngx_module_deps="$ngx_addon_dir/src/ddebug.h \
$ngx_addon_dir/src/ngx_http_modsecurity_common.h \
"
ngx_module_libs="$ngx_feature_libs"
ngx_module_incs="$ngx_feature_path"
ngx_module_order="ngx_http_chunked_filter_module \
ngx_http_v2_filter_module \
ngx_http_range_header_filter_module \
ngx_http_gzip_filter_module \
$ngx_module_name \
$modsecurity_dependency";
. auto/module
else
CFLAGS="$ngx_modsecurity_opt_I $CFLAGS"
NGX_LD_OPT="$ngx_modsecurity_opt_L $NGX_LD_OPT"
CORE_INCS="$CORE_INCS $ngx_feature_path"
CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_modsecurity_module"
NGX_ADDON_SRCS="\
$NGX_ADDON_SRCS \
$ngx_addon_dir/src/ngx_http_modsecurity_module.c \
$ngx_addon_dir/src/ngx_http_modsecurity_pre_access.c \
$ngx_addon_dir/src/ngx_http_modsecurity_header_filter.c \
$ngx_addon_dir/src/ngx_http_modsecurity_body_filter.c \
$ngx_addon_dir/src/ngx_http_modsecurity_log.c \
$ngx_addon_dir/src/ngx_http_modsecurity_rewrite.c \
"
NGX_ADDON_DEPS="\
$NGX_ADDON_DEPS \
$ngx_addon_dir/src/ddebug.h \
$ngx_addon_dir/src/ngx_http_modsecurity_common.h \
"
fi
#
# Nginx does not provide reliable way to introduce our module into required
# place in static ($ngx_module_link=ADDON) compilation mode, so we must
# explicitly update module "ordering rules".
#
if [ "$ngx_module_link" != DYNAMIC ] ; then
# Reposition modsecurity module to satisfy $modsecurity_dependency
# (this mimics dependency resolution made by ngx_add_module() function
# though less optimal in terms of computational complexity).
modules=
found=
for module in $HTTP_FILTER_MODULES; do
# skip our module name from the original list
if [ "$module" = "$ngx_addon_name" ]; then
continue
fi
if [ -z "${found}" ]; then
for item in $modsecurity_dependency; do
if [ "$module" = "$item" ]; then
modules="${modules} $ngx_addon_name"
found=1
break
fi
done
fi
modules="${modules} $module"
done
if [ -z "${found}" ]; then
# This must never happen since ngx_http_copy_filter_module must be in HTTP_FILTER_MODULES
# and we stated dependency on it in $modsecurity_dependency
echo "$0: error: cannot reposition modsecurity module in HTTP_FILTER_MODULES list"
exit 1
fi
HTTP_FILTER_MODULES="${modules}"
fi

37
ngx-modsec.stp Executable file
View File

@ -0,0 +1,37 @@
#!/usr/bin/env stap
global begin_rule
global rules
global rules_phase
# Rules
probe process("/usr/local/lib/libmodsecurity.so.3").function("evaluate@rule.cc*")
{
begin_rule = gettimeofday_us();
}
probe process("/usr/local/lib/libmodsecurity.so.3").function("evaluate@rule.cc*").return
{
elapsed_rule = gettimeofday_us() - begin_rule
rules[$this->m_ruleId] <<< elapsed_rule
rules_phase[$this->m_ruleId] = $this->m_phase
}
# Resume
probe end
{
foreach ([rule] in rules)
{
if (@count(rules[rule])) {
p = rules_phase[rule] - 1;
if (p <= 0) {
p = 1
}
printf("Phase %d;Rule ID: %d %u\n", p, rule, @avg(rules[rule]));
}
}
}

20
release.sh Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
git clean -xfdi
git submodule foreach --recursive git clean -xfdi
VERSION=`git describe --tags`
DIR_NAME="modsecurity-nginx-$VERSION"
TAR_NAME="modsecurity-nginx-$VERSION.tar.gz"
MY_DIR=${PWD##*/}
cd ..
tar --transform "s/^$MY_DIR/$DIR_NAME/" -cvzf $TAR_NAME --exclude .git $MY_DIR
sha256sum $TAR_NAME > $TAR_NAME.sha256
gpg --detach-sign -a $TAR_NAME
cd -
echo $TAR_NAME ": done."

100
src/ddebug.h Normal file
View File

@ -0,0 +1,100 @@
// From: https://raw.githubusercontent.com/openresty/lua-nginx-module/master/src/ddebug.h
/*
* Copyright (C) Yichun Zhang (agentzh)
*/
#ifndef _DDEBUG_H_INCLUDED_
#define _DDEBUG_H_INCLUDED_
#include <ngx_core.h>
/*
* #undef MODSECURITY_DDEBUG
* #define MODSECURITY_DDEBUG 1
*/
/*
* Setting MODSECURITY_SANITY_CHECKS will help you in the debug process. By
* defining MODSECURITY_SANITY_CHECKS a set of functions will be executed in
* order to make sure the well behavior of ModSecurity, letting you know (via
* debug_logs) if something unexpected happens.
*
* If performance is not a concern, it is safe to keep it set.
*
*/
#ifndef MODSECURITY_SANITY_CHECKS
#define MODSECURITY_SANITY_CHECKS 0
#endif
#if defined(MODSECURITY_DDEBUG) && (MODSECURITY_DDEBUG)
# if (NGX_HAVE_VARIADIC_MACROS)
# define dd(...) fprintf(stderr, "modsec *** %s: ", __func__); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, " at %s line %d.\n", __FILE__, __LINE__)
# else
#include <stdarg.h>
#include <stdio.h>
#include <stdarg.h>
static void dd(const char *fmt, ...) {
}
# endif
#else
# if (NGX_HAVE_VARIADIC_MACROS)
# define dd(...)
# else
#include <stdarg.h>
static void dd(const char *fmt, ...) {
}
# endif
#endif
#if defined(MODSECURITY_DDEBUG) && (MODSECURITY_DDEBUG)
#define dd_check_read_event_handler(r) \
dd("r->read_event_handler = %s", \
r->read_event_handler == ngx_http_block_reading ? \
"ngx_http_block_reading" : \
r->read_event_handler == ngx_http_test_reading ? \
"ngx_http_test_reading" : \
r->read_event_handler == ngx_http_request_empty_handler ? \
"ngx_http_request_empty_handler" : "UNKNOWN")
#define dd_check_write_event_handler(r) \
dd("r->write_event_handler = %s", \
r->write_event_handler == ngx_http_handler ? \
"ngx_http_handler" : \
r->write_event_handler == ngx_http_core_run_phases ? \
"ngx_http_core_run_phases" : \
r->write_event_handler == ngx_http_request_empty_handler ? \
"ngx_http_request_empty_handler" : "UNKNOWN")
#else
#define dd_check_read_event_handler(r)
#define dd_check_write_event_handler(r)
#endif
#endif /* _DDEBUG_H_INCLUDED_ */
/* vi:set ft=c ts=4 sw=4 et fdm=marker: */

View File

@ -0,0 +1,184 @@
/*
* ModSecurity connector for nginx, http://www.modsecurity.org/
* Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* If any of the files related to licensing are missing or if you have any
* other questions related to licensing please contact Trustwave Holdings, Inc.
* directly using the email address security@modsecurity.org.
*
*/
#ifndef MODSECURITY_DDEBUG
#define MODSECURITY_DDEBUG 0
#endif
#include "ddebug.h"
#include "ngx_http_modsecurity_common.h"
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
/* XXX: check behaviour on few body filters installed */
ngx_int_t
ngx_http_modsecurity_body_filter_init(void)
{
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_modsecurity_body_filter;
return NGX_OK;
}
ngx_int_t
ngx_http_modsecurity_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_chain_t *chain = in;
ngx_http_modsecurity_ctx_t *ctx = NULL;
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ngx_http_modsecurity_conf_t *mcf;
ngx_list_part_t *part = &r->headers_out.headers.part;
ngx_table_elt_t *data = part->elts;
ngx_uint_t i = 0;
#endif
if (in == NULL) {
return ngx_http_next_body_filter(r, in);
}
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
dd("body filter, recovering ctx: %p", ctx);
if (ctx == NULL) {
return ngx_http_next_body_filter(r, in);
}
if (ctx->intervention_triggered) {
return ngx_http_next_body_filter(r, in);
}
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
mcf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity_module);
if (mcf != NULL && mcf->sanity_checks_enabled != NGX_CONF_UNSET)
{
#if 0
dd("dumping stored ctx headers");
for (i = 0; i < ctx->sanity_headers_out->nelts; i++)
{
ngx_http_modsecurity_header_t *vals = ctx->sanity_headers_out->elts;
ngx_str_t *s2 = &vals[i].name, *s3 = &vals[i].value;
dd(" dump[%d]: name = '%.*s', value = '%.*s'", (int)i,
(int)s2->len, (char*)s2->data,
(int)s3->len, (char*)s3->data);
}
#endif
/*
* Identify if there is a header that was not inspected by ModSecurity.
*/
int worth_to_fail = 0;
for (i = 0; ; i++)
{
int found = 0;
ngx_uint_t j = 0;
ngx_table_elt_t *s1;
ngx_http_modsecurity_header_t *vals;
if (i >= part->nelts)
{
if (part->next == NULL) {
break;
}
part = part->next;
data = part->elts;
i = 0;
}
vals = ctx->sanity_headers_out->elts;
s1 = &data[i];
/*
* Headers that were inspected by ModSecurity.
*/
while (j < ctx->sanity_headers_out->nelts)
{
ngx_str_t *s2 = &vals[j].name;
ngx_str_t *s3 = &vals[j].value;
if (s1->key.len == s2->len && ngx_strncmp(s1->key.data, s2->data, s1->key.len) == 0)
{
if (s1->value.len == s3->len && ngx_strncmp(s1->value.data, s3->data, s1->value.len) == 0)
{
found = 1;
break;
}
}
j++;
}
if (!found) {
dd("header: `%.*s' with value: `%.*s' was not inspected by ModSecurity",
(int) s1->key.len,
(const char *) s1->key.data,
(int) s1->value.len,
(const char *) s1->value.data);
worth_to_fail++;
}
}
if (worth_to_fail)
{
dd("%d header(s) were not inspected by ModSecurity, so exiting", worth_to_fail);
return ngx_http_filter_finalize_request(r,
&ngx_http_modsecurity_module, NGX_HTTP_INTERNAL_SERVER_ERROR);
}
}
#endif
int is_request_processed = 0;
for (; chain != NULL; chain = chain->next)
{
u_char *data = chain->buf->pos;
int ret;
msc_append_response_body(ctx->modsec_transaction, data, chain->buf->last - data);
ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r, 0);
if (ret > 0) {
return ngx_http_filter_finalize_request(r,
&ngx_http_modsecurity_module, ret);
}
/* XXX: chain->buf->last_buf || chain->buf->last_in_chain */
is_request_processed = chain->buf->last_buf;
if (is_request_processed) {
ngx_pool_t *old_pool;
old_pool = ngx_http_modsecurity_pcre_malloc_init(r->pool);
msc_process_response_body(ctx->modsec_transaction);
ngx_http_modsecurity_pcre_malloc_done(old_pool);
/* XXX: I don't get how body from modsec being transferred to nginx's buffer. If so - after adjusting of nginx's
XXX: body we can proceed to adjust body size (content-length). see xslt_body_filter() for example */
ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r, 0);
if (ret > 0) {
return ret;
}
else if (ret < 0) {
return ngx_http_filter_finalize_request(r,
&ngx_http_modsecurity_module, NGX_HTTP_INTERNAL_SERVER_ERROR);
}
}
}
if (!is_request_processed)
{
dd("buffer was not fully loaded! ctx: %p", ctx);
}
/* XXX: xflt_filter() -- return NGX_OK here */
return ngx_http_next_body_filter(r, in);
}

View File

@ -0,0 +1,173 @@
/*
* ModSecurity connector for nginx, http://www.modsecurity.org/
* Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* If any of the files related to licensing are missing or if you have any
* other questions related to licensing please contact Trustwave Holdings, Inc.
* directly using the email address security@modsecurity.org.
*
*/
#ifndef _NGX_HTTP_MODSECURITY_COMMON_H_INCLUDED_
#define _NGX_HTTP_MODSECURITY_COMMON_H_INCLUDED_
#include <nginx.h>
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <modsecurity/modsecurity.h>
#include <modsecurity/transaction.h>
/* #define MSC_USE_RULES_SET 1 */
#if defined(MODSECURITY_CHECK_VERSION)
#if MODSECURITY_VERSION_NUM >= 304010
#define MSC_USE_RULES_SET 1
#endif
#endif
#if defined(MSC_USE_RULES_SET)
#include <modsecurity/rules_set.h>
#else
#include <modsecurity/rules.h>
#endif
/**
* TAG_NUM:
*
* Alpha - 001
* Beta - 002
* Dev - 010
* Rc1 - 051
* Rc2 - 052
* ... - ...
* Release- 100
*
*/
#define MODSECURITY_NGINX_MAJOR "1"
#define MODSECURITY_NGINX_MINOR "0"
#define MODSECURITY_NGINX_PATCHLEVEL "3"
#define MODSECURITY_NGINX_TAG ""
#define MODSECURITY_NGINX_TAG_NUM "100"
#define MODSECURITY_NGINX_VERSION MODSECURITY_NGINX_MAJOR "." \
MODSECURITY_NGINX_MINOR "." MODSECURITY_NGINX_PATCHLEVEL \
MODSECURITY_NGINX_TAG
#define MODSECURITY_NGINX_VERSION_NUM MODSECURITY_NGINX_MAJOR \
MODSECURITY_NGINX_MINOR MODSECURITY_NGINX_PATCHLEVEL \
MODSECURITY_NGINX_TAG_NUM
#define MODSECURITY_NGINX_WHOAMI "ModSecurity-nginx v" \
MODSECURITY_NGINX_VERSION
typedef struct {
ngx_str_t name;
ngx_str_t value;
} ngx_http_modsecurity_header_t;
typedef struct {
ngx_http_request_t *r;
Transaction *modsec_transaction;
ModSecurityIntervention *delayed_intervention;
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
/*
* Should be filled with the headers that were sent to ModSecurity.
*
* The idea is to compare this set of headers with the headers that were
* sent to the client. This check was placed because we don't have control
* over other modules, thus, we may partially inspect the headers.
*
*/
ngx_array_t *sanity_headers_out;
#endif
unsigned waiting_more_body:1;
unsigned body_requested:1;
unsigned processed:1;
unsigned logged:1;
unsigned intervention_triggered:1;
} ngx_http_modsecurity_ctx_t;
typedef struct {
void *pool;
ModSecurity *modsec;
ngx_uint_t rules_inline;
ngx_uint_t rules_file;
ngx_uint_t rules_remote;
} ngx_http_modsecurity_main_conf_t;
typedef struct {
void *pool;
/* RulesSet or Rules */
void *rules_set;
ngx_flag_t enable;
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ngx_flag_t sanity_checks_enabled;
#endif
ngx_http_complex_value_t *transaction_id;
} ngx_http_modsecurity_conf_t;
typedef ngx_int_t (*ngx_http_modsecurity_resolv_header_pt)(ngx_http_request_t *r, ngx_str_t name, off_t offset);
typedef struct {
ngx_str_t name;
ngx_uint_t offset;
ngx_http_modsecurity_resolv_header_pt resolver;
} ngx_http_modsecurity_header_out_t;
extern ngx_module_t ngx_http_modsecurity_module;
/* ngx_http_modsecurity_module.c */
int ngx_http_modsecurity_process_intervention (Transaction *transaction, ngx_http_request_t *r, ngx_int_t early_log);
ngx_http_modsecurity_ctx_t *ngx_http_modsecurity_create_ctx(ngx_http_request_t *r);
char *ngx_str_to_char(ngx_str_t a, ngx_pool_t *p);
#if (NGX_PCRE2)
#define ngx_http_modsecurity_pcre_malloc_init(x) NULL
#define ngx_http_modsecurity_pcre_malloc_done(x) (void)x
#else
ngx_pool_t *ngx_http_modsecurity_pcre_malloc_init(ngx_pool_t *pool);
void ngx_http_modsecurity_pcre_malloc_done(ngx_pool_t *old_pool);
#endif
/* ngx_http_modsecurity_body_filter.c */
ngx_int_t ngx_http_modsecurity_body_filter_init(void);
ngx_int_t ngx_http_modsecurity_body_filter(ngx_http_request_t *r, ngx_chain_t *in);
/* ngx_http_modsecurity_header_filter.c */
ngx_int_t ngx_http_modsecurity_header_filter_init(void);
ngx_int_t ngx_http_modsecurity_header_filter(ngx_http_request_t *r);
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
int ngx_http_modsecurity_store_ctx_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value);
#endif
/* ngx_http_modsecurity_log.c */
void ngx_http_modsecurity_log(void *log, const void* data);
ngx_int_t ngx_http_modsecurity_log_handler(ngx_http_request_t *r);
/* ngx_http_modsecurity_pre_access.c */
ngx_int_t ngx_http_modsecurity_pre_access_handler(ngx_http_request_t *r);
/* ngx_http_modsecurity_rewrite.c */
ngx_int_t ngx_http_modsecurity_rewrite_handler(ngx_http_request_t *r);
#endif /* _NGX_HTTP_MODSECURITY_COMMON_H_INCLUDED_ */

View File

@ -0,0 +1,558 @@
/*
* ModSecurity connector for nginx, http://www.modsecurity.org/
* Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* If any of the files related to licensing are missing or if you have any
* other questions related to licensing please contact Trustwave Holdings, Inc.
* directly using the email address security@modsecurity.org.
*
*/
#ifndef MODSECURITY_DDEBUG
#define MODSECURITY_DDEBUG 0
#endif
#include "ddebug.h"
#include "ngx_http_modsecurity_common.h"
static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_int_t ngx_http_modsecurity_resolv_header_server(ngx_http_request_t *r, ngx_str_t name, off_t offset);
static ngx_int_t ngx_http_modsecurity_resolv_header_date(ngx_http_request_t *r, ngx_str_t name, off_t offset);
static ngx_int_t ngx_http_modsecurity_resolv_header_content_length(ngx_http_request_t *r, ngx_str_t name, off_t offset);
static ngx_int_t ngx_http_modsecurity_resolv_header_content_type(ngx_http_request_t *r, ngx_str_t name, off_t offset);
static ngx_int_t ngx_http_modsecurity_resolv_header_last_modified(ngx_http_request_t *r, ngx_str_t name, off_t offset);
static ngx_int_t ngx_http_modsecurity_resolv_header_connection(ngx_http_request_t *r, ngx_str_t name, off_t offset);
static ngx_int_t ngx_http_modsecurity_resolv_header_transfer_encoding(ngx_http_request_t *r, ngx_str_t name, off_t offset);
static ngx_int_t ngx_http_modsecurity_resolv_header_vary(ngx_http_request_t *r, ngx_str_t name, off_t offset);
ngx_http_modsecurity_header_out_t ngx_http_modsecurity_headers_out[] = {
{ ngx_string("Server"),
offsetof(ngx_http_headers_out_t, server),
ngx_http_modsecurity_resolv_header_server },
{ ngx_string("Date"),
offsetof(ngx_http_headers_out_t, date),
ngx_http_modsecurity_resolv_header_date },
{ ngx_string("Content-Length"),
offsetof(ngx_http_headers_out_t, content_length_n),
ngx_http_modsecurity_resolv_header_content_length },
{ ngx_string("Content-Type"),
offsetof(ngx_http_headers_out_t, content_type),
ngx_http_modsecurity_resolv_header_content_type },
{ ngx_string("Last-Modified"),
offsetof(ngx_http_headers_out_t, last_modified),
ngx_http_modsecurity_resolv_header_last_modified },
{ ngx_string("Connection"),
0,
ngx_http_modsecurity_resolv_header_connection },
{ ngx_string("Transfer-Encoding"),
0,
ngx_http_modsecurity_resolv_header_transfer_encoding },
{ ngx_string("Vary"),
0,
ngx_http_modsecurity_resolv_header_vary },
#if 0
{ ngx_string("Content-Encoding"),
offsetof(ngx_http_headers_out_t, content_encoding),
NGX_TABLE },
{ ngx_string("Cache-Control"),
offsetof(ngx_http_headers_out_t, cache_control),
NGX_ARRAY },
{ ngx_string("Location"),
offsetof(ngx_http_headers_out_t, location),
NGX_TABLE },
{ ngx_string("Content-Range"),
offsetof(ngx_http_headers_out_t, content_range),
NGX_TABLE },
{ ngx_string("Accept-Ranges"),
offsetof(ngx_http_headers_out_t, accept_ranges),
NGX_TABLE },
returiders_out[i].name 1;
{ ngx_string("WWW-Authenticate"),
offsetof(ngx_http_headers_out_t, www_authenticate),
NGX_TABLE },
{ ngx_string("Expires"),
offsetof(ngx_http_headers_out_t, expires),
NGX_TABLE },
#endif
{ ngx_null_string, 0, 0 }
};
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
int
ngx_http_modsecurity_store_ctx_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value)
{
ngx_http_modsecurity_ctx_t *ctx;
ngx_http_modsecurity_conf_t *mcf;
ngx_http_modsecurity_header_t *hdr;
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
if (ctx == NULL || ctx->sanity_headers_out == NULL) {
return NGX_ERROR;
}
mcf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity_module);
if (mcf == NULL || mcf->sanity_checks_enabled == NGX_CONF_UNSET)
{
return NGX_OK;
}
hdr = ngx_array_push(ctx->sanity_headers_out);
if (hdr == NULL) {
return NGX_ERROR;
}
hdr->name.data = ngx_pnalloc(r->pool, name->len);
hdr->value.data = ngx_pnalloc(r->pool, value->len);
if (hdr->name.data == NULL || hdr->value.data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(hdr->name.data, name->data, name->len);
hdr->name.len = name->len;
ngx_memcpy(hdr->value.data, value->data, value->len);
hdr->value.len = value->len;
return NGX_OK;
}
#endif
static ngx_int_t
ngx_http_modsecurity_resolv_header_server(ngx_http_request_t *r, ngx_str_t name, off_t offset)
{
static char ngx_http_server_full_string[] = NGINX_VER;
static char ngx_http_server_string[] = "nginx";
ngx_http_core_loc_conf_t *clcf = NULL;
ngx_http_modsecurity_ctx_t *ctx = NULL;
ngx_str_t value;
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
if (r->headers_out.server == NULL) {
if (clcf->server_tokens) {
value.data = (u_char *)ngx_http_server_full_string;
value.len = sizeof(ngx_http_server_full_string);
} else {
value.data = (u_char *)ngx_http_server_string;
value.len = sizeof(ngx_http_server_string);
}
} else {
ngx_table_elt_t *h = r->headers_out.server;
value.data = h->value.data;
value.len = h->value.len;
}
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ngx_http_modsecurity_store_ctx_header(r, &name, &value);
#endif
return msc_add_n_response_header(ctx->modsec_transaction,
(const unsigned char *) name.data,
name.len,
(const unsigned char *) value.data,
value.len);
}
static ngx_int_t
ngx_http_modsecurity_resolv_header_date(ngx_http_request_t *r, ngx_str_t name, off_t offset)
{
ngx_http_modsecurity_ctx_t *ctx = NULL;
ngx_str_t date;
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
if (r->headers_out.date == NULL) {
date.data = ngx_cached_http_time.data;
date.len = ngx_cached_http_time.len;
} else {
ngx_table_elt_t *h = r->headers_out.date;
date.data = h->value.data;
date.len = h->value.len;
}
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ngx_http_modsecurity_store_ctx_header(r, &name, &date);
#endif
return msc_add_n_response_header(ctx->modsec_transaction,
(const unsigned char *) name.data,
name.len,
(const unsigned char *) date.data,
date.len);
}
static ngx_int_t
ngx_http_modsecurity_resolv_header_content_length(ngx_http_request_t *r, ngx_str_t name, off_t offset)
{
ngx_http_modsecurity_ctx_t *ctx = NULL;
ngx_str_t value;
char buf[NGX_INT64_LEN+2];
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
if (r->headers_out.content_length_n > 0)
{
ngx_sprintf((u_char *)buf, "%O%Z", r->headers_out.content_length_n);
value.data = (unsigned char *)buf;
value.len = strlen(buf);
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ngx_http_modsecurity_store_ctx_header(r, &name, &value);
#endif
return msc_add_n_response_header(ctx->modsec_transaction,
(const unsigned char *) name.data,
name.len,
(const unsigned char *) value.data,
value.len);
}
return 1;
}
static ngx_int_t
ngx_http_modsecurity_resolv_header_content_type(ngx_http_request_t *r, ngx_str_t name, off_t offset)
{
ngx_http_modsecurity_ctx_t *ctx = NULL;
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
if (r->headers_out.content_type.len > 0)
{
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ngx_http_modsecurity_store_ctx_header(r, &name, &r->headers_out.content_type);
#endif
return msc_add_n_response_header(ctx->modsec_transaction,
(const unsigned char *) name.data,
name.len,
(const unsigned char *) r->headers_out.content_type.data,
r->headers_out.content_type.len);
}
return 1;
}
static ngx_int_t
ngx_http_modsecurity_resolv_header_last_modified(ngx_http_request_t *r, ngx_str_t name, off_t offset)
{
ngx_http_modsecurity_ctx_t *ctx = NULL;
u_char buf[1024], *p;
ngx_str_t value;
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
if (r->headers_out.last_modified_time == -1) {
return 1;
}
p = ngx_http_time(buf, r->headers_out.last_modified_time);
value.data = buf;
value.len = (int)(p-buf);
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ngx_http_modsecurity_store_ctx_header(r, &name, &value);
#endif
return msc_add_n_response_header(ctx->modsec_transaction,
(const unsigned char *) name.data,
name.len,
(const unsigned char *) value.data,
value.len);
}
static ngx_int_t
ngx_http_modsecurity_resolv_header_connection(ngx_http_request_t *r, ngx_str_t name, off_t offset)
{
ngx_http_modsecurity_ctx_t *ctx = NULL;
ngx_http_core_loc_conf_t *clcf = NULL;
char *connection = NULL;
ngx_str_t value;
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
if (r->headers_out.status == NGX_HTTP_SWITCHING_PROTOCOLS) {
connection = "upgrade";
} else if (r->keepalive) {
connection = "keep-alive";
if (clcf->keepalive_header)
{
u_char buf[1024];
ngx_sprintf(buf, "timeout=%T%Z", clcf->keepalive_header);
ngx_str_t name2 = ngx_string("Keep-Alive");
value.data = buf;
value.len = strlen((char *)buf);
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ngx_http_modsecurity_store_ctx_header(r, &name2, &value);
#endif
msc_add_n_response_header(ctx->modsec_transaction,
(const unsigned char *) name2.data,
name2.len,
(const unsigned char *) value.data,
value.len);
}
} else {
connection = "close";
}
value.data = (u_char *) connection;
value.len = strlen(connection);
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ngx_http_modsecurity_store_ctx_header(r, &name, &value);
#endif
return msc_add_n_response_header(ctx->modsec_transaction,
(const unsigned char *) name.data,
name.len,
(const unsigned char *) value.data,
value.len);
}
static ngx_int_t
ngx_http_modsecurity_resolv_header_transfer_encoding(ngx_http_request_t *r, ngx_str_t name, off_t offset)
{
ngx_http_modsecurity_ctx_t *ctx = NULL;
if (r->chunked) {
ngx_str_t value = ngx_string("chunked");
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ngx_http_modsecurity_store_ctx_header(r, &name, &value);
#endif
return msc_add_n_response_header(ctx->modsec_transaction,
(const unsigned char *) name.data,
name.len,
(const unsigned char *) value.data,
value.len);
}
return 1;
}
static ngx_int_t
ngx_http_modsecurity_resolv_header_vary(ngx_http_request_t *r, ngx_str_t name, off_t offset)
{
#if (NGX_HTTP_GZIP)
ngx_http_modsecurity_ctx_t *ctx = NULL;
ngx_http_core_loc_conf_t *clcf = NULL;
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (r->gzip_vary && clcf->gzip_vary) {
ngx_str_t value = ngx_string("Accept-Encoding");
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ngx_http_modsecurity_store_ctx_header(r, &name, &value);
#endif
return msc_add_n_response_header(ctx->modsec_transaction,
(const unsigned char *) name.data,
name.len,
(const unsigned char *) value.data,
value.len);
}
#endif
return 1;
}
ngx_int_t
ngx_http_modsecurity_header_filter_init(void)
{
ngx_http_next_header_filter = ngx_http_top_header_filter;
ngx_http_top_header_filter = ngx_http_modsecurity_header_filter;
return NGX_OK;
}
ngx_int_t
ngx_http_modsecurity_header_filter(ngx_http_request_t *r)
{
ngx_http_modsecurity_ctx_t *ctx;
ngx_list_part_t *part = &r->headers_out.headers.part;
ngx_table_elt_t *data = part->elts;
ngx_uint_t i = 0;
int ret = 0;
ngx_uint_t status;
char *http_response_ver;
ngx_pool_t *old_pool;
/* XXX: if NOT_MODIFIED, do we need to process it at all? see xslt_header_filter() */
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
dd("header filter, recovering ctx: %p", ctx);
if (ctx == NULL)
{
dd("something really bad happened or ModSecurity is disabled. going to the next filter.");
return ngx_http_next_header_filter(r);
}
if (ctx->intervention_triggered) {
return ngx_http_next_header_filter(r);
}
/* XXX: can it happen ? already processed i mean */
/* XXX: check behaviour on 'ModSecurity off' */
if (ctx && ctx->processed)
{
/*
* FIXME: verify if this request is already processed.
*/
dd("Already processed... going to the next header...");
return ngx_http_next_header_filter(r);
}
/*
* Lets ask nginx to keep the response body in memory
*
* FIXME: I don't see a reason to keep it `1' when SecResponseBody is disabled.
*/
r->filter_need_in_memory = 1;
ctx->processed = 1;
/*
*
* Assuming ModSecurity module is running immediately before the
* ngx_http_header_filter, we will be able to populate ModSecurity with
* headers from the headers_out structure.
*
* As ngx_http_header_filter place a direct call to the
* ngx_http_write_filter_module, we cannot hook between those two. In order
* to enumerate all headers, we first look at the headers_out structure,
* and later we look into the ngx_list_part_t. The ngx_list_part_t must be
* checked. Other module(s) in the chain may added some content to it.
*
*/
for (i = 0; ngx_http_modsecurity_headers_out[i].name.len; i++)
{
dd(" Sending header to ModSecurity - header: `%.*s'.",
(int) ngx_http_modsecurity_headers_out[i].name.len,
ngx_http_modsecurity_headers_out[i].name.data);
ngx_http_modsecurity_headers_out[i].resolver(r,
ngx_http_modsecurity_headers_out[i].name,
ngx_http_modsecurity_headers_out[i].offset);
}
for (i = 0 ;; i++)
{
if (i >= part->nelts)
{
if (part->next == NULL) {
break;
}
part = part->next;
data = part->elts;
i = 0;
}
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ngx_http_modsecurity_store_ctx_header(r, &data[i].key, &data[i].value);
#endif
/*
* Doing this ugly cast here, explanation on the request_header
*/
msc_add_n_response_header(ctx->modsec_transaction,
(const unsigned char *) data[i].key.data,
data[i].key.len,
(const unsigned char *) data[i].value.data,
data[i].value.len);
}
/* prepare extra paramters for msc_process_response_headers() */
if (r->err_status) {
status = r->err_status;
} else {
status = r->headers_out.status;
}
/*
* NGINX always sends HTTP response with HTTP/1.1, except cases when
* HTTP V2 module is enabled, and request has been posted with HTTP/2.0.
*/
http_response_ver = "HTTP 1.1";
#if (NGX_HTTP_V2)
if (r->stream) {
http_response_ver = "HTTP 2.0";
}
#endif
old_pool = ngx_http_modsecurity_pcre_malloc_init(r->pool);
msc_process_response_headers(ctx->modsec_transaction, status, http_response_ver);
ngx_http_modsecurity_pcre_malloc_done(old_pool);
ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r, 0);
if (r->error_page) {
return ngx_http_next_header_filter(r);
}
if (ret > 0) {
return ngx_http_filter_finalize_request(r, &ngx_http_modsecurity_module, ret);
}
/*
* Proxies will not like this... but it is necessary to unset
* the content length in order to manipulate the content of
* response body in ModSecurity.
*
* This header may arrive at the client before ModSecurity had
* a change to make any modification. That is why it is necessary
* to set this to -1 here.
*
* We need to have some kind of flag the decide if ModSecurity
* will make a modification or not. If not, keep the content and
* make the proxy servers happy.
*
*/
/*
* The line below is commented to make the spdy test to work
*/
//r->headers_out.content_length_n = -1;
return ngx_http_next_header_filter(r);
}

View File

@ -0,0 +1,81 @@
/*
* ModSecurity connector for nginx, http://www.modsecurity.org/
* Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* If any of the files related to licensing are missing or if you have any
* other questions related to licensing please contact Trustwave Holdings, Inc.
* directly using the email address security@modsecurity.org.
*
*/
#ifndef MODSECURITY_DDEBUG
#define MODSECURITY_DDEBUG 0
#endif
#include "ddebug.h"
#include "ngx_http_modsecurity_common.h"
void
ngx_http_modsecurity_log(void *log, const void* data)
{
const char *msg;
if (log == NULL) {
return;
}
msg = (const char *) data;
ngx_log_error(NGX_LOG_INFO, (ngx_log_t *)log, 0, "%s", msg);
}
ngx_int_t
ngx_http_modsecurity_log_handler(ngx_http_request_t *r)
{
ngx_pool_t *old_pool;
ngx_http_modsecurity_ctx_t *ctx;
ngx_http_modsecurity_conf_t *mcf;
dd("catching a new _log_ phase handler");
mcf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity_module);
if (mcf == NULL || mcf->enable != 1)
{
dd("ModSecurity not enabled... returning");
return NGX_OK;
}
/*
if (r->method != NGX_HTTP_GET &&
r->method != NGX_HTTP_POST && r->method != NGX_HTTP_HEAD) {
dd("ModSecurity is not ready to deal with anything different from " \
"POST, GET or HEAD");
return NGX_OK;
}
*/
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
dd("recovering ctx: %p", ctx);
if (ctx == NULL) {
dd("something really bad happened here. returning NGX_ERROR");
return NGX_ERROR;
}
if (ctx->logged) {
dd("already logged earlier");
return NGX_OK;
}
dd("calling msc_process_logging for %p", ctx);
old_pool = ngx_http_modsecurity_pcre_malloc_init(r->pool);
msc_process_logging(ctx->modsec_transaction);
ngx_http_modsecurity_pcre_malloc_done(old_pool);
return NGX_OK;
}

View File

@ -0,0 +1,793 @@
/*
* ModSecurity connector for nginx, http://www.modsecurity.org/
* Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* If any of the files related to licensing are missing or if you have any
* other questions related to licensing please contact Trustwave Holdings, Inc.
* directly using the email address security@modsecurity.org.
*
*/
#ifndef MODSECURITY_DDEBUG
#define MODSECURITY_DDEBUG 0
#endif
#include "ddebug.h"
#include "ngx_http_modsecurity_common.h"
#include "stdio.h"
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static ngx_int_t ngx_http_modsecurity_init(ngx_conf_t *cf);
static void *ngx_http_modsecurity_create_main_conf(ngx_conf_t *cf);
static char *ngx_http_modsecurity_init_main_conf(ngx_conf_t *cf, void *conf);
static void *ngx_http_modsecurity_create_conf(ngx_conf_t *cf);
static char *ngx_http_modsecurity_merge_conf(ngx_conf_t *cf, void *parent, void *child);
static void ngx_http_modsecurity_cleanup_instance(void *data);
static void ngx_http_modsecurity_cleanup_rules(void *data);
/*
* PCRE malloc/free workaround, based on
* https://github.com/openresty/lua-nginx-module/blob/master/src/ngx_http_lua_pcrefix.c
*/
#if !(NGX_PCRE2)
static void *(*old_pcre_malloc)(size_t);
static void (*old_pcre_free)(void *ptr);
static ngx_pool_t *ngx_http_modsec_pcre_pool = NULL;
static void *
ngx_http_modsec_pcre_malloc(size_t size)
{
if (ngx_http_modsec_pcre_pool) {
return ngx_palloc(ngx_http_modsec_pcre_pool, size);
}
fprintf(stderr, "error: modsec pcre malloc failed due to empty pcre pool");
return NULL;
}
static void
ngx_http_modsec_pcre_free(void *ptr)
{
if (ngx_http_modsec_pcre_pool) {
ngx_pfree(ngx_http_modsec_pcre_pool, ptr);
return;
}
#if 0
/* this may happen when called from cleanup handlers */
fprintf(stderr, "error: modsec pcre free failed due to empty pcre pool");
#endif
return;
}
ngx_pool_t *
ngx_http_modsecurity_pcre_malloc_init(ngx_pool_t *pool)
{
ngx_pool_t *old_pool;
if (pcre_malloc != ngx_http_modsec_pcre_malloc) {
ngx_http_modsec_pcre_pool = pool;
old_pcre_malloc = pcre_malloc;
old_pcre_free = pcre_free;
pcre_malloc = ngx_http_modsec_pcre_malloc;
pcre_free = ngx_http_modsec_pcre_free;
return NULL;
}
old_pool = ngx_http_modsec_pcre_pool;
ngx_http_modsec_pcre_pool = pool;
return old_pool;
}
void
ngx_http_modsecurity_pcre_malloc_done(ngx_pool_t *old_pool)
{
ngx_http_modsec_pcre_pool = old_pool;
if (old_pool == NULL) {
pcre_malloc = old_pcre_malloc;
pcre_free = old_pcre_free;
}
}
#endif
/*
* ngx_string's are not null-terminated in common case, so we need to convert
* them into null-terminated ones before passing to ModSecurity
*/
ngx_inline char *ngx_str_to_char(ngx_str_t a, ngx_pool_t *p)
{
char *str = NULL;
if (a.len == 0) {
return NULL;
}
str = ngx_pnalloc(p, a.len+1);
if (str == NULL) {
dd("failed to allocate memory to convert space ngx_string to C string");
/* We already returned NULL for an empty string, so return -1 here to indicate allocation error */
return (char *)-1;
}
ngx_memcpy(str, a.data, a.len);
str[a.len] = '\0';
return str;
}
ngx_inline int
ngx_http_modsecurity_process_intervention (Transaction *transaction, ngx_http_request_t *r, ngx_int_t early_log)
{
char *log = NULL;
ModSecurityIntervention intervention;
intervention.status = 200;
intervention.url = NULL;
intervention.log = NULL;
intervention.disruptive = 0;
ngx_http_modsecurity_ctx_t *ctx = NULL;
dd("processing intervention");
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
if (ctx == NULL)
{
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (msc_intervention(transaction, &intervention) == 0) {
dd("nothing to do");
return 0;
}
log = intervention.log;
if (intervention.log == NULL) {
log = "(no log message was specified)";
}
ngx_log_error(NGX_LOG_ERR, (ngx_log_t *)r->connection->log, 0, "%s", log);
if (intervention.log != NULL) {
free(intervention.log);
}
if (intervention.url != NULL)
{
dd("intervention -- redirecting to: %s with status code: %d", intervention.url, intervention.status);
if (r->header_sent)
{
dd("Headers are already sent. Cannot perform the redirection at this point.");
return -1;
}
/**
* Not sure if it sane to do this indepent of the phase
* but, here we go...
*
* This code cames from: http/ngx_http_special_response.c
* function: ngx_http_send_error_page
* src/http/ngx_http_core_module.c
* From src/http/ngx_http_core_module.c (line 1910) i learnt
* that location->hash should be set to 1.
*
*/
ngx_http_clear_location(r);
ngx_str_t a = ngx_string("");
a.data = (unsigned char *)intervention.url;
a.len = strlen(intervention.url);
ngx_table_elt_t *location = NULL;
location = ngx_list_push(&r->headers_out.headers);
ngx_str_set(&location->key, "Location");
location->value = a;
r->headers_out.location = location;
r->headers_out.location->hash = 1;
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ngx_http_modsecurity_store_ctx_header(r, &location->key, &location->value);
#endif
return intervention.status;
}
if (intervention.status != 200)
{
/**
* FIXME: this will bring proper response code to audit log in case
* when e.g. error_page redirect was triggered, but there still won't be another
* required pieces like response headers etc.
*
*/
msc_update_status_code(ctx->modsec_transaction, intervention.status);
if (early_log) {
dd("intervention -- calling log handler manually with code: %d", intervention.status);
ngx_http_modsecurity_log_handler(r);
ctx->logged = 1;
}
if (r->header_sent)
{
dd("Headers are already sent. Cannot perform the redirection at this point.");
return -1;
}
dd("intervention -- returning code: %d", intervention.status);
return intervention.status;
}
return 0;
}
void
ngx_http_modsecurity_cleanup(void *data)
{
ngx_http_modsecurity_ctx_t *ctx;
ctx = (ngx_http_modsecurity_ctx_t *) data;
msc_transaction_cleanup(ctx->modsec_transaction);
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
/*
* Purge stored context headers. Memory allocated for individual stored header
* name/value pair will be freed automatically when r->pool is destroyed.
*/
ngx_array_destroy(ctx->sanity_headers_out);
#endif
}
ngx_inline ngx_http_modsecurity_ctx_t *
ngx_http_modsecurity_create_ctx(ngx_http_request_t *r)
{
ngx_str_t s;
ngx_pool_cleanup_t *cln;
ngx_http_modsecurity_ctx_t *ctx;
ngx_http_modsecurity_conf_t *mcf;
ngx_http_modsecurity_main_conf_t *mmcf;
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_modsecurity_ctx_t));
if (ctx == NULL)
{
dd("failed to allocate memory for the context.");
return NULL;
}
mmcf = ngx_http_get_module_main_conf(r, ngx_http_modsecurity_module);
mcf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity_module);
dd("creating transaction with the following rules: '%p' -- ms: '%p'", mcf->rules_set, mmcf->modsec);
if (mcf->transaction_id) {
if (ngx_http_complex_value(r, mcf->transaction_id, &s) != NGX_OK) {
return NGX_CONF_ERROR;
}
ctx->modsec_transaction = msc_new_transaction_with_id(mmcf->modsec, mcf->rules_set, (char *) s.data, r->connection->log);
} else {
ctx->modsec_transaction = msc_new_transaction(mmcf->modsec, mcf->rules_set, r->connection->log);
}
dd("transaction created");
ngx_http_set_ctx(r, ctx, ngx_http_modsecurity_module);
cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_http_modsecurity_ctx_t));
if (cln == NULL)
{
dd("failed to create the ModSecurity context cleanup");
return NGX_CONF_ERROR;
}
cln->handler = ngx_http_modsecurity_cleanup;
cln->data = ctx;
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ctx->sanity_headers_out = ngx_array_create(r->pool, 12, sizeof(ngx_http_modsecurity_header_t));
if (ctx->sanity_headers_out == NULL) {
return NGX_CONF_ERROR;
}
#endif
return ctx;
}
char *
ngx_conf_set_rules(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
int res;
char *rules;
ngx_str_t *value;
const char *error;
ngx_pool_t *old_pool;
ngx_http_modsecurity_conf_t *mcf = conf;
ngx_http_modsecurity_main_conf_t *mmcf;
value = cf->args->elts;
rules = ngx_str_to_char(value[1], cf->pool);
if (rules == (char *)-1) {
return NGX_CONF_ERROR;
}
old_pool = ngx_http_modsecurity_pcre_malloc_init(cf->pool);
res = msc_rules_add(mcf->rules_set, rules, &error);
ngx_http_modsecurity_pcre_malloc_done(old_pool);
if (res < 0) {
dd("Failed to load the rules: '%s' - reason: '%s'", rules, error);
return strdup(error);
}
mmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_modsecurity_module);
mmcf->rules_inline += res;
return NGX_CONF_OK;
}
char *
ngx_conf_set_rules_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
int res;
char *rules_set;
ngx_str_t *value;
const char *error;
ngx_pool_t *old_pool;
ngx_http_modsecurity_conf_t *mcf = conf;
ngx_http_modsecurity_main_conf_t *mmcf;
value = cf->args->elts;
rules_set = ngx_str_to_char(value[1], cf->pool);
if (rules_set == (char *)-1) {
return NGX_CONF_ERROR;
}
old_pool = ngx_http_modsecurity_pcre_malloc_init(cf->pool);
res = msc_rules_add_file(mcf->rules_set, rules_set, &error);
ngx_http_modsecurity_pcre_malloc_done(old_pool);
if (res < 0) {
dd("Failed to load the rules from: '%s' - reason: '%s'", rules_set, error);
return strdup(error);
}
mmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_modsecurity_module);
mmcf->rules_file += res;
return NGX_CONF_OK;
}
char *
ngx_conf_set_rules_remote(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
int res;
ngx_str_t *value;
const char *error;
const char *rules_remote_key, *rules_remote_server;
ngx_pool_t *old_pool;
ngx_http_modsecurity_conf_t *mcf = conf;
ngx_http_modsecurity_main_conf_t *mmcf;
value = cf->args->elts;
rules_remote_key = ngx_str_to_char(value[1], cf->pool);
rules_remote_server = ngx_str_to_char(value[2], cf->pool);
if (rules_remote_server == (char *)-1) {
return NGX_CONF_ERROR;
}
if (rules_remote_key == (char *)-1) {
return NGX_CONF_ERROR;
}
old_pool = ngx_http_modsecurity_pcre_malloc_init(cf->pool);
res = msc_rules_add_remote(mcf->rules_set, rules_remote_key, rules_remote_server, &error);
ngx_http_modsecurity_pcre_malloc_done(old_pool);
if (res < 0) {
dd("Failed to load the rules from: '%s' - reason: '%s'", rules_remote_server, error);
return strdup(error);
}
mmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_modsecurity_module);
mmcf->rules_remote += res;
return NGX_CONF_OK;
}
char *ngx_conf_set_transaction_id(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
ngx_str_t *value;
ngx_http_complex_value_t cv;
ngx_http_compile_complex_value_t ccv;
ngx_http_modsecurity_conf_t *mcf = conf;
value = cf->args->elts;
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &value[1];
ccv.complex_value = &cv;
ccv.zero = 1;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}
mcf->transaction_id = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
if (mcf->transaction_id == NULL) {
return NGX_CONF_ERROR;
}
*mcf->transaction_id = cv;
return NGX_CONF_OK;
}
static ngx_command_t ngx_http_modsecurity_commands[] = {
{
ngx_string("modsecurity"),
NGX_HTTP_LOC_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_MAIN_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_modsecurity_conf_t, enable),
NULL
},
{
ngx_string("modsecurity_rules"),
NGX_HTTP_LOC_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
ngx_conf_set_rules,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_modsecurity_conf_t, enable),
NULL
},
{
ngx_string("modsecurity_rules_file"),
NGX_HTTP_LOC_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
ngx_conf_set_rules_file,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_modsecurity_conf_t, enable),
NULL
},
{
ngx_string("modsecurity_rules_remote"),
NGX_HTTP_LOC_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE2,
ngx_conf_set_rules_remote,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_modsecurity_conf_t, enable),
NULL
},
{
ngx_string("modsecurity_transaction_id"),
NGX_HTTP_LOC_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_MAIN_CONF|NGX_CONF_1MORE,
ngx_conf_set_transaction_id,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
static ngx_http_module_t ngx_http_modsecurity_ctx = {
NULL, /* preconfiguration */
ngx_http_modsecurity_init, /* postconfiguration */
ngx_http_modsecurity_create_main_conf, /* create main configuration */
ngx_http_modsecurity_init_main_conf, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_modsecurity_create_conf, /* create location configuration */
ngx_http_modsecurity_merge_conf /* merge location configuration */
};
ngx_module_t ngx_http_modsecurity_module = {
NGX_MODULE_V1,
&ngx_http_modsecurity_ctx, /* module context */
ngx_http_modsecurity_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
static ngx_int_t
ngx_http_modsecurity_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h_rewrite;
ngx_http_handler_pt *h_preaccess;
ngx_http_handler_pt *h_log;
ngx_http_core_main_conf_t *cmcf;
int rc = 0;
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
if (cmcf == NULL)
{
dd("We are not sure how this returns, NGINX doesn't seem to think it will ever be null");
return NGX_ERROR;
}
/**
*
* Seems like we cannot do this very same thing with
* NGX_HTTP_FIND_CONFIG_PHASE. it does not seems to
* be an array. Our next option is the REWRITE.
*
* TODO: check if we can hook prior to NGX_HTTP_REWRITE_PHASE phase.
*
*/
h_rewrite = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);
if (h_rewrite == NULL)
{
dd("Not able to create a new NGX_HTTP_REWRITE_PHASE handle");
return NGX_ERROR;
}
*h_rewrite = ngx_http_modsecurity_rewrite_handler;
/**
*
* Processing the request body on the preaccess phase.
*
* TODO: check if hook into separated phases is the best thing to do.
*
*/
h_preaccess = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
if (h_preaccess == NULL)
{
dd("Not able to create a new NGX_HTTP_PREACCESS_PHASE handle");
return NGX_ERROR;
}
*h_preaccess = ngx_http_modsecurity_pre_access_handler;
/**
* Process the log phase.
*
* TODO: check if the log phase happens like it happens on Apache.
* check if last phase will not hold the request.
*
*/
h_log = ngx_array_push(&cmcf->phases[NGX_HTTP_LOG_PHASE].handlers);
if (h_log == NULL)
{
dd("Not able to create a new NGX_HTTP_LOG_PHASE handle");
return NGX_ERROR;
}
*h_log = ngx_http_modsecurity_log_handler;
rc = ngx_http_modsecurity_header_filter_init();
if (rc != NGX_OK) {
return rc;
}
rc = ngx_http_modsecurity_body_filter_init();
if (rc != NGX_OK) {
return rc;
}
return NGX_OK;
}
static void *
ngx_http_modsecurity_create_main_conf(ngx_conf_t *cf)
{
ngx_pool_cleanup_t *cln;
ngx_http_modsecurity_main_conf_t *conf;
conf = (ngx_http_modsecurity_main_conf_t *) ngx_pcalloc(cf->pool,
sizeof(ngx_http_modsecurity_main_conf_t));
if (conf == NULL)
{
return NGX_CONF_ERROR;
}
/*
* set by ngx_pcalloc():
*
* conf->modsec = NULL;
* conf->pool = NULL;
* conf->rules_inline = 0;
* conf->rules_file = 0;
* conf->rules_remote = 0;
*/
cln = ngx_pool_cleanup_add(cf->pool, 0);
if (cln == NULL) {
return NGX_CONF_ERROR;
}
cln->handler = ngx_http_modsecurity_cleanup_instance;
cln->data = conf;
conf->pool = cf->pool;
/* Create our ModSecurity instance */
conf->modsec = msc_init();
if (conf->modsec == NULL)
{
dd("failed to create the ModSecurity instance");
return NGX_CONF_ERROR;
}
/* Provide our connector information to LibModSecurity */
msc_set_connector_info(conf->modsec, MODSECURITY_NGINX_WHOAMI);
msc_set_log_cb(conf->modsec, ngx_http_modsecurity_log);
dd ("main conf created at: '%p', instance is: '%p'", conf, conf->modsec);
return conf;
}
static char *
ngx_http_modsecurity_init_main_conf(ngx_conf_t *cf, void *conf)
{
ngx_http_modsecurity_main_conf_t *mmcf;
mmcf = (ngx_http_modsecurity_main_conf_t *) conf;
ngx_log_error(NGX_LOG_NOTICE, cf->log, 0,
"%s (rules loaded inline/local/remote: %ui/%ui/%ui)",
MODSECURITY_NGINX_WHOAMI, mmcf->rules_inline,
mmcf->rules_file, mmcf->rules_remote);
return NGX_CONF_OK;
}
static void *
ngx_http_modsecurity_create_conf(ngx_conf_t *cf)
{
ngx_pool_cleanup_t *cln;
ngx_http_modsecurity_conf_t *conf;
conf = (ngx_http_modsecurity_conf_t *) ngx_pcalloc(cf->pool,
sizeof(ngx_http_modsecurity_conf_t));
if (conf == NULL)
{
dd("Failed to allocate space for ModSecurity configuration");
return NGX_CONF_ERROR;
}
/*
* set by ngx_pcalloc():
*
* conf->enable = 0;
* conf->sanity_checks_enabled = 0;
* conf->rules_set = NULL;
* conf->pool = NULL;
* conf->transaction_id = NULL;
*/
conf->enable = NGX_CONF_UNSET;
conf->rules_set = msc_create_rules_set();
conf->pool = cf->pool;
conf->transaction_id = NGX_CONF_UNSET_PTR;
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
conf->sanity_checks_enabled = NGX_CONF_UNSET;
#endif
cln = ngx_pool_cleanup_add(cf->pool, 0);
if (cln == NULL) {
dd("failed to create the ModSecurity configuration cleanup");
return NGX_CONF_ERROR;
}
cln->handler = ngx_http_modsecurity_cleanup_rules;
cln->data = conf;
dd ("conf created at: '%p'", conf);
return conf;
}
static char *
ngx_http_modsecurity_merge_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_modsecurity_conf_t *p = parent;
ngx_http_modsecurity_conf_t *c = child;
#if defined(MODSECURITY_DDEBUG) && (MODSECURITY_DDEBUG)
ngx_http_core_loc_conf_t *clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
#endif
int rules;
const char *error = NULL;
dd("merging loc config [%s] - parent: '%p' child: '%p'",
ngx_str_to_char(clcf->name, cf->pool), parent,
child);
dd(" state - parent: '%d' child: '%d'",
(int) c->enable, (int) p->enable);
ngx_conf_merge_value(c->enable, p->enable, 0);
ngx_conf_merge_ptr_value(c->transaction_id, p->transaction_id, NULL);
#if defined(MODSECURITY_SANITY_CHECKS) && (MODSECURITY_SANITY_CHECKS)
ngx_conf_merge_value(c->sanity_checks_enabled, p->sanity_checks_enabled, 0);
#endif
#if defined(MODSECURITY_DDEBUG) && (MODSECURITY_DDEBUG)
dd("PARENT RULES");
msc_rules_dump(p->rules_set);
dd("CHILD RULES");
msc_rules_dump(c->rules_set);
#endif
rules = msc_rules_merge(c->rules_set, p->rules_set, &error);
if (rules < 0) {
return strdup(error);
}
#if defined(MODSECURITY_DDEBUG) && (MODSECURITY_DDEBUG)
dd("NEW CHILD RULES");
msc_rules_dump(c->rules_set);
#endif
return NGX_CONF_OK;
}
static void
ngx_http_modsecurity_cleanup_instance(void *data)
{
ngx_pool_t *old_pool;
ngx_http_modsecurity_main_conf_t *mmcf;
mmcf = (ngx_http_modsecurity_main_conf_t *) data;
dd("deleting a main conf -- instance is: \"%p\"", mmcf->modsec);
old_pool = ngx_http_modsecurity_pcre_malloc_init(mmcf->pool);
msc_cleanup(mmcf->modsec);
ngx_http_modsecurity_pcre_malloc_done(old_pool);
}
static void
ngx_http_modsecurity_cleanup_rules(void *data)
{
ngx_pool_t *old_pool;
ngx_http_modsecurity_conf_t *mcf;
mcf = (ngx_http_modsecurity_conf_t *) data;
dd("deleting a loc conf -- RuleSet is: \"%p\"", mcf->rules_set);
old_pool = ngx_http_modsecurity_pcre_malloc_init(mcf->pool);
msc_rules_cleanup(mcf->rules_set);
ngx_http_modsecurity_pcre_malloc_done(old_pool);
}
/* vi:set ft=c ts=4 sw=4 et fdm=marker: */

View File

@ -0,0 +1,228 @@
/*
* ModSecurity connector for nginx, http://www.modsecurity.org/
* Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* If any of the files related to licensing are missing or if you have any
* other questions related to licensing please contact Trustwave Holdings, Inc.
* directly using the email address security@modsecurity.org.
*
*/
#ifndef MODSECURITY_DDEBUG
#define MODSECURITY_DDEBUG 0
#endif
#include "ddebug.h"
#include "ngx_http_modsecurity_common.h"
void
ngx_http_modsecurity_request_read(ngx_http_request_t *r)
{
ngx_http_modsecurity_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
#if defined(nginx_version) && nginx_version >= 8011
r->main->count--;
#endif
if (ctx->waiting_more_body)
{
ctx->waiting_more_body = 0;
r->write_event_handler = ngx_http_core_run_phases;
ngx_http_core_run_phases(r);
}
}
ngx_int_t
ngx_http_modsecurity_pre_access_handler(ngx_http_request_t *r)
{
#if 1
ngx_pool_t *old_pool;
ngx_http_modsecurity_ctx_t *ctx;
ngx_http_modsecurity_conf_t *mcf;
dd("catching a new _preaccess_ phase handler");
mcf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity_module);
if (mcf == NULL || mcf->enable != 1)
{
dd("ModSecurity not enabled... returning");
return NGX_DECLINED;
}
/*
* FIXME:
* In order to perform some tests, let's accept everything.
*
if (r->method != NGX_HTTP_GET &&
r->method != NGX_HTTP_POST && r->method != NGX_HTTP_HEAD) {
dd("ModSecurity is not ready to deal with anything different from " \
"POST, GET or HEAD");
return NGX_DECLINED;
}
*/
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
dd("recovering ctx: %p", ctx);
if (ctx == NULL)
{
dd("ctx is null; Nothing we can do, returning an error.");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (ctx->intervention_triggered) {
return NGX_DECLINED;
}
if (ctx->waiting_more_body == 1)
{
dd("waiting for more data before proceed. / count: %d",
r->main->count);
return NGX_DONE;
}
if (ctx->body_requested == 0)
{
ngx_int_t rc = NGX_OK;
ctx->body_requested = 1;
dd("asking for the request body, if any. Count: %d",
r->main->count);
/**
* TODO: Check if there is any benefit to use request_body_in_single_buf set to 1.
*
* saw some module using this request_body_in_single_buf
* but not sure what exactly it does, same for the others options below.
*
* r->request_body_in_single_buf = 1;
*/
r->request_body_in_single_buf = 1;
r->request_body_in_persistent_file = 1;
if (!r->request_body_in_file_only) {
// If the above condition fails, then the flag below will have been
// set correctly elsewhere. We need to set the flag here for other
// conditions (client_body_in_file_only not used but
// client_body_buffer_size is)
r->request_body_in_clean_file = 1;
}
rc = ngx_http_read_client_request_body(r,
ngx_http_modsecurity_request_read);
if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) {
#if (nginx_version < 1002006) || \
(nginx_version >= 1003000 && nginx_version < 1003009)
r->main->count--;
#endif
return rc;
}
if (rc == NGX_AGAIN)
{
dd("nginx is asking us to wait for more data.");
ctx->waiting_more_body = 1;
return NGX_DONE;
}
}
if (ctx->waiting_more_body == 0)
{
int ret = 0;
int already_inspected = 0;
dd("request body is ready to be processed");
r->write_event_handler = ngx_http_core_run_phases;
ngx_chain_t *chain = r->request_body->bufs;
/**
* TODO: Speed up the analysis by sending chunk while they arrive.
*
* Notice that we are waiting for the full request body to
* start to process it, it may not be necessary. We may send
* the chunks to ModSecurity while nginx keep calling this
* function.
*/
if (r->request_body->temp_file != NULL) {
ngx_str_t file_path = r->request_body->temp_file->file.name;
const char *file_name = ngx_str_to_char(file_path, r->pool);
if (file_name == (char*)-1) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/*
* Request body was saved to a file, probably we don't have a
* copy of it in memory.
*/
dd("request body inspection: file -- %s", file_name);
msc_request_body_from_file(ctx->modsec_transaction, file_name);
already_inspected = 1;
} else {
dd("inspection request body in memory.");
}
while (chain && !already_inspected)
{
u_char *data = chain->buf->pos;
msc_append_request_body(ctx->modsec_transaction, data,
chain->buf->last - data);
if (chain->buf->last_buf) {
break;
}
chain = chain->next;
/* XXX: chains are processed one-by-one, maybe worth to pass all chains and then call intervention() ? */
/**
* ModSecurity may perform stream inspection on this buffer,
* it may ask for a intervention in consequence of that.
*
*/
ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r, 0);
if (ret > 0) {
return ret;
}
}
/**
* At this point, all the request body was sent to ModSecurity
* and we want to make sure that all the request body inspection
* happened; consequently we have to check if ModSecurity have
* returned any kind of intervention.
*/
/* XXX: once more -- is body can be modified ? content-length need to be adjusted ? */
old_pool = ngx_http_modsecurity_pcre_malloc_init(r->pool);
msc_process_request_body(ctx->modsec_transaction);
ngx_http_modsecurity_pcre_malloc_done(old_pool);
ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r, 0);
if (r->error_page) {
return NGX_DECLINED;
}
if (ret > 0) {
return ret;
}
}
dd("Nothing to add on the body inspection, reclaiming a NGX_DECLINED");
#endif
return NGX_DECLINED;
}

View File

@ -0,0 +1,228 @@
/*
* ModSecurity connector for nginx, http://www.modsecurity.org/
* Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* If any of the files related to licensing are missing or if you have any
* other questions related to licensing please contact Trustwave Holdings, Inc.
* directly using the email address security@modsecurity.org.
*
*/
#ifndef MODSECURITY_DDEBUG
#define MODSECURITY_DDEBUG 0
#endif
#include "ddebug.h"
#include "ngx_http_modsecurity_common.h"
ngx_int_t
ngx_http_modsecurity_rewrite_handler(ngx_http_request_t *r)
{
ngx_pool_t *old_pool;
ngx_http_modsecurity_ctx_t *ctx;
ngx_http_modsecurity_conf_t *mcf;
mcf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity_module);
if (mcf == NULL || mcf->enable != 1) {
dd("ModSecurity not enabled... returning");
return NGX_DECLINED;
}
/*
if (r->method != NGX_HTTP_GET &&
r->method != NGX_HTTP_POST && r->method != NGX_HTTP_HEAD) {
dd("ModSecurity is not ready to deal with anything different from " \
"POST, GET or HEAD");
return NGX_DECLINED;
}
*/
dd("catching a new _rewrite_ phase handler");
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity_module);
dd("recovering ctx: %p", ctx);
if (ctx == NULL)
{
int ret = 0;
ngx_connection_t *connection = r->connection;
/**
* FIXME: We may want to use struct sockaddr instead of addr_text.
*
*/
ngx_str_t addr_text = connection->addr_text;
ctx = ngx_http_modsecurity_create_ctx(r);
dd("ctx was NULL, creating new context: %p", ctx);
if (ctx == NULL) {
dd("ctx still null; Nothing we can do, returning an error.");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/**
* FIXME: Check if it is possible to hook on nginx on a earlier phase.
*
* At this point we are doing an late connection process. Maybe
* we have to hook into NGX_HTTP_FIND_CONFIG_PHASE, it seems to be the
* erliest phase that nginx allow us to attach those kind of hooks.
*
*/
int client_port = ngx_inet_get_port(connection->sockaddr);
int server_port = ngx_inet_get_port(connection->local_sockaddr);
const char *client_addr = ngx_str_to_char(addr_text, r->pool);
if (client_addr == (char*)-1) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_str_t s;
u_char addr[NGX_SOCKADDR_STRLEN];
s.len = NGX_SOCKADDR_STRLEN;
s.data = addr;
if (ngx_connection_local_sockaddr(r->connection, &s, 0) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
const char *server_addr = ngx_str_to_char(s, r->pool);
if (server_addr == (char*)-1) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
old_pool = ngx_http_modsecurity_pcre_malloc_init(r->pool);
ret = msc_process_connection(ctx->modsec_transaction,
client_addr, client_port,
server_addr, server_port);
ngx_http_modsecurity_pcre_malloc_done(old_pool);
if (ret != 1){
dd("Was not able to extract connection information.");
}
/**
*
* FIXME: Check how we can finalize a request without crash nginx.
*
* I don't think nginx is expecting to finalize a request at that
* point as it seems that it clean the ngx_http_request_t information
* and try to use it later.
*
*/
dd("Processing intervention with the connection information filled in");
ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r, 1);
if (ret > 0) {
ctx->intervention_triggered = 1;
return ret;
}
const char *http_version;
switch (r->http_version) {
case NGX_HTTP_VERSION_9 :
http_version = "0.9";
break;
case NGX_HTTP_VERSION_10 :
http_version = "1.0";
break;
case NGX_HTTP_VERSION_11 :
http_version = "1.1";
break;
#if defined(nginx_version) && nginx_version >= 1009005
case NGX_HTTP_VERSION_20 :
http_version = "2.0";
break;
#endif
default :
http_version = ngx_str_to_char(r->http_protocol, r->pool);
if (http_version == (char*)-1) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if ((http_version != NULL) && (strlen(http_version) > 5) && (!strncmp("HTTP/", http_version, 5))) {
http_version += 5;
} else {
http_version = "1.0";
}
break;
}
const char *n_uri = ngx_str_to_char(r->unparsed_uri, r->pool);
const char *n_method = ngx_str_to_char(r->method_name, r->pool);
if (n_uri == (char*)-1 || n_method == (char*)-1) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (n_uri == NULL) {
dd("uri is of length zero");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
old_pool = ngx_http_modsecurity_pcre_malloc_init(r->pool);
msc_process_uri(ctx->modsec_transaction, n_uri, n_method, http_version);
ngx_http_modsecurity_pcre_malloc_done(old_pool);
dd("Processing intervention with the transaction information filled in (uri, method and version)");
ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r, 1);
if (ret > 0) {
ctx->intervention_triggered = 1;
return ret;
}
/**
* Since incoming request headers are already in place, lets send it to ModSecurity
*
*/
ngx_list_part_t *part = &r->headers_in.headers.part;
ngx_table_elt_t *data = part->elts;
ngx_uint_t i = 0;
for (i = 0 ; /* void */ ; i++) {
if (i >= part->nelts) {
if (part->next == NULL) {
break;
}
part = part->next;
data = part->elts;
i = 0;
}
/**
* By using u_char (utf8_t) I believe nginx is hoping to deal
* with utf8 strings.
* Casting those into to unsigned char * in order to pass
* it to ModSecurity, it will handle with those later.
*
*/
dd("Adding request header: %.*s with value %.*s", (int)data[i].key.len, data[i].key.data, (int) data[i].value.len, data[i].value.data);
msc_add_n_request_header(ctx->modsec_transaction,
(const unsigned char *) data[i].key.data,
data[i].key.len,
(const unsigned char *) data[i].value.data,
data[i].value.len);
}
/**
* Since ModSecurity already knew about all headers, i guess it is safe
* to process this information.
*/
old_pool = ngx_http_modsecurity_pcre_malloc_init(r->pool);
msc_process_request_headers(ctx->modsec_transaction);
ngx_http_modsecurity_pcre_malloc_done(old_pool);
dd("Processing intervention with the request headers information filled in");
ret = ngx_http_modsecurity_process_intervention(ctx->modsec_transaction, r, 1);
if (r->error_page) {
return NGX_DECLINED;
}
if (ret > 0) {
ctx->intervention_triggered = 1;
return ret;
}
}
return NGX_DECLINED;
}

10
tests/README.md Normal file
View File

@ -0,0 +1,10 @@
# Tests
Those are nginx test files. You can copy those files into yours nginx test
tree, to perform the tests using the "prove" utility.
For more information about those tests, read the subsection "Testing your
patch" on the project's README file.
For more about nginx tests, check their repository:
http://hg.nginx.org/nginx-tests/

View File

@ -0,0 +1,233 @@
#!/usr/bin/perl
#
# ModSecurity, http://www.modsecurity.org/
# Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/)
#
# You may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# If any of the files related to licensing are missing or if you have any
# other questions related to licensing please contact Trustwave Holdings, Inc.
# directly using the email address security@modsecurity.org.
#
# Tests for ModSecurity module.
###############################################################################
use warnings;
use strict;
use Test::More;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->has(qw/http/);
$t->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
server {
listen 127.0.0.1:8080;
server_name localhost;
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecRule ARGS "@streq whee" "id:10,phase:2"
SecRule ARGS "@streq whee" "id:11,phase:2"
';
location / {
modsecurity_rules '
SecRule ARGS "@streq root" "id:21,phase:1,auditlog,status:302,redirect:http://www.modsecurity.org"
SecDebugLog %%TESTDIR%%/auditlog-debug-root.txt
SecDebugLogLevel 9
SecAuditEngine RelevantOnly
SecAuditLogParts AB
SecAuditLog %%TESTDIR%%/auditlog-root.txt
SecAuditLogType Serial
SecAuditLogStorageDir %%TESTDIR%%/
';
}
location /subfolder1/subfolder2 {
modsecurity_rules '
SecRule ARGS "@streq subfolder2" "id:41,phase:1,status:302,auditlog,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq subfolder1" "id:42,phase:1,status:302,auditlog,redirect:http://www.modsecurity.org"
SecDebugLog %%TESTDIR%%/auditlog-debug-subfolder2.txt
SecDebugLogLevel 9
SecAuditEngine RelevantOnly
SecAuditLogParts AB
SecResponseBodyAccess On
SecAuditLog %%TESTDIR%%/auditlog-subfolder2.txt
SecAuditLogType Serial
SecAuditLogStorageDir %%TESTDIR%%/
';
}
location /subfolder1 {
modsecurity_rules '
SecRule ARGS "@streq subfolder1" "id:31,phase:1,status:302,auditlog,redirect:http://www.modsecurity.org"
SecDebugLog %%TESTDIR%%/auditlog-debug-subfolder1.txt
SecDebugLogLevel 9
SecAuditLogParts AB
SecAuditEngine RelevantOnly
SecAuditLog %%TESTDIR%%/auditlog-subfolder1.txt
SecAuditLogType Serial
SecAuditLogStorageDir %%TESTDIR%%/
';
}
location /subfolder3/subfolder4 {
modsecurity_rules '
SecResponseBodyAccess On
SecRule ARGS "@streq subfolder4" "id:61,phase:1,status:302,auditlog,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq subfolder3" "id:62,phase:1,status:302,auditlog,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq subfolder4withE" "id:63,phase:1,ctl:auditLogParts=+E,auditlog"
SecDebugLog %%TESTDIR%%/auditlog-debug-subfolder4.txt
SecDebugLogLevel 9
SecAuditEngine RelevantOnly
SecAuditLogParts AB
SecAuditLog %%TESTDIR%%/auditlog-subfolder4.txt
SecAuditLogType Serial
SecAuditLogStorageDir %%TESTDIR%%/
';
}
location /subfolder3 {
modsecurity_rules '
SecRule ARGS "@streq subfolder3" "id:51,phase:1,status:302,auditlog,redirect:http://www.modsecurity.org"
SecDebugLog %%TESTDIR%%/auditlog-debug-subfolder3.txt
SecDebugLogLevel 9
SecAuditLogParts AB
SecAuditEngine RelevantOnly
SecAuditLog %%TESTDIR%%/auditlog-subfolder3.txt
SecAuditLogType Serial
SecAuditLogStorageDir %%TESTDIR%%/
';
}
}
}
EOF
$t->write_file("/index.html", "should be moved/blocked before this.");
mkdir($t->testdir() . '/subfolder1');
$t->write_file("/subfolder1/index.html", "should be moved/blocked before this.");
mkdir($t->testdir() . '/subfolder1/subfolder2');
$t->write_file("/subfolder1/subfolder2/index.html", "should be moved/blocked before this.");
mkdir($t->testdir() . '/subfolder3');
$t->write_file("/subfolder3/index.html", "should be moved/blocked before this.");
mkdir($t->testdir() . '/subfolder3/subfolder4');
$t->write_file("/subfolder3/subfolder4/index.html", "should be moved/blocked before this.");
$t->run();
$t->plan(9);
###############################################################################
my $d = $t->testdir();
my $r;
# Performing requests at root
$r = http_get('/index.html?what=root');
$r = http_get('/index.html?what=subfolder1');
$r = http_get('/index.html?what=subfolder2');
$r = http_get('/index.html?what=subfolder3');
$r = http_get('/index.html?what=subfolder4');
# Performing requests at subfolder1
$r = http_get('/subfolder1/index.html?what=root');
$r = http_get('/subfolder1/index.html?what=subfolder1');
$r = http_get('/subfolder1/index.html?what=subfolder2');
$r = http_get('/subfolder1/index.html?what=subfolder3');
$r = http_get('/subfolder1/index.html?what=subfolder4');
# Performing requests at subfolder2
$r = http_get('/subfolder1/subfolder2/index.html?what=root');
$r = http_get('/subfolder1/subfolder2/index.html?what=subfolder1');
$r = http_get('/subfolder1/subfolder2/index.html?what=subfolder2');
$r = http_get('/subfolder1/subfolder2/index.html?what=subfolder3');
$r = http_get('/subfolder1/subfolder2/index.html?what=subfolder4');
# Performing requests at subfolder3
$r = http_get('/subfolder3/index.html?what=root');
$r = http_get('/subfolder3/index.html?what=subfolder1');
$r = http_get('/subfolder3/index.html?what=subfolder2');
$r = http_get('/subfolder3/index.html?what=subfolder3');
$r = http_get('/subfolder3/index.html?what=subfolder4');
# Performing requests at subfolder4
$r = http_get('/subfolder3/subfolder4/index.html?what=root');
$r = http_get('/subfolder3/subfolder4/index.html?what=subfolder1');
$r = http_get('/subfolder3/subfolder4/index.html?what=subfolder2');
$r = http_get('/subfolder3/subfolder4/index.html?what=subfolder3');
$r = http_get('/subfolder3/subfolder4/index.html?what=subfolder4');
$r = http_get('/subfolder3/subfolder4/index.html?what=subfolder4withE');
my $root = do {
local $/ = undef;
open my $fh, "<", "$d/auditlog-root.txt"
or die "could not open: $!";
<$fh>;
};
my $subfolder1 = do {
local $/ = undef;
open my $fh, "<", "$d/auditlog-subfolder1.txt"
or die "could not open: $!";
<$fh>;
};
my $subfolder2 = do {
local $/ = undef;
open my $fh, "<", "$d/auditlog-subfolder2.txt"
or die "could not open: $!";
<$fh>;
};
my $subfolder3 = do {
local $/ = undef;
open my $fh, "<", "$d/auditlog-subfolder3.txt"
or die "could not open: $!";
<$fh>;
};
my $subfolder4 = do {
local $/ = undef;
open my $fh, "<", "$d/auditlog-subfolder4.txt"
or die "could not open: $!";
<$fh>;
};
like($root, qr/what=root/, 'root');
like($subfolder1, qr/what=subfolder1/, 'subfolder1');
like($subfolder2, qr/what=subfolder2/, 'subfolder2');
like($subfolder2, qr/what=subfolder1/, 'subfolder2 / subfolder1');
like($subfolder3, qr/what=subfolder3/, 'subfolder3');
like($subfolder4, qr/what=subfolder4/, 'subfolder4');
like($subfolder4, qr/what=subfolder3/, 'subfolder4 / subfolder3');
like($subfolder4, qr/what=subfolder4withE/, 'subfolder4');
like($subfolder4, qr/---E--/, 'subfolder4');

View File

@ -0,0 +1,174 @@
#!/usr/bin/perl
#
# ModSecurity, http://www.modsecurity.org/
# Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/)
#
# You may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# If any of the files related to licensing are missing or if you have any
# other questions related to licensing please contact Trustwave Holdings, Inc.
# directly using the email address security@modsecurity.org.
#
# Tests for ModSecurity module.
###############################################################################
use warnings;
use strict;
use Test::More;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->has(qw/http/);
$t->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
server {
listen 127.0.0.1:8080;
server_name s1;
error_page 403 /403.html;
location /403.html {
root %%TESTDIR%%/http;
internal;
}
location / {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecRule ARGS "@streq root" "id:10,phase:1,auditlog,status:403,deny"
SecDebugLog %%TESTDIR%%/auditlog-debug-local.txt
SecDebugLogLevel 9
SecAuditEngine RelevantOnly
SecAuditLogParts ABIJDEFHZ
SecAuditLog %%TESTDIR%%/auditlog-local.txt
SecAuditLogType Serial
SecAuditLogStorageDir %%TESTDIR%%/
';
}
}
server {
listen 127.0.0.1:8080;
server_name s2;
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecRule ARGS "@streq root" "id:10,phase:1,auditlog,status:403,deny"
SecDebugLog %%TESTDIR%%/auditlog-debug-global.txt
SecDebugLogLevel 9
SecAuditEngine RelevantOnly
SecAuditLogParts ABIJDEFHZ
SecAuditLog %%TESTDIR%%/auditlog-global.txt
SecAuditLogType Serial
SecAuditLogStorageDir %%TESTDIR%%/
';
error_page 403 /403.html;
location /403.html {
modsecurity off;
root %%TESTDIR%%/http;
internal;
}
location / {
}
}
}
EOF
my $index_txt = "This is the index page.";
my $custom_txt = "This is a custom error page.";
$t->write_file("/index.html", $index_txt);
mkdir($t->testdir() . '/http');
$t->write_file("/http/403.html", $custom_txt);
$t->run();
$t->plan(10);
###############################################################################
my $d = $t->testdir();
my $t1;
my $t2;
my $t3;
my $t4;
# Performing requests to a server with ModSecurity enabled at location context
$t1 = http_get_host('s1', '/index.html?what=root');
$t2 = http_get_host('s1', '/index.html?what=other');
# Performing requests to a server with ModSecurity enabled at server context
$t3 = http_get_host('s2', '/index.html?what=root');
$t4 = http_get_host('s2', '/index.html?what=other');
my $local = do {
local $/ = undef;
open my $fh, "<", "$d/auditlog-local.txt"
or die "could not open: $!";
<$fh>;
};
my $global = do {
local $/ = undef;
open my $fh, "<", "$d/auditlog-global.txt"
or die "could not open: $!";
<$fh>;
};
like($t1, qr/$custom_txt/, 'ModSecurity at location / root');
like($t2, qr/$index_txt/, 'ModSecurity at location / other');
like($local, qr/what=root/, 'ModSecurity at location / root present in auditlog');
unlike($local, qr/what=other/, 'ModSecurity at location / other not present in auditlog');
like($t3, qr/$custom_txt/, 'ModSecurity at server / root');
like($t4, qr/$index_txt/, 'ModSecurity at server / other');
like($global, qr/what=root/, 'ModSecurity at server / root present in auditlog');
unlike($global, qr/what=other/, 'ModSecurity at server / other not present in auditlog');
like($local, qr/Access denied with code 403/, 'ModSecurity at location / 403 in auditlog');
like($global, qr/Access denied with code 403/, 'ModSecurity at server / 403 in auditlog');
###############################################################################
sub http_get_host {
my ($host, $url) = @_;
return http(<<EOF);
GET $url HTTP/1.0
Host: $host
EOF
}
###############################################################################

View File

@ -0,0 +1,142 @@
#!/usr/bin/perl
#
# ModSecurity, http://www.modsecurity.org/
# Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/)
#
# You may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# If any of the files related to licensing are missing or if you have any
# other questions related to licensing please contact Trustwave Holdings, Inc.
# directly using the email address security@modsecurity.org.
#
# Tests for ModSecurity module.
###############################################################################
use warnings;
use strict;
use Test::More;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->has(qw/http/);
$t->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
server {
listen 127.0.0.1:8080;
server_name localhost;
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecRule ARGS "@streq whee" "id:10,phase:2"
SecRule ARGS "@streq whee" "id:11,phase:2"
';
location / {
modsecurity_rules '
SecRule ARGS "@streq root" "id:21,phase:1,status:302,redirect:http://www.modsecurity.org"
SecDebugLog %%TESTDIR%%/debuglog-root.txt
SecDebugLogLevel 9
';
}
location /subfolder1 {
modsecurity_rules '
SecRule ARGS "@streq subfolder1" "id:31,phase:1,status:302,redirect:http://www.modsecurity.org"
SecDebugLog %%TESTDIR%%/debuglog-subfolder1.txt
SecDebugLogLevel 9
';
location /subfolder1/subfolder2 {
modsecurity_rules '
SecRule ARGS "@streq subfolder2" "id:41,phase:1,status:302,redirect:http://www.modsecurity.org"
SecDebugLog %%TESTDIR%%/debuglog-subfolder2.txt
SecDebugLogLevel 9
';
}
}
}
}
EOF
$t->write_file("/index.html", "should be moved/blocked before this.");
mkdir($t->testdir() . '/subfolder1');
$t->write_file("/subfolder1/index.html", "should be moved/blocked before this.");
mkdir($t->testdir() . '/subfolder1/subfolder2');
$t->write_file("/subfolder1/subfolder2/index.html", "should be moved/blocked before this.");
$t->run();
$t->plan(3);
###############################################################################
my $d = $t->testdir();
my $r;
# Performing requests at root
$r = http_get('/index.html?what=root');
$r = http_get('/index.html?what=subfolder1');
$r = http_get('/index.html?what=subfolder2');
# Performing requests at subfolder1
$r = http_get('/subfolder1/index.html?what=root');
$r = http_get('/subfolder1/index.html?what=subfolder1');
$r = http_get('/subfolder1/index.html?what=subfolder2');
# Performing requests at subfolder2
$r = http_get('/subfolder1/subfolder2/index.html?what=root');
$r = http_get('/subfolder1/subfolder2/index.html?what=subfolder1');
$r = http_get('/subfolder1/subfolder2/index.html?what=subfolder2');
my $root = do {
local $/ = undef;
open my $fh, "<", "$d/debuglog-root.txt"
or die "could not open: $!";
<$fh>;
};
my $subfolder1 = do {
local $/ = undef;
open my $fh, "<", "$d/debuglog-subfolder1.txt"
or die "could not open: $!";
<$fh>;
};
my $subfolder2 = do {
local $/ = undef;
open my $fh, "<", "$d/debuglog-subfolder2.txt"
or die "could not open: $!";
<$fh>;
};
like($root, qr/"what", value "root"/, 'root');
like($subfolder1, qr/"what", value "subfolder1"/, 'subfolder1');
like($subfolder2, qr/"what", value "subfolder2"/, 'subfolder2');

View File

@ -0,0 +1,231 @@
#!/usr/bin/perl
# (C) Andrei Belov
# Tests for ModSecurity-nginx connector (configuration merge).
###############################################################################
use warnings;
use strict;
use Test::More;
use Socket qw/ CRLF /;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->has(qw/http proxy/);
$t->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 128
SecRequestBodyLimitAction Reject
SecRule REQUEST_BODY "@rx BAD BODY" "id:11,phase:request,deny,log,status:403"
';
server {
listen 127.0.0.1:%%PORT_8080%%;
server_name localhost;
location / {
proxy_pass http://127.0.0.1:%%PORT_8081%%;
}
location /modsec-disabled {
modsecurity_rules '
SecRuleEngine Off
';
proxy_pass http://127.0.0.1:%%PORT_8081%%;
}
location /nobodyaccess {
modsecurity_rules '
SecRequestBodyAccess Off
';
proxy_pass http://127.0.0.1:%%PORT_8081%%;
}
location /bodylimitprocesspartial {
modsecurity_rules '
SecRequestBodyLimitAction ProcessPartial
';
proxy_pass http://127.0.0.1:%%PORT_8081%%;
}
location /bodylimitincreased {
modsecurity_rules '
SecRequestBodyLimit 512
';
proxy_pass http://127.0.0.1:%%PORT_8081%%;
}
location /server {
modsecurity off;
location /server/modsec-disabled {
proxy_pass http://127.0.0.1:%%PORT_8082%%;
}
location /server/nobodyaccess {
proxy_pass http://127.0.0.1:%%PORT_8083%%;
}
location /server/bodylimitprocesspartial {
proxy_pass http://127.0.0.1:%%PORT_8084%%;
}
location /server/bodylimitincreased {
proxy_pass http://127.0.0.1:%%PORT_8085%%;
}
}
}
server {
listen 127.0.0.1:%%PORT_8082%%;
modsecurity_rules '
SecRuleEngine Off
';
location / {
proxy_pass http://127.0.0.1:%%PORT_8081%%;
}
}
server {
listen 127.0.0.1:%%PORT_8083%%;
modsecurity_rules '
SecRequestBodyAccess Off
';
location / {
proxy_pass http://127.0.0.1:%%PORT_8081%%;
}
}
server {
listen 127.0.0.1:%%PORT_8084%%;
modsecurity_rules '
SecRequestBodyLimitAction ProcessPartial
';
location / {
proxy_pass http://127.0.0.1:%%PORT_8081%%;
}
}
server {
listen 127.0.0.1:%%PORT_8085%%;
modsecurity_rules '
SecRequestBodyLimit 512
';
location / {
proxy_pass http://127.0.0.1:%%PORT_8081%%;
}
}
}
EOF
$t->run_daemon(\&http_daemon);
$t->run()->waitforsocket('127.0.0.1:' . port(8081));
$t->plan(10);
###############################################################################
like(http_get_body('/', 'GOOD BODY'), qr/TEST-OK-IF-YOU-SEE-THIS/, "http level defaults, pass");
like(http_get_body('/', 'VERY BAD BODY'), qr/^HTTP.*403/, "http level defaults, block");
like(http_get_body('/modsec-disabled', 'VERY BAD BODY'), qr/TEST-OK-IF-YOU-SEE-THIS/, "location override for SecRuleEngine, pass");
like(http_get_body('/nobodyaccess', 'VERY BAD BODY'), qr/TEST-OK-IF-YOU-SEE-THIS/, "location override for SecRequestBodyAccess, pass");
like(http_get_body('/bodylimitprocesspartial', 'BODY' x 33), qr/TEST-OK-IF-YOU-SEE-THIS/, "location override for SecRequestBodyLimitAction, pass");
like(http_get_body('/bodylimitincreased', 'BODY' x 64), qr/TEST-OK-IF-YOU-SEE-THIS/, "location override for SecRequestBodyLimit, pass");
like(http_get_body('/server/modsec-disabled', 'VERY BAD BODY'), qr/TEST-OK-IF-YOU-SEE-THIS/, "server override for SecRuleEngine, pass");
like(http_get_body('/server/nobodyaccess', 'VERY BAD BODY'), qr/TEST-OK-IF-YOU-SEE-THIS/, "server override for SecRequestBodyAccess, pass");
like(http_get_body('/server/bodylimitprocesspartial', 'BODY' x 33), qr/TEST-OK-IF-YOU-SEE-THIS/, "server override for SecRequestBodyLimitAction, pass");
like(http_get_body('/server/bodylimitincreased', 'BODY' x 64), qr/TEST-OK-IF-YOU-SEE-THIS/, "server override for SecRequestBodyLimit, pass");
###############################################################################
sub http_daemon {
my $server = IO::Socket::INET->new(
Proto => 'tcp',
LocalHost => '127.0.0.1:' . port(8081),
Listen => 5,
Reuse => 1
)
or die "Can't create listening socket: $!\n";
local $SIG{PIPE} = 'IGNORE';
while (my $client = $server->accept()) {
$client->autoflush(1);
my $headers = '';
my $uri = '';
while (<$client>) {
$headers .= $_;
last if (/^\x0d?\x0a?$/);
}
$uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
print $client <<'EOF';
HTTP/1.1 200 OK
Connection: close
EOF
print $client "TEST-OK-IF-YOU-SEE-THIS"
unless $headers =~ /^HEAD/i;
close $client;
}
}
sub http_get_body {
my $uri = shift;
my $last = pop;
return http( join '', (map {
my $body = $_;
"GET $uri HTTP/1.1" . CRLF
. "Host: localhost" . CRLF
. "Content-Length: " . (length $body) . CRLF . CRLF
. $body
} @_),
"GET $uri HTTP/1.1" . CRLF
. "Host: localhost" . CRLF
. "Connection: close" . CRLF
. "Content-Length: " . (length $last) . CRLF . CRLF
. $last
);
}
###############################################################################

112
tests/modsecurity-config.t Normal file
View File

@ -0,0 +1,112 @@
#!/usr/bin/perl
#
# ModSecurity, http://www.modsecurity.org/
# Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/)
#
# You may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# If any of the files related to licensing are missing or if you have any
# other questions related to licensing please contact Trustwave Holdings, Inc.
# directly using the email address security@modsecurity.org.
#
# Tests for ModSecurity module.
###############################################################################
use warnings;
use strict;
use Test::More;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->has(qw/http/);
$t->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
server {
listen 127.0.0.1:8080;
server_name localhost;
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecRule ARGS "@streq whee" "id:10,phase:2"
SecRule ARGS "@streq whee" "id:11,phase:2"
';
location / {
modsecurity_rules '
SecRuleEngine On
SecRule ARGS "@streq root" "id:21,phase:1,status:302,redirect:http://www.modsecurity.org"
';
}
location /subfolder1 {
modsecurity_rules '
SecRuleEngine On
SecRule ARGS "@streq subfolder1" "id:31,phase:1,status:302,redirect:http://www.modsecurity.org"
';
location /subfolder1/subfolder2 {
modsecurity_rules '
SecRuleEngine On
SecRule ARGS "@streq subfolder2" "id:41,phase:1,status:302,redirect:http://www.modsecurity.org"
';
}
}
}
}
EOF
$t->write_file("/index.html", "should be moved/blocked before this.");
mkdir($t->testdir() . '/subfolder1');
$t->write_file("/subfolder1/index.html", "should be moved/blocked before this.");
mkdir($t->testdir() . '/subfolder1/subfolder2');
$t->write_file("/subfolder1/subfolder2/index.html", "should be moved/blocked before this.");
$t->run();
$t->plan(9);
###############################################################################
# Performing requests at root
like(http_get('/index.html?what=root'), qr/^HTTP.*302/, 'redirect 302 - root');
like(http_get('/index.html?what=subfolder1'), qr/should be moved\/blocked before this./, 'nothing - requested subfolder1 at root');
like(http_get('/index.html?what=subfolder2'), qr/should be moved\/blocked before this./, 'nothing - requested subfolder2 at root');
# Performing requests at subfolder1
like(http_get('/subfolder1/index.html?what=root'), qr/should be moved\/blocked before this./, 'nothing - requested root at subfolder 1');
like(http_get('/subfolder1/index.html?what=subfolder1'), qr/^HTTP.*302/, 'redirect 302 - subfolder 1');
like(http_get('/subfolder1/index.html?what=subfolder2'), qr/should be moved\/blocked before this./, 'nothing - requested subfolder2 at subfolder1');
# Performing requests at subfolder2
like(http_get('/subfolder1/subfolder2/index.html?what=root'), qr/should be moved\/blocked before this./, 'nothing - requested root at subfolder 2');
like(http_get('/subfolder1/subfolder2/index.html?what=subfolder1'), qr/^HTTP.*302/, 'redirect 302 - subfolder 2');
like(http_get('/subfolder1/subfolder2/index.html?what=subfolder2'), qr/^HTTP.*302/, 'redirect 302 - subfolder 2');

205
tests/modsecurity-h2.t Normal file
View File

@ -0,0 +1,205 @@
#!/usr/bin/perl
# (C) Andrei Belov
# Tests for ModSecurity module (HTTP/2).
###############################################################################
use warnings;
use strict;
use Test::More;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
use Test::Nginx::HTTP2;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->has(qw/http http_v2/);
$t->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
server {
listen 127.0.0.1:8080 http2;
server_name localhost;
location / {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecRule ARGS "@streq whee" "id:10,phase:2"
SecRule ARGS "@streq whee" "id:11,phase:2"
';
}
location /phase1 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:1,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:1,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:1,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:1,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block"
';
}
location /phase2 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:2,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:2,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:2,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:2,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:2,status:403,block"
';
}
location /phase3 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:3,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:3,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:3,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:3,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:3,status:403,block"
';
}
location /phase4 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecResponseBodyAccess On
SecDefaultAction "phase:4,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:4,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:4,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:4,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:4,status:403,block"
';
}
}
}
EOF
$t->write_file("/phase1", "should be moved/blocked before this.");
$t->write_file("/phase2", "should be moved/blocked before this.");
$t->write_file("/phase3", "should be moved/blocked before this.");
$t->write_file("/phase4", "should not be moved/blocked, headers delivered before phase 4.");
$t->run();
$t->plan(20);
###############################################################################
my ($phase, $s, $sid, $frames, $frame);
# Redirect (302)
for $phase (1 .. 3) {
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => "/phase${phase}?what=redirect302" });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
is($frame->{headers}->{':status'}, 302, "redirect 302 - phase ${phase}");
}
SKIP: {
skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE};
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => '/phase4?what=redirect302' });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
is($frame, undef, 'redirect 302 - phase 4');
}
# Redirect (301)
for $phase (1 .. 3) {
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => "/phase${phase}?what=redirect301" });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
is($frame->{headers}->{':status'}, 301, "redirect 301 - phase ${phase}");
}
SKIP: {
skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE};
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => '/phase4?what=redirect301' });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
is($frame, undef, 'redirect 301 - phase 4');
}
# Block (401)
for $phase (1 .. 3) {
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => "/phase${phase}?what=block401" });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
is($frame->{headers}->{':status'}, 401, "block 401 - phase ${phase}");
}
SKIP: {
skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE};
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => '/phase4?what=block401' });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
is($frame, undef, 'block 401 - phase 4');
}
# Block (403)
for $phase (1 .. 3) {
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => "/phase${phase}?what=block403" });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
is($frame->{headers}->{':status'}, 403, "block 403 - phase ${phase}");
}
SKIP: {
skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE};
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => '/phase4?what=block403' });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
is($frame, undef, 'block 403 - phase 4');
}
# Nothing to detect
for $phase (1 .. 3) {
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => "/phase${phase}?what=nothing" });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
is($frame->{data}, "should be moved\/blocked before this.", "nothing phase ${phase}");
}
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => "/phase4?what=nothing" });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
is($frame->{data}, "should not be moved\/blocked, headers delivered before phase 4.", 'nothing phase 4');

View File

@ -0,0 +1,287 @@
#!/usr/bin/perl
# (C) Andrei Belov
# Tests for ModSecurity over the http proxy module (HTTP/2).
###############################################################################
use warnings;
use strict;
use Test::More;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
use Test::Nginx::HTTP2;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->has(qw/http http_v2 proxy/)->plan(23);
$t->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
server {
listen 127.0.0.1:8080 http2;
server_name localhost;
location / {
proxy_pass http://127.0.0.1:8081;
proxy_read_timeout 1s;
}
location /phase1 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:1,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:1,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:1,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:1,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block"
';
proxy_pass http://127.0.0.1:8081;
proxy_read_timeout 1s;
}
location /phase2 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:2,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:2,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:2,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:2,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:2,status:403,block"
';
proxy_pass http://127.0.0.1:8081;
proxy_read_timeout 1s;
}
location /phase3 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:3,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:3,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:3,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:3,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:3,status:403,block"
';
proxy_pass http://127.0.0.1:8081;
proxy_read_timeout 1s;
}
location /phase4 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecResponseBodyAccess On
SecDefaultAction "phase:4,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:4,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:4,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:4,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:4,status:403,block"
';
proxy_pass http://127.0.0.1:8081;
proxy_read_timeout 1s;
}
}
}
EOF
$t->run_daemon(\&http_daemon);
$t->run()->waitforsocket('127.0.0.1:' . port(8081));
###############################################################################
my ($phase, $s, $sid, $frames, $frame);
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => '/' });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
like($frame->{data}, qr/SEE-THIS/, "proxy request");
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => '/multi' });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
like($frame->{data}, qr/AND-THIS/, "proxy request with multiple packets");
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => '/', method => 'HEAD' });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
unlike($frame->{data}, qr/SEE-THIS/, "proxy head request");
# Redirect (302)
for $phase (1 .. 3) {
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => "/phase${phase}?what=redirect302" });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
is($frame->{headers}->{':status'}, 302, "redirect 302 - phase ${phase}");
}
SKIP: {
skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE};
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => '/phase4?what=redirect302' });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
is($frame, undef, 'redirect 302 - phase 4');
}
# Redirect (301)
for $phase (1 .. 3) {
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => "/phase${phase}?what=redirect301" });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
is($frame->{headers}->{':status'}, 301, "redirect 301 - phase ${phase}");
}
SKIP: {
skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE};
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => '/phase4?what=redirect301' });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
is($frame, undef, 'redirect 301 - phase 4');
}
# Block (401)
for $phase (1 .. 3) {
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => "/phase${phase}?what=block401" });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
is($frame->{headers}->{':status'}, 401, "block 401 - phase ${phase}");
}
SKIP: {
skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE};
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => '/phase4?what=block401' });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
is($frame, undef, 'block 401 - phase 4');
}
# Block (403)
for $phase (1 .. 3) {
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => "/phase${phase}?what=block403" });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
is($frame->{headers}->{':status'}, 403, "block 403 - phase ${phase}");
}
SKIP: {
skip 'long test', 1 unless $ENV{TEST_NGINX_UNSAFE};
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => '/phase4?what=block403' });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
is($frame, undef, 'block 403 - phase 4');
}
# Nothing to detect
#like(http_get('/phase1?what=nothing'), qr/phase1\?what=nothing\' not found/, 'nothing phase 1');
#like(http_get('/phase2?what=nothing'), qr/phase2\?what=nothing\' not found/, 'nothing phase 2');
#like(http_get('/phase3?what=nothing'), qr/phase3\?what=nothing\' not found/, 'nothing phase 3');
#like(http_get('/phase4?what=nothing'), qr/phase4\?what=nothing\' not found/, 'nothing phase 4');
for $phase (1 .. 4) {
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ path => "/phase${phase}?what=nothing" });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
like($frame->{data}, qr/phase${phase}\?what=nothing\' not found/, "nothing phase ${phase}");
}
###############################################################################
sub http_daemon {
my $server = IO::Socket::INET->new(
Proto => 'tcp',
LocalHost => '127.0.0.1:' . port(8081),
Listen => 5,
Reuse => 1
)
or die "Can't create listening socket: $!\n";
local $SIG{PIPE} = 'IGNORE';
while (my $client = $server->accept()) {
$client->autoflush(1);
my $headers = '';
my $uri = '';
while (<$client>) {
$headers .= $_;
last if (/^\x0d?\x0a?$/);
}
$uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
if ($uri eq '/') {
print $client <<'EOF';
HTTP/1.1 200 OK
Connection: close
EOF
print $client "TEST-OK-IF-YOU-SEE-THIS"
unless $headers =~ /^HEAD/i;
} elsif ($uri eq '/multi') {
print $client <<"EOF";
HTTP/1.1 200 OK
Connection: close
TEST-OK-IF-YOU-SEE-THIS
EOF
select undef, undef, undef, 0.1;
print $client 'AND-THIS';
} else {
print $client <<"EOF";
HTTP/1.1 404 Not Found
Connection: close
Oops, '$uri' not found
EOF
}
close $client;
}
}
###############################################################################

208
tests/modsecurity-proxy.t Normal file
View File

@ -0,0 +1,208 @@
#!/usr/bin/perl
# Tests for ModSecurity over the http proxy module.
###############################################################################
use warnings;
use strict;
use Test::More;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(23);
$t->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
server {
listen 127.0.0.1:8080;
server_name localhost;
location / {
proxy_pass http://127.0.0.1:8081;
proxy_read_timeout 1s;
}
location /phase1 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:1,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:1,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:1,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:1,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block"
';
proxy_pass http://127.0.0.1:8081;
proxy_read_timeout 1s;
}
location /phase2 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:2,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:2,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:2,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:2,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:2,status:403,block"
';
proxy_pass http://127.0.0.1:8081;
proxy_read_timeout 1s;
}
location /phase3 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:3,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:3,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:3,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:3,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:3,status:403,block"
';
proxy_pass http://127.0.0.1:8081;
proxy_read_timeout 1s;
}
location /phase4 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecResponseBodyAccess On
SecDefaultAction "phase:4,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:4,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:4,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:4,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:4,status:403,block"
';
proxy_pass http://127.0.0.1:8081;
proxy_read_timeout 1s;
}
}
}
EOF
$t->todo_alerts();
$t->run_daemon(\&http_daemon);
$t->run()->waitforsocket('127.0.0.1:' . port(8081));
###############################################################################
like(http_get('/'), qr/SEE-THIS/, 'proxy request');
like(http_get('/multi'), qr/AND-THIS/, 'proxy request with multiple packets');
unlike(http_head('/'), qr/SEE-THIS/, 'proxy head request');
# Redirect (302)
like(http_get('/phase1?what=redirect302'), qr/^HTTP.*302/, 'redirect 302 - phase 1');
like(http_get('/phase2?what=redirect302'), qr/^HTTP.*302/, 'redirect 302 - phase 2');
like(http_get('/phase3?what=redirect302'), qr/^HTTP.*302/, 'redirect 302 - phase 3');
is(http_get('/phase4?what=redirect302'), '', 'redirect 302 - phase 4');
# Redirect (301)
like(http_get('/phase1?what=redirect301'), qr/^HTTP.*301/, 'redirect 301 - phase 1');
like(http_get('/phase2?what=redirect301'), qr/^HTTP.*301/, 'redirect 301 - phase 2');
like(http_get('/phase3?what=redirect301'), qr/^HTTP.*301/, 'redirect 301 - phase 3');
is(http_get('/phase4?what=redirect301'), '', 'redirect 301 - phase 4');
# Block (401)
like(http_get('/phase1?what=block401'), qr/^HTTP.*401/, 'block 401 - phase 1');
like(http_get('/phase2?what=block401'), qr/^HTTP.*401/, 'block 401 - phase 2');
like(http_get('/phase3?what=block401'), qr/^HTTP.*401/, 'block 401 - phase 3');
is(http_get('/phase4?what=block401'), '', 'block 401 - phase 4');
# Block (403)
like(http_get('/phase1?what=block403'), qr/^HTTP.*403/, 'block 403 - phase 1');
like(http_get('/phase2?what=block403'), qr/^HTTP.*403/, 'block 403 - phase 2');
like(http_get('/phase3?what=block403'), qr/^HTTP.*403/, 'block 403 - phase 3');
is(http_get('/phase4?what=block403'), '', 'block 403 - phase 4');
# Nothing to detect
like(http_get('/phase1?what=nothing'), qr/phase1\?what=nothing\' not found/, 'nothing phase 1');
like(http_get('/phase2?what=nothing'), qr/phase2\?what=nothing\' not found/, 'nothing phase 2');
like(http_get('/phase3?what=nothing'), qr/phase3\?what=nothing\' not found/, 'nothing phase 3');
like(http_get('/phase4?what=nothing'), qr/phase4\?what=nothing\' not found/, 'nothing phase 4');
###############################################################################
sub http_daemon {
my $server = IO::Socket::INET->new(
Proto => 'tcp',
LocalHost => '127.0.0.1:' . port(8081),
Listen => 5,
Reuse => 1
)
or die "Can't create listening socket: $!\n";
local $SIG{PIPE} = 'IGNORE';
while (my $client = $server->accept()) {
$client->autoflush(1);
my $headers = '';
my $uri = '';
while (<$client>) {
$headers .= $_;
last if (/^\x0d?\x0a?$/);
}
$uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
if ($uri eq '/') {
print $client <<'EOF';
HTTP/1.1 200 OK
Connection: close
EOF
print $client "TEST-OK-IF-YOU-SEE-THIS"
unless $headers =~ /^HEAD/i;
} elsif ($uri eq '/multi') {
print $client <<"EOF";
HTTP/1.1 200 OK
Connection: close
TEST-OK-IF-YOU-SEE-THIS
EOF
select undef, undef, undef, 0.1;
print $client 'AND-THIS';
} else {
print $client <<"EOF";
HTTP/1.1 404 Not Found
Connection: close
Oops, '$uri' not found
EOF
}
close $client;
}
}
###############################################################################

View File

@ -0,0 +1,224 @@
#!/usr/bin/perl
# (C) Andrei Belov
# Tests for ModSecurity-nginx connector (request body operations, HTTP/2).
###############################################################################
use warnings;
use strict;
use Test::More;
use Socket qw/ CRLF /;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
use Test::Nginx::HTTP2;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->has(qw/http http_v2 proxy auth_request/);
$t->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
server {
listen 127.0.0.1:8081;
location / {
return 200 "TEST-OK-IF-YOU-SEE-THIS";
}
}
server {
listen 127.0.0.1:8080 http2;
server_name localhost;
modsecurity on;
client_header_buffer_size 1024;
location /bodyaccess {
modsecurity_rules '
SecRuleEngine On
SecRequestBodyAccess On
SecRule REQUEST_BODY "@rx BAD BODY" "id:11,phase:request,deny,log,status:403"
';
proxy_pass http://127.0.0.1:8081;
}
location /nobodyaccess {
modsecurity_rules '
SecRuleEngine On
SecRequestBodyAccess Off
SecRule REQUEST_BODY "@rx BAD BODY" "id:21,phase:request,deny,log,status:403"
SecRule ARGS_POST|ARGS_POST_NAMES "@rx BAD ARG" "id:22,phase:request,deny,log,status:403"
';
proxy_pass http://127.0.0.1:8081;
}
location /bodylimitreject {
modsecurity_rules '
SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 128
SecRequestBodyLimitAction Reject
SecRule REQUEST_BODY "@rx BAD BODY" "id:31,phase:request,deny,log,status:403"
';
proxy_pass http://127.0.0.1:8081;
}
location /bodylimitprocesspartial {
modsecurity_rules '
SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 128
SecRequestBodyLimitAction ProcessPartial
SecRule REQUEST_BODY "@rx BAD BODY" "id:41,phase:request,deny,log,status:403"
';
proxy_pass http://127.0.0.1:8081;
}
location = /auth {
return 200;
}
location = /useauth {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecRequestBodyAccess On
';
auth_request /auth;
proxy_pass http://127.0.0.1:8081;
}
}
}
EOF
$t->run();
$t->plan(36);
###############################################################################
my ($s, $sid, $frames, $frame);
foreach my $method (('GET', 'POST', 'PUT', 'DELETE')) {
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ method => $method, path => '/bodyaccess', 'body_more' => 1 });
$s->h2_body('GOOD BODY');
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "${method} request body access on, pass");
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ method => $method, path => '/bodyaccess', 'body_more' => 1 });
$s->h2_body('VERY BAD BODY');
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
is($frame->{headers}->{':status'}, 403, "${method} request body access on, block");
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ method => $method, path => '/nobodyaccess', 'body_more' => 1 });
$s->h2_body('VERY BAD BODY');
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "${method} request body access off, pass");
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ 'body_more' => 1,
headers => [
{name => ':method', value => "${method}" },
{name => ':scheme', value => 'http' },
{name => ':path', value => '/nobodyaccess' },
{name => 'host', value => 'localhost' },
{name => 'content-type', value => 'application/x-www-form-urlencoded' }
] });
$s->h2_body('test=VERY BAD BODY');
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "${method} request body access off (ARGS_POST), pass");
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ method => $method, path => '/bodylimitreject', 'body_more' => 1 });
$s->h2_body('BODY' x 32);
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "${method} request body limit reject, pass");
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ method => $method, path => '/bodylimitreject', 'body_more' => 1 });
$s->h2_body('BODY' x 33);
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
is($frame->{headers}->{':status'}, 403, "${method} request body limit reject, block");
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ method => $method, path => '/bodylimitprocesspartial', 'body_more' => 1 });
$s->h2_body('BODY' x 32 . 'BAD BODY');
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "${method} request body limit process partial, pass");
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ method => $method, path => '/bodylimitprocesspartial', 'body_more' => 1 });
$s->h2_body('BODY' x 30 . 'BAD BODY' x 32);
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
is($frame->{headers}->{':status'}, 403, "${method} request body limit process partial, block");
}
TODO: {
# https://github.com/SpiderLabs/ModSecurity-nginx/issues/163
# https://github.com/nginx/nginx/commit/6c89d752c8ab3a3cc0832927484808b68153f8c4
local $TODO = 'not yet' unless $t->has_version('1.19.3');
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ method => 'POST', path => '/useauth', 'body' => 'BODY' x 16 });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "POST with auth_request (request size < client_header_buffer_size)");
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ method => 'POST', path => '/useauth', 'body' => 'BODY' x 257 });
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "POST with auth_request (request size > client_header_buffer_size)");
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ method => 'POST', path => '/useauth', 'body_more' => 1 });
$s->h2_body('BODY' x 15, { 'body_more' => 1 });
select undef, undef, undef, 0.1;
$s->h2_body('BODY');
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "POST with auth_request (request size < client_header_buffer_size), no preread");
$s = Test::Nginx::HTTP2->new();
$sid = $s->new_stream({ method => 'POST', path => '/useauth', 'body_more' => 1 });
$s->h2_body('BODY' x 256, { 'body_more' => 1 });
select undef, undef, undef, 0.1;
$s->h2_body('BODY');
$frames = $s->read(all => [{ sid => $sid, fin => 1 }]);
($frame) = grep { $_->{type} eq "DATA" } @$frames;
like($frame->{data}, qr/TEST-OK-IF-YOU-SEE-THIS/, "POST with auth_request (request size > client_header_buffer_size), no preread");
}
###############################################################################

View File

@ -0,0 +1,251 @@
#!/usr/bin/perl
# (C) Andrei Belov
# Tests for ModSecurity-nginx connector (request body operations).
###############################################################################
use warnings;
use strict;
use Test::More;
use Socket qw/ CRLF /;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->has(qw/http proxy auth_request/);
$t->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
server {
listen 127.0.0.1:8080;
server_name localhost;
modsecurity on;
client_header_buffer_size 1024;
location /bodyaccess {
modsecurity_rules '
SecRuleEngine On
SecRequestBodyAccess On
SecRule REQUEST_BODY "@rx BAD BODY" "id:11,phase:request,deny,log,status:403"
';
proxy_pass http://127.0.0.1:%%PORT_8081%%;
}
location /nobodyaccess {
modsecurity_rules '
SecRuleEngine On
SecRequestBodyAccess Off
SecRule REQUEST_BODY "@rx BAD BODY" "id:21,phase:request,deny,log,status:403"
SecRule ARGS_POST|ARGS_POST_NAMES "@rx BAD ARG" "id:22,phase:request,deny,log,status:403"
';
proxy_pass http://127.0.0.1:%%PORT_8081%%;
}
location /bodylimitreject {
modsecurity_rules '
SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 128
SecRequestBodyLimitAction Reject
SecRule REQUEST_BODY "@rx BAD BODY" "id:31,phase:request,deny,log,status:403"
';
proxy_pass http://127.0.0.1:%%PORT_8081%%;
}
location /bodylimitrejectserver {
modsecurity off;
proxy_pass http://127.0.0.1:%%PORT_8082%%;
}
location /bodylimitprocesspartial {
modsecurity_rules '
SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 128
SecRequestBodyLimitAction ProcessPartial
SecRule REQUEST_BODY "@rx BAD BODY" "id:41,phase:request,deny,log,status:403"
';
proxy_pass http://127.0.0.1:%%PORT_8081%%;
}
location = /auth {
return 200;
}
location = /useauth {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecRequestBodyAccess On
';
auth_request /auth;
proxy_pass http://127.0.0.1:%%PORT_8081%%;
}
}
server {
listen 127.0.0.1:%%PORT_8082%%;
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 128
SecRequestBodyLimitAction Reject
SecRule REQUEST_BODY "@rx BAD BODY" "id:31,phase:request,deny,log,status:403"
';
location / {
proxy_pass http://127.0.0.1:%%PORT_8081%%;
}
}
}
EOF
$t->run_daemon(\&http_daemon);
$t->run()->waitforsocket('127.0.0.1:' . port(8081));
$t->plan(40);
###############################################################################
foreach my $method (('GET', 'POST', 'PUT', 'DELETE')) {
like(http_req_body($method, '/bodyaccess', 'GOOD BODY'), qr/TEST-OK-IF-YOU-SEE-THIS/, "$method request body access on, pass");
like(http_req_body($method, '/bodyaccess', 'VERY BAD BODY'), qr/^HTTP.*403/, "$method request body access on, block");
like(http_req_body($method, '/nobodyaccess', 'VERY BAD BODY'), qr/TEST-OK-IF-YOU-SEE-THIS/, "$method request body access off, pass");
like(http_req_body_postargs($method, '/nobodyaccess', 'BAD ARG'), qr/TEST-OK-IF-YOU-SEE-THIS/, "$method request body access off (ARGS_POST), pass");
like(http_req_body($method, '/bodylimitreject', 'BODY' x 32), qr/TEST-OK-IF-YOU-SEE-THIS/, "$method request body limit reject, pass");
like(http_req_body($method, '/bodylimitreject', 'BODY' x 33), qr/^HTTP.*403/, "$method request body limit reject, block");
like(http_req_body($method, '/bodylimitprocesspartial', 'BODY' x 32 . 'BAD BODY'), qr/TEST-OK-IF-YOU-SEE-THIS/, "$method request body limit process partial, pass");
like(http_req_body($method, '/bodylimitprocesspartial', 'BODY' x 30 . 'BAD BODY' x 32), qr/^HTTP.*403/, "$method request body limit process partial, block");
}
like(http_req_body('POST', '/useauth', 'BODY' x 16), qr/TEST-OK-IF-YOU-SEE-THIS/, "POST with auth_request (request size < client_header_buffer_size)");
like(http_req_body('POST', '/useauth', 'BODY' x 257), qr/TEST-OK-IF-YOU-SEE-THIS/, "POST with auth_request (request size > client_header_buffer_size)");
like(
http(
'POST /useauth HTTP/1.0' . CRLF
. 'Content-Length: 1028' . CRLF . CRLF
. 'BODY' x 256,
sleep => 0.1,
body => 'BODY'
),
qr/TEST-OK-IF-YOU-SEE-THIS/,
'POST with auth_request (request size > client_header_buffer_size), no preread'
);
like(
http(
'POST /useauth HTTP/1.0' . CRLF
. 'Content-Length: 64' . CRLF . CRLF
. 'BODY' x 15,
sleep => 0.1,
body => 'BODY'
),
qr/TEST-OK-IF-YOU-SEE-THIS/,
'POST with auth_request (request size < client_header_buffer_size), no preread'
);
foreach my $method (('GET', 'POST', 'PUT', 'DELETE')) {
like(http_req_body($method, '/bodylimitrejectserver', 'BODY' x 33), qr/^HTTP.*403/, "$method request body limit reject, block (inherited SecRequestBodyLimit)");
}
###############################################################################
sub http_daemon {
my $server = IO::Socket::INET->new(
Proto => 'tcp',
LocalHost => '127.0.0.1:' . port(8081),
Listen => 5,
Reuse => 1
)
or die "Can't create listening socket: $!\n";
local $SIG{PIPE} = 'IGNORE';
while (my $client = $server->accept()) {
$client->autoflush(1);
my $headers = '';
my $uri = '';
while (<$client>) {
$headers .= $_;
last if (/^\x0d?\x0a?$/);
}
$uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
print $client <<'EOF';
HTTP/1.1 200 OK
Connection: close
EOF
print $client "TEST-OK-IF-YOU-SEE-THIS"
unless $headers =~ /^HEAD/i;
close $client;
}
}
sub http_req_body {
my $method = shift;
my $uri = shift;
my $last = pop;
return http( join '', (map {
my $body = $_;
"$method $uri HTTP/1.1" . CRLF
. "Host: localhost" . CRLF
. "Content-Length: " . (length $body) . CRLF . CRLF
. $body
} @_),
"$method $uri HTTP/1.1" . CRLF
. "Host: localhost" . CRLF
. "Connection: close" . CRLF
. "Content-Length: " . (length $last) . CRLF . CRLF
. $last
);
}
sub http_req_body_postargs {
my $method = shift;
my $uri = shift;
my $last = pop;
return http( join '', (map {
my $body = $_;
"$method $uri HTTP/1.1" . CRLF
. "Host: localhost" . CRLF
. "Content-Type: application/x-www-form-urlencoded" . CRLF
. "Content-Length: " . (length "test=" . $body) . CRLF . CRLF
. "test=" . $body
} @_),
"$method $uri HTTP/1.1" . CRLF
. "Host: localhost" . CRLF
. "Connection: close" . CRLF
. "Content-Type: application/x-www-form-urlencoded" . CRLF
. "Content-Length: " . (length "test=" . $last) . CRLF . CRLF
. "test=" . $last
);
}
###############################################################################

View File

@ -0,0 +1,69 @@
#!/usr/bin/perl
# (C) Andrei Belov
# Tests for ModSecurity-nginx connector (response body operations).
###############################################################################
use warnings;
use strict;
use Test::More;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->has(qw/http/);
$t->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
server {
listen 127.0.0.1:8080;
server_name localhost;
modsecurity on;
location /body1 {
default_type text/plain;
modsecurity_rules '
SecRuleEngine On
SecResponseBodyAccess On
SecResponseBodyLimit 128
SecRule RESPONSE_BODY "@rx BAD BODY" "id:11,phase:response,deny,log,status:403"
';
}
}
}
EOF
$t->write_file("/body1", "BAD BODY");
$t->run();
$t->todo_alerts();
$t->plan(1);
###############################################################################
TODO: {
local $TODO = 'not yet';
like(http_get('/body1'), qr/^HTTP.*403/, 'response body (block)');
}

View File

@ -0,0 +1,79 @@
#!/usr/bin/perl
# (C) Andrei Belov
# Tests for ModSecurity-nginx connector (scoring).
###############################################################################
use warnings;
use strict;
use Test::More;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->has(qw/http/);
$t->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
server {
listen 127.0.0.1:8080;
server_name localhost;
modsecurity on;
location /absolute {
modsecurity_rules '
SecRuleEngine On
SecRule ARGS "@streq badarg1" "id:11,phase:2,setvar:tx.score=1"
SecRule ARGS "@streq badarg2" "id:12,phase:2,setvar:tx.score=2"
SecRule TX:SCORE "@ge 2" "id:199,phase:request,deny,log,status:403"
';
}
location /iterative {
modsecurity_rules '
SecRuleEngine On
SecRule ARGS "@streq badarg1" "id:21,phase:2,setvar:tx.score=+1"
SecRule ARGS "@streq badarg2" "id:22,phase:2,setvar:tx.score=+1"
SecRule ARGS "@streq badarg3" "id:23,phase:2,setvar:tx.score=+1"
SecRule TX:SCORE "@ge 3" "id:299,phase:request,deny,log,status:403"
';
}
}
}
EOF
$t->write_file("/absolute", "should be moved/blocked before this.");
$t->write_file("/iterative", "should be moved/blocked before this.");
$t->run();
$t->plan(5);
###############################################################################
like(http_get('/absolute?what=badarg1'), qr/should be moved\/blocked before this./, 'absolute scoring 1 (pass)');
like(http_get('/absolute?what=badarg2'), qr/^HTTP.*403/, 'absolute scoring 2 (block)');
like(http_get('/iterative?arg1=badarg1'), qr/should be moved\/blocked before this./, 'iterative scoring 1 (pass)');
like(http_get('/iterative?arg1=badarg1&arg2=badarg2'), qr/should be moved\/blocked before this./, 'iterative scoring 2 (pass)');
like(http_get('/iterative?arg1=badarg1&arg2=badarg2&arg3=badarg3'), qr/^HTTP.*403/, 'iterative scoring 3 (block)');

View File

@ -0,0 +1,167 @@
#!/usr/bin/perl
# (C) Andrei Belov
# Tests for ModSecurity-nginx connector (modsecurity_transaction_id).
###############################################################################
use warnings;
use strict;
use Test::More;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->plan(5)->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
modsecurity_transaction_id "tid-HTTP-DEFAULT-$request_id";
server {
listen 127.0.0.1:8080;
server_name server1;
location / {
error_log %%TESTDIR%%/e_s1l1.log info;
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:1,log,deny,status:403"
SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block"
';
}
}
server {
listen 127.0.0.1:8080;
server_name server2;
modsecurity_transaction_id "tid-SERVER-DEFAULT-$request_id";
location / {
error_log %%TESTDIR%%/e_s2l1.log info;
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:1,log,deny,status:403"
SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block"
';
}
location /specific {
error_log %%TESTDIR%%/e_s2l2.log info;
modsecurity on;
modsecurity_transaction_id "tid-LOCATION-SPECIFIC-$request_id";
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:1,log,deny,status:403"
SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block"
';
}
location /debuglog {
modsecurity on;
modsecurity_transaction_id "tid-DEBUG-$request_id";
modsecurity_rules '
SecRuleEngine On
SecDebugLog %%TESTDIR%%/modsec_debug.log
SecDebugLogLevel 4
SecDefaultAction "phase:1,log,deny,status:403"
SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block"
';
}
location /auditlog {
modsecurity on;
modsecurity_transaction_id "tid-AUDIT-$request_id";
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:1,log,deny,status:403"
SecAuditEngine On
SecAuditLogParts A
SecAuditLog %%TESTDIR%%/modsec_audit.log
SecAuditLogType Serial
SecAuditLogStorageDir %%TESTDIR%%/
SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block"
';
}
}
}
EOF
$t->run();
###############################################################################
# charge limit_req
http(<<EOF);
GET /?what=block403 HTTP/1.0
Host: server1
EOF
isnt(lines($t, 'e_s1l1.log', 'unique_id "tid-HTTP-DEFAULT-'), 0, 'http default');
http(<<EOF);
GET /?what=block403 HTTP/1.0
Host: server2
EOF
isnt(lines($t, 'e_s2l1.log', 'unique_id "tid-SERVER-DEFAULT-'), 0, 'server default');
http(<<EOF);
GET /specific/?what=block403 HTTP/1.0
Host: server2
EOF
isnt(lines($t, 'e_s2l2.log', 'unique_id "tid-LOCATION-SPECIFIC-'), 0, 'location specific');
http(<<EOF);
GET /debuglog/?what=block403 HTTP/1.0
Host: server2
EOF
isnt(lines($t, 'modsec_debug.log', 'tid-DEBUG-'), 0, 'libmodsecurity debug log');
http(<<EOF);
GET /auditlog/?what=block403 HTTP/1.0
Host: server2
EOF
isnt(lines($t, 'modsec_audit.log', 'tid-AUDIT-'), 0, 'libmodsecurity audit log');
###############################################################################
sub lines {
my ($t, $file, $pattern) = @_;
my $path = $t->testdir() . '/' . $file;
open my $fh, '<', $path or return "$!";
my $value = map { $_ =~ /\Q$pattern\E/ } (<$fh>);
close $fh;
return $value;
}
###############################################################################

172
tests/modsecurity.t Normal file
View File

@ -0,0 +1,172 @@
#!/usr/bin/perl
#
# ModSecurity, http://www.modsecurity.org/
# Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/)
#
# You may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# If any of the files related to licensing are missing or if you have any
# other questions related to licensing please contact Trustwave Holdings, Inc.
# directly using the email address security@modsecurity.org.
#
# Tests for ModSecurity module.
###############################################################################
use warnings;
use strict;
use Test::More;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
my $t = Test::Nginx->new()->has(qw/http/);
$t->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
server {
listen 127.0.0.1:8080;
server_name localhost;
location / {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecRule ARGS "@streq whee" "id:10,phase:2"
SecRule ARGS "@streq whee" "id:11,phase:2"
';
}
location /phase1 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:1,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:1,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:1,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:1,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:1,status:403,block"
';
}
location /phase2 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:2,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:2,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:2,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:2,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:2,status:403,block"
';
}
location /phase3 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDefaultAction "phase:3,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:3,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:3,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:3,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:3,status:403,block"
';
}
location /phase4 {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecResponseBodyAccess On
SecDefaultAction "phase:4,log,deny,status:403"
SecRule ARGS "@streq redirect301" "id:1,phase:4,status:301,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq redirect302" "id:2,phase:4,status:302,redirect:http://www.modsecurity.org"
SecRule ARGS "@streq block401" "id:3,phase:4,status:401,block"
SecRule ARGS "@streq block403" "id:4,phase:4,status:403,block"
';
}
location /early-block {
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecResponseBodyAccess On
SecDefaultAction "phase:1,log,auditlog,pass"
SecDefaultAction "phase:2,log,auditlog,pass"
SecAction "id:900101,phase:1,nolog,pass,t:none,setvar:tx.trigger_phase1=1"
SecAction "id:900103,phase:1,nolog,pass,t:none,setvar:tx.trigger_phase3=1"
SecAction "id:900105,phase:1,nolog,pass,t:none,setvar:tx.trigger_phase5=1"
SecRule TX:TRIGGER_PHASE1 "@eq 1" "id:901111,phase:1,t:none,deny,log"
SecRule REQUEST_BODY "@rx attack" "id:901121,phase:2,t:none,deny,log"
SecRule TX:TRIGGER_PHASE3 "@eq 1" "id:901131,phase:3,t:none,deny,log"
SecRule RESPONSE_BODY "@rx ok" "id:901141,phase:4,t:none,deny,log"
SecRule TX:TRIGGER_PHASE5 "@eq 1" "id:901151,phase:5,t:none,pass,log,msg:\'This is the phase 5.\'"
';
}
}
}
EOF
$t->write_file("/phase1", "should be moved/blocked before this.");
$t->write_file("/phase2", "should be moved/blocked before this.");
$t->write_file("/phase3", "should be moved/blocked before this.");
$t->write_file("/phase4", "should not be moved/blocked, headers delivered before phase 4.");
$t->write_file("/early-block", "should be moved/blocked before this.");
$t->run();
$t->todo_alerts();
$t->plan(21);
###############################################################################
# Redirect (302)
like(http_get('/phase1?what=redirect302'), qr/^HTTP.*302/, 'redirect 302 - phase 1');
like(http_get('/phase2?what=redirect302'), qr/^HTTP.*302/, 'redirect 302 - phase 2');
like(http_get('/phase3?what=redirect302'), qr/^HTTP.*302/, 'redirect 302 - phase 3');
is(http_get('/phase4?what=redirect302'), '', 'redirect 302 - phase 4');
# Redirect (301)
like(http_get('/phase1?what=redirect301'), qr/^HTTP.*301/, 'redirect 301 - phase 1');
like(http_get('/phase2?what=redirect301'), qr/^HTTP.*301/, 'redirect 301 - phase 2');
like(http_get('/phase3?what=redirect301'), qr/^HTTP.*301/, 'redirect 301 - phase 3');
is(http_get('/phase4?what=redirect301'), '', 'redirect 301 - phase 4');
# Block (401)
like(http_get('/phase1?what=block401'), qr/^HTTP.*401/, 'block 401 - phase 1');
like(http_get('/phase2?what=block401'), qr/^HTTP.*401/, 'block 401 - phase 2');
like(http_get('/phase3?what=block401'), qr/^HTTP.*401/, 'block 401 - phase 3');
is(http_get('/phase4?what=block401'), '', 'block 401 - phase 4');
# Block (403)
like(http_get('/phase1?what=block403'), qr/^HTTP.*403/, 'block 403 - phase 1');
like(http_get('/phase2?what=block403'), qr/^HTTP.*403/, 'block 403 - phase 2');
like(http_get('/phase3?what=block403'), qr/^HTTP.*403/, 'block 403 - phase 3');
is(http_get('/phase4?what=block403'), '', 'block 403 - phase 4');
# Nothing to detect
like(http_get('/phase1?what=nothing'), qr/should be moved\/blocked before this./, 'nothing phase 1');
like(http_get('/phase2?what=nothing'), qr/should be moved\/blocked before this./, 'nothing phase 2');
like(http_get('/phase3?what=nothing'), qr/should be moved\/blocked before this./, 'nothing phase 3');
like(http_get('/phase4?what=nothing'), qr/should not be moved\/blocked, headers delivered before phase 4./, 'nothing phase 4');
# early block (https://github.com/SpiderLabs/ModSecurity-nginx/issues/238)
like(http_get('/early-block'), qr/^HTTP.*403/, 'early block 403 (https://github.com/SpiderLabs/ModSecurity-nginx/issues/238)');

39
tests/nginx-tests-cvt.pl Normal file
View File

@ -0,0 +1,39 @@
#!/usr/bin/perl
#
# Script to adjust nginx tests to include ModSecurity directives. It enables
# us to passively test nginx functionality with ModSecurity module enabled.
#
# sh command line variations:
#
# for i in *.t; do cp -n $i $i.orig; perl nginx-tests-cvt.pl < $i.orig > $i; done
# for i in *.t; do perl nginx-tests-cvt.pl < $i.orig > $i; done
# for i in *.t; do cp $i.orig $i; done
my $ignore = 0;
while (<STDIN>) {
print $_;
$ignore = 1 if (/^mail {/); # skip mail_*.t mail blocks
$ignore = 0 if (/^http {/);
next if ($ignore);
if (/^ *server_name .*;$/) {
next if (/^ *server_name *below;/); # skip duplication on refresh.t
next if (/^ *server_name *many4.example.com;/); # skip duplication on http_server_name.t
print "
modsecurity on;
modsecurity_rules '
SecRuleEngine On
SecDebugLogLevel 9
SecRule ARGS \"\@streq whee\" \"id:10,phase:2\"
SecRule ARGS \"\@streq whee\" \"id:11,phase:2\"
';
";
}
}