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:
commit
a676d333fd
|
@ -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
|
|
@ -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 .
|
|
@ -0,0 +1,2 @@
|
|||
zimmerle = Felipe Zimmerle <felipe@zimmerle.org>
|
||||
defanator = Andrei Belov <defanator@gmail.com>
|
|
@ -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
|
||||
|
|
@ -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.
|
|
@ -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 <path to rules file>*
|
||||
|
||||
**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 <key> <URL to rules>*
|
||||
|
||||
**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 <modsecurity rule>*
|
||||
|
||||
**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).
|
||||
|
||||
### Don’t 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 user’s 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 don’t forget to look for an
|
||||
existing issue before opening a new one.
|
||||
|
||||
Lastly, If you are planning to open an issue on GitHub, please don’t 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.
|
||||
|
||||
|
|
@ -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
|
|
@ -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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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."
|
||||
|
|
@ -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: */
|
|
@ -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);
|
||||
}
|
|
@ -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_ */
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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: */
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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/
|
|
@ -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');
|
|
@ -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
|
||||
}
|
||||
|
||||
###############################################################################
|
|
@ -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');
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
###############################################################################
|
|
@ -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');
|
||||
|
||||
|
|
@ -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');
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
###############################################################################
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
###############################################################################
|
|
@ -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");
|
||||
|
||||
}
|
||||
|
||||
###############################################################################
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
###############################################################################
|
|
@ -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)');
|
||||
}
|
||||
|
|
@ -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)');
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
###############################################################################
|
|
@ -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)');
|
|
@ -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\"
|
||||
';
|
||||
";
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue