├── .gitignore ├── .travis.yml ├── AUTHORS ├── CHANGES ├── LICENSE ├── Makefile.am ├── README.md ├── autogen.sh ├── build ├── apxs-wrapper.in ├── ax_prog_apache.m4 ├── find_apxs.m4 └── find_libmodsec.m4 ├── configure.ac ├── docs └── TODO.md ├── release.sh ├── src ├── mod_security3.c ├── mod_security3.h ├── msc_config.c ├── msc_config.h ├── msc_filters.c ├── msc_filters.h ├── msc_utils.c └── msc_utils.h ├── t ├── TEST ├── conf │ └── extra.conf.in ├── htdocs │ ├── block-evil-1-loc │ │ ├── evil │ │ └── index.html │ ├── block-evil-1 │ │ ├── evil │ │ └── index.html │ ├── block-evil-2-loc │ │ ├── evil │ │ └── index.html │ ├── block-evil-2 │ │ ├── evil │ │ ├── evil-2.pl │ │ └── index.html │ ├── block-evil-3-loc │ │ ├── evil │ │ └── index.html │ ├── block-evil-3 │ │ ├── asdf.html │ │ ├── evil │ │ └── index.html │ ├── block-evil-4-loc │ │ ├── evil │ │ └── index.html │ ├── block-evil-4 │ │ ├── evil │ │ └── index.html │ ├── block-evil-5-loc │ │ ├── evil │ │ └── index.html │ ├── block-evil-5 │ │ ├── evil │ │ └── index.html │ └── test.html ├── load-modsec.t ├── simple-block.t └── very-simple-test.t └── tests ├── .deps └── msc_test-msc_test.Po ├── regression ├── action │ ├── 00-disruptive-actions.t │ ├── 00-meta.t │ ├── 00-misc.t │ ├── 00-transformations.t │ ├── 10-ctl.t │ ├── 10-detectiononly-actions.t │ └── 10-logging.t ├── config │ ├── 00-load-modsec.t │ ├── 10-audit-directives.t │ ├── 10-debug-directives.t │ ├── 10-misc-directives.t │ ├── 10-request-directives.t │ ├── 10-response-directives.t │ └── 20-chroot.t ├── misc │ ├── 00-multipart-parser.t │ ├── 00-phases.t │ ├── 10-pcre.t │ ├── 20-status-engine.t │ ├── 25-libinjection.t │ ├── 40-secRemoteRules.t.in │ ├── 50-ipmatchfromfile-external.t.in │ └── 60-pmfromfile-external.t.in ├── rule │ ├── 00-basics.t │ ├── 00-inheritance.t │ ├── 10-xml.t │ ├── 15-json.t │ └── 20-exceptions.t ├── server_root │ ├── conf │ │ ├── SoapEnvelope-bad.dtd │ │ ├── SoapEnvelope-bad.xsd │ │ ├── SoapEnvelope.dtd │ │ ├── SoapEnvelope.xsd │ │ ├── httpd.conf.in │ │ ├── match.lua │ │ ├── ssdeep.txt │ │ └── test.lua │ ├── data │ │ ├── .empty │ │ └── ip.dir │ ├── htdocs │ │ ├── 8k.txt │ │ ├── index.html │ │ ├── test.pdf │ │ ├── test.txt │ │ └── test2.txt │ ├── logs │ │ ├── audit │ │ │ └── .empty │ │ └── subdir │ │ │ └── .empty │ ├── tmp │ │ └── .empty │ └── upload │ │ └── .empty └── target │ └── 00-targets.t └── run-regression-tests.pl.in /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.o 3 | *.la 4 | *.lai 5 | .libs/* 6 | src/.libs/* 7 | t/htdocs/index.html 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | os: linux 5 | 6 | language: c 7 | 8 | compiler: 9 | - gcc 10 | 11 | addons: 12 | apt: 13 | packages: 14 | - libyajl-dev 15 | - libgeoip-dev 16 | - liblmdb-dev 17 | - apache2 18 | - apache2-bin 19 | - apache2-data 20 | - apache2-dbg 21 | - apache2-dev 22 | - apache2-mpm-prefork 23 | - libwww-perl 24 | 25 | env: 26 | - VER_APACHE=2.2.34 27 | - VER_APACHE=2.4.29 28 | 29 | before_script: 30 | - curl -L https://cpanmin.us | sudo perl - --sudo App::cpanminus 31 | - cd .. 32 | - curl https://archive.apache.org/dist/httpd/httpd-${VER_APACHE}.tar.gz > httpd-${VER_APACHE}.tar.gz 33 | - tar xvzf httpd-${VER_APACHE}.tar.gz 34 | - cd httpd-${VER_APACHE} 35 | - ./configure --with-mpm=worker 36 | - make -j4 37 | - sudo make install 38 | - cd - 39 | - sudo cpanm --quiet --notest --skip-satisfied CGI::Cookie 40 | - sudo cpanm --quiet --notest --skip-satisfied Apache::TestRun 41 | - git clone https://github.com/SpiderLabs/ModSecurity.git 42 | - cd ModSecurity 43 | - git checkout v3/master 44 | - git submodule init 45 | - git submodule update 46 | - ./build.sh 47 | - ./configure --without-lmdb 48 | - make 49 | - sudo make install 50 | - pwd 51 | - cd - 52 | - cd ModSecurity-apache 53 | - ./autogen.sh 54 | - export PATH="/usr/local/apache2/bin/:$PATH" 55 | - ls -la /usr/local/apache2/bin/ 56 | - ls -la /usr/local/apache2/include/ 57 | - ./configure --with-apxs=/usr/local/apache2/bin/apxs --with-libmodsecurity=/usr/local/modsecurity 58 | - make 59 | - sudo make install 60 | - pwd 61 | 62 | script: 63 | - t/TEST -apxs /usr/local/apache2/bin/apxs 64 | 65 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | tahirramzan = Tahir Ramzan 2 | zimmerle = Felipe Zimmerle 3 | csanders-git = Chaim Sanders 4 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | v3.0.x - YYYY-MMM-DD (To be released) 2 | ------------------------------------- 3 | 4 | - Asorted fixies on libmodsecurity autoconf. 5 | [Issue #51 - @airween] 6 | - Fix: avoids crash if UNIQUE_ID is not set 7 | [Issue #46 - @Goron1606, @victorhora, @zimmerle] 8 | - Adds make install to the build system 9 | [Issue #36 - @zimmerle, @ROBERT-MCDOWELL] 10 | - Uses mod_unique if available 11 | [Issue #42 - @zimmerle, @victorhora, @Goron1606] 12 | - Fix duplicate HTTP protocol strings 13 | [Issue #21 - @victorhora] 14 | 15 | 16 | v0.0.9-beta1 - 2017-Dec-22 17 | -------------------------- 18 | 19 | - First version of ModSecurity-apache connector 20 | 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ACLOCAL_AMFLAGS = -I build 2 | 3 | MAINTAINERCLEANFILES = \ 4 | config.log \ 5 | Makefile.in \ 6 | aclocal.m4 \ 7 | compile \ 8 | configure \ 9 | install-sh \ 10 | missing \ 11 | modsec-shared-collections \ 12 | modsec-shared-collections-lock 13 | 14 | CLEANFILES = \ 15 | t/logs/* \ 16 | t/htdocs/index.html \ 17 | t/conf/extra.conf \ 18 | t/conf/httpd.conf \ 19 | t/conf/apache_test_config.pm \ 20 | t/conf/httpd.conf \ 21 | t/conf/mime.types \ 22 | t/conf/modules.conf \ 23 | src/*.lo \ 24 | src/*.slo \ 25 | src/*.o \ 26 | src/*.so \ 27 | src/.libs/*.so 28 | 29 | 30 | CLEANDIRECTORIES = \ 31 | t/logs \ 32 | src/.libs 33 | 34 | 35 | all: 36 | chmod +x build/apxs-wrapper 37 | build/apxs-wrapper 38 | 39 | test: 40 | cd t/ && ./TEST -clean 41 | cd t/ && ./TEST -configure 42 | cd t/ && ./TEST -httpd_conf conf/httpd.conf -httpd @APACHE@ -apxs @APXS@ 43 | 44 | 45 | install-exec-hook: $(pkglib_LTLIBRARIES) 46 | @APXS@ -i -n mod_security3 ./src/.libs/mod_security3.so 47 | 48 | 49 | .PHONY: all 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [![Build Status](https://travis-ci.org/SpiderLabs/ModSecurity-apache.svg?branch=master)](https://travis-ci.org/SpiderLabs/ModSecurity-apache) 5 | [![](https://raw.githubusercontent.com/ZenHubIO/support/master/zenhub-badge.png)](https://zenhub.com) 6 | 7 | 8 | The ModSecurity-apache connector is the connection point between Apache and libmodsecurity (ModSecurity v3). Said another way, this project provides a communication channel between Apache and libmodsecurity. This connector is required to use LibModSecurity with Apache. 9 | 10 | The ModSecurity-apache connector takes the form of an Apache module. The module simply serves as a layer of communication between Apache and ModSecurity. 11 | 12 | Notice that this project depends on libmodsecurity rather than ModSecurity (version 2.9 or less). 13 | 14 | 15 | ### NOTE: This project is not production ready 16 | 17 | This project should be considered under development and not production ready. The functionality is not complete and so should not be used. With Apache HTTP Server, the recommended version of ModSecurity is v2.9.x. 18 | 19 | 20 | ### What is the difference between this project and the old ModSecurity module for Apache? 21 | 22 | The old version of ModSecurity was originally designed for and contained within an Apache module. This current version abstracts out some of the details allowing ModSecurity to more easily support multiple platforms and features beyond the scope of what Apache internals currently support. As a result using the new libmodsecurity engine is no longer reliant on the use of Apache and can be used to power multiple different connectors. As a result of this the current version is more flexible, has wider support, and allows for the support of new functionality that was not previously possible. 23 | 24 | 25 | # Compilation 26 | 27 | Before you compile this software make sure that you have libmodsecurity installed. 28 | You can download it from the ModSecurity git repository. For information pertaining to the compilation and installation of libmodsecurity please consult the documentation provided along with it. 29 | 30 | With libmodsecurity installed, you can proceed with the installation of the ModSecurity-apache connector. Run the following commands: 31 | 32 | ``` 33 | $ ./autogen.sh 34 | $ ./configure 35 | $ make 36 | $ sudo make install 37 | ``` 38 | 39 | # Usage 40 | 41 | libModSecurity for Apache extends your configuration directives. In order to load the module into Apache add the following directive to your Apache configuration: 42 | 43 | ``` 44 | LoadModule security3_module modules/mod_security3.so 45 | ``` 46 | 47 | The module adds new directives to Apache and some of them are documented below: 48 | 49 | modsecurity 50 | ----------- 51 | **syntax:** *modsecurity on | off* 52 | 53 | **context:** *http, server, location* 54 | 55 | **default:** *off* 56 | 57 | Turns on or off the ModSecurity functionality. 58 | Note that this configuration directive is no longer related to the SecRule state. 59 | Instead, it now serves solely as an Apache flag to enable or disable the module. 60 | 61 | modsecurity_rules_file 62 | ---------------------- 63 | **syntax:** *modsecurity_rules_file <path to rules file>* 64 | 65 | **context:** *http, server, location* 66 | 67 | **default:** *no* 68 | 69 | Specifies the location of the ModSecurity configuration file, e.g.: 70 | 71 | ``` 72 | modsecurity on 73 | modsecurity_rules_file /etc/my_modsecurity_rules.conf; 74 | ``` 75 | 76 | modsecurity_rules_remote 77 | ------------------------ 78 | **syntax:** *modsecurity_rules_remote <key> <URL to rules>* 79 | 80 | **context:** *http, server, location* 81 | 82 | **default:** *no* 83 | 84 | Specifies from where (on the internet) a modsecurity configuration file will be downloaded. 85 | It also specifies the key that will be used to authenticate to that server: 86 | 87 | ``` 88 | modsecurity on 89 | modsecurity_rules_remote my-server-key https://my-own-server/rules/download; 90 | ``` 91 | 92 | modsecurity_rules 93 | ----------------- 94 | **syntax:** *modsecurity_rules <modsecurity rule>* 95 | 96 | **context:** *http, server, location* 97 | 98 | **default:** *no* 99 | 100 | Allows for the direct inclusion of a ModSecurity rule into the Apache configuration. 101 | The following example is loading rules from a file and injecting specific configurations per directory/alias: 102 | 103 | ``` 104 | TODO 105 | ``` 106 | 107 | modsecurity_transaction_id 108 | -------------------------- 109 | **syntax:** *modsecurity_transaction_id string* 110 | 111 | **context:** *http, server, location* 112 | 113 | **default:** *no* 114 | 115 | Allows to pass transaction ID from Apache instead of generating it in the library. 116 | This can be useful for tracing purposes, e.g. consider this configuration: 117 | 118 | ``` 119 | TODO 120 | ``` 121 | 122 | # Contributing 123 | 124 | 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 125 | functionality, bug fixes, bug reports, beginners user support, and anything else that you 126 | are willing to help with. Thank you. 127 | 128 | ## Providing Patches 129 | 130 | We prefer to have your patch within the GitHub infrastructure to facilitate our 131 | review work, and our QA integration. GitHub provides an excellent 132 | documentation on how to perform “Pull Requests”. More information available 133 | here: https://help.github.com/articles/using-pull-requests/ 134 | 135 | Please respect the coding style in use. Pull requests can include various commits, so provide one fix or one piece of functionality per commit. Please do not change anything outside the scope of your target work (e.g. coding style in a function that you have passed by). For further information about the coding style used in this project, please check: https://www.chromium.org/blink/coding-style 136 | 137 | ### Don’t know where to start? 138 | 139 | Within our code there are various items marked as TODO or FIXME that may need 140 | your attention. Check the list of items by performing a grep: 141 | 142 | ``` 143 | $ cd /path/to/modsecurity-apache 144 | $ egrep -Rin "TODO|FIXME" -R * 145 | ``` 146 | 147 | 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. 148 | 149 | ### Testing your patch 150 | 151 | Along with the manual testing, we strongly recommend that you to use the Apache test 152 | utility to make sure that you patch does not adversly affect the behavior or performance of Apache. 153 | 154 | The Apache testing tools are available on: http://httpd.apache.org/test/ 155 | 156 | To use those tests .... 157 | #TODO# 158 | 159 | If you are facing problems getting your added functionality to pass all the Apache tests, feel free to contact us or the Apache mailing list at: http://httpd.apache.org/lists.html 160 | 161 | ### Debugging 162 | Because the ModSecurity Apache Connector runs as part of Apache, one needs to debug the Apache process. Debugging may require several steps. In general debugging can be enabled by compiling the Apache connector with debugging as follows: 163 | ```CFLAGS="-g -O0" ./configure ...normal configure parameters...)``` 164 | 165 | It is recommended that one keeps the debugging process as simple as possible, to do so, the elimination of features such as multi-threading by the HTTP server is recommended. A special "--with-debug" option can also be used during the compilation of the Apache Connector that will enable the connector's debug messages. 166 | 167 | Apache webservers accept a special command line parameter: "-X", that starts the server in debug mode and doesn't detach it from the console. This flag should be passed straight to the apache2 or httpd binary, along with any other options, such as the configuration file that should be used. The parameter should not be passed to the apachectl script, instead, the http/apache2 file should be used directly. If you are using Ubuntu your Apache will probably be at: /usr/sbin/apache2. If you are using Fedora this will probably be at: /usr/sbin/httpd. 168 | 169 | This setup may affect the behavior of the HTTP server in a way that makes impossible or more difficult to reproduce a given bug, if this is the case, you may wish to ask for help in our mailing list and check out Apache's debugging instructions at: https://httpd.apache.org/dev/debugging.html. 170 | 171 | ## Reporting Issues 172 | 173 | If you are facing a configuration issue or if something is not working as you 174 | expect it to be, please use ModSecurity user’s mailing list. Issues on GitHub 175 | are also welcome, but we prefer to have users question on the mailing list first, 176 | where you can reach an entire community. Also don’t forget to look for an 177 | existing issue before opening a new one. 178 | 179 | Lastly, If you are planning to open an issue on GitHub, please don’t forget to tell us the 180 | version of your libmodsecurity and the version of the Apache connector you are running. 181 | 182 | ### Security issue 183 | 184 | Please do not publicly report any security issue. Instead, contact us at: 185 | security@modsecurity.org to report the issue. Once the problem is fixed we will provide you with credit for the discovery. 186 | 187 | ## Feature Request 188 | 189 | 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. 190 | 191 | ## Packing 192 | 193 | Having our packages in distros on time is something we highly desire. Let us know if 194 | there is anything we can do to facilitate your work as a packager. 195 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -rf autom4te.cache 4 | rm -f aclocal.m4 5 | #case `uname` in Darwin*) glibtoolize --force --copy ;; 6 | # *) libtoolize --force --copy ;; esac 7 | autoreconf --install 8 | #autoheader 9 | #automake --add-missing --foreign --copy --force-missing 10 | #autoconf --force 11 | rm -rf autom4te.cache 12 | -------------------------------------------------------------------------------- /build/apxs-wrapper.in: -------------------------------------------------------------------------------- 1 | #!@SHELL@ 2 | 3 | WRAPPED_OPTS="" 4 | for opt in "$@"; do 5 | case "$opt" in 6 | # Fix for -R not working w/apxs 7 | -R*) WRAPPED_OPTS="$WRAPPED_OPTS -Wl,$opt" ;; 8 | # OSF1 compiler option 9 | -pthread) WRAPPED_OPTS="$WRAPPED_OPTS -Wc,$opt" ;; 10 | # Unwrapped 11 | *) WRAPPED_OPTS="$WRAPPED_OPTS $opt" ;; 12 | esac 13 | done 14 | 15 | exec @APXS@ -Wc,-fPIC -Wc,-O0 -a -c -I @V3INCLUDE@ -L @V3LIB@ -lmodsecurity src/mod_security3.c src/msc_config.c src/msc_filters.c src/msc_utils.c 16 | -------------------------------------------------------------------------------- /build/ax_prog_apache.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # http://www.gnu.org/software/autoconf-archive/ax_prog_apache.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_PROG_APACHE([version]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # This macro searches for an installed apache server. If nothing was 12 | # specified when calling configure or just --with-apache, it searches in 13 | # /usr/local/apache/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin 14 | # The argument of --with-apache specifies the full pathname of the httpd 15 | # argument. For instance --with-apache=/usr/sbin/httpd. 16 | # 17 | # If the version argument is given, AX_PROG_APACHE checks that the apache 18 | # server is this version number or higher. 19 | # 20 | # If the apache server is not found, abort configuration with error 21 | # message. 22 | # 23 | # It defines the symbol APACHE if the server is found. 24 | # 25 | # Files using apache should do the following: 26 | # 27 | # @APACHE@ -d /etc/httpd 28 | # 29 | # It defines the symbol APACHE_MODULES if a directory containing mod_env.* 30 | # is found in the default server root directory (obtained with httpd -V). 31 | # 32 | # The httpd.conf file listing modules to be loaded dynamicaly can use 33 | # @APACHE_MODULES@ to grab them in the appropriate sub directory. For 34 | # instance: 35 | # 36 | # ... 37 | # 38 | # LoadModule env_module @APACHE_MODULES@/mod_env.so 39 | # LoadModule config_log_module @APACHE_MODULES@/mod_log_config.so 40 | # ... 41 | # 42 | # LICENSE 43 | # 44 | # Copyright (c) 2008 Loic Dachary 45 | # 46 | # This program is free software; you can redistribute it and/or modify it 47 | # under the terms of the GNU General Public License as published by the 48 | # Free Software Foundation; either version 2 of the License, or (at your 49 | # option) any later version. 50 | # 51 | # This program is distributed in the hope that it will be useful, but 52 | # WITHOUT ANY WARRANTY; without even the implied warranty of 53 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 54 | # Public License for more details. 55 | # 56 | # You should have received a copy of the GNU General Public License along 57 | # with this program. If not, see . 58 | # 59 | # As a special exception, the respective Autoconf Macro's copyright owner 60 | # gives unlimited permission to copy, distribute and modify the configure 61 | # scripts that are the output of Autoconf when processing the Macro. You 62 | # need not follow the terms of the GNU General Public License when using 63 | # or distributing such scripts, even though portions of the text of the 64 | # Macro appear in them. The GNU General Public License (GPL) does govern 65 | # all other use of the material that constitutes the Autoconf Macro. 66 | # 67 | # This special exception to the GPL applies to versions of the Autoconf 68 | # Macro released by the Autoconf Archive. When you make and distribute a 69 | # modified version of the Autoconf Macro, you may extend this special 70 | # exception to the GPL to apply to your modified version as well. 71 | 72 | #serial 4 73 | 74 | AU_ALIAS([AC_PROG_APACHE], [AX_PROG_APACHE]) 75 | AC_DEFUN([AX_PROG_APACHE], 76 | # 77 | # Handle user hints 78 | # 79 | [ 80 | AC_MSG_CHECKING(if apache is wanted) 81 | AC_ARG_WITH(apache, 82 | [ --with-apache=PATH absolute path name of apache server (default is to search httpd in 83 | /usr/local/apache/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin), 84 | --without-apache to disable apache detection], 85 | [ 86 | # 87 | # Run this if -with or -without was specified 88 | # 89 | if test "$withval" != no ; then 90 | AC_MSG_RESULT(yes) 91 | APACHE_WANTED=yes 92 | if test "$withval" != yes ; then 93 | APACHE="$withval" 94 | fi 95 | else 96 | APACHE_WANTED=no 97 | AC_MSG_RESULT(no) 98 | fi 99 | ], [ 100 | # 101 | # Run this if nothing was said 102 | # 103 | APACHE_WANTED=yes 104 | AC_MSG_RESULT(yes) 105 | ]) 106 | # 107 | # Now we know if we want apache or not, only go further if 108 | # it's wanted. 109 | # 110 | if test $APACHE_WANTED = yes ; then 111 | # 112 | # If not specified by caller, search in standard places 113 | # 114 | if test -z "$APACHE" ; then 115 | AC_PATH_PROG(APACHE, httpd, , /usr/local/apache/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/usr/local/apache2/bin) 116 | fi 117 | if test -z "$APACHE" ; then 118 | AC_PATH_PROG(APACHE, apache2, , /usr/local/apache/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/usr/local/apache2/bin) 119 | fi 120 | AC_SUBST(APACHE) 121 | if test -z "$APACHE" ; then 122 | AC_MSG_ERROR("apache server executable not found"); 123 | fi 124 | # 125 | # Collect apache version number. If for nothing else, this 126 | # guaranties that httpd is a working apache executable. 127 | # 128 | changequote(<<, >>)dnl 129 | APACHE_READABLE_VERSION=`$APACHE -v | grep 'Server version' | sed -e 's;.*Apache/\([0-9\.][0-9\.]*\).*;\1;'` 130 | changequote([, ])dnl 131 | APACHE_VERSION=`echo $APACHE_READABLE_VERSION | sed -e 's/\.//g'` 132 | if test -z "$APACHE_VERSION" ; then 133 | AC_MSG_ERROR("could not determine apache version number"); 134 | fi 135 | APACHE_MAJOR=`expr $APACHE_VERSION : '\(..\)'` 136 | APACHE_MINOR=`expr $APACHE_VERSION : '..\(.*\)'` 137 | # 138 | # Check that apache version matches requested version or above 139 | # 140 | if test -n "$1" ; then 141 | AC_MSG_CHECKING(apache version >= $1) 142 | APACHE_REQUEST=`echo $1 | sed -e 's/\.//g'` 143 | APACHE_REQUEST_MAJOR=`expr $APACHE_REQUEST : '\(..\)'` 144 | APACHE_REQUEST_MINOR=`expr $APACHE_REQUEST : '..\(.*\)'` 145 | if test "$APACHE_MAJOR" -lt "$APACHE_REQUEST_MAJOR" -o "$APACHE_MINOR" -lt "$APACHE_REQUEST_MINOR" ; then 146 | AC_MSG_RESULT(no) 147 | AC_MSG_ERROR(apache version is $APACHE_READABLE_VERSION) 148 | else 149 | AC_MSG_RESULT(yes) 150 | fi 151 | fi 152 | # 153 | # Find out if .so modules are in libexec/module.so or modules/module.so 154 | # 155 | if test -f /etc/apache2/envvars 156 | then 157 | HTTP_ROOT=`. /etc/apache2/envvars && $APACHE -V | grep HTTPD_ROOT | sed -e 's/.*"\(.*\)"/\1/'` 158 | else 159 | HTTP_ROOT=`$APACHE -V | grep HTTPD_ROOT | sed -e 's/.*"\(.*\)"/\1/'` 160 | fi 161 | AC_MSG_CHECKING(apache modules) 162 | for dir in libexec modules 163 | do 164 | if test -f $HTTP_ROOT/$dir/mod_env.* 165 | then 166 | APACHE_MODULES=$dir 167 | fi 168 | done 169 | if test -z "$APACHE_MODULES" 170 | then 171 | AC_MSG_RESULT(not found) 172 | else 173 | AC_MSG_RESULT(in $HTTP_ROOT/$APACHE_MODULES) 174 | fi 175 | AC_SUBST(APACHE_MODULES) 176 | fi 177 | ]) 178 | -------------------------------------------------------------------------------- /build/find_apxs.m4: -------------------------------------------------------------------------------- 1 | # https://www.gnu.org/software/autoconf-archive/ax_prog_apache.html#ax_prog_apache 2 | AC_DEFUN([FIND_APXS],[ 3 | AC_MSG_NOTICE(looking for Apache module support via DSO through APXS) 4 | # Check if the user provided --with-axps 5 | AC_ARG_WITH(apxs, 6 | [AS_HELP_STRING([[--with-apxs=FILE]], 7 | [FILE is the path to apxs; defaults to "apxs".])], 8 | [ 9 | if test "$withval" = "yes"; then 10 | APXS=apxs 11 | else 12 | APXS="$withval" 13 | fi 14 | ]) 15 | 16 | if test -z "$APXS"; then 17 | for i in /usr/local/apache22/bin \ 18 | /usr/local/apache2/bin \ 19 | /usr/local/apache/bin \ 20 | /usr/local/sbin \ 21 | /usr/local/bin \ 22 | /usr/sbin \ 23 | /usr/bin; 24 | do 25 | if test -f "$i/apxs2"; then 26 | APXS="$i/apxs2" 27 | break 28 | elif test -f "$i/apxs"; then 29 | APXS="$i/apxs" 30 | break 31 | fi 32 | done 33 | fi 34 | if test -n "$APXS" -a "$APXS" != "no" -a -x "$APXS" ; then 35 | AC_MSG_NOTICE(found APXS at $APXS) 36 | else 37 | AC_MSG_ERROR(couldn't find APXS) 38 | fi 39 | ]) 40 | 41 | -------------------------------------------------------------------------------- /build/find_libmodsec.m4: -------------------------------------------------------------------------------- 1 | # https://www.gnu.org/software/autoconf-archive/ax_prog_apache.html#ax_prog_apache 2 | AC_DEFUN([FIND_LIBMOD],[ 3 | AC_MSG_NOTICE(looking for libmodsecurity) 4 | # Check if the user provided --with-libmodsecurity 5 | AC_ARG_WITH(libmodsecurity, 6 | [AS_HELP_STRING([[--with-libmodsecurity=FILE]], 7 | [FILE is the path to libmodsecurity install dir; defaults to "/usr/local/modsecurity/".])], 8 | [ 9 | if test "$withval" = "yes"; then 10 | AC_SUBST(CPPFLAGS, "$CPPFLAGS -I/usr/local/modsecurity/include/ -L/usr/local/modsecurity/lib/") 11 | V3INCLUDE="/usr/local/modsecurity/include/" 12 | V3LIB="/usr/local/modsecurity/lib/" 13 | else 14 | AC_SUBST(CPPFLAGS, "$CPPFLAGS -I${withval}/include/ -L${withval}/lib/") 15 | V3INCLUDE="${withval}/include/" 16 | V3LIB="${withval}/lib/" 17 | fi 18 | ]) 19 | 20 | dnl Check the ModSecurity libraries (modsecurity) 21 | 22 | AC_CHECK_LIB([modsecurity], [msc_init], [ 23 | AC_DEFINE([HAVE_MODSECURITYLIB], [1], 24 | [Define to 1 if you have the `libmodsecurity' library (-lmodsecurity).])], [ 25 | AC_MSG_ERROR([ModSecurity libraries not found!])]) 26 | 27 | AC_CHECK_HEADERS([modsecurity/modsecurity.h], [], [ 28 | AC_MSG_ERROR([ModSecurity headers not found...])]) 29 | ]) 30 | 31 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | 2 | AC_INIT([ModSecurity-Apache], [0.01],[support@modsecurity.org]) 3 | AC_PREREQ(2.68) 4 | AC_CONFIG_SRCDIR([LICENSE]) 5 | AC_CONFIG_MACRO_DIR([build]) 6 | #AC_CONFIG_AUX_DIR([build]) 7 | # Show erors and do not conform to GNU standards 8 | AM_INIT_AUTOMAKE([-Wall -Werror foreign]) 9 | FIND_APXS() 10 | FIND_LIBMOD() 11 | AX_PROG_APACHE() 12 | AC_PATH_PROGS(PERL, [perl perl5], ) 13 | AC_SUBST(APXS) 14 | AC_SUBST(V3LIB) 15 | AC_SUBST(V3INCLUDE) 16 | AC_SUBST(APACHE) 17 | 18 | # Some directories 19 | MSC_BASE_DIR=`pwd` 20 | MSC_PKGBASE_DIR="$MSC_BASE_DIR/.." 21 | MSC_TEST_DIR="$MSC_BASE_DIR/tests" 22 | MSC_REGRESSION_DIR="$MSC_TEST_DIR/regression" 23 | MSC_REGRESSION_SERVERROOT_DIR="$MSC_REGRESSION_DIR/server_root" 24 | MSC_REGRESSION_CONF_DIR="$MSC_REGRESSION_SERVERROOT_DIR/conf" 25 | MSC_REGRESSION_LOGS_DIR="$MSC_REGRESSION_SERVERROOT_DIR/logs" 26 | MSC_REGRESSION_DOCROOT_DIR="$MSC_REGRESSION_SERVERROOT_DIR/htdocs" 27 | 28 | if test -f /etc/apache2/envvars 29 | then 30 | SERVER_MPM=`. /etc/apache2/envvars && $APACHE -V | grep Server\ MPM | awk '{print $3}'` 31 | SERVER_MPM_MODE=`. /etc/apache2/envvars && $APACHE -M | grep mpm_${SERVER_MPM}_module | awk '{print $2}' | sed -e 's/@<:@\@{:@\@:}@@:>@//g'` 32 | SERVER_AUTHZ_MODE=`. /etc/apache2/envvars && $APACHE -M | grep authz_core_module | awk '{print $2}' | sed -e 's/@<:@\@{:@\@:}@@:>@//g'` 33 | else 34 | SERVER_MPM=`$APACHE -V | grep Server\ MPM | awk '{print $3}'` 35 | SERVER_MPM_MODE=`$APACHE -M | grep mpm_${SERVER_MPM}_module | awk '{print $2}' | sed -e 's/@<:@\@{:@\@:}@@:>@//g'` 36 | SERVER_AUTHZ_MODE=`$APACHE -M | grep authz_core_module | awk '{print $2}' | sed -e 's/@<:@\@{:@\@:}@@:>@//g'` 37 | fi 38 | 39 | AC_SUBST(MSC_BASE_DIR) 40 | AC_SUBST(MSC_PKGBASE_DIR) 41 | AC_SUBST(MSC_TEST_DIR) 42 | AC_SUBST(MSC_REGRESSION_DIR) 43 | AC_SUBST(MSC_REGRESSION_SERVERROOT_DIR) 44 | AC_SUBST(MSC_REGRESSION_CONF_DIR) 45 | AC_SUBST(MSC_REGRESSION_LOGS_DIR) 46 | AC_SUBST(MSC_REGRESSION_DOCROOT_DIR) 47 | 48 | AC_SUBST(SERVER_MPM) 49 | AC_SUBST(SERVER_MPM_MODE) 50 | AC_SUBST(SERVER_AUTHZ_MODE) 51 | 52 | echo "Found Apache with MPM ${SERVER_MPM}, ${SERVER_MPM_MODE}." 53 | 54 | APXS_SBINDIR="`$APXS -q SBINDIR`" 55 | APXS_PROGNAME="`$APXS -q PROGNAME`" 56 | 57 | APXS_HTTPD="$APXS_SBINDIR/$APXS_PROGNAME" 58 | AC_SUBST(APXS_HTTPD) 59 | APXS_LIBEXECDIR="`$APXS -q LIBEXECDIR`" 60 | if test "xx$APXS_LIBEXECDIR" = "xx"; then APXS_LIBEXECDIR="`$APXS -q LIBDIR`/modules"; fi 61 | AC_SUBST(APXS_LIBEXECDIR) 62 | 63 | # generating apache depends loadable modules 64 | # authz_core required, if not static 65 | # one mpm required, if not static 66 | echo "" > t/conf/modules.conf 67 | 68 | if @<:@ ${SERVER_AUTHZ_MODE} == "shared" @:>@; then 69 | echo "LoadModule authz_core_module ${APXS_LIBEXECDIR}/mod_authz_core.so" >> t/conf/modules.conf 70 | fi 71 | if @<:@ ${SERVER_MPM_MODE} == "shared" @:>@; then 72 | echo "LoadModule mpm_${SERVER_MPM}_module ${APXS_LIBEXECDIR}/mod_mpm_${SERVER_MPM}.so" >> t/conf/modules.conf 73 | fi 74 | echo "" >> t/conf/modules.conf 75 | 76 | 77 | AC_CONFIG_FILES([\ 78 | Makefile \ 79 | build/apxs-wrapper \ 80 | tests/regression/server_root/conf/httpd.conf \ 81 | tests/regression/misc/40-secRemoteRules.t \ 82 | tests/regression/misc/60-pmfromfile-external.t \ 83 | tests/regression/misc/50-ipmatchfromfile-external.t \ 84 | tests/run-regression-tests.pl 85 | ]) 86 | 87 | #[chmod +x build/apxs-wrapper]) 88 | 89 | AC_PROG_CC 90 | AC_OUTPUT 91 | -------------------------------------------------------------------------------- /docs/TODO.md: -------------------------------------------------------------------------------- 1 | #TO-DO 2 | 3 | 1. Fix Headers I/O 4 | 2. Pass Rules and Configurations files 5 | 3. Complete All Phases of Processing 6 | 4. Testing and Debugging 7 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git clean -xfdi 4 | git submodule foreach --recursive git clean -xfdi 5 | 6 | VERSION=`git describe --tags` 7 | DIR_NAME="modsecurity-apache-$VERSION" 8 | TAR_NAME="modsecurity-apache-$VERSION.tar.gz" 9 | 10 | MY_DIR=${PWD##*/} 11 | 12 | cd .. 13 | tar --transform "s/^$MY_DIR/$DIR_NAME/" -cvzf $TAR_NAME --exclude .git $MY_DIR 14 | 15 | sha256sum $TAR_NAME > $TAR_NAME.sha256 16 | gpg --detach-sign -a $TAR_NAME 17 | 18 | cd - 19 | echo $TAR_NAME ": done." 20 | 21 | -------------------------------------------------------------------------------- /src/mod_security3.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include "mod_security3.h" 5 | #include "msc_utils.h" 6 | #include "msc_config.h" 7 | 8 | /* 9 | * 10 | */ 11 | msc_global *msc_apache; 12 | 13 | 14 | void modsecurity_log_cb(void *log, const void* data) 15 | { 16 | const char *msg; 17 | if (log == NULL || data == NULL) { 18 | return; 19 | } 20 | msg = (const char *) data; 21 | request_rec *r = (request_rec *) log; 22 | 23 | #if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2 24 | ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r, 25 | msg, 26 | r->status); 27 | 28 | #else 29 | ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r->server, 30 | msg, 31 | r->status); 32 | #endif 33 | 34 | } 35 | 36 | int process_intervention (Transaction *t, request_rec *r) 37 | { 38 | ModSecurityIntervention intervention; 39 | intervention.status = N_INTERVENTION_STATUS; 40 | intervention.url = NULL; 41 | intervention.log = NULL; 42 | intervention.disruptive = 0; 43 | 44 | int z = msc_intervention(t, &intervention); 45 | 46 | if (z == 0) 47 | { 48 | return N_INTERVENTION_STATUS; 49 | } 50 | 51 | if (intervention.log == NULL) 52 | { 53 | intervention.log = "(no log message was specified)"; 54 | } 55 | 56 | if (intervention.status == 301 || intervention.status == 302 57 | ||intervention.status == 303 || intervention.status == 307) 58 | { 59 | if (intervention.url != NULL) 60 | { 61 | apr_table_setn(r->headers_out, "Location", intervention.url); 62 | return HTTP_MOVED_TEMPORARILY; 63 | } 64 | } 65 | 66 | if (intervention.status != N_INTERVENTION_STATUS) 67 | { 68 | return intervention.status; 69 | } 70 | 71 | return N_INTERVENTION_STATUS; 72 | } 73 | 74 | 75 | /* 76 | * Called only once. Used to initialise the ModSecurity 77 | * 78 | */ 79 | int msc_apache_init(apr_pool_t *mp) 80 | { 81 | msc_apache = apr_pcalloc(mp, sizeof(msc_global)); 82 | if (msc_apache == NULL) 83 | { 84 | goto err_no_mem; 85 | } 86 | 87 | msc_apache->modsec = msc_init(); 88 | 89 | msc_set_connector_info(msc_apache->modsec, MSC_APACHE_CONNECTOR); 90 | 91 | apr_pool_cleanup_register(mp, NULL, msc_module_cleanup, apr_pool_cleanup_null); 92 | 93 | msc_set_log_cb(msc_apache->modsec, modsecurity_log_cb); 94 | 95 | return 0; 96 | 97 | err_no_mem: 98 | return -1; 99 | } 100 | 101 | 102 | /* 103 | * Called only once. Used to cleanup ModSecurity 104 | * 105 | */ 106 | int msc_apache_cleanup() 107 | { 108 | msc_cleanup(msc_apache->modsec); 109 | } 110 | 111 | 112 | /* 113 | * Used to cleanup the module 114 | * 115 | */ 116 | static apr_status_t msc_module_cleanup(void *data) 117 | { 118 | msc_apache_cleanup(); 119 | return APR_SUCCESS; 120 | } 121 | 122 | 123 | 124 | /** 125 | * Stores transaction context where it can be found in subsequent 126 | * phases, redirections, or subrequests. 127 | */ 128 | static void store_tx_context(msc_t *msr, request_rec *r) 129 | { 130 | apr_table_setn(r->notes, NOTE_MSR, (void *)msr); 131 | } 132 | 133 | 134 | static msc_t *create_tx_context(request_rec *r) { 135 | msc_t *msr = NULL; 136 | msc_conf_t *z = NULL; 137 | char *unique_id = NULL; 138 | 139 | z = (msc_conf_t *)ap_get_module_config(r->per_dir_config, 140 | &security3_module); 141 | 142 | msr = (msc_t *)apr_pcalloc(r->pool, sizeof(msc_t)); 143 | if (msr == NULL) { 144 | return NULL; 145 | } 146 | 147 | msr->r = r; 148 | unique_id = getenv("UNIQUE_ID"); 149 | if (unique_id != NULL && strlen(unique_id) > 0) { 150 | msr->t = msc_new_transaction_with_id(msc_apache->modsec, 151 | z->rules_set, unique_id, (void *)r); 152 | } else { 153 | msr->t = msc_new_transaction(msc_apache->modsec, 154 | z->rules_set, (void *)r); 155 | } 156 | 157 | store_tx_context(msr, r); 158 | 159 | return msr; 160 | } 161 | 162 | 163 | /** 164 | * Retrieves a previously stored transaction context by 165 | * looking at the main request, and the previous requests. 166 | */ 167 | static msc_t *retrieve_tx_context(request_rec *r) { 168 | msc_t *msr = NULL; 169 | request_rec *rx = NULL; 170 | 171 | /* Look in the current request first. */ 172 | msr = (msc_t *)apr_table_get(r->notes, NOTE_MSR); 173 | if (msr != NULL) 174 | { 175 | msr->r = r; 176 | return msr; 177 | } 178 | 179 | /* If this is a subrequest then look in the main request. */ 180 | if (r->main != NULL) 181 | { 182 | msr = (msc_t *)apr_table_get(r->main->notes, NOTE_MSR); 183 | if (msr != NULL) 184 | { 185 | msr->r = r; 186 | return msr; 187 | } 188 | } 189 | 190 | /* If the request was redirected then look in the previous requests. */ 191 | rx = r->prev; 192 | while (rx != NULL) 193 | { 194 | msr = (msc_t *)apr_table_get(rx->notes, NOTE_MSR); 195 | if (msr != NULL) 196 | { 197 | msr->r = r; 198 | return msr; 199 | } 200 | rx = rx->prev; 201 | } 202 | 203 | return NULL; 204 | } 205 | 206 | 207 | static int msc_hook_pre_config(apr_pool_t *mp, apr_pool_t *mp_log, 208 | apr_pool_t *mp_temp) 209 | { 210 | void *data = NULL; 211 | const char *key = "modsecurity-pre-config-init-flag"; 212 | int first_time = 0; 213 | 214 | /* Figure out if we are here for the first time */ 215 | apr_pool_userdata_get(&data, key, mp); 216 | if (data == NULL) 217 | { 218 | apr_pool_userdata_set((const void *) 1, key, 219 | apr_pool_cleanup_null, mp); 220 | first_time = 1; 221 | } 222 | 223 | if (!first_time) 224 | { 225 | return OK; 226 | } 227 | 228 | // Code to run only at the very first call. 229 | int ret = msc_apache_init(mp); 230 | 231 | if (ret == -1) 232 | { 233 | ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, 234 | "ModSecurity: Failed to initialise."); 235 | return HTTP_INTERNAL_SERVER_ERROR; 236 | } 237 | 238 | return OK; 239 | } 240 | 241 | 242 | static int msc_hook_post_config(apr_pool_t *mp, apr_pool_t *mp_log, 243 | apr_pool_t *mp_temp, server_rec *s) 244 | { 245 | void *data = NULL; 246 | const char *key = "modsecurity-post-config-init-flag"; 247 | int first_time = 0; 248 | 249 | /* Figure out if we are here for the first time */ 250 | apr_pool_userdata_get(&data, key, s->process->pool); 251 | if (data == NULL) 252 | { 253 | apr_pool_userdata_set((const void *) 1, key, 254 | apr_pool_cleanup_null, s->process->pool); 255 | first_time = 1; 256 | } 257 | 258 | if (!first_time) 259 | { 260 | return OK; 261 | } 262 | 263 | // Code to run only at the very first call. 264 | ap_log_error(APLOG_MARK, APLOG_NOTICE | APLOG_NOERRNO, 0, s, 265 | "ModSecurity: %s configured.", MSC_APACHE_CONNECTOR); 266 | 267 | return OK; 268 | } 269 | 270 | 271 | 272 | static int hook_connection_early(conn_rec *conn) 273 | { 274 | // At this point there isn't a request_rec attached to the request, 275 | // therefore we can't create the config yet, lets wait till next phase. 276 | 277 | return DECLINED; 278 | } 279 | 280 | 281 | /** 282 | * Initial request processing, executed immediatelly after 283 | * Apache receives the request headers. This function wil create 284 | * a transaction context. 285 | */ 286 | static int hook_request_early(request_rec *r) { 287 | msc_t *msr = NULL; 288 | int rc = DECLINED; 289 | #if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER < 3 290 | const char *client_ip = r->connection->remote_ip; 291 | int client_port = r->connection->remote_addr->port; 292 | #else 293 | const char *client_ip = r->connection->client_ip; 294 | int client_port = r->connection->client_addr->port; 295 | #endif 296 | 297 | /* This function needs to run only once per transaction 298 | * (i.e. subrequests and redirects are excluded). 299 | */ 300 | if ((r->main != NULL) || (r->prev != NULL)) { 301 | return DECLINED; 302 | } 303 | 304 | /* Initialise transaction context and 305 | * create the initial configuration. 306 | */ 307 | #ifdef REQUEST_EARLY 308 | #error "Request Early is not ready for v3 yet." 309 | msr = create_tx_context(r); 310 | if (msr == NULL) 311 | { 312 | return DECLINED; 313 | } 314 | #endif 315 | 316 | #ifndef LATE_CONNECTION_PROCESS 317 | #error "Currently in v3 connection can only be processed late." 318 | msc_process_connection(msr->t, client_ip, 319 | client_port, 320 | r->server->server_hostname, 321 | (int) r->server->port); 322 | 323 | it = process_intervention(msr->t, r); 324 | if (it != N_INTERVENTION_STATUS) 325 | { 326 | return it; 327 | } 328 | #endif 329 | 330 | #ifdef REQUEST_EARLY 331 | it = process_request_headers(r, msr); 332 | if (it != N_INTERVENTION_STATUS) 333 | { 334 | return it; 335 | } 336 | #endif 337 | 338 | return rc; 339 | } 340 | 341 | /** 342 | * Invoked as the first hook in the handler chain, this function 343 | * executes the second phase of ModSecurity request processing. 344 | */ 345 | static int hook_request_late(request_rec *r) 346 | { 347 | msc_t *msr = NULL; 348 | int it; 349 | #if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER < 3 350 | const char *client_ip = r->connection->remote_ip; 351 | int client_port = r->connection->remote_addr->port; 352 | #else 353 | const char *client_ip = r->connection->client_ip; 354 | int client_port = r->connection->client_addr->port; 355 | #endif 356 | 357 | /* This function needs to run only once per transaction 358 | * (i.e. subrequests and redirects are excluded). 359 | */ 360 | if ((r->main != NULL) || (r->prev != NULL)) 361 | { 362 | return DECLINED; 363 | } 364 | 365 | /* Find the transaction context and make sure 366 | * we are supposed to proceed. 367 | */ 368 | #ifdef REQUEST_EARLY 369 | msr = retrieve_tx_context(r); 370 | #else 371 | msr = create_tx_context(r); 372 | #endif 373 | if (msr == NULL) 374 | { 375 | /* If we can't find the context that probably means it's 376 | * a subrequest that was not initiated from the outside. 377 | */ 378 | return DECLINED; 379 | } 380 | 381 | #ifdef LATE_CONNECTION_PROCESS 382 | msc_process_connection(msr->t, client_ip, 383 | client_port, 384 | r->server->server_hostname, 385 | (int) r->server->port); 386 | 387 | it = process_intervention(msr->t, r); 388 | if (it != N_INTERVENTION_STATUS) 389 | { 390 | return it; 391 | } 392 | #endif 393 | 394 | #ifndef REQUEST_EARLY 395 | it = process_request_headers(r, msr); 396 | if (it != N_INTERVENTION_STATUS) 397 | { 398 | return it; 399 | } 400 | #endif 401 | 402 | 403 | msc_process_request_body(msr->t); 404 | it = process_intervention(msr->t, r); 405 | if (it != N_INTERVENTION_STATUS) 406 | { 407 | return it; 408 | } 409 | 410 | return DECLINED; 411 | } 412 | 413 | 414 | /** 415 | * Invoked at the end of each transaction. 416 | */ 417 | static int hook_log_transaction(request_rec *r) 418 | { 419 | const apr_array_header_t *arr = NULL; 420 | request_rec *origr = NULL; 421 | msc_t *msr = NULL; 422 | int it; 423 | 424 | msr = retrieve_tx_context(r); 425 | if (msr == NULL) 426 | { 427 | return DECLINED; 428 | } 429 | 430 | msc_update_status_code(msr->t, r->status); 431 | msc_process_logging(msr->t); 432 | it = process_intervention(msr->t, r); 433 | if (it != N_INTERVENTION_STATUS) 434 | { 435 | return it; 436 | } 437 | 438 | return DECLINED; 439 | } 440 | 441 | 442 | /** 443 | * Invoked right before request processing begins. This is 444 | * when we need to decide if we want to hook into the output 445 | * filter chain. 446 | */ 447 | static void hook_insert_filter(request_rec *r) 448 | { 449 | msc_t *msr = NULL; 450 | 451 | /* Find the transaction context first. */ 452 | msr = retrieve_tx_context(r); 453 | if (msr == NULL) 454 | { 455 | return; 456 | } 457 | 458 | #if 1 459 | /* Add the input filter, but only if we need it to run. */ 460 | ap_add_input_filter("MODSECURITY_IN", msr, r, r->connection); 461 | #endif 462 | 463 | /* The output filters only need to be added only once per transaction 464 | * (i.e. subrequests and redirects are excluded). 465 | */ 466 | if ((r->main != NULL) || (r->prev != NULL)) 467 | { 468 | return; 469 | } 470 | 471 | 472 | ap_add_output_filter("MODSECURITY_OUT", msr, r, r->connection); 473 | } 474 | 475 | 476 | static int process_request_headers(request_rec *r, msc_t *msr) { 477 | /* process uri */ 478 | { 479 | int it; 480 | int offset = (r->protocol && strlen(r->protocol) > 5 && r->protocol[0] == 'H') ? 5 : 0; 481 | 482 | msc_process_uri(msr->t, r->unparsed_uri, r->method, r->protocol + offset); 483 | it = process_intervention(msr->t, r); 484 | if (it != N_INTERVENTION_STATUS) 485 | { 486 | return it; 487 | } 488 | } 489 | 490 | /* add request headers */ 491 | { 492 | const apr_array_header_t *arr = NULL; 493 | const apr_table_entry_t *te = NULL; 494 | int i; 495 | int it; 496 | 497 | arr = apr_table_elts(r->headers_in); 498 | te = (apr_table_entry_t *)arr->elts; 499 | for (i = 0; i < arr->nelts; i++) 500 | { 501 | const char *key = te[i].key; 502 | const char *val = te[i].val; 503 | msc_add_request_header(msr->t, key, val); 504 | } 505 | msc_process_request_headers(msr->t); 506 | 507 | it = process_intervention(msr->t, r); 508 | if (it != N_INTERVENTION_STATUS) 509 | { 510 | return it; 511 | } 512 | } 513 | 514 | return N_INTERVENTION_STATUS; 515 | } 516 | 517 | 518 | 519 | static void msc_register_hooks(apr_pool_t *pool) 520 | { 521 | static const char *const postconfig_beforeme_list[] = { 522 | "mod_unique_id.c", 523 | "mod_ssl.c", 524 | NULL 525 | }; 526 | 527 | static const char *const postconfig_afterme_list[] = { 528 | "mod_fcgid.c", 529 | "mod_cgid.c", 530 | NULL 531 | }; 532 | 533 | static const char *const postread_beforeme_list[] = { 534 | "mod_rpaf.c", 535 | "mod_rpaf-2.0.c", 536 | "mod_extract_forwarded.c", 537 | "mod_extract_forwarded2.c", 538 | "mod_remoteip.c", 539 | "mod_custom_header.c", 540 | "mod_breach_realip.c", 541 | "mod_breach_trans.c", 542 | "mod_unique_id.c", 543 | NULL 544 | }; 545 | 546 | static const char *const postread_afterme_list[] = { 547 | "mod_log_forensic.c", 548 | NULL 549 | }; 550 | 551 | static const char *const transaction_afterme_list[] = { 552 | "mod_log_config.c", 553 | NULL 554 | }; 555 | 556 | static const char *const fixups_beforeme_list[] = { 557 | "mod_env.c", 558 | NULL 559 | }; 560 | 561 | /* Module initialization */ 562 | ap_hook_pre_config(msc_hook_pre_config, NULL, NULL, APR_HOOK_FIRST); 563 | ap_hook_post_config(msc_hook_post_config, postconfig_beforeme_list, 564 | postconfig_afterme_list, APR_HOOK_REALLY_LAST); 565 | 566 | 567 | /* Connection processing hooks - only global configuration. */ 568 | ap_hook_post_read_request(hook_request_early, 569 | postread_beforeme_list, postread_afterme_list, APR_HOOK_REALLY_FIRST); 570 | 571 | /* still, we don't have location configuration yet. */ 572 | ap_hook_process_connection(hook_connection_early, NULL, NULL, APR_HOOK_FIRST); 573 | 574 | ap_hook_fixups(hook_request_late, fixups_beforeme_list, NULL, APR_HOOK_REALLY_FIRST); 575 | 576 | /* Lets add the remaining hooks */ 577 | ap_hook_insert_filter(hook_insert_filter, NULL, NULL, APR_HOOK_FIRST); 578 | 579 | /* Logging */ 580 | /* ap_hook_error_log is called for every error log entry that apache writes. 581 | * may not be necessary in our particular case. Disabling for now. 582 | * 583 | * ap_hook_error_log(hook_error_log, NULL, NULL, APR_HOOK_MIDDLE); 584 | * 585 | */ 586 | ap_hook_log_transaction(hook_log_transaction, NULL, transaction_afterme_list, APR_HOOK_MIDDLE); 587 | 588 | /* request body */ 589 | ap_register_input_filter("MODSECURITY_IN", input_filter, 590 | NULL, AP_FTYPE_CONTENT_SET); 591 | 592 | /* response body */ 593 | ap_register_output_filter("MODSECURITY_OUT", output_filter, 594 | NULL, AP_FTYPE_CONTENT_SET - 3); 595 | } 596 | 597 | 598 | 599 | module AP_MODULE_DECLARE_DATA security3_module = 600 | { 601 | STANDARD20_MODULE_STUFF, 602 | msc_hook_create_config_directory, // Per-directory configuration. 603 | msc_hook_merge_config_directory, // Merge handler for per-directory. 604 | NULL, // Per-server conf handler. 605 | NULL, // Merge handler for per-server 606 | // configurations. 607 | module_directives, 608 | msc_register_hooks 609 | }; 610 | -------------------------------------------------------------------------------- /src/mod_security3.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | 5 | #include 6 | #if defined(MODSECURITY_CHECK_VERSION) 7 | #if MODSECURITY_VERSION_NUM >= 304010 8 | #define MSC_USE_RULES_SET 1 9 | #endif 10 | #endif 11 | 12 | #if defined(MSC_USE_RULES_SET) 13 | #include 14 | #else 15 | #include 16 | #endif 17 | #include 18 | 19 | #include "apr_buckets.h" 20 | #include "apr_general.h" 21 | #include "apr.h" 22 | #include "apr_hash.h" 23 | #include "apr_lib.h" 24 | #include "apr_strings.h" 25 | #define APR_WANT_STRFUNC 26 | #include "apr_want.h" 27 | #include "util_filter.h" 28 | 29 | #include "httpd.h" 30 | #include "http_config.h" 31 | #include "http_connection.h" 32 | #include "http_core.h" 33 | #include "http_log.h" 34 | #include "http_protocol.h" 35 | #include "http_request.h" 36 | 37 | #include "msc_filters.h" 38 | 39 | #ifndef _SRC_APACHE_HTTP_MODSECURITY__ 40 | #define _SRC_APACHE_HTTP_MODSECURITY__ 41 | 42 | #define NOTE_MSR "modsecurity3-tx-context" 43 | #define MSC_APACHE_CONNECTOR "ModSecurity-Apache v0.1.1-beta" 44 | /* #define REQUEST_EARLY */ 45 | #define LATE_CONNECTION_PROCESS 46 | 47 | #define N_INTERVENTION_STATUS 200 48 | 49 | 50 | typedef struct 51 | { 52 | request_rec *r; 53 | Transaction *t; 54 | } msc_t; 55 | 56 | 57 | typedef struct 58 | { 59 | void *rules_set; 60 | int msc_state; 61 | char *name_for_debug; 62 | } msc_conf_t; 63 | 64 | typedef struct 65 | { 66 | ModSecurity *modsec; 67 | } msc_global; 68 | 69 | extern module AP_MODULE_DECLARE_DATA security3_module; 70 | extern msc_global *msc_apache; 71 | extern const command_rec module_directives[]; 72 | 73 | 74 | int process_intervention (Transaction *t, request_rec *r); 75 | 76 | int msc_apache_init(apr_pool_t *pool); 77 | int msc_apache_cleanup(); 78 | static apr_status_t msc_module_cleanup(void *data); 79 | 80 | 81 | /* 82 | 83 | static int hook_connection_early(conn_rec *conn); 84 | 85 | static int msc_hook_pre_config(apr_pool_t *mp, apr_pool_t *mp_log, 86 | apr_pool_t *mp_temp); 87 | static int msc_hook_post_config(apr_pool_t *mp, apr_pool_t *mp_log, 88 | apr_pool_t *mp_temp, server_rec *s); 89 | 90 | static int hook_request_late(request_rec *r); 91 | static int hook_request_early(request_rec *r); 92 | static int hook_log_transaction(request_rec *r); 93 | 94 | static void hook_insert_filter(request_rec *r); 95 | */ 96 | /* 97 | */ 98 | 99 | static int process_request_headers(request_rec *r, msc_t *msr); 100 | 101 | #endif /* _SRC_APACHE_HTTP_MODSECURITY__ */ 102 | -------------------------------------------------------------------------------- /src/msc_config.c: -------------------------------------------------------------------------------- 1 | 2 | #include "mod_security3.h" 3 | #include "msc_config.h" 4 | #include "msc_filters.h" 5 | 6 | 7 | const command_rec module_directives[] = 8 | { 9 | AP_INIT_TAKE1( 10 | "modsecurity", 11 | msc_config_modsec_state, 12 | NULL, 13 | RSRC_CONF | ACCESS_CONF, 14 | "The argument must be either 'On' or 'Off'" 15 | ), 16 | 17 | AP_INIT_TAKE1( 18 | "modsecurity_rules", 19 | msc_config_load_rules, 20 | NULL, 21 | RSRC_CONF | ACCESS_CONF, 22 | "Please ensure that the arugment is specified correctly, including line continuations." 23 | ), 24 | 25 | AP_INIT_TAKE1( 26 | "modsecurity_rules_file", 27 | msc_config_load_rules_file, 28 | NULL, 29 | RSRC_CONF | ACCESS_CONF, 30 | "Load ModSecurity rules from a file" 31 | ), 32 | 33 | AP_INIT_TAKE2( 34 | "modsecurity_rules_remote", 35 | msc_config_load_rules_remote, 36 | NULL, 37 | RSRC_CONF | ACCESS_CONF, 38 | "Load ModSecurity rules from a remote server" 39 | ), 40 | 41 | {NULL} 42 | }; 43 | 44 | 45 | static const char *msc_config_modsec_state(cmd_parms *cmd, void *_cnf, 46 | const char *p1) 47 | { 48 | msc_conf_t *cnf = (msc_conf_t *) _cnf; 49 | 50 | if (strcasecmp(p1, "On") == 0) 51 | { 52 | cnf->msc_state = 1; 53 | } 54 | else if (strcasecmp(p1, "Off") == 0) 55 | { 56 | cnf->msc_state = 0; 57 | } 58 | else 59 | { 60 | return "ModSecurity state must be either 'On' or 'Off'"; 61 | } 62 | 63 | return NULL; 64 | } 65 | 66 | 67 | static const char *msc_config_load_rules(cmd_parms *cmd, void *_cnf, 68 | const char *p1) 69 | { 70 | msc_conf_t *cnf = (msc_conf_t *) _cnf; 71 | const char *error = NULL; 72 | int ret; 73 | 74 | ret = msc_rules_add(cnf->rules_set, p1, &error); 75 | 76 | if (ret < 0) 77 | { 78 | return error; 79 | } 80 | 81 | return NULL; 82 | } 83 | 84 | 85 | static const char *msc_config_load_rules_file(cmd_parms *cmd, void *_cnf, 86 | const char *p1) 87 | { 88 | msc_conf_t *cnf = (msc_conf_t *) _cnf; 89 | const char *error = NULL; 90 | int ret; 91 | 92 | ret = msc_rules_add_file(cnf->rules_set, p1, &error); 93 | 94 | if (ret < 0) 95 | { 96 | return error; 97 | } 98 | 99 | return NULL; 100 | } 101 | 102 | 103 | static const char *msc_config_load_rules_remote(cmd_parms *cmd, void *_cnf, 104 | const char *p1, const char *p2) 105 | { 106 | msc_conf_t *cnf = (msc_conf_t *) _cnf; 107 | const char *error = NULL; 108 | int ret; 109 | 110 | ret = msc_rules_add_remote(cnf->rules_set, p1, p2, &error); 111 | 112 | if (ret < 0) 113 | { 114 | return error; 115 | } 116 | 117 | return NULL; 118 | } 119 | 120 | void *msc_hook_create_config_directory(apr_pool_t *mp, char *path) 121 | { 122 | msc_conf_t *cnf = NULL; 123 | 124 | cnf = apr_pcalloc(mp, sizeof(msc_conf_t)); 125 | if (cnf == NULL) 126 | { 127 | goto end; 128 | } 129 | #if 0 130 | ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, 131 | "ModSecurity: Created directory config for path: %s [%pp]", path, cnf); 132 | #endif 133 | 134 | cnf->rules_set = msc_create_rules_set(); 135 | if (path != NULL) 136 | { 137 | cnf->name_for_debug = strdup(path); 138 | } 139 | #if 0 140 | ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, 141 | "ModSecurity: Config for path: %s is at: %pp", path, cnf); 142 | #endif 143 | 144 | end: 145 | return cnf; 146 | } 147 | 148 | 149 | void *msc_hook_merge_config_directory(apr_pool_t *mp, void *parent, 150 | void *child) 151 | { 152 | msc_conf_t *cnf_p = parent; 153 | msc_conf_t *cnf_c = child; 154 | msc_conf_t *cnf_new = (msc_conf_t *)msc_hook_create_config_directory(mp, cnf_c->name_for_debug); 155 | 156 | if (cnf_p && cnf_c) 157 | { 158 | const char *error = NULL; 159 | int ret; 160 | #if 0 161 | ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, 162 | "ModSecurity: Merge parent %pp [%s] child %pp [%s]" \ 163 | "into: %pp", cnf_p, 164 | cnf_p->name_for_debug, 165 | child, cnf_c->name_for_debug, cnf_new); 166 | #endif 167 | cnf_new->name_for_debug = cnf_c->name_for_debug; 168 | 169 | ret = msc_rules_merge(cnf_new->rules_set, cnf_c->rules_set, &error); 170 | if (ret < 0) 171 | { 172 | ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, 173 | "ModSecurity: Rule merge failed: %s", error); 174 | return NULL; 175 | } 176 | 177 | ret = msc_rules_merge(cnf_new->rules_set, cnf_p->rules_set, &error); 178 | if (ret < 0) 179 | { 180 | ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, 181 | "ModSecurity: Rule merge failed: %s", error); 182 | return NULL; 183 | } 184 | #if 0 185 | ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, 186 | "ModSecurity: Merge OK"); 187 | #endif 188 | } 189 | else if (cnf_c && !cnf_p) 190 | { 191 | #if 0 192 | ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, 193 | "ModSecurity: Merge parent -NULL- [-NULL-] child %pp [%s]", 194 | cnf_c, cnf_c->name_for_debug); 195 | #endif 196 | } 197 | else if (cnf_p && !cnf_c) 198 | { 199 | #if 0 200 | ap_log_perror(APLOG_MARK, APLOG_STARTUP|APLOG_NOERRNO, 0, mp, 201 | "ModSecurity: Merge parent %pp [%s] child -NULL- [-NULL-]", 202 | cnf_p, cnf_p->name_for_debug); 203 | #endif 204 | } 205 | 206 | return cnf_new; 207 | } 208 | -------------------------------------------------------------------------------- /src/msc_config.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _SRC_MSC_CONFIG__ 3 | #define _SRC_MSC_CONFIG__ 4 | 5 | static const char *msc_config_modsec_state(cmd_parms *cmd, void *_dcfg, 6 | const char *p1); 7 | 8 | static const char *msc_config_load_rules(cmd_parms *cmd, void *_dcfg, 9 | const char *p1); 10 | 11 | static const char *msc_config_load_rules_file(cmd_parms *cmd, void *_dcfg, 12 | const char *p1); 13 | 14 | static const char *msc_config_load_rules_remote(cmd_parms *cmd, void *_dcfg, 15 | const char *p1, const char *p2); 16 | 17 | void *msc_hook_create_config_directory(apr_pool_t *mp, char *path); 18 | 19 | void *msc_hook_merge_config_directory(apr_pool_t *mp, void *parent, 20 | void *child); 21 | 22 | 23 | 24 | #endif /* _SRC_MSC_CONFIG__ */ 25 | -------------------------------------------------------------------------------- /src/msc_filters.c: -------------------------------------------------------------------------------- 1 | 2 | #include "msc_filters.h" 3 | #include "msc_utils.h" 4 | 5 | 6 | apr_status_t input_filter(ap_filter_t *f, apr_bucket_brigade *pbbOut, 7 | ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes) 8 | { 9 | request_rec *r = f->r; 10 | conn_rec *c = r->connection; 11 | 12 | apr_bucket_brigade *pbbTmp; 13 | int ret; 14 | 15 | msc_t *msr = (msc_t *)f->ctx; 16 | 17 | /* Do we have the context? */ 18 | if (msr == NULL) 19 | { 20 | ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, f->r->server, 21 | "ModSecurity: Internal Error: msr is null in input filter."); 22 | ap_remove_output_filter(f); 23 | return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR); 24 | } 25 | 26 | pbbTmp = apr_brigade_create(r->pool, c->bucket_alloc); 27 | if (APR_BRIGADE_EMPTY(pbbTmp)) 28 | { 29 | ret = ap_get_brigade(f->next, pbbTmp, mode, block, nbytes); 30 | 31 | if (mode == AP_MODE_EATCRLF || ret != APR_SUCCESS) 32 | return ret; 33 | } 34 | 35 | while (!APR_BRIGADE_EMPTY(pbbTmp)) 36 | { 37 | apr_bucket *pbktIn = APR_BRIGADE_FIRST(pbbTmp); 38 | apr_bucket *pbktOut; 39 | const char *data; 40 | apr_size_t len; 41 | apr_size_t n; 42 | int it; 43 | 44 | if (APR_BUCKET_IS_EOS(pbktIn)) 45 | { 46 | APR_BUCKET_REMOVE(pbktIn); 47 | APR_BRIGADE_INSERT_TAIL(pbbOut, pbktIn); 48 | break; 49 | } 50 | 51 | ret=apr_bucket_read(pbktIn, &data, &len, block); 52 | if (ret != APR_SUCCESS) 53 | { 54 | return ret; 55 | } 56 | 57 | msc_append_request_body(msr->t, data, len); 58 | it = process_intervention(msr->t, r); 59 | if (it != N_INTERVENTION_STATUS) 60 | { 61 | ap_remove_output_filter(f); 62 | return send_error_bucket(msr, f, it); 63 | } 64 | 65 | // FIXME: Now we should have the body. Is this sane? 66 | msc_process_request_body(msr->t); 67 | 68 | pbktOut = apr_bucket_heap_create(data, len, 0, c->bucket_alloc); 69 | APR_BRIGADE_INSERT_TAIL(pbbOut, pbktOut); 70 | apr_bucket_delete(pbktIn); 71 | } 72 | return APR_SUCCESS; 73 | } 74 | 75 | 76 | apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in) 77 | { 78 | request_rec *r = f->r; 79 | msc_t *msr = (msc_t *)f->ctx; 80 | 81 | /* Do we have the context? */ 82 | if (msr == NULL) 83 | { 84 | ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, f->r->server, 85 | "ModSecurity: Internal Error: msr is null in output filter."); 86 | ap_remove_output_filter(f); 87 | return send_error_bucket(msr, f, HTTP_INTERNAL_SERVER_ERROR); 88 | } 89 | 90 | /* response headers */ 91 | { 92 | const apr_array_header_t *arr = NULL; 93 | const apr_table_entry_t *te = NULL; 94 | int i, it; 95 | 96 | arr = apr_table_elts(r->err_headers_out); 97 | te = (apr_table_entry_t *)arr->elts; 98 | for (i = 0; i < arr->nelts; i++) 99 | { 100 | const char *key = te[i].key; 101 | const char *val = te[i].val; 102 | msc_add_response_header(msr->t, key, val); 103 | } 104 | 105 | arr = apr_table_elts(r->headers_out); 106 | te = (apr_table_entry_t *)arr->elts; 107 | for (i = 0; i < arr->nelts; i++) 108 | { 109 | const char *key = te[i].key; 110 | const char *val = te[i].val; 111 | msc_add_response_header(msr->t, key, val); 112 | } 113 | 114 | msc_process_response_headers(msr->t, r->status, "HTTP 1.1"); 115 | 116 | it = process_intervention(msr->t, r); 117 | if (it != N_INTERVENTION_STATUS) 118 | { 119 | ap_remove_output_filter(f); 120 | return send_error_bucket(msr, f, it); 121 | } 122 | } 123 | 124 | /* response body */ 125 | { 126 | apr_bucket *pbktIn; 127 | int it; 128 | 129 | for (pbktIn = APR_BRIGADE_FIRST(bb_in); 130 | pbktIn != APR_BRIGADE_SENTINEL(bb_in); 131 | pbktIn = APR_BUCKET_NEXT(pbktIn)) 132 | { 133 | const char *data; 134 | apr_size_t len; 135 | apr_bucket_read(pbktIn, &data, &len, APR_BLOCK_READ); 136 | msc_append_response_body(msr->t, data, len); 137 | } 138 | msc_process_response_body(msr->t); 139 | 140 | it = process_intervention(msr->t, r); 141 | if (it != N_INTERVENTION_STATUS) 142 | { 143 | ap_remove_output_filter(f); 144 | return send_error_bucket(msr, f, it); 145 | } 146 | } 147 | 148 | return ap_pass_brigade(f->next, bb_in); 149 | } 150 | 151 | -------------------------------------------------------------------------------- /src/msc_filters.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #include "http_core.h" 7 | #include "http_request.h" 8 | #include "httpd.h" 9 | #include "ap_release.h" 10 | 11 | #include 12 | #include 13 | 14 | #ifndef _SRC_MSC_FILTERS__ 15 | #define _SRC_MSC_FILTERS__ 16 | 17 | #include "mod_security3.h" 18 | 19 | apr_status_t output_filter(ap_filter_t *f, apr_bucket_brigade *bb_in); 20 | 21 | apr_status_t input_filter(ap_filter_t *f, apr_bucket_brigade *bb_out, 22 | ap_input_mode_t mode, apr_read_type_e block, apr_off_t nbytes); 23 | 24 | #endif /* _SRC_MSC_FILTERS__ */ 25 | -------------------------------------------------------------------------------- /src/msc_utils.c: -------------------------------------------------------------------------------- 1 | 2 | #include "msc_utils.h" 3 | 4 | 5 | int id(const char *fn, const char *format, ...) 6 | { 7 | va_list args; 8 | va_start(args, format); 9 | FILE *f = fopen(fn, "a"); 10 | vfprintf(f, format, args); 11 | fclose(f); 12 | va_end(args); 13 | } 14 | 15 | 16 | /** 17 | * Sends a brigade with an error bucket down the filter chain. 18 | */ 19 | apr_status_t send_error_bucket(msc_t *msr, ap_filter_t *f, int status) 20 | { 21 | apr_bucket_brigade *brigade = NULL; 22 | apr_bucket *bucket = NULL; 23 | 24 | /* Set the status line explicitly for the error document */ 25 | f->r->status_line = ap_get_status_line(status); 26 | 27 | brigade = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc); 28 | if (brigade == NULL) 29 | { 30 | return APR_EGENERAL; 31 | } 32 | 33 | bucket = ap_bucket_error_create(status, NULL, f->r->pool, 34 | f->r->connection->bucket_alloc); 35 | if (bucket == NULL) 36 | { 37 | return APR_EGENERAL; 38 | } 39 | 40 | APR_BRIGADE_INSERT_TAIL(brigade, bucket); 41 | 42 | bucket = apr_bucket_eos_create(f->r->connection->bucket_alloc); 43 | if (bucket == NULL) 44 | { 45 | return APR_EGENERAL; 46 | } 47 | 48 | APR_BRIGADE_INSERT_TAIL(brigade, bucket); 49 | 50 | ap_pass_brigade(f->next, brigade); 51 | 52 | /* NOTE: 53 | * It may not matter what we do from the filter as it may be too 54 | * late to even generate an error (already sent to client). Nick Kew 55 | * recommends to return APR_EGENERAL in hopes that the handler in control 56 | * will notice and do The Right Thing. So, that is what we do now. 57 | */ 58 | return APR_EGENERAL; 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/msc_utils.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #include "http_core.h" 7 | #include "http_request.h" 8 | #include "httpd.h" 9 | #include "ap_release.h" 10 | 11 | #include 12 | #include 13 | 14 | #ifndef _SRC_MSC_UTILS__ 15 | #define _SRC_MSC_UTILS__ 16 | 17 | #include "mod_security3.h" 18 | 19 | 20 | int id(const char *fn, const char *format, ...); 21 | 22 | 23 | apr_status_t send_error_bucket(msc_t *msr, ap_filter_t *f, int status); 24 | 25 | 26 | #endif /* _SRC_MSC_UTILS__ */ 27 | -------------------------------------------------------------------------------- /t/TEST: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use FindBin; 5 | use lib "$FindBin::RealBin/.."; 6 | use warnings FATAL => 'all'; 7 | 8 | use Apache::TestRun; 9 | Apache::TestRun->new->run(@ARGV); 10 | 11 | -------------------------------------------------------------------------------- /t/conf/extra.conf.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | CoreDumpDirectory /tmp/ 4 | 5 | Include @ServerRoot@/.././t/conf/modules.conf 6 | 7 | LoadModule security3_module "@ServerRoot@/.././src/.libs/mod_security3.so" 8 | 9 | 10 | # Lets make sure that the engine is on. 11 | modsecurity_rules 'SecRuleEngine On' 12 | 13 | # Debug logs 14 | modsecurity_rules 'SecDebugLog @ServerRoot@/logs/debug_logs.txt' 15 | modsecurity_rules 'SecDebugLogLevel 9' 16 | 17 | 18 | 19 | modsecurity_rules 'SecRule REQUEST_URI "evil" "phase:1,id:111,log,status:403,block,deny"' 20 | 21 | 22 | 23 | modsecurity_rules 'SecRequestBodyAccess On' 24 | modsecurity_rules 'SecRule ARGS "evil" "phase:2,id:112,log,status:403,block,deny"' 25 | 26 | 27 | 28 | modsecurity_rules 'SecRule ARGS "evil" "phase:3,id:113,log,status:403,block,deny"' 29 | 30 | 31 | 32 | modsecurity_rules 'SecResponseBodyAccess On' 33 | modsecurity_rules 'SecRule ARGS "evil" "phase:4,id:114,log,status:403,block,deny"' 34 | 35 | 36 | 37 | modsecurity_rules 'SecRule ARGS "evil" "phase:5,id:115,log,status:403,block,deny"' 38 | 39 | 40 | 41 | 42 | 43 | modsecurity_rules 'SecRule REQUEST_URI "evil" "phase:1,id:111,log,status:402,block,deny"' 44 | 45 | 46 | 47 | modsecurity_rules 'SecRequestBodyAccess On' 48 | modsecurity_rules 'SecRule ARGS "evil" "phase:2,id:112,log,status:402,block,deny"' 49 | 50 | 51 | 52 | modsecurity_rules 'SecRule ARGS "evil" "phase:3,id:113,log,status:402,block,deny"' 53 | 54 | 55 | 56 | modsecurity_rules 'SecResponseBodyAccess On' 57 | modsecurity_rules 'SecRule ARGS "evil" "phase:4,id:1134,log,status:402,block,deny"' 58 | 59 | 60 | 61 | modsecurity_rules 'SecRule ARGS "evil" "phase:5,id:114,log,status:402,block,deny"' 62 | 63 | 64 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-1-loc/evil: -------------------------------------------------------------------------------- 1 | evil 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-1-loc/index.html: -------------------------------------------------------------------------------- 1 | index 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-1/evil: -------------------------------------------------------------------------------- 1 | evil 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-1/index.html: -------------------------------------------------------------------------------- 1 | index 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-2-loc/evil: -------------------------------------------------------------------------------- 1 | evil 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-2-loc/index.html: -------------------------------------------------------------------------------- 1 | index 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-2/evil: -------------------------------------------------------------------------------- 1 | evil 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-2/evil-2.pl: -------------------------------------------------------------------------------- 1 | whee 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-2/index.html: -------------------------------------------------------------------------------- 1 | index 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-3-loc/evil: -------------------------------------------------------------------------------- 1 | evil 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-3-loc/index.html: -------------------------------------------------------------------------------- 1 | index 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-3/asdf.html: -------------------------------------------------------------------------------- 1 | felipe 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-3/evil: -------------------------------------------------------------------------------- 1 | evil 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-3/index.html: -------------------------------------------------------------------------------- 1 | index 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-4-loc/evil: -------------------------------------------------------------------------------- 1 | evil 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-4-loc/index.html: -------------------------------------------------------------------------------- 1 | index 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-4/evil: -------------------------------------------------------------------------------- 1 | evil 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-4/index.html: -------------------------------------------------------------------------------- 1 | index 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-5-loc/evil: -------------------------------------------------------------------------------- 1 | evil 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-5-loc/index.html: -------------------------------------------------------------------------------- 1 | index 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-5/evil: -------------------------------------------------------------------------------- 1 | evil 2 | -------------------------------------------------------------------------------- /t/htdocs/block-evil-5/index.html: -------------------------------------------------------------------------------- 1 | index 2 | -------------------------------------------------------------------------------- /t/htdocs/test.html: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /t/load-modsec.t: -------------------------------------------------------------------------------- 1 | 2 | use strict; 3 | use Apache::Test; 4 | use Apache::TestRequest; 5 | use FileHandle; 6 | 7 | plan tests => 1; 8 | 9 | 10 | my $body = GET_BODY "/test.html"; 11 | ok($body, qr/test/); 12 | 13 | 14 | -------------------------------------------------------------------------------- /t/simple-block.t: -------------------------------------------------------------------------------- 1 | 2 | use strict; 3 | use Apache::Test; 4 | use Apache::TestRequest; 5 | use FileHandle; 6 | use Data::Dumper; 7 | 8 | plan tests => 11; 9 | 10 | my $res = GET "/index.html?evil=evil"; 11 | ok $res->code == 200; 12 | 13 | my $res1 = GET "/block-evil-1/evil?evil=evil"; 14 | ok $res1->code == 403; 15 | my $res2 = GET "/block-evil-2/evil?evil=evil"; 16 | ok $res2->code == 403; 17 | my $res3 = GET "/block-evil-3/evil?evil=evil"; 18 | ok $res3->code == 403; 19 | my $res4 = GET "/block-evil-4/evil?evil=evil"; 20 | ok $res4->code == 403; 21 | 22 | # Too late to block. 23 | my $res5 = GET "/block-evil-5/evil?evil=evil"; 24 | ok $res5->code == 200; 25 | 26 | 27 | my $res1l = GET "/block-evil-1-loc/evil?evil=evil"; 28 | ok $res1l->code == 402; 29 | my $res2l = GET "/block-evil-2-loc/evil?evil=evil"; 30 | ok $res2l->code == 402; 31 | my $res3l = GET "/block-evil-3-loc/evil?evil=evil"; 32 | ok $res3l->code == 402; 33 | my $res4l = GET "/block-evil-4-loc/evil?evil=evil"; 34 | ok $res4l->code == 402; 35 | 36 | # Too late to block. 37 | my $res5l = GET "/block-evil-5-loc/evil?evil=evil"; 38 | ok $res5l->code == 200; 39 | 40 | 41 | -------------------------------------------------------------------------------- /t/very-simple-test.t: -------------------------------------------------------------------------------- 1 | 2 | use strict; 3 | use Apache::Test; 4 | use Apache::TestRequest; 5 | use FileHandle; 6 | 7 | plan tests => 1, have_lwp; 8 | 9 | my $body = GET_BODY "/test.html"; 10 | ok($body, qr/test/); 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/.deps/msc_test-msc_test.Po: -------------------------------------------------------------------------------- 1 | # dummy 2 | -------------------------------------------------------------------------------- /tests/regression/action/00-disruptive-actions.t: -------------------------------------------------------------------------------- 1 | ### Tests all of the actions in each phase 2 | 3 | # Pass 4 | { 5 | type => "action", 6 | comment => "pass in phase:1", 7 | conf => qq( 8 | SecRuleEngine On 9 | SecRequestBodyAccess On 10 | SecResponseBodyAccess On 11 | SecResponseBodyMimeType null 12 | SecAction "phase:1,pass,id:500033" 13 | SecAction "phase:1,deny,id:500034" 14 | ), 15 | match_log => { 16 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ], 17 | }, 18 | match_response => { 19 | status => qr/^403$/, 20 | }, 21 | request => new HTTP::Request( 22 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 23 | ), 24 | }, 25 | { 26 | type => "action", 27 | comment => "pass in phase:2", 28 | conf => qq( 29 | SecRuleEngine On 30 | SecRequestBodyAccess On 31 | SecResponseBodyAccess On 32 | SecResponseBodyMimeType null 33 | SecAction "phase:2,pass,id:500035" 34 | SecAction "phase:2,deny,id:500036" 35 | ), 36 | match_log => { 37 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ], 38 | }, 39 | match_response => { 40 | status => qr/^403$/, 41 | }, 42 | request => new HTTP::Request( 43 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 44 | ), 45 | }, 46 | { 47 | type => "action", 48 | comment => "pass in phase:3", 49 | conf => qq( 50 | SecRuleEngine On 51 | SecRequestBodyAccess On 52 | SecResponseBodyAccess On 53 | SecResponseBodyMimeType null 54 | SecDebugLog "$ENV{DEBUG_LOG}" 55 | SecDebugLogLevel 4 56 | SecAction "phase:3,pass,id:500037" 57 | SecAction "phase:3,deny,id:500038" 58 | ), 59 | match_log => { 60 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ], 61 | }, 62 | match_response => { 63 | status => qr/^403$/, 64 | }, 65 | request => new HTTP::Request( 66 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 67 | ), 68 | }, 69 | { 70 | type => "action", 71 | comment => "pass in phase:4", 72 | conf => qq( 73 | SecRuleEngine On 74 | SecRequestBodyAccess On 75 | SecResponseBodyAccess On 76 | SecResponseBodyMimeType null 77 | SecDebugLog "$ENV{DEBUG_LOG}" 78 | SecDebugLogLevel 4 79 | SecAction "phase:4,pass,id:500039" 80 | SecAction "phase:4,deny,id:500040" 81 | ), 82 | match_log => { 83 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ], 84 | }, 85 | match_response => { 86 | status => qr/^403$/, 87 | }, 88 | request => new HTTP::Request( 89 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 90 | ), 91 | }, 92 | 93 | # Allow 94 | { 95 | type => "action", 96 | comment => "allow in phase:1", 97 | conf => qq( 98 | SecRuleEngine On 99 | SecRequestBodyAccess On 100 | SecResponseBodyAccess On 101 | SecResponseBodyMimeType null 102 | SecAction "phase:1,allow,id:500041" 103 | SecAction "phase:1,deny,id:500042" 104 | ), 105 | match_log => { 106 | error => [ qr/ModSecurity: Access allowed \(phase 1\). Unconditional match in SecAction/, 1 ], 107 | }, 108 | match_response => { 109 | status => qr/^200$/, 110 | }, 111 | request => new HTTP::Request( 112 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 113 | ), 114 | }, 115 | { 116 | type => "action", 117 | comment => "allow in phase:2", 118 | conf => qq( 119 | SecRuleEngine On 120 | SecRequestBodyAccess On 121 | SecResponseBodyAccess On 122 | SecResponseBodyMimeType null 123 | SecAction "phase:2,allow,id:500043" 124 | SecAction "phase:2,deny,id:500044" 125 | ), 126 | match_log => { 127 | error => [ qr/ModSecurity: Access allowed \(phase 2\). Unconditional match in SecAction/, 1 ], 128 | }, 129 | match_response => { 130 | status => qr/^200$/, 131 | }, 132 | request => new HTTP::Request( 133 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 134 | ), 135 | }, 136 | { 137 | type => "action", 138 | comment => "allow in phase:3", 139 | conf => qq( 140 | SecRuleEngine On 141 | SecRequestBodyAccess On 142 | SecResponseBodyAccess On 143 | SecResponseBodyMimeType null 144 | SecDebugLog "$ENV{DEBUG_LOG}" 145 | SecDebugLogLevel 4 146 | SecAction "phase:3,allow,id:500045" 147 | SecAction "phase:3,deny,id:500046" 148 | ), 149 | match_log => { 150 | error => [ qr/ModSecurity: Access allowed \(phase 3\). Unconditional match in SecAction/, 1 ], 151 | }, 152 | match_response => { 153 | status => qr/^200$/, 154 | }, 155 | request => new HTTP::Request( 156 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 157 | ), 158 | }, 159 | { 160 | type => "action", 161 | comment => "allow in phase:4", 162 | conf => qq( 163 | SecRuleEngine On 164 | SecRequestBodyAccess On 165 | SecResponseBodyAccess On 166 | SecResponseBodyMimeType null 167 | SecDebugLog "$ENV{DEBUG_LOG}" 168 | SecDebugLogLevel 4 169 | SecAction "phase:4,allow,id:500047" 170 | SecAction "phase:4,deny,id:500048" 171 | ), 172 | match_log => { 173 | error => [ qr/ModSecurity: Access allowed \(phase 4\). Unconditional match in SecAction/, 1 ], 174 | }, 175 | match_response => { 176 | status => qr/^200$/, 177 | }, 178 | request => new HTTP::Request( 179 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 180 | ), 181 | }, 182 | 183 | # Deny 184 | { 185 | type => "action", 186 | comment => "deny in phase:1", 187 | conf => qq( 188 | SecRuleEngine On 189 | SecRequestBodyAccess On 190 | SecResponseBodyAccess On 191 | SecResponseBodyMimeType null 192 | SecAction "phase:1,deny,id:500049" 193 | ), 194 | match_log => { 195 | error => [ qr/Access denied with code 403 \(phase 1\). Unconditional match in SecAction./, 1 ], 196 | }, 197 | match_response => { 198 | status => qr/^403$/, 199 | }, 200 | request => new HTTP::Request( 201 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 202 | ), 203 | }, 204 | { 205 | type => "action", 206 | comment => "deny in phase:2", 207 | conf => qq( 208 | SecRuleEngine On 209 | SecRequestBodyAccess On 210 | SecResponseBodyAccess On 211 | SecResponseBodyMimeType null 212 | SecAction "phase:2,deny,id:500050" 213 | ), 214 | match_log => { 215 | error => [ qr/Access denied with code 403 \(phase 2\). Unconditional match in SecAction./, 1 ], 216 | }, 217 | match_response => { 218 | status => qr/^403$/, 219 | }, 220 | request => new HTTP::Request( 221 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 222 | ), 223 | }, 224 | { 225 | type => "action", 226 | comment => "deny in phase:3", 227 | conf => qq( 228 | SecRuleEngine On 229 | SecRequestBodyAccess On 230 | SecResponseBodyAccess On 231 | SecResponseBodyMimeType null 232 | SecDebugLog "$ENV{DEBUG_LOG}" 233 | SecDebugLogLevel 4 234 | SecAction "phase:3,deny,id:500051" 235 | ), 236 | match_log => { 237 | error => [ qr/Access denied with code 403 \(phase 3\). Unconditional match in SecAction./, 1 ], 238 | }, 239 | match_response => { 240 | status => qr/^403$/, 241 | }, 242 | request => new HTTP::Request( 243 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 244 | ), 245 | }, 246 | { 247 | type => "action", 248 | comment => "deny in phase:4", 249 | conf => qq( 250 | SecRuleEngine On 251 | SecRequestBodyAccess On 252 | SecResponseBodyAccess On 253 | SecResponseBodyMimeType null 254 | SecDebugLog "$ENV{DEBUG_LOG}" 255 | SecDebugLogLevel 4 256 | SecAction "phase:4,deny,id:500052" 257 | ), 258 | match_log => { 259 | error => [ qr/Access denied with code 403 \(phase 4\). Unconditional match in SecAction./, 1 ], 260 | }, 261 | match_response => { 262 | status => qr/^403$/, 263 | }, 264 | request => new HTTP::Request( 265 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 266 | ), 267 | }, 268 | 269 | # Drop 270 | { 271 | type => "action", 272 | comment => "drop in phase:1", 273 | conf => qq( 274 | SecRuleEngine On 275 | SecRequestBodyAccess On 276 | SecResponseBodyAccess On 277 | SecResponseBodyMimeType null 278 | SecAction "phase:1,drop,id:500053" 279 | ), 280 | match_log => { 281 | error => [ qr/Access denied with connection close \(phase 1\). Unconditional match in SecAction./, 1 ], 282 | }, 283 | match_response => { 284 | status => qr/^500$/, 285 | }, 286 | request => new HTTP::Request( 287 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 288 | ), 289 | }, 290 | { 291 | type => "action", 292 | comment => "drop in phase:2", 293 | conf => qq( 294 | SecRuleEngine On 295 | SecRequestBodyAccess On 296 | SecResponseBodyAccess On 297 | SecResponseBodyMimeType null 298 | SecAction "phase:2,drop,id:500054" 299 | ), 300 | match_log => { 301 | error => [ qr/Access denied with connection close \(phase 2\). Unconditional match in SecAction./, 1 ], 302 | }, 303 | match_response => { 304 | status => qr/^500$/, 305 | }, 306 | request => new HTTP::Request( 307 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 308 | ), 309 | }, 310 | { 311 | type => "action", 312 | comment => "drop in phase:3", 313 | conf => qq( 314 | SecRuleEngine On 315 | SecRequestBodyAccess On 316 | SecResponseBodyAccess On 317 | SecResponseBodyMimeType null 318 | SecDebugLog "$ENV{DEBUG_LOG}" 319 | SecDebugLogLevel 4 320 | SecAction "phase:3,drop,id:500055" 321 | ), 322 | match_log => { 323 | error => [ qr/Access denied with connection close \(phase 3\). Unconditional match in SecAction./, 1 ], 324 | }, 325 | match_response => { 326 | status => qr/^500$/, 327 | }, 328 | request => new HTTP::Request( 329 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 330 | ), 331 | }, 332 | { 333 | type => "action", 334 | comment => "drop in phase:4", 335 | conf => qq( 336 | SecRuleEngine On 337 | SecRequestBodyAccess On 338 | SecResponseBodyAccess On 339 | SecResponseBodyMimeType null 340 | SecDebugLog "$ENV{DEBUG_LOG}" 341 | SecDebugLogLevel 4 342 | SecAction "phase:4,drop,id:500056" 343 | ), 344 | match_log => { 345 | error => [ qr/Access denied with connection close \(phase 4\). Unconditional match in SecAction./, 1 ], 346 | }, 347 | match_response => { 348 | status => qr/^500$/, 349 | }, 350 | request => new HTTP::Request( 351 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 352 | ), 353 | }, 354 | 355 | # Redirect 356 | { 357 | type => "action", 358 | comment => "redirect in phase:1 (get)", 359 | conf => qq( 360 | SecRuleEngine On 361 | SecRequestBodyAccess On 362 | SecResponseBodyAccess On 363 | SecResponseBodyMimeType null 364 | SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500001" 365 | ), 366 | match_log => { 367 | error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 1\)/, 1 ], 368 | }, 369 | match_response => { 370 | status => qr/^200$/, 371 | content => qr/^TEST$/, 372 | }, 373 | request => new HTTP::Request( 374 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", 375 | ), 376 | }, 377 | { 378 | type => "action", 379 | comment => "redirect in phase:2 (get)", 380 | conf => qq( 381 | SecRuleEngine On 382 | SecRequestBodyAccess On 383 | SecResponseBodyAccess On 384 | SecResponseBodyMimeType null 385 | SecRule REQUEST_URI "\@streq /test2.txt" "phase:2,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500002" 386 | ), 387 | match_log => { 388 | error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 2\)/, 1 ], 389 | }, 390 | match_response => { 391 | status => qr/^200$/, 392 | content => qr/^TEST$/, 393 | }, 394 | request => new HTTP::Request( 395 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", 396 | ), 397 | }, 398 | { 399 | type => "action", 400 | comment => "redirect in phase:3 (get)", 401 | conf => qq( 402 | SecRuleEngine On 403 | SecRequestBodyAccess On 404 | SecResponseBodyAccess On 405 | SecResponseBodyMimeType null 406 | SecDebugLog "$ENV{DEBUG_LOG}" 407 | SecDebugLogLevel 4 408 | SecRule REQUEST_URI "\@streq /test2.txt" "phase:3,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500003" 409 | ), 410 | match_log => { 411 | error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 3\)/, 1 ], 412 | }, 413 | match_response => { 414 | status => qr/^200$/, 415 | content => qr/^TEST$/, 416 | }, 417 | request => new HTTP::Request( 418 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", 419 | ), 420 | }, 421 | { 422 | type => "action", 423 | comment => "redirect in phase:4 (get)", 424 | conf => qq( 425 | SecRuleEngine On 426 | SecRequestBodyAccess On 427 | SecResponseBodyAccess On 428 | SecResponseBodyMimeType null 429 | SecDebugLog "$ENV{DEBUG_LOG}" 430 | SecDebugLogLevel 4 431 | SecRule REQUEST_URI "\@streq /test2.txt" "phase:4,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',id:500004" 432 | ), 433 | match_log => { 434 | error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 4\)/, 1 ], 435 | }, 436 | match_response => { 437 | status => qr/^200$/, 438 | content => qr/^TEST$/, 439 | }, 440 | request => new HTTP::Request( 441 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", 442 | ), 443 | }, 444 | 445 | 446 | -------------------------------------------------------------------------------- /tests/regression/action/00-meta.t: -------------------------------------------------------------------------------- 1 | ### Test meta actions 2 | 3 | # TODO: id 4 | # TODO: logdata 5 | # TODO: msg 6 | # TODO: rev 7 | # TODO: severity 8 | # TODO: tag 9 | -------------------------------------------------------------------------------- /tests/regression/action/00-misc.t: -------------------------------------------------------------------------------- 1 | ### Test misc actions 2 | 3 | # TODO: block 4 | # TODO: capture 5 | # TODO: chain 6 | # TODO: deprecatevar 7 | # TODO: exec 8 | # TODO: expirevar 9 | # TODO: initcol 10 | # TODO: multiMatch 11 | # TODO: pause 12 | # TODO: sanitiseArg 13 | # TODO: sanitiseMatched 14 | # TODO: sanitiseRequestHeader 15 | # TODO: sanitiseResponseHeader 16 | # TODO: setuid 17 | # TODO: setsid 18 | # TODO: setenv 19 | # TODO: setvar 20 | # TODO: skip 21 | # TODO: skipAfter 22 | # TODO: xmlns 23 | -------------------------------------------------------------------------------- /tests/regression/action/00-transformations.t: -------------------------------------------------------------------------------- 1 | ### Transformation tests 2 | 3 | # NOTE: individual tests done in unit tests 4 | 5 | # TODO: t:none to override default 6 | # TODO: t:none inline 7 | # TODO: combined 8 | # TODO: caching 9 | -------------------------------------------------------------------------------- /tests/regression/action/10-ctl.t: -------------------------------------------------------------------------------- 1 | ### ctl 2 | 3 | ### ruleRemoveById 4 | { 5 | type => "action", 6 | comment => "ruleRemoveById existing rule across phases", 7 | conf => qq( 8 | SecRuleEngine On 9 | SecAction "phase:2,id:666,deny" 10 | SecAction "phase:1,pass,ctl:ruleRemoveById=666,id:500030" 11 | ), 12 | match_log => { 13 | }, 14 | match_response => { 15 | status => qr/^200$/, 16 | }, 17 | request => new HTTP::Request( 18 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 19 | ), 20 | }, 21 | { 22 | type => "action", 23 | comment => "ruleRemoveById future rule across phases", 24 | conf => qq( 25 | SecRuleEngine On 26 | SecAction "phase:1,pass,ctl:ruleRemoveById=666,id:500031" 27 | SecAction "phase:2,id:666,deny" 28 | ), 29 | match_log => { 30 | }, 31 | match_response => { 32 | status => qr/^200$/, 33 | }, 34 | request => new HTTP::Request( 35 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 36 | ), 37 | }, 38 | { 39 | type => "action", 40 | comment => "ruleRemoveById future rule same phase", 41 | conf => qq( 42 | SecRuleEngine On 43 | SecAction "phase:1,pass,ctl:ruleRemoveById=666,id:500032" 44 | SecAction "phase:1,id:666,deny" 45 | ), 46 | match_log => { 47 | }, 48 | match_response => { 49 | status => qr/^200$/, 50 | }, 51 | request => new HTTP::Request( 52 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 53 | ), 54 | }, 55 | 56 | 57 | -------------------------------------------------------------------------------- /tests/regression/action/10-detectiononly-actions.t: -------------------------------------------------------------------------------- 1 | ### Tests all of the actions in each phase in detection only mode 2 | 3 | # Pass 4 | { 5 | type => "action", 6 | comment => "pass in phase:1", 7 | conf => qq( 8 | SecRuleEngine DetectionOnly 9 | SecRequestBodyAccess On 10 | SecResponseBodyAccess On 11 | SecResponseBodyMimeType null 12 | SecDebugLog "$ENV{DEBUG_LOG}" 13 | SecDebugLogLevel 9 14 | SecAction "phase:1,pass,msg:'PASSED',id:500057" 15 | SecAction "phase:1,deny,msg:'DENIED',id:500058" 16 | ), 17 | match_log => { 18 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*PASSED/, 1 ], 19 | }, 20 | match_response => { 21 | status => qr/^200$/, 22 | }, 23 | request => new HTTP::Request( 24 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 25 | ), 26 | }, 27 | { 28 | type => "action", 29 | comment => "pass in phase:2", 30 | conf => qq( 31 | SecRuleEngine DetectionOnly 32 | SecRequestBodyAccess On 33 | SecResponseBodyAccess On 34 | SecResponseBodyMimeType null 35 | SecAction "phase:2,pass,msg:'PASSED',id:500059" 36 | SecAction "phase:2,deny,msg:'DENIED',id:500060" 37 | ), 38 | match_log => { 39 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*PASSED/, 1 ], 40 | }, 41 | match_response => { 42 | status => qr/^200$/, 43 | }, 44 | request => new HTTP::Request( 45 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 46 | ), 47 | }, 48 | { 49 | type => "action", 50 | comment => "pass in phase:3", 51 | conf => qq( 52 | SecRuleEngine DetectionOnly 53 | SecRequestBodyAccess On 54 | SecResponseBodyAccess On 55 | SecResponseBodyMimeType null 56 | SecDebugLog "$ENV{DEBUG_LOG}" 57 | SecDebugLogLevel 4 58 | SecAction "phase:3,pass,msg:'PASSED',id:500061" 59 | SecAction "phase:3,deny,msg:'DENIED',id:500062" 60 | ), 61 | match_log => { 62 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*PASSED/, 1 ], 63 | }, 64 | match_response => { 65 | status => qr/^200$/, 66 | }, 67 | request => new HTTP::Request( 68 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 69 | ), 70 | }, 71 | { 72 | type => "action", 73 | comment => "pass in phase:4", 74 | conf => qq( 75 | SecRuleEngine DetectionOnly 76 | SecRequestBodyAccess On 77 | SecResponseBodyAccess On 78 | SecResponseBodyMimeType null 79 | SecDebugLog "$ENV{DEBUG_LOG}" 80 | SecDebugLogLevel 4 81 | SecAction "phase:4,pass,msg:'PASSED',id:500063" 82 | SecAction "phase:4,deny,msg:'DENIED',id:500064" 83 | ), 84 | match_log => { 85 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*PASSED/, 1 ], 86 | }, 87 | match_response => { 88 | status => qr/^200$/, 89 | }, 90 | request => new HTTP::Request( 91 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 92 | ), 93 | }, 94 | 95 | # Allow 96 | { 97 | type => "action", 98 | comment => "allow in phase:1", 99 | conf => qq( 100 | SecRuleEngine DetectionOnly 101 | SecRequestBodyAccess On 102 | SecResponseBodyAccess On 103 | SecResponseBodyMimeType null 104 | SecAction "phase:1,allow,msg:'ALLOWED',id:500065" 105 | SecAction "phase:1,deny,msg:'DENIED',id:500066" 106 | ), 107 | match_log => { 108 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*ALLOWED/, 1 ], 109 | -error => [ qr/Access allowed/, 1 ], 110 | # TODO: Allow should probably stop rule execution 111 | # -error => [ qr/DENIED/, 1 ], 112 | }, 113 | match_response => { 114 | status => qr/^200$/, 115 | }, 116 | request => new HTTP::Request( 117 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 118 | ), 119 | }, 120 | { 121 | type => "action", 122 | comment => "allow in phase:2", 123 | conf => qq( 124 | SecRuleEngine DetectionOnly 125 | SecRequestBodyAccess On 126 | SecResponseBodyAccess On 127 | SecResponseBodyMimeType null 128 | SecAction "phase:2,allow,msg:'ALLOWED',id:500067" 129 | SecAction "phase:2,deny,msg:'DENIED',id:500068" 130 | ), 131 | match_log => { 132 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*ALLOWED/, 1 ], 133 | -error => [ qr/Access allowed/, 1 ], 134 | # TODO: Allow should probably stop rule execution 135 | # -error => [ qr/DENIED/, 1 ], 136 | }, 137 | match_response => { 138 | status => qr/^200$/, 139 | }, 140 | request => new HTTP::Request( 141 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 142 | ), 143 | }, 144 | { 145 | type => "action", 146 | comment => "allow in phase:3", 147 | conf => qq( 148 | SecRuleEngine DetectionOnly 149 | SecRequestBodyAccess On 150 | SecResponseBodyAccess On 151 | SecResponseBodyMimeType null 152 | SecAction "phase:3,allow,msg:'ALLOWED',id:500069" 153 | SecAction "phase:3,deny,msg:'DENIED',id:500070" 154 | ), 155 | match_log => { 156 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*ALLOWED/, 1 ], 157 | -error => [ qr/Access allowed/, 1 ], 158 | # TODO: Allow should probably stop rule execution 159 | # -error => [ qr/DENIED/, 1 ], 160 | }, 161 | match_response => { 162 | status => qr/^200$/, 163 | }, 164 | request => new HTTP::Request( 165 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 166 | ), 167 | }, 168 | { 169 | type => "action", 170 | comment => "allow in phase:4", 171 | conf => qq( 172 | SecRuleEngine DetectionOnly 173 | SecRequestBodyAccess On 174 | SecResponseBodyAccess On 175 | SecResponseBodyMimeType null 176 | SecAction "phase:4,allow,msg:'ALLOWED',id:500071" 177 | SecAction "phase:4,deny,msg:'DENIED',id:500072" 178 | ), 179 | match_log => { 180 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*ALLOWED/, 1 ], 181 | -error => [ qr/Access allowed/, 1 ], 182 | # TODO: Allow should probably stop rule execution 183 | # -error => [ qr/DENIED/, 1 ], 184 | }, 185 | match_response => { 186 | status => qr/^200$/, 187 | }, 188 | request => new HTTP::Request( 189 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 190 | ), 191 | }, 192 | 193 | # Deny 194 | { 195 | type => "action", 196 | comment => "deny in phase:1", 197 | conf => qq( 198 | SecRuleEngine DetectionOnly 199 | SecRequestBodyAccess On 200 | SecResponseBodyAccess On 201 | SecResponseBodyMimeType null 202 | SecAction "phase:1,deny,msg:'DENIED',id:500073" 203 | ), 204 | match_log => { 205 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DENIED/, 1 ], 206 | -error => [ qr/Access denied/, 1 ], 207 | }, 208 | match_response => { 209 | status => qr/^200$/, 210 | }, 211 | request => new HTTP::Request( 212 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 213 | ), 214 | }, 215 | { 216 | type => "action", 217 | comment => "deny in phase:2", 218 | conf => qq( 219 | SecRuleEngine DetectionOnly 220 | SecRequestBodyAccess On 221 | SecResponseBodyAccess On 222 | SecResponseBodyMimeType null 223 | SecAction "phase:2,deny,msg:'DENIED',id:500074" 224 | ), 225 | match_log => { 226 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DENIED/, 1 ], 227 | -error => [ qr/Access denied/, 1 ], 228 | }, 229 | match_response => { 230 | status => qr/^200$/, 231 | }, 232 | request => new HTTP::Request( 233 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 234 | ), 235 | }, 236 | { 237 | type => "action", 238 | comment => "deny in phase:3", 239 | conf => qq( 240 | SecRuleEngine DetectionOnly 241 | SecRequestBodyAccess On 242 | SecResponseBodyAccess On 243 | SecResponseBodyMimeType null 244 | SecAction "phase:3,deny,msg:'DENIED',id:500075" 245 | ), 246 | match_log => { 247 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DENIED/, 1 ], 248 | -error => [ qr/Access denied/, 1 ], 249 | }, 250 | match_response => { 251 | status => qr/^200$/, 252 | }, 253 | request => new HTTP::Request( 254 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 255 | ), 256 | }, 257 | { 258 | type => "action", 259 | comment => "deny in phase:4", 260 | conf => qq( 261 | SecRuleEngine DetectionOnly 262 | SecRequestBodyAccess On 263 | SecResponseBodyAccess On 264 | SecResponseBodyMimeType null 265 | SecAction "phase:4,deny,msg:'DENIED',id:500076" 266 | ), 267 | match_log => { 268 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DENIED/, 1 ], 269 | -error => [ qr/Access denied/, 1 ], 270 | }, 271 | match_response => { 272 | status => qr/^200$/, 273 | }, 274 | request => new HTTP::Request( 275 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 276 | ), 277 | }, 278 | 279 | # Drop 280 | { 281 | type => "action", 282 | comment => "drop in phase:1", 283 | conf => qq( 284 | SecRuleEngine DetectionOnly 285 | SecRequestBodyAccess On 286 | SecResponseBodyAccess On 287 | SecResponseBodyMimeType null 288 | SecAction "phase:1,drop,msg:'DROPPED',id:500077" 289 | ), 290 | match_log => { 291 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DROPPED/, 1 ], 292 | -error => [ qr/Access denied/, 1 ], 293 | }, 294 | match_response => { 295 | status => qr/^200$/, 296 | }, 297 | request => new HTTP::Request( 298 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 299 | ), 300 | }, 301 | { 302 | type => "action", 303 | comment => "drop in phase:2", 304 | conf => qq( 305 | SecRuleEngine DetectionOnly 306 | SecRequestBodyAccess On 307 | SecResponseBodyAccess On 308 | SecResponseBodyMimeType null 309 | SecAction "phase:2,drop,msg:'DROPPED',id:500078" 310 | ), 311 | match_log => { 312 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DROPPED/, 1 ], 313 | -error => [ qr/Access denied/, 1 ], 314 | }, 315 | match_response => { 316 | status => qr/^200$/, 317 | }, 318 | request => new HTTP::Request( 319 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 320 | ), 321 | }, 322 | { 323 | type => "action", 324 | comment => "drop in phase:3", 325 | conf => qq( 326 | SecRuleEngine DetectionOnly 327 | SecRequestBodyAccess On 328 | SecResponseBodyAccess On 329 | SecResponseBodyMimeType null 330 | SecAction "phase:3,drop,msg:'DROPPED',id:500079" 331 | ), 332 | match_log => { 333 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DROPPED/, 1 ], 334 | -error => [ qr/Access denied/, 1 ], 335 | }, 336 | match_response => { 337 | status => qr/^200$/, 338 | }, 339 | request => new HTTP::Request( 340 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 341 | ), 342 | }, 343 | { 344 | type => "action", 345 | comment => "drop in phase:4", 346 | conf => qq( 347 | SecRuleEngine DetectionOnly 348 | SecRequestBodyAccess On 349 | SecResponseBodyAccess On 350 | SecResponseBodyMimeType null 351 | SecAction "phase:4,drop,msg:'DROPPED',id:500080" 352 | ), 353 | match_log => { 354 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DROPPED/, 1 ], 355 | -error => [ qr/Access denied/, 1 ], 356 | }, 357 | match_response => { 358 | status => qr/^200$/, 359 | }, 360 | request => new HTTP::Request( 361 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 362 | ), 363 | }, 364 | 365 | # Redirect 366 | { 367 | type => "action", 368 | comment => "redirect in phase:1 (get)", 369 | conf => qq( 370 | SecRuleEngine DetectionOnly 371 | SecRequestBodyAccess On 372 | SecResponseBodyAccess On 373 | SecResponseBodyMimeType null 374 | SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'REDIRECTED',id:500009" 375 | ), 376 | match_log => { 377 | error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*REDIRECTED/, 1 ], 378 | -error => [ qr/Access denied/, 1 ], 379 | }, 380 | match_response => { 381 | status => qr/^200$/, 382 | content => qr/^TEST 2$/, 383 | }, 384 | request => new HTTP::Request( 385 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", 386 | ), 387 | }, 388 | { 389 | type => "action", 390 | comment => "redirect in phase:2 (get)", 391 | conf => qq( 392 | SecRuleEngine DetectionOnly 393 | SecRequestBodyAccess On 394 | SecResponseBodyAccess On 395 | SecResponseBodyMimeType null 396 | SecRule REQUEST_URI "\@streq /test2.txt" "phase:2,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'REDIRECTED',id:500010" 397 | ), 398 | match_log => { 399 | error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*REDIRECTED/, 1 ], 400 | -error => [ qr/Access denied/, 1 ], 401 | }, 402 | match_response => { 403 | status => qr/^200$/, 404 | content => qr/^TEST 2$/, 405 | }, 406 | request => new HTTP::Request( 407 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", 408 | ), 409 | }, 410 | { 411 | type => "action", 412 | comment => "redirect in phase:3 (get)", 413 | conf => qq( 414 | SecRuleEngine DetectionOnly 415 | SecRequestBodyAccess On 416 | SecResponseBodyAccess On 417 | SecResponseBodyMimeType null 418 | SecRule REQUEST_URI "\@streq /test2.txt" "phase:3,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'REDIRECTED',id:500011" 419 | ), 420 | match_log => { 421 | error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*REDIRECTED/, 1 ], 422 | -error => [ qr/Access denied/, 1 ], 423 | }, 424 | match_response => { 425 | status => qr/^200$/, 426 | content => qr/^TEST 2$/, 427 | }, 428 | request => new HTTP::Request( 429 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", 430 | ), 431 | }, 432 | { 433 | type => "action", 434 | comment => "redirect in phase:4 (get)", 435 | conf => qq( 436 | SecRuleEngine DetectionOnly 437 | SecRequestBodyAccess On 438 | SecResponseBodyAccess On 439 | SecResponseBodyMimeType null 440 | SecRule REQUEST_URI "\@streq /test2.txt" "phase:4,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'REDIRECTED',id:500012" 441 | ), 442 | match_log => { 443 | error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*REDIRECTED/, 1 ], 444 | -error => [ qr/Access denied/, 1 ], 445 | }, 446 | match_response => { 447 | status => qr/^200$/, 448 | content => qr/^TEST 2$/, 449 | }, 450 | request => new HTTP::Request( 451 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt", 452 | ), 453 | }, 454 | 455 | 456 | -------------------------------------------------------------------------------- /tests/regression/action/10-logging.t: -------------------------------------------------------------------------------- 1 | ### Logging tests 2 | 3 | # log/nolog (pass) 4 | { 5 | type => "action", 6 | comment => "log (pass)", 7 | conf => qq( 8 | SecRuleEngine On 9 | SecDebugLog "$ENV{DEBUG_LOG}" 10 | SecDebugLogLevel 9 11 | SecAuditLogRelevantStatus xxx 12 | SecAuditEngine RelevantOnly 13 | SecAuditLog "$ENV{AUDIT_LOG}" 14 | SecAction "phase:1,pass,log,id:500006" 15 | ), 16 | match_log => { 17 | error => [ qr/ModSecurity: Warning\. Unconditional match in SecAction\./, 1 ], 18 | audit => [ qr/Message: Warning\. Unconditional match in SecAction\./, 1 ], 19 | }, 20 | match_response => { 21 | status => qr/^200$/, 22 | }, 23 | request => new HTTP::Request( 24 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 25 | ), 26 | }, 27 | { 28 | type => "action", 29 | comment => "nolog (pass)", 30 | conf => qq( 31 | SecRuleEngine On 32 | SecDebugLog "$ENV{DEBUG_LOG}" 33 | SecDebugLogLevel 9 34 | SecAuditLogRelevantStatus xxx 35 | SecAuditEngine RelevantOnly 36 | SecAuditLog "$ENV{AUDIT_LOG}" 37 | SecAction "phase:1,pass,nolog,id:500007" 38 | ), 39 | match_log => { 40 | -error => [ qr/500007/, 1 ], 41 | -audit => [ qr/./, 1 ], 42 | }, 43 | match_response => { 44 | status => qr/^200$/, 45 | }, 46 | request => new HTTP::Request( 47 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 48 | ), 49 | }, 50 | 51 | # log/nolog (deny) 52 | { 53 | type => "action", 54 | comment => "log (deny)", 55 | conf => qq( 56 | SecRuleEngine On 57 | SecDebugLog "$ENV{DEBUG_LOG}" 58 | SecDebugLogLevel 9 59 | SecAuditLogRelevantStatus xxx 60 | SecAuditEngine RelevantOnly 61 | SecAuditLog "$ENV{AUDIT_LOG}" 62 | SecAction "phase:1,deny,status:403,log,id:500008" 63 | ), 64 | match_log => { 65 | error => [ qr/ModSecurity: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], 66 | audit => [ qr/Message: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], 67 | }, 68 | match_response => { 69 | status => qr/^403$/, 70 | }, 71 | request => new HTTP::Request( 72 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 73 | ), 74 | }, 75 | { 76 | type => "action", 77 | comment => "nolog (deny)", 78 | conf => qq( 79 | SecRuleEngine On 80 | SecDebugLog "$ENV{DEBUG_LOG}" 81 | SecDebugLogLevel 9 82 | SecAuditLogRelevantStatus xxx 83 | SecAuditEngine RelevantOnly 84 | SecAuditLog "$ENV{AUDIT_LOG}" 85 | SecAction "phase:1,deny,status:403,nolog,id:500009" 86 | ), 87 | match_log => { 88 | -error => [ qr/500009/, 1 ], 89 | -audit => [ qr/./, 1 ], 90 | }, 91 | match_response => { 92 | status => qr/^403$/, 93 | }, 94 | request => new HTTP::Request( 95 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 96 | ), 97 | }, 98 | 99 | # auditlog/noauditlog (pass) 100 | { 101 | type => "action", 102 | comment => "auditlog (pass)", 103 | conf => qq( 104 | SecRuleEngine On 105 | SecDebugLog "$ENV{DEBUG_LOG}" 106 | SecDebugLogLevel 9 107 | SecAuditLogRelevantStatus xxx 108 | SecAuditEngine RelevantOnly 109 | SecAuditLog "$ENV{AUDIT_LOG}" 110 | SecAction "phase:1,pass,auditlog,id:500010" 111 | ), 112 | match_log => { 113 | error => [ qr/ModSecurity: Warning\. Unconditional match in SecAction\./, 1 ], 114 | audit => [ qr/Message: Warning\. Unconditional match in SecAction\./, 1 ], 115 | }, 116 | match_response => { 117 | status => qr/^200$/, 118 | }, 119 | request => new HTTP::Request( 120 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 121 | ), 122 | }, 123 | { 124 | type => "action", 125 | comment => "noauditlog (pass)", 126 | conf => qq( 127 | SecRuleEngine On 128 | SecDebugLog "$ENV{DEBUG_LOG}" 129 | SecDebugLogLevel 9 130 | SecAuditLogRelevantStatus xxx 131 | SecAuditEngine RelevantOnly 132 | SecAuditLog "$ENV{AUDIT_LOG}" 133 | SecAction "phase:1,pass,noauditlog,id:500011" 134 | ), 135 | match_log => { 136 | error => [ qr/ModSecurity: Warning\. Unconditional match in SecAction\./, 1 ], 137 | -audit => [ qr/./, 1 ], 138 | }, 139 | match_response => { 140 | status => qr/^200$/, 141 | }, 142 | request => new HTTP::Request( 143 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 144 | ), 145 | }, 146 | 147 | # auditlog/noauditlog (deny) 148 | { 149 | type => "action", 150 | comment => "auditlog (deny)", 151 | conf => qq( 152 | SecRuleEngine On 153 | SecDebugLog "$ENV{DEBUG_LOG}" 154 | SecDebugLogLevel 9 155 | SecAuditLogRelevantStatus xxx 156 | SecAuditEngine RelevantOnly 157 | SecAuditLog "$ENV{AUDIT_LOG}" 158 | SecAction "phase:1,deny,status:403,auditlog,id:500012" 159 | ), 160 | match_log => { 161 | error => [ qr/ModSecurity: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], 162 | audit => [ qr/Message: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], 163 | }, 164 | match_response => { 165 | status => qr/^403$/, 166 | }, 167 | request => new HTTP::Request( 168 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 169 | ), 170 | }, 171 | { 172 | type => "action", 173 | comment => "noauditlog (deny)", 174 | conf => qq( 175 | SecRuleEngine On 176 | SecDebugLog "$ENV{DEBUG_LOG}" 177 | SecDebugLogLevel 9 178 | SecAuditLogRelevantStatus xxx 179 | SecAuditEngine RelevantOnly 180 | SecAuditLog "$ENV{AUDIT_LOG}" 181 | SecAction "phase:1,deny,status:403,noauditlog,id:500013" 182 | ), 183 | match_log => { 184 | error => [ qr/ModSecurity: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], 185 | -audit => [ qr/./, 1 ], 186 | }, 187 | match_response => { 188 | status => qr/^403$/, 189 | }, 190 | request => new HTTP::Request( 191 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 192 | ), 193 | }, 194 | 195 | # All log/nolog auditlog/noauditlog combos (pass) 196 | { 197 | type => "action", 198 | comment => "log,auditlog (pass)", 199 | conf => qq( 200 | SecRuleEngine On 201 | SecDebugLog "$ENV{DEBUG_LOG}" 202 | SecDebugLogLevel 9 203 | SecAuditLogRelevantStatus xxx 204 | SecAuditEngine RelevantOnly 205 | SecAuditLog "$ENV{AUDIT_LOG}" 206 | SecAction "phase:1,pass,log,auditlog,id:500014" 207 | ), 208 | match_log => { 209 | error => [ qr/ModSecurity: Warning\. Unconditional match in SecAction\./, 1 ], 210 | audit => [ qr/Message: Warning\. Unconditional match in SecAction\./, 1 ], 211 | }, 212 | match_response => { 213 | status => qr/^200$/, 214 | }, 215 | request => new HTTP::Request( 216 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 217 | ), 218 | }, 219 | { 220 | type => "action", 221 | comment => "log,noauditlog (pass)", 222 | conf => qq( 223 | SecRuleEngine On 224 | SecDebugLog "$ENV{DEBUG_LOG}" 225 | SecDebugLogLevel 9 226 | SecAuditLogRelevantStatus xxx 227 | SecAuditEngine RelevantOnly 228 | SecAuditLog "$ENV{AUDIT_LOG}" 229 | SecAction "phase:1,pass,log,noauditlog,id:500015" 230 | ), 231 | match_log => { 232 | error => [ qr/ModSecurity: Warning\. Unconditional match in SecAction\./, 1 ], 233 | -audit => [ qr/./, 1 ], 234 | }, 235 | match_response => { 236 | status => qr/^200$/, 237 | }, 238 | request => new HTTP::Request( 239 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 240 | ), 241 | }, 242 | { 243 | type => "action", 244 | comment => "nolog,auditlog (pass)", 245 | conf => qq( 246 | SecRuleEngine On 247 | SecDebugLog "$ENV{DEBUG_LOG}" 248 | SecDebugLogLevel 9 249 | SecAuditLogRelevantStatus xxx 250 | SecAuditEngine RelevantOnly 251 | SecAuditLog "$ENV{AUDIT_LOG}" 252 | SecAction "phase:1,pass,nolog,auditlog,id:500016" 253 | ), 254 | match_log => { 255 | audit => [ qr/-H--\s+Message: .*Stopwatch: /s, 1 ], 256 | }, 257 | match_response => { 258 | status => qr/^200$/, 259 | }, 260 | request => new HTTP::Request( 261 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 262 | ), 263 | }, 264 | { 265 | type => "action", 266 | comment => "nolog,noauditlog (pass)", 267 | conf => qq( 268 | SecRuleEngine On 269 | SecDebugLog "$ENV{DEBUG_LOG}" 270 | SecDebugLogLevel 9 271 | SecAuditLogRelevantStatus xxx 272 | SecAuditEngine RelevantOnly 273 | SecAuditLog "$ENV{AUDIT_LOG}" 274 | SecAction "phase:1,pass,nolog,noauditlog,id:500017" 275 | ), 276 | match_log => { 277 | -error => [ qr/500017/, 1 ], 278 | -audit => [ qr/./, 1 ], 279 | }, 280 | match_response => { 281 | status => qr/^200$/, 282 | }, 283 | request => new HTTP::Request( 284 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 285 | ), 286 | }, 287 | { 288 | type => "action", 289 | comment => "auditlog,log (pass)", 290 | conf => qq( 291 | SecRuleEngine On 292 | SecDebugLog "$ENV{DEBUG_LOG}" 293 | SecDebugLogLevel 9 294 | SecAuditLogRelevantStatus xxx 295 | SecAuditEngine RelevantOnly 296 | SecAuditLog "$ENV{AUDIT_LOG}" 297 | SecAction "phase:1,pass,auditlog,log,id:500018" 298 | ), 299 | match_log => { 300 | error => [ qr/ModSecurity: Warning\. Unconditional match in SecAction\./, 1 ], 301 | audit => [ qr/Message: Warning\. Unconditional match in SecAction\./, 1 ], 302 | }, 303 | match_response => { 304 | status => qr/^200$/, 305 | }, 306 | request => new HTTP::Request( 307 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 308 | ), 309 | }, 310 | { 311 | type => "action", 312 | comment => "auditlog,nolog (pass)", 313 | conf => qq( 314 | SecRuleEngine On 315 | SecDebugLog "$ENV{DEBUG_LOG}" 316 | SecDebugLogLevel 9 317 | SecAuditLogRelevantStatus xxx 318 | SecAuditEngine RelevantOnly 319 | SecAuditLog "$ENV{AUDIT_LOG}" 320 | SecAction "phase:1,pass,auditlog,nolog,id:500019" 321 | ), 322 | match_log => { 323 | -error => [ qr/500019/, 1 ], 324 | -audit => [ qr/./, 1 ], 325 | }, 326 | match_response => { 327 | status => qr/^200$/, 328 | }, 329 | request => new HTTP::Request( 330 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 331 | ), 332 | }, 333 | { 334 | type => "action", 335 | comment => "noauditlog,log (pass)", 336 | conf => qq( 337 | SecRuleEngine On 338 | SecDebugLog "$ENV{DEBUG_LOG}" 339 | SecDebugLogLevel 9 340 | SecAuditLogRelevantStatus xxx 341 | SecAuditEngine RelevantOnly 342 | SecAuditLog "$ENV{AUDIT_LOG}" 343 | SecAction "phase:1,pass,noauditlog,log,id:500020" 344 | ), 345 | match_log => { 346 | error => [ qr/ModSecurity: Warning\. Unconditional match in SecAction\./, 1 ], 347 | -audit => [ qr/./, 1 ], 348 | }, 349 | match_response => { 350 | status => qr/^200$/, 351 | }, 352 | request => new HTTP::Request( 353 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 354 | ), 355 | }, 356 | { 357 | type => "action", 358 | comment => "noauditlog,nolog (pass)", 359 | conf => qq( 360 | SecRuleEngine On 361 | SecDebugLog "$ENV{DEBUG_LOG}" 362 | SecDebugLogLevel 9 363 | SecAuditLogRelevantStatus xxx 364 | SecAuditEngine RelevantOnly 365 | SecAuditLog "$ENV{AUDIT_LOG}" 366 | SecAction "phase:1,pass,noauditlog,nolog,id:500021" 367 | ), 368 | match_log => { 369 | -error => [ qr/500021/, 1 ], 370 | -audit => [ qr/./, 1 ], 371 | }, 372 | match_response => { 373 | status => qr/^200$/, 374 | }, 375 | request => new HTTP::Request( 376 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 377 | ), 378 | }, 379 | 380 | # All log/nolog auditlog/noauditlog combos (deny) 381 | { 382 | type => "action", 383 | comment => "log,auditlog (deny)", 384 | conf => qq( 385 | SecRuleEngine On 386 | SecDebugLog "$ENV{DEBUG_LOG}" 387 | SecDebugLogLevel 9 388 | SecAuditLogRelevantStatus xxx 389 | SecAuditEngine RelevantOnly 390 | SecAuditLog "$ENV{AUDIT_LOG}" 391 | SecAction "phase:1,deny,status:403,log,auditlog,id:500022" 392 | ), 393 | match_log => { 394 | error => [ qr/ModSecurity: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], 395 | audit => [ qr/Message: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], 396 | }, 397 | match_response => { 398 | status => qr/^403$/, 399 | }, 400 | request => new HTTP::Request( 401 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 402 | ), 403 | }, 404 | { 405 | type => "action", 406 | comment => "log,noauditlog (deny)", 407 | conf => qq( 408 | SecRuleEngine On 409 | SecDebugLog "$ENV{DEBUG_LOG}" 410 | SecDebugLogLevel 9 411 | SecAuditLogRelevantStatus xxx 412 | SecAuditEngine RelevantOnly 413 | SecAuditLog "$ENV{AUDIT_LOG}" 414 | SecAction "phase:1,deny,status:403,log,noauditlog,id:500023" 415 | ), 416 | match_log => { 417 | error => [ qr/ModSecurity: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], 418 | -audit => [ qr/./, 1 ], 419 | }, 420 | match_response => { 421 | status => qr/^403$/, 422 | }, 423 | request => new HTTP::Request( 424 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 425 | ), 426 | }, 427 | { 428 | type => "action", 429 | comment => "nolog,auditlog (deny)", 430 | conf => qq( 431 | SecRuleEngine On 432 | SecDebugLog "$ENV{DEBUG_LOG}" 433 | SecDebugLogLevel 9 434 | SecAuditLogRelevantStatus xxx 435 | SecAuditEngine RelevantOnly 436 | SecAuditLog "$ENV{AUDIT_LOG}" 437 | SecAction "phase:1,deny,status:403,nolog,auditlog,id:500024" 438 | ), 439 | match_log => { 440 | audit => [ qr/-H--\s+Message: .*Stopwatch: /s, 1 ], 441 | }, 442 | match_response => { 443 | -error => [ qr/ModSecurity: /, 1 ], 444 | status => qr/^403$/, 445 | }, 446 | request => new HTTP::Request( 447 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 448 | ), 449 | }, 450 | { 451 | type => "action", 452 | comment => "nolog,noauditlog (deny)", 453 | conf => qq( 454 | SecRuleEngine On 455 | SecDebugLog "$ENV{DEBUG_LOG}" 456 | SecDebugLogLevel 9 457 | SecAuditLogRelevantStatus xxx 458 | SecAuditEngine RelevantOnly 459 | SecAuditLog "$ENV{AUDIT_LOG}" 460 | SecAction "phase:1,deny,status:403,nolog,noauditlog,id:500025" 461 | ), 462 | match_log => { 463 | -error => [ qr/500025/, 1 ], 464 | -audit => [ qr/./, 1 ], 465 | }, 466 | match_response => { 467 | status => qr/^403$/, 468 | }, 469 | request => new HTTP::Request( 470 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 471 | ), 472 | }, 473 | { 474 | type => "action", 475 | comment => "auditlog,log (deny)", 476 | conf => qq( 477 | SecRuleEngine On 478 | SecDebugLog "$ENV{DEBUG_LOG}" 479 | SecDebugLogLevel 9 480 | SecAuditLogRelevantStatus xxx 481 | SecAuditEngine RelevantOnly 482 | SecAuditLog "$ENV{AUDIT_LOG}" 483 | SecAction "phase:1,deny,status:403,auditlog,log,id:500026" 484 | ), 485 | match_log => { 486 | error => [ qr/ModSecurity: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], 487 | audit => [ qr/Message: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], 488 | }, 489 | match_response => { 490 | status => qr/^403$/, 491 | }, 492 | request => new HTTP::Request( 493 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 494 | ), 495 | }, 496 | { 497 | type => "action", 498 | comment => "auditlog,nolog (deny)", 499 | conf => qq( 500 | SecRuleEngine On 501 | SecDebugLog "$ENV{DEBUG_LOG}" 502 | SecDebugLogLevel 9 503 | SecAuditLogRelevantStatus xxx 504 | SecAuditEngine RelevantOnly 505 | SecAuditLog "$ENV{AUDIT_LOG}" 506 | SecAction "phase:1,deny,status:403,auditlog,nolog,id:500027" 507 | ), 508 | match_log => { 509 | -error => [ qr/500027/, 1 ], 510 | -audit => [ qr/./, 1 ], 511 | }, 512 | match_response => { 513 | status => qr/^403$/, 514 | }, 515 | request => new HTTP::Request( 516 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 517 | ), 518 | }, 519 | { 520 | type => "action", 521 | comment => "noauditlog,log (deny)", 522 | conf => qq( 523 | SecRuleEngine On 524 | SecDebugLog "$ENV{DEBUG_LOG}" 525 | SecDebugLogLevel 9 526 | SecAuditLogRelevantStatus xxx 527 | SecAuditEngine RelevantOnly 528 | SecAuditLog "$ENV{AUDIT_LOG}" 529 | SecAction "phase:1,deny,status:403,noauditlog,log,id:500028" 530 | ), 531 | match_log => { 532 | error => [ qr/ModSecurity: Access denied with code 403 \(phase 1\)\. Unconditional match in SecAction\./, 1 ], 533 | -audit => [ qr/./, 1 ], 534 | }, 535 | match_response => { 536 | status => qr/^403$/, 537 | }, 538 | request => new HTTP::Request( 539 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 540 | ), 541 | }, 542 | { 543 | type => "action", 544 | comment => "noauditlog,nolog (deny)", 545 | conf => qq( 546 | SecRuleEngine On 547 | SecDebugLog "$ENV{DEBUG_LOG}" 548 | SecDebugLogLevel 9 549 | SecAuditLogRelevantStatus xxx 550 | SecAuditEngine RelevantOnly 551 | SecAuditLog "$ENV{AUDIT_LOG}" 552 | SecAction "phase:1,deny,status:403,noauditlog,nolog,id:500029" 553 | ), 554 | match_log => { 555 | -error => [ qr/500029/, 1 ], 556 | -audit => [ qr/./, 1 ], 557 | }, 558 | match_response => { 559 | status => qr/^403$/, 560 | }, 561 | request => new HTTP::Request( 562 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 563 | ), 564 | }, 565 | -------------------------------------------------------------------------------- /tests/regression/config/00-load-modsec.t: -------------------------------------------------------------------------------- 1 | { 2 | type => "config", 3 | comment => "module loaded", 4 | match_log => { 5 | error => { 6 | apache_v3 => [ qr/ModSecurity-Apache/, 10 ], 7 | apache => [ qr/ModSecurity for Apache.* configured\./, 10 ], 8 | nginx => [ qr/ModSecurity for nginx.* configured\./, 10 ], 9 | }, 10 | }, 11 | }, 12 | { 13 | type => "config", 14 | comment => "minimal config", 15 | conf => sub { 16 | # Open the minimal conf file, substituting the 17 | # relative log paths with full paths. 18 | open(C, "<$ENV{DIST_ROOT}/modsecurity.conf-minimal") or die "$!\n"; 19 | (my $conf = join('', )) =~ s#Log logs/#Log $ENV{TEST_SERVER_ROOT}/logs/#g; 20 | close C; 21 | 22 | return $conf; 23 | }, 24 | match_log => { 25 | error => { 26 | apache_v3 => [ qr/ModSecurity-Apache/, 10 ], 27 | apache => [ qr/ModSecurity for Apache.* configured\./, 10 ], 28 | nginx => [ qr/ModSecurity for nginx.* configured\./, 10 ], 29 | }, 30 | }, 31 | }, 32 | -------------------------------------------------------------------------------- /tests/regression/config/10-audit-directives.t: -------------------------------------------------------------------------------- 1 | ### SecAudit* directive tests 2 | 3 | # SecAuditEngine 4 | { 5 | type => "config", 6 | comment => "SecAuditEngine On", 7 | conf => qq( 8 | SecAuditEngine On 9 | SecAuditLog $ENV{AUDIT_LOG} 10 | ), 11 | match_log => { 12 | audit => [ qr/./, 1 ], 13 | }, 14 | match_response => { 15 | status => qr/^200$/, 16 | }, 17 | request => new HTTP::Request( 18 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 19 | ), 20 | }, 21 | { 22 | type => "config", 23 | comment => "SecAuditEngine Off", 24 | conf => qq( 25 | SecAuditEngine Off 26 | SecAuditLog $ENV{AUDIT_LOG} 27 | ), 28 | match_log => { 29 | -audit => [ qr/./, 1 ], 30 | }, 31 | match_response => { 32 | status => qr/^200$/, 33 | }, 34 | request => new HTTP::Request( 35 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 36 | ), 37 | }, 38 | { 39 | type => "config", 40 | comment => "SecAuditEngine RelevantOnly (pos)", 41 | conf => qq( 42 | SecRuleEngine On 43 | SecAuditEngine RelevantOnly 44 | SecAuditLog $ENV{AUDIT_LOG} 45 | SecDebugLog $ENV{DEBUG_LOG} 46 | SecDebugLogLevel 9 47 | SecResponseBodyAccess On 48 | SecDefaultAction "phase:2,log,auditlog,pass" 49 | SecRule REQUEST_URI "." "phase:4,deny,id:500251" 50 | ), 51 | match_log => { 52 | audit => [ qr/./, 1 ], 53 | }, 54 | match_response => { 55 | status => qr/^403$/, 56 | }, 57 | request => new HTTP::Request( 58 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 59 | ), 60 | }, 61 | { 62 | type => "config", 63 | comment => "SecAuditEngine RelevantOnly (neg)", 64 | conf => qq( 65 | SecAuditEngine RelevantOnly 66 | SecAuditLog $ENV{AUDIT_LOG} 67 | SecResponseBodyAccess On 68 | SecDefaultAction "phase:2,log,auditlog,pass" 69 | ), 70 | match_log => { 71 | -audit => [ qr/./, 1 ], 72 | }, 73 | match_response => { 74 | status => qr/^200$/, 75 | }, 76 | request => new HTTP::Request( 77 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 78 | ), 79 | }, 80 | 81 | # SecAuditLogType & SecAuditLogStorageDir 82 | { 83 | type => "config", 84 | comment => "SecAuditLogType Serial", 85 | conf => qq( 86 | SecAuditEngine On 87 | SecAuditLog $ENV{AUDIT_LOG} 88 | SecAuditLogType Serial 89 | ), 90 | match_log => { 91 | audit => [ qr/./, 1 ], 92 | }, 93 | match_response => { 94 | status => qr/^404$/, 95 | }, 96 | request => new HTTP::Request( 97 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/bogus", 98 | ), 99 | }, 100 | { 101 | type => "config", 102 | comment => "SecAuditLogType Concurrent", 103 | conf => qq( 104 | SecAuditEngine On 105 | SecAuditLog $ENV{AUDIT_LOG} 106 | SecAuditLogType Concurrent 107 | SecAuditLogStorageDir "$ENV{LOGS_DIR}/audit" 108 | ), 109 | test => sub { 110 | ### Perl code to parse the audit log entry and verify 111 | ### that the concurrent audit log exists and contains 112 | ### the correct data. 113 | ### 114 | ### TODO: Need some API for this :) 115 | ### 116 | ### FIXME: Just workable with apache, the timing to load auditlog from nginx 117 | ### is not correct, so the test is failing even when it should pass. 118 | ### Disabling it for now until we figure out a way to handle that. 119 | 120 | # Parse log 121 | #my $alogre = qr/^(?:\S+)\ (?:\S+)\ (?:\S+)\ (?:\S+)\ \[(?:[^:]+):(?:\d+:\d+:\d+)\ (?:[^\]]+)\]\ \"(?:.*)\"\ (?:\d+)\ (?:\S+)\ \"(?:.*)\"\ \"(?:.*)\"\ (\S+)\ \"(?:.*)\"\ (\S+)\ (?:\d+)\ (?:\d+)\ (?:\S+)(?:.*)$/m; 122 | #my $alog = match_log("audit", $alogre, 1); 123 | #chomp $alog; 124 | #dbg("Alog: $alog\n"); 125 | #my @log = ($alog =~ m/$alogre/); 126 | #my($id, $fn) = ($log[0], $log[1]); 127 | #if (!$id or !$fn) { 128 | #dbg("LOG ENTRY: $alog"); 129 | #die "Failed to parse audit log: $ENV{AUDIT_LOG}\n"; 130 | #} 131 | 132 | # Verify concurrent log exists 133 | #my $alogdatafn = "$ENV{LOGS_DIR}/audit$fn"; 134 | #if (! -e "$alogdatafn") { 135 | #die "Audit log does not exist: $alogdatafn\n"; 136 | #} 137 | 138 | # Verify concurrent log contents 139 | #if (defined match_file($alogdatafn, qr/^--[^-]+-A--.*$id.*-Z--$/s)) { 140 | #return 0; 141 | #} 142 | 143 | # Error 144 | #dbg("LOGDATA: \"$FILE{$alogdatafn}{buf}\""); 145 | #die "Audit log data did not match.\n"; 146 | return 0; 147 | }, 148 | match_response => { 149 | status => qr/^200$/, 150 | }, 151 | request => new HTTP::Request( 152 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 153 | ), 154 | }, 155 | 156 | # SecAuditLogRelevantStatus 157 | { 158 | type => "config", 159 | comment => "SecAuditLogRelevantStatus (pos)", 160 | conf => qq( 161 | SecAuditEngine RelevantOnly 162 | SecAuditLog $ENV{AUDIT_LOG} 163 | SecAuditLogRelevantStatus "^4" 164 | ), 165 | match_log => { 166 | audit => [ qr/./, 1 ], 167 | }, 168 | match_response => { 169 | status => qr/^404$/, 170 | }, 171 | request => new HTTP::Request( 172 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/bogus", 173 | ), 174 | }, 175 | { 176 | type => "config", 177 | comment => "SecAuditLogRelevantStatus (neg)", 178 | conf => qq( 179 | SecAuditEngine RelevantOnly 180 | SecAuditLog $ENV{AUDIT_LOG} 181 | SecAuditLogRelevantStatus "^4" 182 | ), 183 | match_log => { 184 | -audit => [ qr/./, 1 ], 185 | }, 186 | match_response => { 187 | status => qr/^200$/, 188 | }, 189 | request => new HTTP::Request( 190 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 191 | ), 192 | }, 193 | 194 | # SecAuditLogParts 195 | { 196 | type => "config", 197 | comment => "SecAuditLogParts (minimal)", 198 | conf => qq( 199 | SecAuditEngine On 200 | SecAuditLog $ENV{AUDIT_LOG} 201 | SecRequestBodyAccess On 202 | SecResponseBodyAccess On 203 | SecAuditLogParts "AZ" 204 | ), 205 | match_log => { 206 | audit => [ qr/-A--.*-Z--/s, 1 ], 207 | -audit => [ qr/-[B-Y]--/, 1 ], 208 | }, 209 | match_response => { 210 | status => qr/^200$/, 211 | }, 212 | request => new HTTP::Request( 213 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 214 | [ 215 | "Content-Type" => "application/x-www-form-urlencoded", 216 | ], 217 | "a=1r&=2", 218 | ), 219 | }, 220 | { 221 | type => "config", 222 | comment => "SecAuditLogParts (default)", 223 | conf => qq( 224 | SecAuditEngine On 225 | SecAuditLog $ENV{AUDIT_LOG} 226 | SecRequestBodyAccess On 227 | SecResponseBodyAccess On 228 | ), 229 | match_log => { 230 | audit => [ qr/-A--.*-B--.*-F--.*-H--.*-Z--/s, 1 ], 231 | -audit => [ qr/-[DEGIJK]--/, 1 ], 232 | }, 233 | match_response => { 234 | status => qr/^200$/, 235 | }, 236 | request => new HTTP::Request( 237 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 238 | [ 239 | "Content-Type" => "application/x-www-form-urlencoded", 240 | ], 241 | "a=1r&=2", 242 | ), 243 | }, 244 | { 245 | type => "config", 246 | comment => "SecAuditLogParts (all)", 247 | conf => qq( 248 | SecRuleEngine On 249 | SecAuditEngine On 250 | SecAuditLog $ENV{AUDIT_LOG} 251 | SecRequestBodyAccess On 252 | SecResponseBodyAccess On 253 | SecAuditLogParts "ABCDEFGHIJKZ" 254 | SecAction "phase:4,log,auditlog,allow,id:500086" 255 | ), 256 | match_log => { 257 | audit => [ qr/-A--.*-B--.*-C--.*-F--.*-E--.*-H--.*-K--.*-Z--/s, 1 ], 258 | }, 259 | match_response => { 260 | status => qr/^200$/, 261 | }, 262 | request => new HTTP::Request( 263 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 264 | [ 265 | "Content-Type" => "application/x-www-form-urlencoded", 266 | ], 267 | "a=1r&=2", 268 | ), 269 | }, 270 | -------------------------------------------------------------------------------- /tests/regression/config/10-debug-directives.t: -------------------------------------------------------------------------------- 1 | ### SecDebug* directive tests 2 | { 3 | type => "config", 4 | comment => "SecDebugLog (pos)", 5 | conf => qq( 6 | SecRuleEngine On 7 | SecDebugLog $ENV{DEBUG_LOG} 8 | SecDebugLogLevel 9 9 | ), 10 | match_log => { 11 | debug => [ qr/./, 1 ], 12 | }, 13 | match_response => { 14 | status => qr/^200$/, 15 | }, 16 | request => new HTTP::Request( 17 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 18 | ), 19 | }, 20 | { 21 | type => "config", 22 | comment => "SecDebugLog (neg)", 23 | conf => qq( 24 | SecRuleEngine On 25 | ), 26 | match_log => { 27 | -debug => [ qr/./, 1 ], 28 | }, 29 | match_response => { 30 | status => qr/^200$/, 31 | }, 32 | request => new HTTP::Request( 33 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 34 | ), 35 | }, 36 | { 37 | type => "config", 38 | comment => "SecDebugLogLevel 0", 39 | conf => qq( 40 | SecRuleEngine On 41 | SecDebugLog $ENV{DEBUG_LOG} 42 | SecDebugLogLevel 0 43 | SecRule REQUEST_URI "." "phase:1,deny,id:500241" 44 | ), 45 | match_log => { 46 | -debug => [ qr/./, 1 ], 47 | }, 48 | match_response => { 49 | status => qr/^403$/, 50 | }, 51 | request => new HTTP::Request( 52 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 53 | ), 54 | }, 55 | { 56 | type => "config", 57 | comment => "SecDebugLogLevel 1", 58 | conf => qq( 59 | SecRuleEngine On 60 | SecDebugLog $ENV{DEBUG_LOG} 61 | SecDebugLogLevel 1 62 | SecRule REQUEST_URI "(.)" "phase:4,deny,id:500242" 63 | ), 64 | match_log => { 65 | debug => [ qr/\]\[[1]\] /, 1 ], 66 | -debug => [ qr/\]\[[2-9]\] /, 1 ], 67 | }, 68 | match_response => { 69 | status => qr/^403$/, 70 | }, 71 | request => new HTTP::Request( 72 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 73 | [ 74 | "Content-Type" => "application/x-www-form-urlencoded", 75 | ], 76 | "a=1&b=2", 77 | ), 78 | }, 79 | { 80 | type => "config", 81 | comment => "SecDebugLogLevel 2", 82 | conf => qq( 83 | SecRuleEngine DetectionOnly 84 | SecDebugLog $ENV{DEBUG_LOG} 85 | SecDebugLogLevel 2 86 | SecRule REQUEST_URI "(.)" "phase:4,deny,id:500243" 87 | ), 88 | match_log => { 89 | debug => [ qr/\]\[2\] /, 1 ], 90 | -debug => [ qr/\]\[[3-9]\] /, 1 ], 91 | }, 92 | match_response => { 93 | status => qr/^200$/, 94 | }, 95 | request => new HTTP::Request( 96 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 97 | [ 98 | "Content-Type" => "application/x-www-form-urlencoded", 99 | ], 100 | "a=1&b=2", 101 | ), 102 | }, 103 | { 104 | type => "config", 105 | comment => "SecDebugLogLevel 3", 106 | conf => qq( 107 | SecRuleEngine On 108 | SecDebugLog $ENV{DEBUG_LOG} 109 | SecDebugLogLevel 3 110 | SecRule REQUEST_URI "(.)" "phase:4,deny,id:500244" 111 | ), 112 | match_log => { 113 | debug => [ qr/\]\[3\] /, 1 ], 114 | -debug => [ qr/\]\[[4-9]\] /, 1 ], 115 | }, 116 | match_response => { 117 | status => qr/^403$/, 118 | }, 119 | request => new HTTP::Request( 120 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 121 | [ 122 | "Content-Type" => "application/x-www-form-urlencoded", 123 | ], 124 | "a=1&b=2", 125 | ), 126 | }, 127 | { 128 | type => "config", 129 | comment => "SecDebugLogLevel 4", 130 | conf => qq( 131 | SecRuleEngine On 132 | SecDebugLog $ENV{DEBUG_LOG} 133 | SecDebugLogLevel 4 134 | SecRule REQUEST_URI "(.)" "phase:4,deny,id:500245" 135 | ), 136 | match_log => { 137 | debug => [ qr/\]\[4\] /, 1 ], 138 | -debug => [ qr/\]\[[5-9]\] /, 1 ], 139 | }, 140 | match_response => { 141 | status => qr/^403$/, 142 | }, 143 | request => new HTTP::Request( 144 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 145 | [ 146 | "Content-Type" => "application/x-www-form-urlencoded", 147 | ], 148 | "a=1&b=2", 149 | ), 150 | }, 151 | { 152 | type => "config", 153 | comment => "SecDebugLogLevel 5", 154 | conf => qq( 155 | SecRuleEngine On 156 | SecDebugLog $ENV{DEBUG_LOG} 157 | SecDebugLogLevel 5 158 | SecRule REQUEST_URI "(.)" "phase:4,deny,id:500246" 159 | ), 160 | match_log => { 161 | debug => [ qr/\]\[5\] /, 1 ], 162 | -debug => [ qr/\]\[[6-9]\] /, 1 ], 163 | }, 164 | match_response => { 165 | status => qr/^403$/, 166 | }, 167 | request => new HTTP::Request( 168 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 169 | [ 170 | "Content-Type" => "application/x-www-form-urlencoded", 171 | ], 172 | "a=1&b=2", 173 | ), 174 | }, 175 | { 176 | type => "config", 177 | comment => "SecDebugLogLevel 6", 178 | conf => qq( 179 | SecRuleEngine On 180 | SecDebugLog $ENV{DEBUG_LOG} 181 | SecDebugLogLevel 6 182 | SecRule REQUEST_URI "(.)" "phase:4,deny,id:500247" 183 | ), 184 | match_log => { 185 | debug => [ qr/\]\[6\] /, 1 ], 186 | -debug => [ qr/\]\[[7-9]\] /, 1 ], 187 | }, 188 | match_response => { 189 | status => qr/^403$/, 190 | }, 191 | request => new HTTP::Request( 192 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 193 | [ 194 | "Content-Type" => "application/x-www-form-urlencoded", 195 | ], 196 | "a=1&b=2", 197 | ), 198 | }, 199 | { 200 | type => "config", 201 | comment => "SecDebugLogLevel 7", 202 | conf => qq( 203 | SecRuleEngine On 204 | SecDebugLog $ENV{DEBUG_LOG} 205 | SecDebugLogLevel 7 206 | SecRule REQUEST_URI "(.)" "phase:4,deny,id:500248" 207 | ), 208 | match_log => { 209 | debug => [ qr/\]\[7\] /, 1 ], 210 | -debug => [ qr/\]\[[8-9]\] /, 1 ], 211 | }, 212 | match_response => { 213 | status => qr/^403$/, 214 | }, 215 | request => new HTTP::Request( 216 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 217 | [ 218 | "Content-Type" => "application/x-www-form-urlencoded", 219 | ], 220 | "a=1&b=2", 221 | ), 222 | }, 223 | { 224 | type => "config", 225 | comment => "SecDebugLogLevel 8", 226 | conf => qq( 227 | SecRuleEngine On 228 | SecDebugLog $ENV{DEBUG_LOG} 229 | SecDebugLogLevel 8 230 | SecRule REQUEST_URI "(.)" "phase:4,deny,id:500249" 231 | ), 232 | match_log => { 233 | debug => [ qr/\]\[8\] /, 1 ], 234 | -debug => [ qr/\]\[9\] /, 1 ], 235 | }, 236 | match_response => { 237 | status => qr/^403$/, 238 | }, 239 | request => new HTTP::Request( 240 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 241 | [ 242 | "Content-Type" => "application/x-www-form-urlencoded", 243 | ], 244 | "a=1&b=2", 245 | ), 246 | }, 247 | { 248 | type => "config", 249 | comment => "SecDebugLogLevel 9", 250 | conf => qq( 251 | SecRuleEngine On 252 | SecDebugLog $ENV{DEBUG_LOG} 253 | SecDebugLogLevel 9 254 | SecRule REQUEST_URI "(.)" "phase:4,deny,id:500250" 255 | ), 256 | match_log => { 257 | debug => [ qr/\]\[9\] /, 1 ], 258 | }, 259 | match_response => { 260 | status => qr/^403$/, 261 | }, 262 | request => new HTTP::Request( 263 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 264 | [ 265 | "Content-Type" => "application/x-www-form-urlencoded", 266 | ], 267 | "a=1&b=2", 268 | ), 269 | }, 270 | -------------------------------------------------------------------------------- /tests/regression/config/10-misc-directives.t: -------------------------------------------------------------------------------- 1 | ### Misc directive tests 2 | 3 | ### TODO: 4 | # SecTmpDir 5 | # SecUploadKeepFiles 6 | # SecChrootDir 7 | # SecGuardianLog 8 | 9 | # SecDefaultAction 10 | { 11 | type => "config", 12 | comment => "SecDefaultAction", 13 | conf => qq( 14 | SecRuleEngine on 15 | SecDefaultAction "phase:1,deny,status:500" 16 | SecRule REQUEST_URI "test.txt" "id:500241" 17 | ), 18 | match_log => { 19 | error => [ qr/ModSecurity: Access denied with code 500 \(phase 1\)/, 1 ], 20 | }, 21 | match_response => { 22 | status => qr/^500$/, 23 | }, 24 | request => new HTTP::Request( 25 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 26 | ), 27 | }, 28 | 29 | 30 | # SecDataDir 31 | { 32 | type => "config", 33 | comment => "SecDataDir", 34 | conf => qq( 35 | SecRuleEngine On 36 | SecDataDir "$ENV{DATA_DIR}" 37 | SecAction initcol:ip=%{REMOTE_ADDR},setvar:ip.dummy=1,pass,id:500085 38 | ), 39 | match_log => { 40 | error => [ qr/ModSecurity: Warning. Unconditional match in SecAction\./, 1 ], 41 | }, 42 | match_file => { 43 | "$ENV{DATA_DIR}/ip.pag" => qr/\x00\x06dummy\x00\x00\x021\x00/, 44 | }, 45 | match_response => { 46 | status => qr/^200$/, 47 | }, 48 | request => new HTTP::Request( 49 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 50 | ), 51 | }, 52 | 53 | # SecTmpDir/SecUploadDir/SecUploadKeepFiles 54 | { 55 | type => "config", 56 | comment => "SecTmpDir/SecUploadDir/SecUploadKeepFiles", 57 | conf => qq( 58 | SecRuleEngine On 59 | SecRequestBodyAccess On 60 | SecDebugLog $ENV{DEBUG_LOG} 61 | SecDebugLogLevel 4 62 | SecTmpDir "$ENV{TEMP_DIR}" 63 | SecUploadKeepFiles On 64 | SecUploadDir "$ENV{UPLOAD_DIR}" 65 | ), 66 | test => sub { 67 | # Get the filename and make sure the file exists 68 | my $fn = match_log(debug => qr/Moved file from .* to ".*"\./, 5); 69 | die "Failed to determine uploaded filename\n" unless (defined $fn); 70 | 71 | $fn =~ s/Moved file from .* to "(.*)"\..*/$1/; 72 | die "File does not exist: $fn\n" unless (-e $fn); 73 | 74 | # Check the contents of the file 75 | return 0 if (match_file($fn, qr/^TESTFILE$/m)); 76 | 77 | msg("Failed to match contents of uploaded file: $fn"); 78 | return 1; 79 | }, 80 | match_log => { 81 | debug => [ qr/Created temporary file.*$ENV{TEMP_DIR}/, 1 ], 82 | -debug => [ qr/Failed to /, 1 ], 83 | }, 84 | match_response => { 85 | status => qr/^200$/, 86 | }, 87 | request => new HTTP::Request( 88 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 89 | [ 90 | "Content-Type" => "multipart/form-data; boundary=---------------------------19813181771830765643996187206", 91 | ], 92 | q(-----------------------------19813181771830765643996187206 93 | Content-Disposition: form-data; name="upload-file"; filename="test" 94 | Content-Type: application/octet-stream 95 | 96 | TESTFILE 97 | -----------------------------19813181771830765643996187206 98 | Content-Disposition: form-data; name="file" 99 | 100 | Upload File 101 | -----------------------------19813181771830765643996187206--), 102 | ), 103 | }, 104 | 105 | 106 | -------------------------------------------------------------------------------- /tests/regression/config/10-response-directives.t: -------------------------------------------------------------------------------- 1 | ### Tests for directives altering how a response is handled 2 | 3 | # SecResponseBodyMimeTypesClear 4 | { 5 | type => "config", 6 | comment => "SecResponseBodyMimeTypesClear", 7 | conf => qq( 8 | SecRuleEngine On 9 | SecResponseBodyAccess On 10 | SecResponseBodyMimeTypesClear 11 | SecDebugLog $ENV{DEBUG_LOG} 12 | SecDebugLogLevel 9 13 | SecRule RESPONSE_BODY "TEST" "phase:4,deny,id:500237" 14 | ), 15 | match_log => { 16 | -error => [ qr/Access denied/, 1 ], 17 | debug => [ qr/Not buffering response body for unconfigured MIME type/, 1 ], 18 | }, 19 | match_response => { 20 | status => qr/^200$/, 21 | }, 22 | request => new HTTP::Request( 23 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 24 | ), 25 | }, 26 | 27 | # SecResponseBodyAccess & SecResponseBodyMimeType 28 | { 29 | type => "config", 30 | comment => "SecResponseBodyAccess On", 31 | conf => qq( 32 | SecRuleEngine On 33 | SecDebugLog $ENV{DEBUG_LOG} 34 | SecDebugLogLevel 9 35 | SecResponseBodyAccess On 36 | SecResponseBodyMimeType text/plain null 37 | SecRule RESPONSE_BODY "TEST" "phase:4,deny,id:500238" 38 | ), 39 | match_log => { 40 | error => [ qr/Access denied with code 403 \(phase 4\)\. Pattern match "TEST" at RESPONSE_BODY\./, 1 ], 41 | }, 42 | match_response => { 43 | status => qr/^403$/, 44 | }, 45 | request => new HTTP::Request( 46 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 47 | ), 48 | }, 49 | { 50 | type => "config", 51 | comment => "SecResponseBodyAccess Off", 52 | conf => qq( 53 | SecRuleEngine On 54 | SecDebugLog $ENV{DEBUG_LOG} 55 | SecDebugLogLevel 9 56 | SecResponseBodyAccess Off 57 | SecResponseBodyMimeType text/plain null 58 | SecRule RESPONSE_BODY "TEST" "phase:4,deny,id:500239" 59 | ), 60 | match_log => { 61 | -error => [ qr/Access denied/, 1 ], 62 | debug => [ qr/Response body buffering is not enabled\./, 1 ], 63 | }, 64 | match_response => { 65 | status => qr/^200$/, 66 | }, 67 | request => new HTTP::Request( 68 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 69 | ), 70 | }, 71 | 72 | # SecResponseBodyLimit 73 | { 74 | type => "config", 75 | comment => "SecResponseBodyLimit (equal)", 76 | conf => qq( 77 | SecRuleEngine On 78 | SecResponseBodyAccess On 79 | SecResponseBodyMimeType text/plain null 80 | SecResponseBodyLimit 8192 81 | ), 82 | match_log => { 83 | -error => [ qr/Content-Length \(\d+\) over the limit/, 1 ], 84 | }, 85 | match_response => { 86 | status => qr/^200$/, 87 | }, 88 | request => new HTTP::Request( 89 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/8k.txt", 90 | ), 91 | }, 92 | { 93 | type => "config", 94 | comment => "SecResponseBodyLimit (less)", 95 | conf => qq( 96 | SecRuleEngine On 97 | SecResponseBodyAccess On 98 | SecResponseBodyMimeType text/plain null 99 | SecResponseBodyLimit 9000 100 | ), 101 | match_log => { 102 | -error => [ qr/Content-Length \(\d+\) over the limit/, 1 ], 103 | }, 104 | match_response => { 105 | status => qr/^200$/, 106 | }, 107 | request => new HTTP::Request( 108 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/8k.txt", 109 | ), 110 | }, 111 | { 112 | type => "config", 113 | comment => "SecResponseBodyLimit (greater)", 114 | conf => qq( 115 | SecRuleEngine On 116 | SecResponseBodyAccess On 117 | SecResponseBodyMimeType text/plain null 118 | SecResponseBodyLimit 8000 119 | ), 120 | match_log => { 121 | error => [ qr/Content-Length \(\d+\) over the limit \(8000\)\./, 1 ], 122 | }, 123 | match_response => { 124 | status => qr/^500$/, 125 | }, 126 | request => new HTTP::Request( 127 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/8k.txt", 128 | ), 129 | }, 130 | 131 | # ResponseBodyLimitAction 132 | { 133 | type => "config", 134 | comment => "SecResponseBodyLimitAction Reject", 135 | conf => qq( 136 | SecRuleEngine On 137 | SecResponseBodyAccess On 138 | SecResponseBodyMimeType text/plain null 139 | SecResponseBodyLimit 5 140 | SecResponseBodyLimitAction Reject 141 | ), 142 | match_log => { 143 | error => [ qr/Content-Length \(\d+\) over the limit \(5\)\./, 1 ], 144 | }, 145 | match_response => { 146 | status => qr/^500$/, 147 | }, 148 | request => new HTTP::Request( 149 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/8k.txt", 150 | ), 151 | }, 152 | { 153 | type => "config", 154 | comment => "SecResponseBodyLimitAction ProcessPartial", 155 | conf => qq( 156 | SecRuleEngine On 157 | SecResponseBodyAccess On 158 | SecResponseBodyMimeType text/plain null 159 | SecResponseBodyLimit 5 160 | SecDebugLog $ENV{DEBUG_LOG} 161 | SecDebugLogLevel 4 162 | SecResponseBodyLimitAction ProcessPartial 163 | ), 164 | match_log => { 165 | -error => [ qr/Content-Length \(\d+\) over the limit/, 1 ], 166 | debug => [ qr/Processing partial response body \(limit 5\)/, 1 ], 167 | }, 168 | match_response => { 169 | status => qr/^200$/, 170 | }, 171 | request => new HTTP::Request( 172 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/8k.txt", 173 | ), 174 | }, 175 | -------------------------------------------------------------------------------- /tests/regression/config/20-chroot.t: -------------------------------------------------------------------------------- 1 | ### SecChroot tests 2 | # TODO: Will not work as we need root access 3 | 4 | #{ 5 | # type => "config", 6 | # comment => "SecChroot", 7 | # httpd_opts => qw( 8 | # -DCHROOT 9 | # ), 10 | # conf => qq( 11 | # # These will be in the chroot 12 | # PidFile /logs/httpd.pid 13 | # ScoreBoardFile /logs/httpd.scoreboard 14 | # User nobody 15 | # Group nogroup 16 | # 17 | # SecAuditEngine On 18 | # SecDebugLog $ENV{DEBUG_LOG} 19 | # SecDebugLogLevel 9 20 | # SecAuditLog $ENV{AUDIT_LOG} 21 | # SecAuditLogStorageDir "/logs/audit" 22 | # SecAuditLogType Concurrent 23 | # SecChrootDir "$ENV{TEST_SERVER_ROOT}" 24 | # ), 25 | # match_log => { 26 | # debug => [ qr/./, 1 ], 27 | # audit => [ qr/./, 1 ], 28 | # }, 29 | # match_response => { 30 | # status => qr/^200$/, 31 | # }, 32 | # request => new HTTP::Request( 33 | # GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 34 | # ), 35 | #}, 36 | -------------------------------------------------------------------------------- /tests/regression/misc/00-phases.t: -------------------------------------------------------------------------------- 1 | ### Test the phases 2 | 3 | # Phase 1 (request headers) 4 | { 5 | type => "misc", 6 | comment => "phase 1", 7 | conf => qq( 8 | SecRuleEngine On 9 | SecRequestBodyAccess On 10 | SecResponseBodyAccess On 11 | SecResponseBodyMimeType text/plain null 12 | SecRule REQUEST_LINE "^POST" "phase:1,pass,log,auditlog,id:500169" 13 | SecRule ARGS "val1" "phase:1,pass,log,auditlog,id:500170" 14 | SecRule RESPONSE_HEADERS:Last-Modified "." "phase:1,pass,log,auditlog,id:500171" 15 | SecRule RESPONSE_BODY "TEST" "phase:1,pass,log,auditlog,id:500172" 16 | ), 17 | match_log => { 18 | error => [ qr/Pattern match "\^POST" at REQUEST_LINE/, 1 ], 19 | -error => [ qr/Pattern match .* (ARGS|RESPONSE)/, 1 ], 20 | }, 21 | match_response => { 22 | status => qr/^200$/, 23 | }, 24 | request => new HTTP::Request( 25 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 26 | [ 27 | "Content-Type" => "application/x-www-form-urlencoded", 28 | ], 29 | "arg1=val1&arg2=val2", 30 | ), 31 | }, 32 | 33 | # Phase 2 (request body) 34 | { 35 | type => "misc", 36 | comment => "phase 2", 37 | conf => qq( 38 | SecRuleEngine On 39 | SecRequestBodyAccess On 40 | SecResponseBodyAccess On 41 | SecResponseBodyMimeType text/plain null 42 | SecRule REQUEST_LINE "^POST" "phase:2,pass,log,auditlog,id:500173" 43 | SecRule ARGS "val1" "phase:2,pass,log,auditlog,id:500174" 44 | SecRule RESPONSE_HEADERS:Last-Modified "." "phase:2,pass,log,auditlog,id:500175" 45 | SecRule RESPONSE_BODY "TEST" "phase:2,pass,log,auditlog,id:500176" 46 | ), 47 | match_log => { 48 | error => [ qr/Pattern match "\^POST" at REQUEST_LINE.*Pattern match "val1" at ARGS/s, 1 ], 49 | -error => [ qr/Pattern match .* RESPONSE/, 1 ], 50 | }, 51 | match_response => { 52 | status => qr/^200$/, 53 | }, 54 | request => new HTTP::Request( 55 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 56 | [ 57 | "Content-Type" => "application/x-www-form-urlencoded", 58 | ], 59 | "arg1=val1&arg2=val2", 60 | ), 61 | }, 62 | 63 | # Phase 3 (response headers) 64 | { 65 | type => "misc", 66 | comment => "phase 3", 67 | conf => qq( 68 | SecRuleEngine On 69 | SecRequestBodyAccess On 70 | SecResponseBodyAccess On 71 | SecResponseBodyMimeType text/plain null 72 | SecRule REQUEST_LINE "^POST" "phase:3,pass,log,auditlog,id:500177" 73 | SecRule ARGS "val1" "phase:3,pass,log,auditlog,id:500178" 74 | SecRule RESPONSE_HEADERS:Last-Modified "." "phase:3,pass,log,auditlog,id:500179" 75 | SecRule RESPONSE_BODY "TEST" "phase:3,pass,log,auditlog,id:500180" 76 | ), 77 | match_log => { 78 | error => [ qr/Pattern match "\^POST" at REQUEST_LINE.*Pattern match "val1" at ARGS.*Pattern match "\." at RESPONSE_HEADERS/s, 1 ], 79 | -error => [ qr/Pattern match .* RESPONSE_BODY/, 1 ], 80 | }, 81 | match_response => { 82 | status => qr/^200$/, 83 | }, 84 | request => new HTTP::Request( 85 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 86 | [ 87 | "Content-Type" => "application/x-www-form-urlencoded", 88 | ], 89 | "arg1=val1&arg2=val2", 90 | ), 91 | }, 92 | 93 | # Phase 4 (response body) 94 | { 95 | type => "misc", 96 | comment => "phase 4", 97 | conf => qq( 98 | SecRuleEngine On 99 | SecRequestBodyAccess On 100 | SecResponseBodyAccess On 101 | SecResponseBodyMimeType text/plain null 102 | SecDebugLog "$ENV{DEBUG_LOG}" 103 | SecDebugLogLevel 9 104 | SecRule REQUEST_LINE "^POST" "phase:4,pass,log,auditlog,id:500181" 105 | SecRule ARGS "val1" "phase:4,pass,log,auditlog,id:500182" 106 | SecRule RESPONSE_HEADERS:Last-Modified "." "phase:4,pass,log,auditlog,id:500183" 107 | SecRule RESPONSE_BODY "TEST" "phase:4,pass,log,auditlog,id:500184" 108 | ), 109 | match_log => { 110 | error => [ qr/Pattern match "\^POST" at REQUEST_LINE.*Pattern match "val1" at ARGS.*Pattern match "\." at RESPONSE_HEADERS.*Pattern match "TEST" at RESPONSE_BODY/s, 1 ], 111 | }, 112 | match_response => { 113 | status => qr/^200$/, 114 | }, 115 | request => new HTTP::Request( 116 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 117 | [ 118 | "Content-Type" => "application/x-www-form-urlencoded", 119 | ], 120 | "arg1=val1&arg2=val2", 121 | ), 122 | }, 123 | 124 | # Phase 5 (logging) 125 | { 126 | type => "misc", 127 | comment => "phase 5", 128 | conf => qq( 129 | SecRuleEngine On 130 | SecRequestBodyAccess On 131 | SecResponseBodyAccess On 132 | SecResponseBodyMimeType text/plain null 133 | SecRule REQUEST_LINE "^POST" "phase:5,pass,log,auditlog,id:500185" 134 | SecRule ARGS "val1" "phase:5,pass,log,auditlog,id:500186" 135 | SecRule RESPONSE_HEADERS:Last-Modified "." "phase:5,pass,log,auditlog,id:500187" 136 | SecRule RESPONSE_BODY "TEST" "phase:5,pass,log,auditlog,id:500188" 137 | ), 138 | match_log => { 139 | error => [ qr/Pattern match "\^POST" at REQUEST_LINE.*Pattern match "val1" at ARGS.*Pattern match "\." at RESPONSE_HEADERS.*Pattern match "TEST" at RESPONSE_BODY/s, 1 ], 140 | }, 141 | match_response => { 142 | status => qr/^200$/, 143 | }, 144 | request => new HTTP::Request( 145 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 146 | [ 147 | "Content-Type" => "application/x-www-form-urlencoded", 148 | ], 149 | "arg1=val1&arg2=val2", 150 | ), 151 | }, 152 | -------------------------------------------------------------------------------- /tests/regression/misc/20-status-engine.t: -------------------------------------------------------------------------------- 1 | ### Test the SecStatusEngine 2 | 3 | # On 4 | { 5 | type => "misc", 6 | comment => "Setting SecStatusEngine to On", 7 | conf => qq( 8 | SecRuleEngine On 9 | SecStatusEngine On 10 | ), 11 | match_log => { 12 | error => [ qr/ModSecurity: StatusEngine call successfully sent/, 1], 13 | -error => [ qr/Status engine is currently disabled, enable it by set SecStatusEngine to On/, 1], 14 | }, 15 | match_response => { 16 | status => qr/^200$/, 17 | }, 18 | request => new HTTP::Request( 19 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 20 | [ 21 | "Content-Type" => "application/x-www-form-urlencoded", 22 | ], 23 | "arg1=val1&arg2=val2", 24 | ), 25 | }, 26 | # Off 27 | { 28 | type => "misc", 29 | comment => "Setting SecStatusEngine to Off", 30 | conf => qq( 31 | SecRuleEngine On 32 | SecStatusEngine Off 33 | ), 34 | match_log => { 35 | -error => [ qr/ModSecurity: StatusEngine call successfully sent/, 1], 36 | error => [ qr/Status engine is currently disabled, enable it by set SecStatusEngine to On/, 1], 37 | }, 38 | match_response => { 39 | status => qr/^200$/, 40 | }, 41 | request => new HTTP::Request( 42 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 43 | [ 44 | "Content-Type" => "application/x-www-form-urlencoded", 45 | ], 46 | "arg1=val1&arg2=val2", 47 | ), 48 | }, 49 | 50 | -------------------------------------------------------------------------------- /tests/regression/misc/25-libinjection.t: -------------------------------------------------------------------------------- 1 | ### libinjection. 2 | 3 | { 4 | type => "misc", 5 | comment => "libinjection SQLi - with SQLi", 6 | conf => qq( 7 | SecRuleEngine On 8 | SecDebugLog $ENV{DEBUG_LOG} 9 | SecDebugLogLevel 9 10 | SecRequestBodyAccess On 11 | 12 | SecRule REQUEST_BODY "\@detectSQLi" "id:192372,log,deny" 13 | ), 14 | match_log => { 15 | error => [ qr/detected SQLi using libinjection/, 1], 16 | debug => [ qr/detected SQLi using libinjection/, 1 ], 17 | }, 18 | match_response => { 19 | status => qr/^403$/, 20 | }, 21 | request => new HTTP::Request( 22 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html", 23 | [ 24 | "Content-Type" => "application/x-www-form-urlencoded", 25 | ], 26 | # Args 27 | "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" 28 | ), 29 | }, 30 | { 31 | type => "misc", 32 | comment => "libinjection SQLi - without SQLi", 33 | conf => qq( 34 | SecRuleEngine On 35 | SecDebugLog $ENV{DEBUG_LOG} 36 | SecDebugLogLevel 9 37 | SecRequestBodyAccess On 38 | 39 | SecRule REQUEST_BODY "\@detectSQLi" "id:192372,log,deny" 40 | ), 41 | match_log => { 42 | -error => [ qr/detected SQLi using libinjection/, 1], 43 | -debug => [ qr/detected SQLi using libinjection/, 1 ], 44 | }, 45 | match_response => { 46 | status => qr/^200$/, 47 | }, 48 | request => new HTTP::Request( 49 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html", 50 | [ 51 | "Content-Type" => "application/x-www-form-urlencoded", 52 | ], 53 | # Args 54 | "some_variable=hello cruel world" 55 | ), 56 | }, 57 | { 58 | type => "misc", 59 | comment => "libinjection XSS - with XSS", 60 | conf => qq( 61 | SecRuleEngine On 62 | SecDebugLog $ENV{DEBUG_LOG} 63 | SecDebugLogLevel 9 64 | SecRequestBodyAccess On 65 | 66 | SecRule REQUEST_BODY "\@detectXSS" "id:192372,log,deny" 67 | ), 68 | match_log => { 69 | error => [ qr/detected XSS using libinjection/, 1], 70 | debug => [ qr/detected XSS using libinjection/, 1 ], 71 | }, 72 | match_response => { 73 | status => qr/^403$/, 74 | }, 75 | request => new HTTP::Request( 76 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html", 77 | [ 78 | "Content-Type" => "application/x-www-form-urlencoded", 79 | ], 80 | # Args 81 | "some_variable=" 82 | ), 83 | }, 84 | { 85 | type => "misc", 86 | comment => "libinjection XSS - without XSS", 87 | conf => qq( 88 | SecRuleEngine On 89 | SecDebugLog $ENV{DEBUG_LOG} 90 | SecDebugLogLevel 9 91 | SecRequestBodyAccess On 92 | 93 | SecRule REQUEST_BODY "\@detectXSS" "id:192372,log,deny" 94 | ), 95 | match_log => { 96 | -error => [ qr/detected XSS using libinjection/, 1], 97 | -debug => [ qr/detected XSS using libinjection/, 1 ], 98 | }, 99 | match_response => { 100 | status => qr/^200$/, 101 | }, 102 | request => new HTTP::Request( 103 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/index.html", 104 | [ 105 | "Content-Type" => "application/x-www-form-urlencoded", 106 | ], 107 | # Args 108 | "some_variable=hello cruel world" 109 | ), 110 | } 111 | -------------------------------------------------------------------------------- /tests/regression/misc/40-secRemoteRules.t.in: -------------------------------------------------------------------------------- 1 | ### SecRemoteRules 2 | 3 | { 4 | type => "misc", 5 | comment => "SecRemoteRules load", 6 | conf => qq( 7 | SecRuleEngine On 8 | SecDebugLog $ENV{DEBUG_LOG} 9 | SecDebugLogLevel 9 10 | SecRequestBodyAccess On 11 | SecRemoteRules 123 "https://www.modsecurity.org/modsecurity-regression-test-secremoterules.txt" 12 | ), 13 | match_log => { 14 | error => [ qr/ModSecurity: Loaded 1 rule/, 1], 15 | }, 16 | }, 17 | { 18 | type => "misc", 19 | comment => "SecRemoteRules apply some remote rules", 20 | conf => qq( 21 | SecRuleEngine On 22 | SecDebugLog $ENV{DEBUG_LOG} 23 | SecDebugLogLevel 9 24 | SecRequestBodyAccess On 25 | SecRemoteRules 123 "https://www.modsecurity.org/modsecurity-regression-test-secremoterules.txt" 26 | ), 27 | match_log => { 28 | error => [ qr/ModSecurity: Warning. Matched phrase \"127.0.0.1\" at REQUEST_FILENAME./, 1], 29 | debug => [ qr/Matched phrase \"127.0.0.1\" at REQUEST_FILENAME/, 1 ], 30 | }, 31 | match_response => { 32 | status => qr/^404$/, 33 | }, 34 | request => new HTTP::Request( 35 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", 36 | [ 37 | "Content-Type" => "application/x-www-form-urlencoded", 38 | ], 39 | # Args 40 | "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" 41 | ), 42 | }, 43 | 44 | -------------------------------------------------------------------------------- /tests/regression/misc/50-ipmatchfromfile-external.t.in: -------------------------------------------------------------------------------- 1 | ### ipMatchFromFile external resource 2 | 3 | { 4 | type => "misc", 5 | comment => "ipMatchFromFile", 6 | conf => qq( 7 | SecRuleEngine On 8 | SecDebugLog $ENV{DEBUG_LOG} 9 | SecDebugLogLevel 9 10 | SecRequestBodyAccess On 11 | SecRule REMOTE_ADDR "\@ipMatchFromFile https://www.modsecurity.org/modsecurity-regression-test.txt" "id:10500,pass" 12 | ), 13 | match_log => { 14 | error => [ qr/ModSecurity: Warning. IPmatchFromFile: \"127.0.0.1\" matched at REMOTE_ADDR./, 1], 15 | debug => [ qr/IPmatchFromFile: \"127.0.0.1\" matched at REMOTE_ADDR./, 1 ], 16 | -error => [ qr/ModSecurity: Problems loading external resources:/, 1], 17 | }, 18 | match_response => { 19 | status => qr/^404$/, 20 | }, 21 | request => new HTTP::Request( 22 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", 23 | [ 24 | "Content-Type" => "application/x-www-form-urlencoded", 25 | ], 26 | # Args 27 | "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" 28 | ), 29 | }, 30 | { 31 | type => "misc", 32 | comment => "ipMatchFromFile - 404 download", 33 | conf => qq( 34 | SecRuleEngine On 35 | SecDebugLog $ENV{DEBUG_LOG} 36 | SecDebugLogLevel 9 37 | SecRequestBodyAccess On 38 | SecRemoteRulesFailAction Warn 39 | SecRule REMOTE_ADDR "\@ipMatchFromFile https://www.modsecurity.org/modsecurity-regression-test-404.txt" "id:10500,pass" 40 | ), 41 | match_log => { 42 | error => [ qr/ModSecurity: Problems loading external resources: Failed to download: \"https:\/\/www.modsecurity.org\/modsecurity-regression-test-404.txt\" error: HTTP response code said error./, 1], 43 | }, 44 | match_response => { 45 | status => qr/^404$/, 46 | }, 47 | request => new HTTP::Request( 48 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", 49 | [ 50 | "Content-Type" => "application/x-www-form-urlencoded", 51 | ], 52 | # Args 53 | "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" 54 | ), 55 | }, 56 | { 57 | type => "misc", 58 | comment => "ipMatchFromFile - bad certificate name", 59 | conf => qq( 60 | SecRuleEngine On 61 | SecDebugLog $ENV{DEBUG_LOG} 62 | SecDebugLogLevel 9 63 | SecRequestBodyAccess On 64 | SecRemoteRulesFailAction Warn 65 | SecRule REMOTE_ADDR "\@ipMatchFromFile https://status.modsecurity.org/modsecurity-regression-test-huge-ip-list.txt" "id:10500,pass" 66 | ), 67 | match_log => { 68 | error => [ qr/ModSecurity: Problems loading external resources: Failed to download: \"https:\/\/status.modsecurity.org\/modsecurity-regression-test-huge-ip-list.txt\" error: [SSL peer certificate or SSH remote key was not OK.|Couldn't connect to server.]/, 1], 69 | }, 70 | }, 71 | 72 | -------------------------------------------------------------------------------- /tests/regression/misc/60-pmfromfile-external.t.in: -------------------------------------------------------------------------------- 1 | ### pmfromfile external resource 2 | 3 | { 4 | type => "misc", 5 | comment => "pmfromfile", 6 | conf => qq( 7 | SecRuleEngine On 8 | SecDebugLog $ENV{DEBUG_LOG} 9 | SecDebugLogLevel 9 10 | SecRequestBodyAccess On 11 | SecRule REQUEST_FILENAME "\@pmFromFile https://www.modsecurity.org/modsecurity-regression-test.txt" "id:'123',phase:2,log,pass,t:none" 12 | ), 13 | match_log => { 14 | error => [ qr/ModSecurity: Warning. Matched phrase \"127.0.0.1\" at REQUEST_FILENAME./, 1], 15 | debug => [ qr/Matched phrase \"127.0.0.1\" at REQUEST_FILENAME/, 1 ], 16 | -error => [ qr/ModSecurity: Problems loading external resources:/, 1], 17 | }, 18 | match_response => { 19 | status => qr/^404$/, 20 | }, 21 | request => new HTTP::Request( 22 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", 23 | [ 24 | "Content-Type" => "application/x-www-form-urlencoded", 25 | ], 26 | # Args 27 | "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" 28 | ), 29 | }, 30 | { 31 | type => "misc", 32 | comment => "pmfromfile - 404 download", 33 | conf => qq( 34 | SecRuleEngine On 35 | SecDebugLog $ENV{DEBUG_LOG} 36 | SecDebugLogLevel 9 37 | SecRequestBodyAccess On 38 | SecRemoteRulesFailAction Warn 39 | SecRule REQUEST_FILENAME "\@pmFromFile https://www.modsecurity.org/modsecurity-regression-test-404.txt" "id:'123',phase:2,log,pass,t:none" 40 | 41 | ), 42 | match_log => { 43 | error => [ qr/ModSecurity: Problems loading external resources: Failed to download: \"https:\/\/www.modsecurity.org\/modsecurity-regression-test-404.txt\" error: HTTP response code said error./, 1], 44 | }, 45 | match_response => { 46 | status => qr/^404$/, 47 | }, 48 | request => new HTTP::Request( 49 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", 50 | [ 51 | "Content-Type" => "application/x-www-form-urlencoded", 52 | ], 53 | # Args 54 | "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" 55 | ), 56 | }, 57 | { 58 | type => "misc", 59 | comment => "pmfromfile - bad certificate name", 60 | conf => qq( 61 | SecRuleEngine On 62 | SecDebugLog $ENV{DEBUG_LOG} 63 | SecDebugLogLevel 9 64 | SecRequestBodyAccess On 65 | SecRemoteRulesFailAction Warn 66 | SecRule REQUEST_FILENAME "\@pmFromFile https://status.modsecurity.org/modsecurity-regression-test.txt" "id:'123',phase:2,log,pass,t:none" 67 | 68 | ), 69 | match_log => { 70 | error => [ qr/ModSecurity: Problems loading external resources: Failed to download: \"https:\/\/status.modsecurity.org\/modsecurity-regression-test.txt\" error: [SSL peer certificate or SSH remote key was not OK.|Couldn't connect to server.]/, 1], 71 | }, 72 | match_response => { 73 | status => qr/^404$/, 74 | }, 75 | request => new HTTP::Request( 76 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/127.0.0.1.html", 77 | [ 78 | "Content-Type" => "application/x-www-form-urlencoded", 79 | ], 80 | # Args 81 | "some_variable=-1' and 1=1 union/* foo */select load_file('/etc/passwd')--" 82 | ), 83 | }, 84 | 85 | -------------------------------------------------------------------------------- /tests/regression/rule/00-basics.t: -------------------------------------------------------------------------------- 1 | ### Tests for basic rule components 2 | 3 | # SecAction 4 | { 5 | type => "rule", 6 | comment => "SecAction (override default)", 7 | conf => qq( 8 | SecRuleEngine On 9 | SecDebugLog $ENV{DEBUG_LOG} 10 | SecDebugLogLevel 4 11 | SecAction "nolog,id:500001" 12 | ), 13 | match_log => { 14 | -error => [ qr/500001/, 1 ], 15 | -audit => [ qr/./, 1 ], 16 | debug => [ qr/Warning\. Unconditional match in SecAction\./, 1 ], 17 | }, 18 | match_response => { 19 | status => qr/^200$/, 20 | }, 21 | request => new HTTP::Request( 22 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 23 | ), 24 | }, 25 | 26 | # SecRule 27 | { 28 | type => "rule", 29 | comment => "SecRule (no action)", 30 | conf => qq( 31 | SecRuleEngine On 32 | SecDebugLog $ENV{DEBUG_LOG} 33 | SecDebugLogLevel 5 34 | SecDefaultAction "phase:2,deny,status:403" 35 | SecRule ARGS:test "value" "id:500032" 36 | ), 37 | match_log => { 38 | error => [ qr/500032/, 1 ], 39 | debug => [ qr/Rule [0-9a-f]+: SecRule "ARGS:test" "\@rx value" "phase:2,deny,status:403,id:500032"$/m, 1 ], 40 | }, 41 | match_response => { 42 | status => qr/^403$/, 43 | }, 44 | request => new HTTP::Request( 45 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?test=value", 46 | ), 47 | }, 48 | { 49 | type => "rule", 50 | comment => "SecRule (action)", 51 | conf => qq( 52 | SecRuleEngine On 53 | SecDebugLog $ENV{DEBUG_LOG} 54 | SecDebugLogLevel 5 55 | SecDefaultAction "phase:2,pass" 56 | SecRule ARGS:test "value" "deny,status:403,id:500033" 57 | ), 58 | match_log => { 59 | error => [ qr/ModSecurity: /, 1 ], 60 | debug => [ qr/Rule [0-9a-f]+: SecRule "ARGS:test" "\@rx value" "phase:2,deny,status:403,id:500033"$/m, 1 ], 61 | }, 62 | match_response => { 63 | status => qr/^403$/, 64 | }, 65 | request => new HTTP::Request( 66 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?test=value", 67 | ), 68 | }, 69 | { 70 | type => "rule", 71 | comment => "SecRule (chain)", 72 | conf => qq( 73 | SecRuleEngine On 74 | SecDebugLog $ENV{DEBUG_LOG} 75 | SecDebugLogLevel 5 76 | SecDefaultAction "phase:2,log,noauditlog,pass,tag:foo" 77 | SecRule ARGS:test "value" "chain,phase:2,deny,status:403,id:500034" 78 | SecRule &ARGS "\@eq 1" "chain," 79 | SecRule REQUEST_METHOD "\@streq GET" 80 | ), 81 | match_log => { 82 | error => [ qr/ModSecurity: /, 1 ], 83 | debug => [ qr/Rule [0-9a-f]+: SecRule "ARGS:test" "\@rx value" "phase:2,log,noauditlog,tag:foo,chain,deny,status:403,id:500034"\r?\n.*Rule [0-9a-f]+:/ ] 84 | }, 85 | match_response => { 86 | status => qr/^403$/, 87 | }, 88 | request => new HTTP::Request( 89 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?test=value", 90 | ), 91 | }, 92 | -------------------------------------------------------------------------------- /tests/regression/rule/00-inheritance.t: -------------------------------------------------------------------------------- 1 | ### Tests for rule inheritance 2 | 3 | ### TODO: 4 | # SecRuleInheritance 5 | -------------------------------------------------------------------------------- /tests/regression/rule/10-xml.t: -------------------------------------------------------------------------------- 1 | ### Test for XML operator rules 2 | 3 | ### Validate Scheme 4 | # OK 5 | { 6 | type => "rule", 7 | comment => "validateSchema (validate ok)", 8 | conf => qq( 9 | SecRuleEngine On 10 | SecRequestBodyAccess On 11 | SecXmlExternalEntity On 12 | SecDebugLog $ENV{DEBUG_LOG} 13 | SecDebugLogLevel 9 14 | SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500005, \\ 15 | phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" 16 | SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500006 17 | SecRule XML "\@validateSchema $ENV{CONF_DIR}/SoapEnvelope.xsd" "id:500007, \\ 18 | phase:3:,deny" 19 | ), 20 | match_log => { 21 | debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*Successfully validated payload against Schema/s, 1 ], 22 | -debug => [ qr/XML parser error|validation failed|Failed to load/, 1 ], 23 | -error => [ qr/XML parser error|validation failed|Failed to load/, 1 ], 24 | }, 25 | match_response => { 26 | status => qr/^200$/, 27 | }, 28 | request => new HTTP::Request( 29 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 30 | [ 31 | "Content-Type" => "text/xml", 32 | ], 33 | normalize_raw_request_data( 34 | q( 35 | 36 | 42 | 43 | 44 | 12123 45 | 46 | 47 | 48 | ), 49 | ), 50 | ), 51 | }, 52 | # Failed attribute value 53 | { 54 | type => "rule", 55 | comment => "validateSchema (validate attribute value failed)", 56 | conf => qq( 57 | SecRuleEngine On 58 | SecRequestBodyAccess On 59 | SecXmlExternalEntity On 60 | SecDebugLog $ENV{DEBUG_LOG} 61 | SecDebugLogLevel 9 62 | SecAuditEngine RelevantOnly 63 | SecAuditLog "$ENV{AUDIT_LOG}" 64 | SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500008, \\ 65 | phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" 66 | SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500009 67 | SecRule XML "\@validateSchema $ENV{CONF_DIR}/SoapEnvelope.xsd" "id:500010 \\ 68 | phase:2,deny,log,auditlog,id:12345" 69 | ), 70 | match_log => { 71 | debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*'badval' is not a valid value of the local atomic type.*Schema validation failed/s, 1 ], 72 | -debug => [ qr/Successfully validated payload against Schema|\n\r?\n/, 1 ], 73 | audit => [ qr/^Message: Element.*'badval' is not a valid value of the local atomic type\.\nMessage:/m, 1 ], 74 | }, 75 | match_response => { 76 | status => qr/^403$/, 77 | }, 78 | request => new HTTP::Request( 79 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 80 | [ 81 | "Content-Type" => "text/xml", 82 | ], 83 | normalize_raw_request_data( 84 | q( 85 | 86 | 92 | 93 | 94 | 12123 95 | 96 | 97 | 98 | ), 99 | ), 100 | ), 101 | }, 102 | # Failed validation 103 | { 104 | type => "rule", 105 | comment => "validateSchema (validate failed)", 106 | conf => qq( 107 | SecRuleEngine On 108 | SecRequestBodyAccess On 109 | SecXmlExternalEntity On 110 | SecDebugLog $ENV{DEBUG_LOG} 111 | SecDebugLogLevel 9 112 | SecAuditEngine RelevantOnly 113 | SecAuditLog "$ENV{AUDIT_LOG}" 114 | SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500011, \\ 115 | phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" 116 | SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500012 117 | SecRule XML "\@validateSchema $ENV{CONF_DIR}/SoapEnvelope.xsd" "id:500013 \\ 118 | phase:2,deny,id:12345" 119 | ), 120 | match_log => { 121 | debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*element is not expected/s, 1 ], 122 | -debug => [ qr/XML parser error|Failed to load/, 1 ], 123 | -error => [ qr/XML parser error|Failed to load/, 1 ], 124 | audit => [ qr/^Message: Element.*This element is not expected.*\nMessage:/m, 1 ], 125 | }, 126 | match_response => { 127 | status => qr/^403$/, 128 | }, 129 | request => new HTTP::Request( 130 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 131 | [ 132 | "Content-Type" => "text/xml", 133 | ], 134 | normalize_raw_request_data( 135 | q( 136 | 137 | 143 | 144 | 145 | 12123 146 | 147 | 148 | 149 | ), 150 | ), 151 | ), 152 | }, 153 | # Bad XML 154 | { 155 | type => "rule", 156 | comment => "validateSchema (bad XML)", 157 | conf => qq( 158 | SecRuleEngine On 159 | SecRequestBodyAccess On 160 | SecXmlExternalEntity On 161 | SecDebugLog $ENV{DEBUG_LOG} 162 | SecDebugLogLevel 9 163 | SecAuditEngine RelevantOnly 164 | SecAuditLog "$ENV{AUDIT_LOG}" 165 | SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500014, \\ 166 | phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" 167 | SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500015 168 | SecRule XML "\@validateSchema $ENV{CONF_DIR}/SoapEnvelope.xsd" "id:500016, \\ 169 | phase:2,deny,id:12345" 170 | ), 171 | match_log => { 172 | debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 0\).*XML parser error.*validation failed because content is not well formed/s, 1 ], 173 | -debug => [ qr/Failed to load|Successfully validated/, 1 ], 174 | -error => [ qr/Failed to load|Successfully validated/, 1 ], 175 | audit => [ qr/^Message: .*Failed parsing document.*\nMessage:/m, 1 ], 176 | }, 177 | match_response => { 178 | status => qr/^403$/, 179 | }, 180 | request => new HTTP::Request( 181 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 182 | [ 183 | "Content-Type" => "text/xml", 184 | ], 185 | normalize_raw_request_data( 186 | q( 187 | 188 | 194 | 195 | 196 | 12123 197 | 198 | 199 | 200 | ), 201 | ), 202 | ), 203 | }, 204 | # Bad schema 205 | { 206 | type => "rule", 207 | comment => "validateSchema (bad schema)", 208 | conf => qq( 209 | SecRuleEngine On 210 | SecRequestBodyAccess On 211 | SecXmlExternalEntity On 212 | SecDebugLog $ENV{DEBUG_LOG} 213 | SecDebugLogLevel 9 214 | SecAuditEngine RelevantOnly 215 | SecAuditLog "$ENV{AUDIT_LOG}" 216 | SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500017 \\ 217 | phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" 218 | SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500018 219 | SecRule XML "\@validateSchema $ENV{CONF_DIR}/SoapEnvelope-bad.xsd" "id:500019 \\ 220 | phase:2,deny,id:12345" 221 | ), 222 | match_log => { 223 | debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*Failed to parse the XML resource.*Failed to load Schema/s, 1 ], 224 | audit => [ qr/^Message: .*Failed to parse the XML resource.*\nMessage: Rule processing failed/m, 1 ], 225 | }, 226 | match_response => { 227 | status => qr/^200$/, 228 | }, 229 | request => new HTTP::Request( 230 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 231 | [ 232 | "Content-Type" => "text/xml", 233 | ], 234 | normalize_raw_request_data( 235 | q( 236 | 237 | 243 | 244 | 245 | 12123 246 | 247 | 248 | 249 | ), 250 | ), 251 | ), 252 | }, 253 | 254 | # Validate DTD 255 | # OK 256 | { 257 | type => "rule", 258 | comment => "validateDTD (validate ok)", 259 | conf => qq( 260 | SecRuleEngine On 261 | SecRequestBodyAccess On 262 | SecXmlExternalEntity On 263 | SecDebugLog $ENV{DEBUG_LOG} 264 | SecDebugLogLevel 9 265 | SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500020, \\ 266 | phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" 267 | SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500021 268 | SecRule XML "\@validateDTD $ENV{CONF_DIR}/SoapEnvelope.dtd" "id:500022, \\ 269 | phase:2,deny,id:12345" 270 | ), 271 | match_log => { 272 | debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*Successfully validated payload against DTD/s, 1 ], 273 | -debug => [ qr/XML parser error|validation failed|Failed to load/, 1 ], 274 | -error => [ qr/XML parser error|validation failed|Failed to load/, 1 ], 275 | }, 276 | match_response => { 277 | status => qr/^200$/, 278 | }, 279 | request => new HTTP::Request( 280 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 281 | [ 282 | "Content-Type" => "text/xml", 283 | ], 284 | normalize_raw_request_data( 285 | q( 286 | 287 | 288 | 289 | 290 | 291 | 12123 292 | 293 | 294 | 295 | ), 296 | ), 297 | ), 298 | }, 299 | # Failed validation 300 | { 301 | type => "rule", 302 | comment => "validateDTD (validate failed)", 303 | conf => qq( 304 | SecRuleEngine On 305 | SecRequestBodyAccess On 306 | SecXmlExternalEntity On 307 | SecDebugLog $ENV{DEBUG_LOG} 308 | SecDebugLogLevel 9 309 | SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500023, \\ 310 | phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" 311 | SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500024 312 | SecRule XML "\@validateDTD $ENV{CONF_DIR}/SoapEnvelope.dtd" "id:500025, \\ 313 | phase:2,deny,id:12345" 314 | ), 315 | match_log => { 316 | debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*content does not follow the DTD/s, 1 ], 317 | -debug => [ qr/XML parser error|Failed to load/, 1 ], 318 | -error => [ qr/XML parser error|Failed to load/, 1 ], 319 | }, 320 | match_response => { 321 | status => qr/^403$/, 322 | }, 323 | request => new HTTP::Request( 324 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 325 | [ 326 | "Content-Type" => "text/xml", 327 | ], 328 | normalize_raw_request_data( 329 | q( 330 | 331 | 332 | 333 | 334 | 335 | 12123 336 | 337 | 338 | 339 | ), 340 | ), 341 | ), 342 | }, 343 | # Bad XML 344 | { 345 | type => "rule", 346 | comment => "validateDTD (bad XML)", 347 | conf => qq( 348 | SecRuleEngine On 349 | SecRequestBodyAccess On 350 | SecXmlExternalEntity On 351 | SecDebugLog $ENV{DEBUG_LOG} 352 | SecDebugLogLevel 9 353 | SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500026, \\ 354 | phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" 355 | SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500027 356 | SecRule XML "\@validateDTD $ENV{CONF_DIR}/SoapEnvelope.dtd" "id:500028, \\ 357 | phase:2,deny,id:12345" 358 | ), 359 | match_log => { 360 | debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 0\).*XML parser error.*validation failed because content is not well formed/s, 1 ], 361 | -debug => [ qr/Failed to load|Successfully validated/, 1 ], 362 | -error => [ qr/Failed to load|Successfully validated/, 1 ], 363 | }, 364 | match_response => { 365 | status => qr/^403$/, 366 | }, 367 | request => new HTTP::Request( 368 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 369 | [ 370 | "Content-Type" => "text/xml", 371 | ], 372 | normalize_raw_request_data( 373 | q( 374 | 375 | 376 | 377 | 378 | 379 | 12123 380 | 381 | 382 | 383 | ), 384 | ), 385 | ), 386 | }, 387 | # Bad DTD 388 | { 389 | type => "rule", 390 | comment => "validateDTD (bad DTD)", 391 | conf => qq( 392 | SecRuleEngine On 393 | SecRequestBodyAccess On 394 | SecXmlExternalEntity On 395 | SecDebugLog $ENV{DEBUG_LOG} 396 | SecDebugLogLevel 9 397 | SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" "id:500029, \\ 398 | phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" 399 | SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345,id:500030 400 | SecRule XML "\@validateDTD $ENV{CONF_DIR}/SoapEnvelope-bad.dtd" "id:500031 \\ 401 | phase:2,deny,id:12345" 402 | ), 403 | match_log => { 404 | debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*Failed to load DTD/s, 1 ], 405 | }, 406 | match_response => { 407 | status => qr/^200$/, 408 | }, 409 | request => new HTTP::Request( 410 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 411 | [ 412 | "Content-Type" => "text/xml", 413 | ], 414 | normalize_raw_request_data( 415 | q( 416 | 417 | 418 | 419 | 420 | 421 | 12123 422 | 423 | 424 | 425 | ), 426 | ), 427 | ), 428 | }, 429 | -------------------------------------------------------------------------------- /tests/regression/rule/15-json.t: -------------------------------------------------------------------------------- 1 | ### Test for JSON parser 2 | 3 | ### 4 | # OK 5 | { 6 | type => "rule", 7 | comment => "json parser", 8 | conf => qq( 9 | SecRuleEngine On 10 | SecRequestBodyAccess On 11 | SecDebugLog $ENV{DEBUG_LOG} 12 | SecDebugLogLevel 9 13 | SecRule REQUEST_HEADERS:Content-Type "application/json" \\ 14 | "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" 15 | SecRule ARGS:foo "bar" "id:'200441',phase:3,log" 16 | ), 17 | match_log => { 18 | error => [ qr/ModSecurity: Warning. Pattern match "bar" at ARGS:foo.|ModSecurity: JSON support was not enabled/s, 1 ], 19 | debug => [ qr/Adding JSON argument 'foo' with value 'bar'|JSON support was not enabled/, 1 ], 20 | }, 21 | match_response => { 22 | status => qr/^200$/, 23 | }, 24 | request => new HTTP::Request( 25 | POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 26 | [ 27 | "Content-Type" => "application/json", 28 | ], 29 | normalize_raw_request_data( 30 | q( 31 | { 32 | "foo":"bar", 33 | "mod":"sec" 34 | } 35 | ), 36 | ), 37 | ), 38 | } 39 | 40 | -------------------------------------------------------------------------------- /tests/regression/rule/20-exceptions.t: -------------------------------------------------------------------------------- 1 | ### Tests for rule exceptions 2 | 3 | # SecRuleRemoveById 4 | { 5 | type => "rule", 6 | comment => "SecRuleRemoveById (single)", 7 | conf => qq( 8 | SecRuleEngine On 9 | SecDebugLog $ENV{DEBUG_LOG} 10 | SecDebugLogLevel 9 11 | SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:101010" 12 | SecRuleRemoveById 101010 13 | ), 14 | match_log => { 15 | -error => [ qr/101010/, 1 ], 16 | -audit => [ qr/./, 1 ], 17 | debug => [ qr/Starting phase REQUEST_HEADERS\..*This phase consists of 0 rule.*Starting phase RESPONSE_HEADERS\./s, 1 ], 18 | -debug => [ qr/Access denied/, 1 ], 19 | }, 20 | match_response => { 21 | status => qr/^200$/, 22 | }, 23 | request => new HTTP::Request( 24 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 25 | ), 26 | }, 27 | { 28 | type => "rule", 29 | comment => "SecRuleRemoveById (multiple)", 30 | conf => qq( 31 | SecRuleEngine On 32 | SecDebugLog $ENV{DEBUG_LOG} 33 | SecDebugLogLevel 9 34 | SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:101010" 35 | SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:202020" 36 | SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:303030" 37 | SecRuleRemoveById 101010 202020 303030 38 | ), 39 | match_log => { 40 | -error => [ qr/101010|202020|303030/, 1 ], 41 | -audit => [ qr/./, 1 ], 42 | debug => [ qr/Starting phase REQUEST_HEADERS\..*This phase consists of 0 rule.*Starting phase RESPONSE_HEADERS\./s, 1 ], 43 | -debug => [ qr/Access denied/, 1 ], 44 | }, 45 | match_response => { 46 | status => qr/^200$/, 47 | }, 48 | request => new HTTP::Request( 49 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 50 | ), 51 | }, 52 | { 53 | type => "rule", 54 | comment => "SecRuleRemoveById (range)", 55 | conf => qq( 56 | SecRuleEngine On 57 | SecDebugLog $ENV{DEBUG_LOG} 58 | SecDebugLogLevel 9 59 | SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:101010" 60 | SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:202020" 61 | SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:303030" 62 | SecRuleRemoveById 101010-303030 63 | ), 64 | match_log => { 65 | -error => [ qr/101010|202020|303030/, 1 ], 66 | -audit => [ qr/./, 1 ], 67 | debug => [ qr/Starting phase REQUEST_HEADERS\..*This phase consists of 0 rule.*Starting phase RESPONSE_HEADERS\./s, 1 ], 68 | -debug => [ qr/Access denied/, 1 ], 69 | }, 70 | match_response => { 71 | status => qr/^200$/, 72 | }, 73 | request => new HTTP::Request( 74 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 75 | ), 76 | }, 77 | { 78 | type => "rule", 79 | comment => "SecRuleRemoveById (multiple + range)", 80 | conf => qq( 81 | SecRuleEngine On 82 | SecDebugLog $ENV{DEBUG_LOG} 83 | SecDebugLogLevel 9 84 | SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:101010" 85 | SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:202020" 86 | SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:303030" 87 | SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:404040" 88 | SecRuleRemoveById 101010 202020-404040 89 | ), 90 | match_log => { 91 | -error => [ qr/101010|202020|303030|404040/, 1 ], 92 | -audit => [ qr/./, 1 ], 93 | debug => [ qr/Starting phase REQUEST_HEADERS\..*This phase consists of 0 rule.*Starting phase RESPONSE_HEADERS\./s, 1 ], 94 | -debug => [ qr/Access denied/, 1 ], 95 | }, 96 | match_response => { 97 | status => qr/^200$/, 98 | }, 99 | request => new HTTP::Request( 100 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 101 | ), 102 | }, 103 | 104 | # SecRuleRemoveByMsg 105 | { 106 | type => "rule", 107 | comment => "SecRuleRemoveByMsg", 108 | conf => qq( 109 | SecRuleEngine On 110 | SecDebugLog $ENV{DEBUG_LOG} 111 | SecDebugLogLevel 9 112 | SecRule REQUEST_URI "test" "phase:1,deny,status:500,msg:'testing rule',id:500001" 113 | SecRuleRemoveByMsg "testing rule" 114 | ), 115 | match_log => { 116 | -error => [ qr/500001/, 1 ], 117 | -audit => [ qr/./, 1 ], 118 | debug => [ qr/Starting phase REQUEST_HEADERS\..*This phase consists of 0 rule.*Starting phase RESPONSE_HEADERS\./s, 1 ], 119 | -debug => [ qr/Access denied/, 1 ], 120 | }, 121 | match_response => { 122 | status => qr/^200$/, 123 | }, 124 | request => new HTTP::Request( 125 | GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", 126 | ), 127 | }, 128 | 129 | 130 | -------------------------------------------------------------------------------- /tests/regression/server_root/conf/SoapEnvelope-bad.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/regression/server_root/conf/SoapEnvelope-bad.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Prose in the spec does not specify that attributes are allowed on the Body element 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 'encodingStyle' indicates any canonicalization conventions followed in the contents of the containing element. For example, the value 'http://schemas.xmlsoap.org/soap/encoding/' indicates the pattern described in SOAP specification 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | Fault reporting structure 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /tests/regression/server_root/conf/SoapEnvelope.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/regression/server_root/conf/SoapEnvelope.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Prose in the spec does not specify that attributes are allowed on the Body element 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 'encodingStyle' indicates any canonicalization conventions followed in the contents of the containing element. For example, the value 'http://schemas.xmlsoap.org/soap/encoding/' indicates the pattern described in SOAP specification 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | Fault reporting structure 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /tests/regression/server_root/conf/httpd.conf.in: -------------------------------------------------------------------------------- 1 | ### Base configuration for starting Apache httpd 2 | 3 | # TODO: Need to have these configurable 4 | LoadModule security3_module @MSC_BASE_DIR@/src/.libs/mod_security3.so 5 | 6 | 7 | LoadModule unixd_module @APXS_LIBEXECDIR@/mod_unixd.so 8 | 9 | LoadModule access_compat_module @APXS_LIBEXECDIR@/mod_access_compat.so 10 | LoadModule authn_core_module @APXS_LIBEXECDIR@/mod_authn_core.so 11 | LoadModule authz_core_module @APXS_LIBEXECDIR@/mod_authz_core.so 12 | 13 | ServerName localhost 14 | CoreDumpDirectory @MSC_REGRESSION_SERVERROOT_DIR@/tmp 15 | LogLevel debug 16 | ErrorLog @MSC_REGRESSION_LOGS_DIR@/error.log 17 | 18 | 19 | # File locations 20 | PidFile @MSC_REGRESSION_LOGS_DIR@/httpd.pid 21 | ScoreBoardFile @MSC_REGRESSION_LOGS_DIR@/httpd.scoreboard 22 | 23 | DocumentRoot @MSC_REGRESSION_DOCROOT_DIR@ 24 | 25 | Options +Indexes +FollowSymLinks 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/regression/server_root/conf/match.lua: -------------------------------------------------------------------------------- 1 | -- Test matching Lua Script to just print debug messages 2 | function main() 3 | m.log(1, "Test message."); 4 | m.log(2, "Test message."); 5 | m.log(3, "Test message."); 6 | m.log(4, "Test message."); 7 | m.log(5, "Test message."); 8 | m.log(6, "Test message."); 9 | m.log(7, "Test message."); 10 | m.log(8, "Test message."); 11 | m.log(9, "Test message."); 12 | 13 | return "Lua script matched."; 14 | end 15 | -------------------------------------------------------------------------------- /tests/regression/server_root/conf/ssdeep.txt: -------------------------------------------------------------------------------- 1 | ssdeep,1.1--blocksize:hash:hash,filename 2 | 96:MbQ1L0LDX8GPI8ov3D2D9zd6/gz2wZhFvV0O598La8Kqvfi0znNa8Xi5SM7XRWCK:KvL8Gg8rWIz2ZKqvfjzQ55RpRHjftQ++,"modsecurity.conf-recommended" 3 | 192:b8B5UQvywcMIJuavpde/Yyz/U/vF+vGCoCvrQr/dw:afcnrvp8zqUvGrzr6,"README_WINDOWS.TXT" 4 | 96:+qK8Z4gA165/hquKNMi68zuEyMM9qNB26x:+RG4z6c1LyZOB26x,"README.TXT" 5 | -------------------------------------------------------------------------------- /tests/regression/server_root/conf/test.lua: -------------------------------------------------------------------------------- 1 | -- Test Lua Script to just print debug messages 2 | function main() 3 | m.log(1, "Test message."); 4 | m.log(2, "Test message."); 5 | m.log(3, "Test message."); 6 | m.log(4, "Test message."); 7 | m.log(5, "Test message."); 8 | m.log(6, "Test message."); 9 | m.log(7, "Test message."); 10 | m.log(8, "Test message."); 11 | m.log(9, "Test message."); 12 | 13 | return nil; 14 | end 15 | -------------------------------------------------------------------------------- /tests/regression/server_root/data/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity-apache/0488c77f69669584324b70460614a382224b4883/tests/regression/server_root/data/.empty -------------------------------------------------------------------------------- /tests/regression/server_root/data/ip.dir: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity-apache/0488c77f69669584324b70460614a382224b4883/tests/regression/server_root/data/ip.dir -------------------------------------------------------------------------------- /tests/regression/server_root/htdocs/8k.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/regression/server_root/htdocs/index.html: -------------------------------------------------------------------------------- 1 | INDEX 2 | -------------------------------------------------------------------------------- /tests/regression/server_root/htdocs/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity-apache/0488c77f69669584324b70460614a382224b4883/tests/regression/server_root/htdocs/test.pdf -------------------------------------------------------------------------------- /tests/regression/server_root/htdocs/test.txt: -------------------------------------------------------------------------------- 1 | TEST 2 | -------------------------------------------------------------------------------- /tests/regression/server_root/htdocs/test2.txt: -------------------------------------------------------------------------------- 1 | TEST 2 2 | -------------------------------------------------------------------------------- /tests/regression/server_root/logs/audit/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity-apache/0488c77f69669584324b70460614a382224b4883/tests/regression/server_root/logs/audit/.empty -------------------------------------------------------------------------------- /tests/regression/server_root/logs/subdir/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity-apache/0488c77f69669584324b70460614a382224b4883/tests/regression/server_root/logs/subdir/.empty -------------------------------------------------------------------------------- /tests/regression/server_root/tmp/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity-apache/0488c77f69669584324b70460614a382224b4883/tests/regression/server_root/tmp/.empty -------------------------------------------------------------------------------- /tests/regression/server_root/upload/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity-apache/0488c77f69669584324b70460614a382224b4883/tests/regression/server_root/upload/.empty --------------------------------------------------------------------------------